830 lines
20 KiB
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;
|
|
}
|