Files
pristontale-eu-linux/native/pt_world_fix.c

830 lines
20 KiB
C

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <tlhelp32.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <wchar.h>
#define GAME_DLL_MAP_ENTRY_RVA 0x007c0b40u
#define GAME_DLL_LOGIN_RETURN_RVA 0x0079f157u
#define GAME_DLL_CODE_CAVE_RVA 0x01195ce0u
#define GAME_DLL_LSTRCMPA_IAT_RVA 0x01196280u
#define GAME_DLL_VIRTUAL_PROTECT_IAT_RVA 0x01196108u
#define GAME_DLL_FLUSH_INSTRUCTION_CACHE_IAT_RVA 0x01196140u
#define GAME_DLL_GET_CURRENT_PROCESS_IAT_RVA 0x01196344u
#define GAME_DLL_MINIMUM_IMAGE_SIZE 0x01196348u
#define REMOTE_ALLOCATION_SIZE 4096u
#define REMOTE_RECLAIM_OFFSET 0x0600u
#define REMOTE_MAP_JUMP_OFFSET 0x0680u
#define REMOTE_STATE_OFFSET 0x0700u
#define REMOTE_OLD_CAVE_PROTECT_OFFSET 0x0704u
#define REMOTE_OLD_ENTRY_PROTECT_OFFSET 0x0708u
#define REMOTE_TEMP_PROTECT_OFFSET 0x070cu
#define WORLD_FIX_WAIT_MILLISECONDS 60000u
static const BYTE MAP_ENTRY_ORIGINAL[] = {0x55, 0x8b, 0xec, 0x6a, 0xff};
static const BYTE LOGIN_COMPARE_ORIGINAL[] = {
0x85, 0xc0, 0x0f, 0x85, 0x6b, 0x01, 0x00, 0x00,
};
static const BYTE MAP_ENTRY_JUMP[] = {0xe9, 0x9b, 0x51, 0x9d, 0x00};
/*
* Exact 97-byte entity-surface reclaim routine verified for V21.75.83.
* Its relative calls and return jump are valid at GAME_DLL_CODE_CAVE_RVA.
*/
static const BYTE PROVEN_RECLAIM_HOOK[] = {
0x9c, 0x60, 0xe8, 0x49, 0x01, 0x36, 0xff, 0x85,
0xc0, 0x74, 0x4a, 0x89, 0xc3, 0x8d, 0x70, 0x08,
0x8d, 0xbe, 0x50, 0x5d, 0x01, 0x00, 0x39, 0xfe,
0x74, 0x3b, 0x80, 0xbe, 0x58, 0x03, 0x00, 0x00,
0x00, 0x74, 0x2a, 0x8b, 0x06, 0x8b, 0x4c, 0x24,
0x28, 0x85, 0xc9, 0x74, 0x08, 0x3b, 0x81, 0xd4,
0x1f, 0x00, 0x00, 0x74, 0x18, 0x8b, 0x4c, 0x24,
0x2c, 0x85, 0xc9, 0x74, 0x08, 0x3b, 0x81, 0xd4,
0x1f, 0x00, 0x00, 0x74, 0x08, 0x50, 0x89, 0xd9,
0xe8, 0xa3, 0x20, 0x36, 0xff, 0x81, 0xc6, 0x50,
0x04, 0x00, 0x00, 0xeb, 0xc1, 0x61, 0x9d, 0x55,
0x89, 0xe5, 0x6a, 0xff, 0xe9, 0x04, 0xae, 0x62,
0xff,
};
typedef struct CodeBuffer
{
BYTE *data;
SIZE_T capacity;
SIZE_T length;
BOOL valid;
} CodeBuffer;
typedef struct WorldFixInstallation
{
DWORD remote_state;
LPVOID remote_allocation;
} WorldFixInstallation;
static DWORD address_from_rva(DWORD base, DWORD rva)
{
return base + rva;
}
static BOOL read_exact(HANDLE process, DWORD address, void *buffer, SIZE_T size)
{
SIZE_T read_size = 0;
return ReadProcessMemory(
process,
(LPCVOID)(uintptr_t)address,
buffer,
size,
&read_size
) && read_size == size;
}
static BOOL write_exact(HANDLE process, DWORD address, const void *buffer, SIZE_T size)
{
SIZE_T written_size = 0;
return WriteProcessMemory(
process,
(LPVOID)(uintptr_t)address,
buffer,
size,
&written_size
) && written_size == size;
}
static void emit_byte(CodeBuffer *code, BYTE value)
{
if (!code->valid || code->length >= code->capacity)
{
code->valid = FALSE;
return;
}
code->data[code->length++] = value;
}
static void emit_dword(CodeBuffer *code, DWORD value)
{
SIZE_T index;
for (index = 0; index < sizeof(value); ++index)
{
emit_byte(code, (BYTE)((value >> (index * 8u)) & 0xffu));
}
}
static void emit_bytes(CodeBuffer *code, const BYTE *values, SIZE_T size)
{
SIZE_T index;
for (index = 0; index < size; ++index)
{
emit_byte(code, values[index]);
}
}
static SIZE_T emit_near_jump_placeholder(CodeBuffer *code, BYTE condition)
{
SIZE_T displacement_offset;
if (condition != 0)
{
emit_byte(code, 0x0f);
emit_byte(code, condition);
}
else
{
emit_byte(code, 0xe9);
}
displacement_offset = code->length;
emit_dword(code, 0);
return displacement_offset;
}
static void patch_near_jump(CodeBuffer *code, SIZE_T displacement_offset, SIZE_T target_offset)
{
int32_t displacement;
if (!code->valid || displacement_offset + sizeof(displacement) > code->capacity)
{
code->valid = FALSE;
return;
}
displacement = (int32_t)(target_offset - (displacement_offset + sizeof(displacement)));
memcpy(code->data + displacement_offset, &displacement, sizeof(displacement));
}
static void emit_push_immediate(CodeBuffer *code, DWORD value)
{
emit_byte(code, 0x68);
emit_dword(code, value);
}
static void emit_call_iat(CodeBuffer *code, DWORD iat_address)
{
emit_byte(code, 0xff);
emit_byte(code, 0x15);
emit_dword(code, iat_address);
}
static void emit_set_state(CodeBuffer *code, DWORD state_address, BYTE value)
{
emit_byte(code, 0xc6);
emit_byte(code, 0x05);
emit_dword(code, state_address);
emit_byte(code, value);
}
static void emit_restore_iat(
CodeBuffer *code,
DWORD iat_address,
DWORD original_lstrcmp,
DWORD virtual_protect_iat,
DWORD original_iat_protect,
DWORD temporary_protect_address
)
{
/* mov dword ptr [iat_address], original_lstrcmp */
emit_byte(code, 0xc7);
emit_byte(code, 0x05);
emit_dword(code, iat_address);
emit_dword(code, original_lstrcmp);
emit_push_immediate(code, temporary_protect_address);
emit_push_immediate(code, original_iat_protect);
emit_push_immediate(code, sizeof(DWORD));
emit_push_immediate(code, iat_address);
emit_call_iat(code, virtual_protect_iat);
}
static BOOL build_remote_hook(
BYTE *image,
SIZE_T image_size,
DWORD remote_base,
DWORD game_dll_base,
DWORD original_lstrcmp,
DWORD original_iat_protect,
SIZE_T *code_size
)
{
CodeBuffer code;
DWORD map_entry = address_from_rva(game_dll_base, GAME_DLL_MAP_ENTRY_RVA);
DWORD code_cave = address_from_rva(game_dll_base, GAME_DLL_CODE_CAVE_RVA);
DWORD login_return = address_from_rva(game_dll_base, GAME_DLL_LOGIN_RETURN_RVA);
DWORD lstrcmp_iat = address_from_rva(game_dll_base, GAME_DLL_LSTRCMPA_IAT_RVA);
DWORD virtual_protect_iat = address_from_rva(
game_dll_base,
GAME_DLL_VIRTUAL_PROTECT_IAT_RVA
);
DWORD flush_iat = address_from_rva(
game_dll_base,
GAME_DLL_FLUSH_INSTRUCTION_CACHE_IAT_RVA
);
DWORD get_process_iat = address_from_rva(
game_dll_base,
GAME_DLL_GET_CURRENT_PROCESS_IAT_RVA
);
DWORD reclaim_source = remote_base + REMOTE_RECLAIM_OFFSET;
DWORD jump_source = remote_base + REMOTE_MAP_JUMP_OFFSET;
DWORD state_address = remote_base + REMOTE_STATE_OFFSET;
DWORD old_cave_protect = remote_base + REMOTE_OLD_CAVE_PROTECT_OFFSET;
DWORD old_entry_protect = remote_base + REMOTE_OLD_ENTRY_PROTECT_OFFSET;
DWORD temporary_protect = remote_base + REMOTE_TEMP_PROTECT_OFFSET;
SIZE_T nonzero_jump;
SIZE_T wrong_caller_jump;
SIZE_T already_triggered_jump;
SIZE_T cave_failure_jump;
SIZE_T entry_failure_jump;
SIZE_T success_jump;
SIZE_T finish_offset;
SIZE_T entry_failure_offset;
SIZE_T failure_offset;
SIZE_T success_offset;
if (image_size < REMOTE_TEMP_PROTECT_OFFSET + sizeof(DWORD))
{
return FALSE;
}
memset(image, 0, image_size);
memcpy(
image + REMOTE_RECLAIM_OFFSET,
PROVEN_RECLAIM_HOOK,
sizeof(PROVEN_RECLAIM_HOOK)
);
memcpy(image + REMOTE_MAP_JUMP_OFFSET, MAP_ENTRY_JUMP, sizeof(MAP_ENTRY_JUMP));
code.data = image;
code.capacity = REMOTE_RECLAIM_OFFSET;
code.length = 0;
code.valid = TRUE;
/* Standard-call lstrcmpA wrapper, preserving every nonvolatile register. */
emit_bytes(&code, (const BYTE[]){0x55, 0x89, 0xe5, 0x53, 0x56, 0x57}, 6);
emit_bytes(&code, (const BYTE[]){0xff, 0x75, 0x0c, 0xff, 0x75, 0x08}, 6);
emit_byte(&code, 0xb8);
emit_dword(&code, original_lstrcmp);
emit_bytes(&code, (const BYTE[]){0xff, 0xd0, 0x85, 0xc0}, 4);
nonzero_jump = emit_near_jump_placeholder(&code, 0x85); /* jne finish */
emit_bytes(&code, (const BYTE[]){0x81, 0x7d, 0x04}, 3);
emit_dword(&code, login_return);
wrong_caller_jump = emit_near_jump_placeholder(&code, 0x85); /* jne finish */
emit_bytes(&code, (const BYTE[]){0x80, 0x3d}, 2);
emit_dword(&code, state_address);
emit_byte(&code, 0x00);
already_triggered_jump = emit_near_jump_placeholder(&code, 0x85); /* jne finish */
emit_set_state(&code, state_address, 1);
/* Make the two exact publisher-code ranges writable only after login. */
emit_push_immediate(&code, old_cave_protect);
emit_push_immediate(&code, PAGE_EXECUTE_READWRITE);
emit_push_immediate(&code, sizeof(PROVEN_RECLAIM_HOOK));
emit_push_immediate(&code, code_cave);
emit_call_iat(&code, virtual_protect_iat);
emit_bytes(&code, (const BYTE[]){0x85, 0xc0}, 2);
cave_failure_jump = emit_near_jump_placeholder(&code, 0x84); /* je failure */
emit_push_immediate(&code, old_entry_protect);
emit_push_immediate(&code, PAGE_EXECUTE_READWRITE);
emit_push_immediate(&code, sizeof(MAP_ENTRY_JUMP));
emit_push_immediate(&code, map_entry);
emit_call_iat(&code, virtual_protect_iat);
emit_bytes(&code, (const BYTE[]){0x85, 0xc0}, 2);
entry_failure_jump = emit_near_jump_placeholder(&code, 0x84); /* je entry_failure */
/* Copy only after the exact account-ID comparison returned success. */
emit_byte(&code, 0xfc);
emit_byte(&code, 0xbe);
emit_dword(&code, reclaim_source);
emit_byte(&code, 0xbf);
emit_dword(&code, code_cave);
emit_byte(&code, 0xb9);
emit_dword(&code, sizeof(PROVEN_RECLAIM_HOOK));
emit_bytes(&code, (const BYTE[]){0xf3, 0xa4}, 2);
emit_byte(&code, 0xbe);
emit_dword(&code, jump_source);
emit_byte(&code, 0xbf);
emit_dword(&code, map_entry);
emit_byte(&code, 0xb9);
emit_dword(&code, sizeof(MAP_ENTRY_JUMP));
emit_bytes(&code, (const BYTE[]){0xf3, 0xa4}, 2);
/* x86 is coherent, but use the publisher's own imports for an explicit flush. */
emit_call_iat(&code, get_process_iat);
emit_push_immediate(&code, 0);
emit_push_immediate(&code, 0);
emit_byte(&code, 0x50);
emit_call_iat(&code, flush_iat);
/* Restore publisher section protections after the five-byte entry change. */
emit_push_immediate(&code, temporary_protect);
emit_bytes(&code, (const BYTE[]){0xff, 0x35}, 2);
emit_dword(&code, old_entry_protect);
emit_push_immediate(&code, sizeof(MAP_ENTRY_JUMP));
emit_push_immediate(&code, map_entry);
emit_call_iat(&code, virtual_protect_iat);
emit_push_immediate(&code, temporary_protect);
emit_bytes(&code, (const BYTE[]){0xff, 0x35}, 2);
emit_dword(&code, old_cave_protect);
emit_push_immediate(&code, sizeof(PROVEN_RECLAIM_HOOK));
emit_push_immediate(&code, code_cave);
emit_call_iat(&code, virtual_protect_iat);
emit_restore_iat(
&code,
lstrcmp_iat,
original_lstrcmp,
virtual_protect_iat,
original_iat_protect,
temporary_protect
);
emit_set_state(&code, state_address, 2);
success_jump = emit_near_jump_placeholder(&code, 0); /* jmp finish */
entry_failure_offset = code.length;
emit_push_immediate(&code, temporary_protect);
emit_bytes(&code, (const BYTE[]){0xff, 0x35}, 2);
emit_dword(&code, old_cave_protect);
emit_push_immediate(&code, sizeof(PROVEN_RECLAIM_HOOK));
emit_push_immediate(&code, code_cave);
emit_call_iat(&code, virtual_protect_iat);
failure_offset = code.length;
emit_restore_iat(
&code,
lstrcmp_iat,
original_lstrcmp,
virtual_protect_iat,
original_iat_protect,
temporary_protect
);
emit_set_state(&code, state_address, 3);
success_offset = code.length;
emit_bytes(&code, (const BYTE[]){0x31, 0xc0}, 2); /* successful lstrcmpA result */
finish_offset = code.length;
emit_bytes(&code, (const BYTE[]){0x5f, 0x5e, 0x5b, 0x89, 0xec, 0x5d, 0xc2, 0x08, 0x00}, 9);
patch_near_jump(&code, nonzero_jump, finish_offset);
patch_near_jump(&code, wrong_caller_jump, finish_offset);
patch_near_jump(&code, already_triggered_jump, success_offset);
patch_near_jump(&code, cave_failure_jump, failure_offset);
patch_near_jump(&code, entry_failure_jump, entry_failure_offset);
patch_near_jump(&code, success_jump, success_offset);
if (!code.valid)
{
return FALSE;
}
*code_size = code.length;
return TRUE;
}
static DWORD find_game_dll(DWORD process_id, DWORD *module_size)
{
HANDLE snapshot;
MODULEENTRY32W module;
DWORD result = 0;
snapshot = CreateToolhelp32Snapshot(
TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32,
process_id
);
if (snapshot == INVALID_HANDLE_VALUE)
{
return 0;
}
memset(&module, 0, sizeof(module));
module.dwSize = sizeof(module);
if (Module32FirstW(snapshot, &module))
{
do
{
if (_wcsicmp(module.szModule, L"game.dll") == 0)
{
result = (DWORD)(uintptr_t)module.modBaseAddr;
*module_size = module.modBaseSize;
break;
}
}
while (Module32NextW(snapshot, &module));
}
CloseHandle(snapshot);
return result;
}
static DWORD wait_for_game_dll(
HANDLE process,
DWORD process_id,
DWORD *module_size
)
{
ULONGLONG deadline = GetTickCount64() + WORLD_FIX_WAIT_MILLISECONDS;
DWORD base;
while (GetTickCount64() < deadline)
{
if (WaitForSingleObject(process, 25) == WAIT_OBJECT_0)
{
return 0;
}
base = find_game_dll(process_id, module_size);
if (base != 0)
{
return base;
}
Sleep(10);
}
return 0;
}
static BOOL verify_game_dll_memory(HANDLE process, DWORD base, DWORD module_size)
{
BYTE map_entry[sizeof(MAP_ENTRY_ORIGINAL)];
BYTE login_compare[sizeof(LOGIN_COMPARE_ORIGINAL)];
BYTE code_cave[sizeof(PROVEN_RECLAIM_HOOK)];
DWORD iat_values[4];
if (module_size < GAME_DLL_MINIMUM_IMAGE_SIZE)
{
return FALSE;
}
if (!read_exact(
process,
address_from_rva(base, GAME_DLL_MAP_ENTRY_RVA),
map_entry,
sizeof(map_entry)
) || memcmp(map_entry, MAP_ENTRY_ORIGINAL, sizeof(map_entry)) != 0)
{
return FALSE;
}
if (!read_exact(
process,
address_from_rva(base, GAME_DLL_LOGIN_RETURN_RVA),
login_compare,
sizeof(login_compare)
) || memcmp(login_compare, LOGIN_COMPARE_ORIGINAL, sizeof(login_compare)) != 0)
{
return FALSE;
}
if (!read_exact(
process,
address_from_rva(base, GAME_DLL_CODE_CAVE_RVA),
code_cave,
sizeof(code_cave)
))
{
return FALSE;
}
for (SIZE_T index = 0; index < sizeof(code_cave); ++index)
{
if (code_cave[index] != 0)
{
return FALSE;
}
}
if (!read_exact(
process,
address_from_rva(base, GAME_DLL_VIRTUAL_PROTECT_IAT_RVA),
&iat_values[0],
sizeof(iat_values[0])
) || !read_exact(
process,
address_from_rva(base, GAME_DLL_FLUSH_INSTRUCTION_CACHE_IAT_RVA),
&iat_values[1],
sizeof(iat_values[1])
) || !read_exact(
process,
address_from_rva(base, GAME_DLL_GET_CURRENT_PROCESS_IAT_RVA),
&iat_values[2],
sizeof(iat_values[2])
) || !read_exact(
process,
address_from_rva(base, GAME_DLL_LSTRCMPA_IAT_RVA),
&iat_values[3],
sizeof(iat_values[3])
))
{
return FALSE;
}
return iat_values[0] != 0 && iat_values[1] != 0
&& iat_values[2] != 0 && iat_values[3] != 0;
}
static BOOL install_world_fix(
HANDLE process,
DWORD game_dll_base,
WorldFixInstallation *installation
)
{
BYTE remote_image[REMOTE_ALLOCATION_SIZE];
DWORD original_lstrcmp;
DWORD original_iat_protect;
DWORD hook_address;
DWORD lstrcmp_iat = address_from_rva(
game_dll_base,
GAME_DLL_LSTRCMPA_IAT_RVA
);
LPVOID remote;
SIZE_T code_size;
if (!read_exact(
process,
lstrcmp_iat,
&original_lstrcmp,
sizeof(original_lstrcmp)
))
{
return FALSE;
}
remote = VirtualAllocEx(
process,
NULL,
REMOTE_ALLOCATION_SIZE,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE
);
if (remote == NULL)
{
return FALSE;
}
if (!build_remote_hook(
remote_image,
sizeof(remote_image),
(DWORD)(uintptr_t)remote,
game_dll_base,
original_lstrcmp,
PAGE_READONLY,
&code_size
))
{
VirtualFreeEx(process, remote, 0, MEM_RELEASE);
return FALSE;
}
if (!write_exact(
process,
(DWORD)(uintptr_t)remote,
remote_image,
sizeof(remote_image)
))
{
VirtualFreeEx(process, remote, 0, MEM_RELEASE);
return FALSE;
}
if (!VirtualProtectEx(
process,
(LPVOID)(uintptr_t)lstrcmp_iat,
sizeof(DWORD),
PAGE_READWRITE,
&original_iat_protect
))
{
VirtualFreeEx(process, remote, 0, MEM_RELEASE);
return FALSE;
}
/* Rebuild so the callback restores the exact protection returned above. */
if (!build_remote_hook(
remote_image,
sizeof(remote_image),
(DWORD)(uintptr_t)remote,
game_dll_base,
original_lstrcmp,
original_iat_protect,
&code_size
) || !write_exact(
process,
(DWORD)(uintptr_t)remote,
remote_image,
sizeof(remote_image)
))
{
DWORD ignored;
VirtualProtectEx(
process,
(LPVOID)(uintptr_t)lstrcmp_iat,
sizeof(DWORD),
original_iat_protect,
&ignored
);
VirtualFreeEx(process, remote, 0, MEM_RELEASE);
return FALSE;
}
hook_address = (DWORD)(uintptr_t)remote;
if (!write_exact(process, lstrcmp_iat, &hook_address, sizeof(hook_address)))
{
DWORD ignored;
VirtualProtectEx(
process,
(LPVOID)(uintptr_t)lstrcmp_iat,
sizeof(DWORD),
original_iat_protect,
&ignored
);
VirtualFreeEx(process, remote, 0, MEM_RELEASE);
return FALSE;
}
FlushInstructionCache(process, remote, code_size);
installation->remote_state = (DWORD)(uintptr_t)remote + REMOTE_STATE_OFFSET;
installation->remote_allocation = remote;
return TRUE;
}
static DWORD wait_for_game_and_report(
HANDLE process,
const WorldFixInstallation *installation
)
{
BOOL reported = FALSE;
for (;;)
{
DWORD wait_result = WaitForSingleObject(process, 100);
if (wait_result == WAIT_OBJECT_0)
{
DWORD exit_code = 1;
GetExitCodeProcess(process, &exit_code);
return exit_code;
}
if (!reported && installation->remote_state != 0)
{
BYTE state = 0;
if (read_exact(process, installation->remote_state, &state, sizeof(state)))
{
if (state == 2)
{
fprintf(
stderr,
"PTWORLD: login accepted; world-transition fix activated in memory.\n"
);
reported = TRUE;
}
else if (state == 3)
{
fprintf(
stderr,
"PTWORLD: login succeeded, but the verified memory fix could not be activated.\n"
);
reported = TRUE;
}
}
}
}
}
static int run_self_test(void)
{
BYTE image[REMOTE_ALLOCATION_SIZE];
SIZE_T code_size = 0;
if (!build_remote_hook(
image,
sizeof(image),
0x50000000u,
0x10000000u,
0x7b621000u,
PAGE_READONLY,
&code_size
) || code_size == 0 || code_size >= REMOTE_RECLAIM_OFFSET)
{
fprintf(stderr, "PTWORLD-SELFTEST: remote hook construction failed.\n");
return 1;
}
if (memcmp(
image + REMOTE_RECLAIM_OFFSET,
PROVEN_RECLAIM_HOOK,
sizeof(PROVEN_RECLAIM_HOOK)
) != 0 || memcmp(
image + REMOTE_MAP_JUMP_OFFSET,
MAP_ENTRY_JUMP,
sizeof(MAP_ENTRY_JUMP)
) != 0 || image[REMOTE_STATE_OFFSET] != 0)
{
fprintf(stderr, "PTWORLD-SELFTEST: remote payload layout failed.\n");
return 1;
}
fprintf(
stdout,
"PTWORLD-SELFTEST: ok; remote code=%lu bytes.\n",
(unsigned long)code_size
);
return 0;
}
int wmain(int argument_count, wchar_t **arguments)
{
STARTUPINFOW startup;
PROCESS_INFORMATION process;
WorldFixInstallation installation;
WCHAR game_path[MAX_PATH];
WCHAR command_line[MAX_PATH + 64];
DWORD game_dll_base;
DWORD game_dll_size = 0;
DWORD exit_code;
DWORD path_length;
if (
argument_count == 2
&& wcscmp(arguments[1], L"--self-test") == 0
)
{
return run_self_test();
}
memset(&startup, 0, sizeof(startup));
memset(&process, 0, sizeof(process));
memset(&installation, 0, sizeof(installation));
startup.cb = sizeof(startup);
path_length = GetFullPathNameW(L"Game.exe", MAX_PATH, game_path, NULL);
if (path_length == 0 || path_length >= MAX_PATH)
{
fprintf(stderr, "PTWORLD: Game.exe is not available in the working directory.\n");
return 111;
}
if (swprintf(
command_line,
MAX_PATH + 64,
L"\"%ls\" PTEU_REBORN",
game_path
) < 0)
{
fprintf(stderr, "PTWORLD: could not construct the game command line.\n");
return 111;
}
if (!CreateProcessW(
game_path,
command_line,
NULL,
NULL,
TRUE,
0,
NULL,
NULL,
&startup,
&process
))
{
fprintf(
stderr,
"PTWORLD: could not start Game.exe (Windows error %lu).\n",
(unsigned long)GetLastError()
);
return 111;
}
CloseHandle(process.hThread);
game_dll_base = wait_for_game_dll(
process.hProcess,
process.dwProcessId,
&game_dll_size
);
if (game_dll_base == 0)
{
fprintf(
stderr,
"PTWORLD: game.dll was not available for verification; continuing pristine.\n"
);
}
else if (!verify_game_dll_memory(process.hProcess, game_dll_base, game_dll_size))
{
fprintf(
stderr,
"PTWORLD: game.dll memory did not match V21.75.83; continuing pristine.\n"
);
}
else if (!install_world_fix(process.hProcess, game_dll_base, &installation))
{
fprintf(
stderr,
"PTWORLD: the post-login hook could not be armed; continuing pristine.\n"
);
}
else
{
fprintf(
stderr,
"PTWORLD: publisher game.dll verified; post-login world fix armed.\n"
);
}
exit_code = wait_for_game_and_report(process.hProcess, &installation);
CloseHandle(process.hProcess);
return (int)exit_code;
}