Added launcher
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Prospect.Launcher.Invoke.Structs;
|
||||
|
||||
namespace Prospect.Launcher.Invoke
|
||||
{
|
||||
internal static class Kernel32
|
||||
{
|
||||
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Auto)]
|
||||
public static extern bool CreateProcess(string lpApplicationName,
|
||||
string lpCommandLine, IntPtr lpProcessAttributes,
|
||||
IntPtr lpThreadAttributes,
|
||||
bool bInheritHandles, ProcessCreationFlags dwCreationFlags,
|
||||
IntPtr lpEnvironment, string lpCurrentDirectory,
|
||||
ref StartupInfo lpStartupInfo,
|
||||
out ProcessInformation lpProcessInformation);
|
||||
|
||||
public static bool CreateProcess(
|
||||
string lpApplicationName,
|
||||
string lpCommandLine,
|
||||
ProcessCreationFlags dwCreationFlags,
|
||||
ref StartupInfo lpStartupInfo,
|
||||
out ProcessInformation lpProcessInformation) => CreateProcess(lpApplicationName, lpCommandLine, IntPtr.Zero,
|
||||
IntPtr.Zero, false, dwCreationFlags, IntPtr.Zero, null, ref lpStartupInfo, out lpProcessInformation);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool ReadProcessMemory(
|
||||
IntPtr hProcess,
|
||||
IntPtr lpBaseAddress,
|
||||
byte[] lpBuffer,
|
||||
Int32 nSize,
|
||||
out IntPtr lpNumberOfBytesRead);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern unsafe bool WriteProcessMemory(
|
||||
IntPtr hProcess,
|
||||
IntPtr lpBaseAddress,
|
||||
byte* lpBuffer,
|
||||
Int32 nSize,
|
||||
out IntPtr lpNumberOfBytesWritten
|
||||
);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, uint flNewProtect, out uint lpflOldProtect);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Prospect.Launcher.Invoke.Structs;
|
||||
|
||||
namespace Prospect.Launcher.Invoke
|
||||
{
|
||||
public class PSAPI
|
||||
{
|
||||
[DllImport("psapi.dll", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
|
||||
public static extern int EnumProcessModules(IntPtr hProcess, [Out] IntPtr lphModule, uint cb, out uint lpcbNeeded);
|
||||
|
||||
[DllImport("psapi.dll", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
|
||||
public static extern bool GetModuleInformation(IntPtr hProcess, IntPtr hModule, out ModuleInfo lpModInfo, int cb);
|
||||
|
||||
[DllImport("psapi.dll")]
|
||||
public static extern uint GetModuleFileNameEx(IntPtr hProcess, IntPtr hModule, [Out] StringBuilder lpBaseName, [In] [MarshalAs(UnmanagedType.U4)] int nSize);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Prospect.Launcher.Invoke.Structs
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct ModuleInfo
|
||||
{
|
||||
public IntPtr lpBaseOfDll;
|
||||
public uint SizeOfImage;
|
||||
public IntPtr EntryPoint;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
|
||||
namespace Prospect.Launcher.Invoke.Structs
|
||||
{
|
||||
[Flags]
|
||||
internal enum ProcessCreationFlags : uint
|
||||
{
|
||||
ZERO_FLAG = 0x00000000,
|
||||
CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
|
||||
CREATE_DEFAULT_ERROR_MODE = 0x04000000,
|
||||
CREATE_NEW_CONSOLE = 0x00000010,
|
||||
CREATE_NEW_PROCESS_GROUP = 0x00000200,
|
||||
CREATE_NO_WINDOW = 0x08000000,
|
||||
CREATE_PROTECTED_PROCESS = 0x00040000,
|
||||
CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000,
|
||||
CREATE_SEPARATE_WOW_VDM = 0x00001000,
|
||||
CREATE_SHARED_WOW_VDM = 0x00001000,
|
||||
CREATE_SUSPENDED = 0x00000004,
|
||||
CREATE_UNICODE_ENVIRONMENT = 0x00000400,
|
||||
DEBUG_ONLY_THIS_PROCESS = 0x00000002,
|
||||
DEBUG_PROCESS = 0x00000001,
|
||||
DETACHED_PROCESS = 0x00000008,
|
||||
EXTENDED_STARTUPINFO_PRESENT = 0x00080000,
|
||||
INHERIT_PARENT_AFFINITY = 0x00010000
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Prospect.Launcher.Invoke.Structs
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct ProcessInformation
|
||||
{
|
||||
public IntPtr hProcess;
|
||||
public IntPtr hThread;
|
||||
public uint dwProcessId;
|
||||
public uint dwThreadId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Prospect.Launcher.Invoke.Structs
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct StartupInfo
|
||||
{
|
||||
public uint cb;
|
||||
public string lpReserved;
|
||||
public string lpDesktop;
|
||||
public string lpTitle;
|
||||
public uint dwX;
|
||||
public uint dwY;
|
||||
public uint dwXSize;
|
||||
public uint dwYSize;
|
||||
public uint dwXCountChars;
|
||||
public uint dwYCountChars;
|
||||
public uint dwFillAttribute;
|
||||
public uint dwFlags;
|
||||
public short wShowWindow;
|
||||
public short cbReserved2;
|
||||
public IntPtr lpReserved2;
|
||||
public IntPtr hStdInput;
|
||||
public IntPtr hStdOutput;
|
||||
public IntPtr hStdError;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Prospect.Launcher.Invoke;
|
||||
using Prospect.Launcher.Invoke.Structs;
|
||||
|
||||
namespace Prospect.Launcher
|
||||
{
|
||||
public class MemoryHelper
|
||||
{
|
||||
private readonly IntPtr _handle;
|
||||
private readonly IntPtr _baseAddress;
|
||||
|
||||
private MemoryHelper(IntPtr handle, IntPtr baseAddress)
|
||||
{
|
||||
_handle = handle;
|
||||
_baseAddress = baseAddress;
|
||||
}
|
||||
|
||||
public void WriteU32(int offset, uint value)
|
||||
{
|
||||
Span<byte> data = stackalloc byte[sizeof(int)];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(data, value);
|
||||
Write(offset, data);
|
||||
}
|
||||
|
||||
public void WriteASCII(int offset, string value)
|
||||
{
|
||||
// + 1 for NULL character
|
||||
var dataCount = Encoding.ASCII.GetByteCount(value);
|
||||
Span<byte> data = stackalloc byte[dataCount + 1];
|
||||
Encoding.ASCII.GetBytes(value, data);
|
||||
Write(offset, data);
|
||||
}
|
||||
|
||||
public void WriteUnicode(int offset, string value)
|
||||
{
|
||||
// + 2 for NULL character
|
||||
var dataCount = Encoding.Unicode.GetByteCount(value);
|
||||
Span<byte> data = stackalloc byte[dataCount + 2];
|
||||
Encoding.Unicode.GetBytes(value, data);
|
||||
Write(offset, data);
|
||||
}
|
||||
|
||||
public unsafe void Write(int offset, Span<byte> data)
|
||||
{
|
||||
var addr = _baseAddress + offset;
|
||||
|
||||
// Modify protection.
|
||||
if (!Kernel32.VirtualProtectEx(_handle, addr, data.Length, 0x40, out var oldProtect))
|
||||
{
|
||||
throw new Exception($"Unable to change protection {Marshal.GetLastWin32Error()}.");
|
||||
}
|
||||
|
||||
fixed (byte* pData = data)
|
||||
{
|
||||
var write = Kernel32.WriteProcessMemory(_handle, _baseAddress + offset, pData, data.Length, out _);
|
||||
|
||||
// Restore protection.
|
||||
Kernel32.VirtualProtectEx(_handle, addr, data.Length, oldProtect, out _);
|
||||
|
||||
if (!write)
|
||||
{
|
||||
throw new Exception($"Unable to write process memory {Marshal.GetLastWin32Error()}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] Read(int offset, int count)
|
||||
{
|
||||
var buffer = new byte[count];
|
||||
|
||||
if (!Kernel32.ReadProcessMemory(_handle, _baseAddress + offset, buffer, count, out _))
|
||||
{
|
||||
throw new Exception("Unable to read process memory.");
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static MemoryHelper CreateForHandle(IntPtr handle, string gameBinary)
|
||||
{
|
||||
var gameBinaryName = Path.GetFileName(gameBinary);
|
||||
|
||||
var hMods = new IntPtr[512];
|
||||
var hModsHandle = GCHandle.Alloc(hMods, GCHandleType.Pinned);
|
||||
|
||||
try
|
||||
{
|
||||
var hModsPtr = hModsHandle.AddrOfPinnedObject();
|
||||
var hModsSize = (uint)(Marshal.SizeOf(typeof(IntPtr)) * (hMods.Length));
|
||||
|
||||
if (PSAPI.EnumProcessModules(handle, hModsPtr, hModsSize, out var cbNeeded) == 1)
|
||||
{
|
||||
var moduleCount = (int)(cbNeeded / Marshal.SizeOf(typeof(IntPtr)));
|
||||
|
||||
for (var i = 0; i < moduleCount; i++)
|
||||
{
|
||||
var stringBuilder = new StringBuilder(1024);
|
||||
|
||||
if (PSAPI.GetModuleFileNameEx(handle, hMods[i], stringBuilder, stringBuilder.Capacity) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!gameBinaryName.Equals(Path.GetFileName(stringBuilder.ToString())))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!PSAPI.GetModuleInformation(handle, hMods[i], out var modInfo, Marshal.SizeOf<ModuleInfo>()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return new MemoryHelper(handle, modInfo.lpBaseOfDll);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
hModsHandle.Free();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace Prospect.Launcher
|
||||
{
|
||||
public static class Patches
|
||||
{
|
||||
private const string DefaultPlayFabUrl = ".playfabapi.com";
|
||||
|
||||
public static bool ChangePlayFabUrl(MemoryHelper memory, string value)
|
||||
{
|
||||
if (value.Length > DefaultPlayFabUrl.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var lengthWithNull = (uint)(value.Length + 1);
|
||||
|
||||
// Modify wide string.
|
||||
memory.WriteU32(0xCA45A9 + 2, lengthWithNull * 2); // mov r8d, 20h
|
||||
memory.WriteUnicode(0x4431860, value); // text "UTF-16LE", '.playfabapi.com',0
|
||||
|
||||
// Modify normal string.
|
||||
memory.WriteU32(0x605F44 + 1, lengthWithNull); // mov edx, 10h
|
||||
memory.WriteU32(0x605F86 + 2, lengthWithNull); // mov r9d, 10h
|
||||
memory.WriteASCII(0x4431588, value); // '.playfabapi.com',0
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using Prospect.Launcher.Invoke;
|
||||
using Prospect.Launcher.Invoke.Structs;
|
||||
|
||||
namespace Prospect.Launcher
|
||||
{
|
||||
internal static class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// Steam AppId.
|
||||
/// </summary>
|
||||
private const string AppId = "1600360";
|
||||
|
||||
/// <summary>
|
||||
/// Executable name of the game.
|
||||
/// </summary>
|
||||
private const string FileName = "Prospect-Win64-Shipping.exe";
|
||||
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
Console.WriteLine("Usage: ./Prospect.Launcher <Game directory>");
|
||||
return;
|
||||
}
|
||||
|
||||
var gamePath = Path.Combine(args[0], "Prospect", "Binaries", "Win64");
|
||||
var gameBinary = Path.Combine(gamePath, FileName);
|
||||
var gameArgs = new List<string>();
|
||||
|
||||
// 2EA46 = Default
|
||||
// A22AB = The Cycle Playtest
|
||||
gameArgs.Add("-log");
|
||||
gameArgs.Add("-windowed");
|
||||
gameArgs.Add("-noeac");
|
||||
gameArgs.Add("-nointro");
|
||||
gameArgs.Add("-steam_auth");
|
||||
gameArgs.Add("PF_TITLEID=A22AB");
|
||||
|
||||
// Ensure "steam_appid.txt" exists, to fix steam authentication.
|
||||
var steamAppId = Path.Combine(gamePath, "steam_appid.txt");
|
||||
|
||||
if (!File.Exists(steamAppId))
|
||||
{
|
||||
File.WriteAllText(steamAppId, AppId);
|
||||
}
|
||||
|
||||
// Spawn.
|
||||
var startupInfo = new StartupInfo();
|
||||
|
||||
if (!Kernel32.CreateProcess(gameBinary, string.Join(' ', gameArgs), ProcessCreationFlags.ZERO_FLAG, ref startupInfo, out var processInfo))
|
||||
{
|
||||
Console.WriteLine("Failed to spawn game.");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine("Spawned.");
|
||||
|
||||
// Modify.
|
||||
MemoryHelper memory;
|
||||
|
||||
while ((memory = MemoryHelper.CreateForHandle(processInfo.hProcess, gameBinary)) == null)
|
||||
{
|
||||
Console.WriteLine("Failed to obtain MemoryHelper.");
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
if (!Patches.ChangePlayFabUrl(memory, ".localhost"))
|
||||
{
|
||||
Console.WriteLine("Failed to patch playfab url.");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine("Patched.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user