// Proof of concept code for a CoH 1.7 MapHack #include #include // Definitions of classes to be used struct tLocString { wchar_t const* c_str() const; }; struct tVector3f { float x,y,z; }; struct tPlayer { tLocString const& GetName () const; char const* GetRaceName () const; }; struct tEntity { tPlayer* GetOwner () const; tVector3f const& GetPosition () const; }; // Function forward definitions bool InjectIntoRelicCoh (); void CreateHook (unsigned char* pInterceptLoc, DWORD pHookFunction, DWORD iHookLen); void CreateHook_WriteJump (unsigned char *pInterceptLoc, DWORD pTarget); void CreateHook_WriteCall (unsigned char *pInterceptLoc, DWORD pTarget); void Hook_ScarCreated (); void Hook_ScarUpdated (); void Hook_ScarDestroyed (); HWND Graphical_CreateWindow (); void Graphical_Render (HDC hDC); // Imported functions tPlayer* (__stdcall *g_fGetPlayerAt) (unsigned long); unsigned long (__stdcall *g_fGetPlayerCount) (void); tEntity* (__stdcall *g_fGetEntityAt) (unsigned long); unsigned long (__stdcall *g_fGetEntityCount) (void); float (__stdcall *g_fGetWorldWidth) (void); float (__stdcall *g_fGetWorldLength) (void); // Globals HWND g_hOurWindow; HMODULE g_hLocalizerDll; HMODULE g_hSimEngineDll; HMODULE g_hModDll; // Constants const unsigned char k_opcode_NOP = 0x90; const unsigned char k_opcode_CALL = 0xE8; const int k_oplen_CALL = 5; const unsigned char k_opcode_JMP = 0xE9; const int k_oplen_JMP = 5; // Template functions template void GetUnexportedProcAddress(R pRoot, unsigned long iOffset, T& pDest) { pDest = (T)(((unsigned char*)pRoot) + iOffset); } template T upper(T a, T b) { if(a > b) return a; return b; } // Functions BOOL APIENTRY DllMain(HMODULE hModule, DWORD iReason, LPVOID pReserved) { if(iReason == DLL_PROCESS_ATTACH) { DisableThreadLibraryCalls(hModule); return InjectIntoRelicCoh() ? TRUE : FALSE; } return TRUE; } bool InjectIntoRelicCoh() { g_hSimEngineDll = LoadLibrary("SimEngine.dll"); g_hModDll = LoadLibrary("WW2Mod.dll"); if( (g_hSimEngineDll == 0) || (g_hModDll == 0) ) return false; GetUnexportedProcAddress(GetProcAddress(g_hModDll, "GetDllInterface"), 0x230A0,g_fGetEntityCount); GetUnexportedProcAddress(GetProcAddress(g_hModDll, "GetDllInterface"), 0x23670,g_fGetEntityAt); GetUnexportedProcAddress(GetProcAddress(g_hSimEngineDll, "?Init@LuaRuleSystem@@QAEXPAVLuaConfig@@@Z"), 0x300, g_fGetWorldLength); GetUnexportedProcAddress(GetProcAddress(g_hSimEngineDll, "?Init@LuaRuleSystem@@QAEXPAVLuaConfig@@@Z"), 0x320, g_fGetWorldWidth); GetUnexportedProcAddress(GetProcAddress(g_hSimEngineDll, "?Init@LuaRuleSystem@@QAEXPAVLuaConfig@@@Z"), 0x390, g_fGetPlayerCount); GetUnexportedProcAddress(GetProcAddress(g_hSimEngineDll, "?Init@LuaRuleSystem@@QAEXPAVLuaConfig@@@Z"), 0x900, g_fGetPlayerAt); CreateHook((unsigned char*)GetProcAddress(g_hSimEngineDll, "?StartScript@Scar@@QAE_NXZ"), (DWORD)Hook_ScarCreated, 6); CreateHook((unsigned char*)GetProcAddress(g_hSimEngineDll, "?Update@Scar@@QAEXM@Z"), (DWORD)Hook_ScarUpdated, 6); CreateHook((unsigned char*)GetProcAddress(g_hSimEngineDll, "??1Scar@@QAE@XZ"), (DWORD)Hook_ScarDestroyed, 7); return true; } // Hooks __declspec(naked) void Hook_ScarCreated() { __asm push eax; push ecx; push edx; push esi; g_hOurWindow = Graphical_CreateWindow(); __asm pop esi; pop edx; pop ecx; pop eax; ret; } void Hook_ScarUpdated_Real() { HDC hDC = GetDC(g_hOurWindow); try { Graphical_Render(hDC); } catch(...) {} ReleaseDC(g_hOurWindow, hDC); } __declspec(naked) void Hook_ScarUpdated() { __asm push eax; push ecx; push edx; push esi; Hook_ScarUpdated_Real(); __asm pop esi; pop edx; pop ecx; pop eax; ret; } __declspec(naked) void Hook_ScarDestroyed() { __asm push eax; push ecx; push edx; push esi; DestroyWindow(g_hOurWindow); UnregisterClass("ConceptHaxx_Window", (HINSTANCE)GetModuleHandle(0)); __asm pop esi; pop edx; pop ecx; pop eax; ret; } // Graphicals void Graphical_Render(HDC hDC) { RECT r; r.left = 0; r.top = 0; r.right = 1280; r.bottom = 1024; FillRect(hDC, &r, (HBRUSH)(COLOR_WINDOW + 1)); unsigned long iEntityCount = g_fGetEntityCount(); int iXOff = 0; for(unsigned long iPlayer = 1; iPlayer <= g_fGetPlayerCount(); ++iPlayer,iXOff+=151) { tPlayer* pPlayer = g_fGetPlayerAt(iPlayer); char sBuff[256]; sprintf(sBuff, "%S", pPlayer->GetName().c_str()); TextOut(hDC, iXOff, 0, sBuff, strlen(sBuff)); sprintf(sBuff, "%s", pPlayer->GetRaceName()); TextOut(hDC, iXOff, 24, sBuff, strlen(sBuff)); HBRUSH hbrBlack = (HBRUSH)GetStockObject(BLACK_BRUSH); int iMapYOff = 50; r.left = iXOff; r.right = iXOff + 150; r.top = iMapYOff; r.bottom = iMapYOff + 150; FillRect(hDC, &r, hbrBlack); ++r.left; --r.right; --r.bottom; ++r.top; float fMapN = upper(g_fGetWorldLength(), g_fGetWorldWidth()); float fMapMul = ((float)(r.right - r.left)) / (fMapN * 2.0); for(unsigned long i = 0; i < iEntityCount; ++i) { tEntity *pEntity = g_fGetEntityAt(i); const tVector3f & oPos = pEntity->GetPosition(); float fX = ((oPos.x + fMapN) * fMapMul) + 0.5; float fY = ((-oPos.z + fMapN) * fMapMul) + 0.5; tPlayer *pOwner = pEntity->GetOwner(); if(pOwner == pPlayer) { SetPixel(hDC, r.left + (int)fX, r.top + (int)fY, RGB(0, 255, 0)); } else if(pOwner) { SetPixel(hDC, r.left + (int)fX, r.top + (int)fY, RGB(255, 0, 0)); } } } } HWND Graphical_CreateWindow() { WNDCLASSEX wndclass; wndclass.cbSize = sizeof(wndclass); wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = DefWindowProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = (HINSTANCE)GetModuleHandle(0); wndclass.hIcon = 0; wndclass.hIconSm = 0; wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = "ConceptHaxx_Window"; RegisterClassEx(&wndclass); HWND hWnd = CreateWindow("ConceptHaxx_Window", "MapHack", WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX, 1280, 0, 1280, 1024, NULL, NULL, (HINSTANCE)GetModuleHandle(0), NULL); ShowWindow(hWnd, SW_SHOWNOACTIVATE); return hWnd; } // Hook creating functions void CreateHook(unsigned char* pInterceptLoc, DWORD pHookFunction, DWORD iHookLen) { DWORD dwPermission; unsigned char *pTrampoline; VirtualProtect((LPVOID)pInterceptLoc, iHookLen, PAGE_EXECUTE_READWRITE, &dwPermission); pTrampoline = new unsigned char[k_oplen_CALL + iHookLen + k_oplen_JMP]; CreateHook_WriteCall(pTrampoline, pHookFunction); memcpy(pTrampoline + k_oplen_CALL, pInterceptLoc, iHookLen); CreateHook_WriteJump(pTrampoline + k_oplen_CALL + iHookLen, (DWORD)(pInterceptLoc + iHookLen)); CreateHook_WriteJump(pInterceptLoc, (DWORD)pTrampoline); for(DWORD i = k_oplen_JMP; i < iHookLen; ++i) pInterceptLoc[i] = k_opcode_NOP; VirtualProtect((LPVOID)pInterceptLoc, iHookLen, dwPermission, &dwPermission); VirtualProtect((LPVOID)pTrampoline, k_oplen_CALL + iHookLen + k_oplen_JMP, PAGE_EXECUTE_READWRITE, &dwPermission); } void CreateHook_WriteJump(unsigned char *pInterceptLoc, DWORD pTarget) { *pInterceptLoc = k_opcode_JMP; *((LPDWORD)( pInterceptLoc + 1 ) ) = pTarget - ( ((DWORD)pInterceptLoc) + 5 ); } void CreateHook_WriteCall(unsigned char *pInterceptLoc, DWORD pTarget) { *pInterceptLoc = k_opcode_CALL; *((LPDWORD)( pInterceptLoc + 1 ) ) = pTarget - ( ((DWORD)pInterceptLoc) + 5 ); } // Reimplemented functions for Relic classes __declspec(naked) tLocString const& tPlayer::GetName() const { __asm lea eax, [ecx+12]; __asm retn; } __declspec(naked) char const* tPlayer::GetRaceName() const { __asm mov eax, [ecx+196]; __asm add eax, 12; __asm retn; } __declspec(naked) wchar_t const* tLocString::c_str() const { __asm mov eax, [ecx]; __asm mov eax, [eax]; __asm retn; } __declspec(naked) tPlayer* tEntity::GetOwner() const { __asm mov eax, [ecx+52]; __asm retn; } __declspec(naked) tVector3f const& tEntity::GetPosition() const { __asm lea eax, [ecx+124]; __asm retn; }