// LockPort companion client mod, API 1.2.1. // Build: zig cc -target x86-windows-gnu -shared -O2 LockPort.c -o LockPort.dll // Designed for the inspected OctoWoW 1.18.1 x86 executable layout. // No background game thread: Lua APIs run only on the game's Lua thread. #define WIN32_LEAN_AND_MEAN #include #include #include #include typedef void (__stdcall *LoadFunctionsFn)(void); typedef int (__fastcall *LuaFunction)(void *); typedef void (__fastcall *RegisterFn)(const char *, LuaFunction); typedef const char *(__fastcall *ToStringFn)(void *, int); typedef void (__fastcall *PushStringFn)(void *, const char *); typedef void (__fastcall *PushBooleanFn)(void *, int); typedef int (__fastcall *GetTopFn)(void *); typedef void (__fastcall *SetTopFn)(void *, int); typedef int32_t (__fastcall *SpellSlotFn)(const char *, uint32_t *); typedef uint64_t (__fastcall *UnitGuidFn)(const char *); typedef void (__fastcall *BookCastFn)(uint32_t, uint32_t, uint64_t); static LoadFunctionsFn originalLoad; static void *trampoline; static DWORD luaThread; static DWORD lastAttempt; static int hasAttempt; static unsigned char saved[6]; static SIZE_T patchLength; static int __fastcall Version(void *state) { ((PushStringFn)0x006F3890)(state, "1.2.1"); return 1; } static int validUnit(const char *s) { if (!s) return 0; int limit; if (strncmp(s, "raid", 4) == 0) { s += 4; limit = 40; } else if (strncmp(s, "party", 5) == 0) { s += 5; limit = 4; } else return 0; if (*s < '1' || *s > '9') return 0; int number = *s++ - '0'; if (*s >= '0' && *s <= '9') number = number * 10 + *s++ - '0'; return *s == 0 && number >= 1 && number <= limit; } static int result(void *state, int attempted, const char *reason) { ((PushBooleanFn)0x006F39F0)(state, attempted); ((PushStringFn)0x006F3890)(state, reason); return 2; } static int __fastcall Summon(void *state) { DWORD now = GetTickCount(); const char *unit = ((ToStringFn)0x006F3690)(state, 1); if (GetCurrentThreadId() != luaThread) return result(state, 0, "wrong thread"); if (!validUnit(unit)) return result(state, 0, "invalid group unit"); if (hasAttempt && (DWORD)(now - lastAttempt) < 2000) return result(state, 0, "native rate limit"); // Resolve an explicit target and the actual spellbook entry. No Lua // string execution, hardware-input simulation, or target-dependent cast. uint64_t guid = ((UnitGuidFn)0x00515970)(unit); if (!guid) return result(state, 0, "unit unavailable"); uint32_t book = 0; int32_t slot = ((SpellSlotFn)0x004B3950)("Ritual of Summoning", &book); if (slot < 0 || slot >= 1024 || book != 0) return result(state, 0, "ritual not in spellbook"); lastAttempt = now; hasAttempt = 1; // The action gate (0x494A50, kind 2) refuses casts from restricted // frame scripts such as OnUpdate, including native calls made by Lua. // Mirror command-dispatch context scoping: save, clear for this one // engine call, and restore immediately. No persistent gate patch. volatile uintptr_t *context = (volatile uintptr_t *)0x00CEEAC0; volatile uint32_t *depth = (volatile uint32_t *)0x00CEEAC4; uintptr_t savedContext = *context; uint32_t savedDepth = *depth; *context = 0; *depth = 0; ((BookCastFn)0x004B3300)((uint32_t)slot, book, guid); *context = savedContext; *depth = savedDepth; return result(state, 1, "spellbook request sent"); } static void __stdcall LoadHook(void) { originalLoad(); luaThread = GetCurrentThreadId(); // Pin the module before giving Lua pointers into its code. HMODULE pinned = NULL; GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_PIN, (LPCSTR)(uintptr_t)&Version, &pinned); ((RegisterFn)0x00704120)("GetLockPortVersion", Version); ((RegisterFn)0x00704120)("LockPortNativeSummon", Summon); } typedef struct { DWORD rva; unsigned char bytes[16]; } Signature; static const Signature signatures[] = { {0x90250,{0x56,0xe8,0xfa,0x60,0x27,0x00,0xe8,0x45,0x66,0x0a,0x00,0xe8,0x90,0xf0,0x05,0x00}}, {0xb3950,{0x55,0x8b,0xec,0x81,0xec,0x04,0x06,0x00,0x00,0x56,0x57,0x68,0x00,0x04,0x00,0x00}}, {0xb3300,{0x55,0x8b,0xec,0x83,0xec,0x20,0x53,0x56,0x8b,0xf2,0x57,0x89,0x75,0xf8,0x8b,0xd9}}, {0x115970,{0x55,0x8b,0xec,0x51,0x56,0x68,0x90,0x00,0x00,0x00,0x8b,0xf1,0xe8,0xcf,0x2b,0xf5}}, {0x304120,{0x56,0x57,0x8b,0xf9,0xe8,0xa7,0xff,0xff,0xff,0x8b,0xf0,0x6a,0x00,0x8b,0xce,0xe8}}, {0x304cd0,{0x56,0x6a,0x00,0x8b,0xf1,0x52,0x56,0xe8,0x14,0x5a,0xf4,0xff,0x8b,0xd0,0x8b,0xce}}, {0x2f3690,{0x56,0x57,0x8b,0xf9,0xe8,0x77,0xfd,0xff,0xff,0x8b,0xf0,0x85,0xf6,0x75,0x03,0x5f}}, {0x2f3890,{0x85,0xd2,0x56,0x8b,0xf1,0x75,0x06,0x5e,0xe9,0x53,0xff,0xff,0xff,0x57,0x83,0xc9}}, {0x2f39f0,{0x8b,0x41,0x08,0xc7,0x00,0x01,0x00,0x00,0x00,0x56,0x8b,0xf2,0x8b,0x15,0xc0,0xea}}, {0x2f3070,{0x8b,0x41,0x08,0x2b,0x41,0x0c,0xc1,0xf8,0x04,0xc3,0x90,0x90,0x90,0x90,0x90,0x90}}, {0x2f3080,{0x85,0xd2,0x8b,0x41,0x08,0x7c,0x38,0x56,0x8b,0x71,0x0c,0xc1,0xe2,0x04,0x03,0xf2}} }; static int fileOffset(const unsigned char *image, DWORD rva, DWORD *offset) { const IMAGE_DOS_HEADER *dos = (const IMAGE_DOS_HEADER *)image; const IMAGE_NT_HEADERS32 *nt = (const IMAGE_NT_HEADERS32 *)(image + dos->e_lfanew); const IMAGE_SECTION_HEADER *sections = IMAGE_FIRST_SECTION(nt); for (unsigned int i = 0; i < nt->FileHeader.NumberOfSections; ++i) { const IMAGE_SECTION_HEADER *s = sections + i; if (rva >= s->VirtualAddress && rva + 16 <= s->VirtualAddress + s->SizeOfRawData) { *offset = s->PointerToRawData + rva - s->VirtualAddress; return 1; } } return 0; } static int matchesClient(void) { unsigned char *base = (unsigned char *)GetModuleHandleA(NULL); if ((uintptr_t)base != 0x00400000) return 0; IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER *)base; if (dos->e_magic != IMAGE_DOS_SIGNATURE || dos->e_lfanew <= 0 || dos->e_lfanew > 4096) return 0; IMAGE_NT_HEADERS32 *nt = (IMAGE_NT_HEADERS32 *)(base + dos->e_lfanew); if (nt->Signature != IMAGE_NT_SIGNATURE || nt->FileHeader.Machine != IMAGE_FILE_MACHINE_I386 || nt->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC || nt->OptionalHeader.SizeOfImage < 0x00900000) return 0; char filename[MAX_PATH]; if (!GetModuleFileNameA(NULL, filename, MAX_PATH) || strlen(filename) >= MAX_PATH - 1) return 0; HANDLE file = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (file == INVALID_HANDLE_VALUE) return 0; int ok = 1; for (SIZE_T i = 0; i < sizeof(signatures) / sizeof(signatures[0]); ++i) { DWORD offset = 0, count = 0; unsigned char bytes[16]; if (!fileOffset(base, signatures[i].rva, &offset) || SetFilePointer(file, offset, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER || !ReadFile(file, bytes, sizeof(bytes), &count, NULL) || count != sizeof(bytes) || memcmp(bytes, signatures[i].bytes, sizeof(bytes))) { ok = 0; break; } } CloseHandle(file); return ok; } static void relativeJump(unsigned char *at, const void *destination) { at[0] = 0xE9; int32_t displacement = (int32_t)((uintptr_t)destination - (uintptr_t)at - 5); memcpy(at + 1, &displacement, 4); } static int install(void) { if (!matchesClient()) return 0; // These clients require a registration compatibility hook for Lua functions // living in a DLL. Nampower, SuperWoW, and ClassicAPI provide it. // Do not alter this shared engine check or interfere with their hooks. unsigned char registrationSupport = *(unsigned char *)0x0042A320; if (registrationSupport != 0xE9 && registrationSupport != 0xC3) return 0; unsigned char *target = (unsigned char *)0x00490250; // Preserve an existing loader detour (Nampower/SuperWoW load order). if (target[0] == 0xE9) { int32_t displacement; memcpy(&displacement, target + 1, 4); unsigned char *previous = target + 5 + displacement; MEMORY_BASIC_INFORMATION info; if (!VirtualQuery(previous, &info, sizeof(info)) || info.State != MEM_COMMIT || !(info.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY))) return 0; originalLoad = (LoadFunctionsFn)previous; patchLength = 5; } else { if (memcmp(target, signatures[0].bytes, 6)) return 0; // Two whole instructions: push esi; call rel32. Relocate that call. trampoline = VirtualAlloc(NULL, 16, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (!trampoline) return 0; unsigned char *t = (unsigned char *)trampoline; memcpy(t, target, 6); int32_t displacement; memcpy(&displacement, target + 2, 4); uintptr_t callTarget = (uintptr_t)target + 6 + displacement; displacement = (int32_t)(callTarget - (uintptr_t)t - 6); memcpy(t + 2, &displacement, 4); relativeJump(t + 6, target + 6); DWORD old; if (!VirtualProtect(t, 16, PAGE_EXECUTE_READ, &old)) { VirtualFree(t, 0, MEM_RELEASE); trampoline = NULL; return 0; } FlushInstructionCache(GetCurrentProcess(), t, 16); originalLoad = (LoadFunctionsFn)t; patchLength = 6; } DWORD old; if (!VirtualProtect(target, patchLength, PAGE_EXECUTE_READWRITE, &old)) return 0; memcpy(saved, target, patchLength); relativeJump(target, (void *)(uintptr_t)&LoadHook); if (patchLength == 6) target[5] = 0x90; DWORD ignored; VirtualProtect(target, patchLength, old, &ignored); FlushInstructionCache(GetCurrentProcess(), target, patchLength); return 1; } BOOL WINAPI DllMain(HINSTANCE module, DWORD reason, LPVOID reserved) { (void)reserved; if (reason == DLL_PROCESS_ATTACH) { DisableThreadLibraryCalls(module); if (!install()) { OutputDebugStringA("LockPort.dll: unsupported client or loader hook; no changes made.\n"); return FALSE; } } // This plugin has process lifetime. Do not hot-unload/reload it. return TRUE; }