diff --git a/Client/game_sa/HookSystem.h b/Client/game_sa/HookSystem.h index a67466a40d..f29a6aeae9 100644 --- a/Client/game_sa/HookSystem.h +++ b/Client/game_sa/HookSystem.h @@ -74,6 +74,24 @@ bool HookInstall(DWORD dwInstallAddress, T dwHookHandler, int iJmpCodeSize = 5) } } +// For install addresses outside the ranges MemCpy knows how to unprotect; the page must have +// been made writable beforehand (see SetInitialVirtualProtect) +template +bool HookInstallFast(DWORD dwInstallAddress, T dwHookHandler, int iJmpCodeSize = 5) +{ + BYTE JumpBytes[MAX_JUMPCODE_SIZE]; + MemSetFast(JumpBytes, 0x90, MAX_JUMPCODE_SIZE); + if (CreateJump(dwInstallAddress, (DWORD)FunctionPointerToVoidP(dwHookHandler), JumpBytes)) + { + MemCpyFast((PVOID)dwInstallAddress, JumpBytes, iJmpCodeSize); + return true; + } + else + { + return false; + } +} + // Auto detect requirement of US/EU hook installation #define EZHookInstall(type) HookInstall(HOOKPOS_##type, (DWORD)HOOK_##type, HOOKSIZE_##type); diff --git a/Client/multiplayer_sa/CMultiplayerSA_CrashFixHacks.cpp b/Client/multiplayer_sa/CMultiplayerSA_CrashFixHacks.cpp index 27f4936fa6..daac9f4288 100644 --- a/Client/multiplayer_sa/CMultiplayerSA_CrashFixHacks.cpp +++ b/Client/multiplayer_sa/CMultiplayerSA_CrashFixHacks.cpp @@ -4092,6 +4092,40 @@ static int _cdecl CFileLoader_LoadVehicleObject_sscanf(const char* s, const char rearWheelSize, wheelUpgradeClass); } +////////////////////////////////////////////////////////////////////////////////////////// +// +// CPathFind's per map area path node loader calls malloc() for m_pPathNodes[area] (offset +// 0x804) without checking the result, then loops over it using m_dwNumNodes[area] (offset +// 0xfa4) as the count. If the allocation fails, that loop dereferences a null pointer and +// crashes. +// +// Right where the node count gets loaded for the loop's bound check, this hook also checks +// whether m_pPathNodes[area] is null, and if so forces the count to 0 so the loop is skipped. +// +////////////////////////////////////////////////////////////////////////////////////////// +#define HOOKPOS_CPathFind_LoadPathNodeCount_Mid 0x0156F966 +#define HOOKSIZE_CPathFind_LoadPathNodeCount_Mid 7 +DWORD RETURN_CPathFind_LoadPathNodeCount_Mid = 0x0156F96D; +static void __declspec(naked) HOOK_CPathFind_LoadPathNodeCount_Mid() +{ + MTA_VERIFY_HOOK_LOCAL_SIZE; + + // clang-format off + __asm + { + // Replicate the overwritten instruction: EAX = m_dwNumNodes[area] + mov eax, dword ptr [esi + edi*4 + 0x0fa4] + + // If m_pPathNodes[area] failed to allocate, force the node count to 0 for this loop + cmp dword ptr [esi + edi*4 + 0x804], 0 + jne nodesOk + xor eax, eax + nodesOk: + jmp RETURN_CPathFind_LoadPathNodeCount_Mid + } + // clang-format on +} + ////////////////////////////////////////////////////////////////////////////////////////// // // Setup hooks for CrashFixHacks @@ -4191,4 +4225,9 @@ void CMultiplayerSA::InitHooks_CrashFixHacks() // Fix uninitialized wheel scale in CFileLoader::LoadVehicleObject on Win11 24H2 HookInstallCall(CALL_CFileLoader_LoadVehicleObject_sscanf, (DWORD)CFileLoader_LoadVehicleObject_sscanf); + + // This address sits further into the executable than EZHookInstall's normal range check + // allows, so it is installed with HookInstallFast instead; the page is unprotected up front + // in SetInitialVirtualProtect + HookInstallFast(HOOKPOS_CPathFind_LoadPathNodeCount_Mid, (DWORD)HOOK_CPathFind_LoadPathNodeCount_Mid, HOOKSIZE_CPathFind_LoadPathNodeCount_Mid); } diff --git a/Shared/sdk/SharedUtil.MemAccess.hpp b/Shared/sdk/SharedUtil.MemAccess.hpp index e4a487e0f6..3e3ac4af82 100644 --- a/Shared/sdk/SharedUtil.MemAccess.hpp +++ b/Shared/sdk/SharedUtil.MemAccess.hpp @@ -49,6 +49,9 @@ namespace SharedUtil VirtualProtect((LPVOID)0x6AE000, 0x1000, PAGE_EXECUTE_READWRITE, &oldProt); VirtualProtect((LPVOID)0x729000, 0x1000, PAGE_EXECUTE_READWRITE, &oldProt); VirtualProtect((LPVOID)0x742000, 0x1000, PAGE_EXECUTE_READWRITE, &oldProt); + + // Used by the CPathFind crash fix in multiplayer_sa/CMultiplayerSA_CrashFixHacks.cpp, installed via the Fast path. + VirtualProtect((LPVOID)0x156F000, 0x1000, PAGE_EXECUTE_READWRITE, &oldProt); } // Returns true if pAddr is in slow mem area