From 8a8c141af6058f5fe8f684f94706c5050d53ad8f Mon Sep 17 00:00:00 2001 From: Sam Wilde Date: Wed, 6 Nov 2024 11:21:50 +0000 Subject: [PATCH] feat(game): add heap corruption checking Add EnableHeapValidation=1 to CitizenFX.ini to preemptively check for heap corruption --- code/client/shared/atPool.h | 1 + code/components/gta-core-five/component.lua | 3 +- .../gta-core-five/src/GameSkeleton.cpp | 10 + .../gta-core-rdr3/src/GameSkeleton.cpp | 10 + .../gta-core-rdr3/src/SimpleAllocator.cpp | 482 ++++++++++++++++++ .../gta-streaming-five/include/Streaming.h | 2 +- .../src/LoadStreamingFile.cpp | 98 ++-- 7 files changed, 540 insertions(+), 66 deletions(-) create mode 100644 code/components/gta-core-rdr3/src/SimpleAllocator.cpp diff --git a/code/client/shared/atPool.h b/code/client/shared/atPool.h index 6dbc4491be..e4894cdef6 100644 --- a/code/client/shared/atPool.h +++ b/code/client/shared/atPool.h @@ -1,5 +1,6 @@ #pragma once +// Actually fwBasePool class atPoolBase { protected: diff --git a/code/components/gta-core-five/component.lua b/code/components/gta-core-five/component.lua index 6bef353d2f..804b893851 100644 --- a/code/components/gta-core-five/component.lua +++ b/code/components/gta-core-five/component.lua @@ -3,7 +3,8 @@ return function() links 'CitiCore' files { - 'components/gta-core-rdr3/src/ErrorHandler.cpp' + 'components/gta-core-rdr3/src/ErrorHandler.cpp', + 'components/gta-core-rdr3/src/SimpleAllocator.cpp' } add_dependencies { 'vendor:eastl' } end diff --git a/code/components/gta-core-five/src/GameSkeleton.cpp b/code/components/gta-core-five/src/GameSkeleton.cpp index 31a5499f03..1bf2fba312 100644 --- a/code/components/gta-core-five/src/GameSkeleton.cpp +++ b/code/components/gta-core-five/src/GameSkeleton.cpp @@ -9,6 +9,8 @@ #include #include +extern void ValidateHeaps(); + static std::unordered_map g_initFunctionNames; namespace rage @@ -95,6 +97,8 @@ namespace rage void gameSkeleton::RunInitFunctions(InitFunctionType type) { + ValidateHeaps(); + trace(__FUNCTION__ ": Running %s init functions\n", InitFunctionTypeToString(type)); OnInitFunctionStart(type); @@ -112,6 +116,8 @@ namespace rage for (int index : entry->functions) { + ValidateHeaps(); + auto func = m_initFunctions[index]; if (OnInitFunctionInvoking(type, i, func)) @@ -130,6 +136,8 @@ namespace rage OnInitFunctionInvoked(type, func); ++i; + + ValidateHeaps(); } OnInitFunctionEndOrder(type, entry->order); @@ -139,6 +147,8 @@ namespace rage OnInitFunctionEnd(type); + ValidateHeaps(); + trace(__FUNCTION__ ": Done running %s init functions!\n", InitFunctionTypeToString(type)); } diff --git a/code/components/gta-core-rdr3/src/GameSkeleton.cpp b/code/components/gta-core-rdr3/src/GameSkeleton.cpp index 5e406ee1a1..952be1d89c 100644 --- a/code/components/gta-core-rdr3/src/GameSkeleton.cpp +++ b/code/components/gta-core-rdr3/src/GameSkeleton.cpp @@ -8,6 +8,8 @@ #include +extern void ValidateHeaps(); + static std::unordered_map g_initFunctionNames; // rage::strStreamingEngine::ms_bIsPerformingAsyncInit @@ -88,6 +90,8 @@ namespace rage void gameSkeleton::RunInitFunctions(InitFunctionType type) { + ValidateHeaps(); + trace(__FUNCTION__ ": Running %s init functions\n", InitFunctionTypeToString(type)); OnInitFunctionStart(type); @@ -109,6 +113,8 @@ namespace rage { for (int index : entry->functions) { + ValidateHeaps(); + auto func = m_initFunctions[index]; bool isAsync = (func.asyncInitMask & type) != 0; @@ -134,6 +140,8 @@ namespace rage OnInitFunctionInvoked(type, func); ++i; + + ValidateHeaps(); } } @@ -146,6 +154,8 @@ namespace rage OnInitFunctionEnd(type); + ValidateHeaps(); + trace(__FUNCTION__ ": Done running %s init functions!\n", InitFunctionTypeToString(type)); } diff --git a/code/components/gta-core-rdr3/src/SimpleAllocator.cpp b/code/components/gta-core-rdr3/src/SimpleAllocator.cpp new file mode 100644 index 0000000000..441a840758 --- /dev/null +++ b/code/components/gta-core-rdr3/src/SimpleAllocator.cpp @@ -0,0 +1,482 @@ +#include "StdInc.h" +#include "Hooking.h" + +#include + +#include + +#include +#include + +#include + +namespace rage +{ +struct sysMemSimpleAllocator; + +struct sysCriticalSectionToken +{ + CRITICAL_SECTION Impl; + + void Lock() + { + EnterCriticalSection(&Impl); + } + + void Unlock() + { + LeaveCriticalSection(&Impl); + } +}; + +struct sysSmallocator +{ +#ifdef GTA_FIVE + struct Chunk + { + Chunk* Prev; + Chunk* Next; + int FreeCount; + void* FirstFree; + sysSmallocator* Owner; + char padding[20]; + }; + + Chunk* First; + uint16_t EntrySize; + uint16_t ChunkCount; +#elif IS_RDR3 + struct Chunk + { + uint64_t Status; + Chunk* NextChunk; + Chunk* PrevChunk; + sysSmallocator* Owner; + bool IsFull; + char padding[15]; + }; + + uint64_t Status; + void* Sema; + Chunk* HazardPtrs[4]; + sysCriticalSectionToken Token; + Chunk* FreeChunks; + Chunk* LastFreeChunk; + Chunk* FullChunks; + Chunk* LastFullChunk; + Chunk* ActiveChunk; + Chunk* EmptyChunks; + sysMemSimpleAllocator* Owner; + uint16_t NumChunks; + uint16_t ChunkSize; +#endif +}; + +struct sysMemSimpleAllocator +{ + struct Node + { + uint32_t Guard; + uint32_t Size; + uint32_t PrevOffs; + uint32_t AllocId : 26; + uint32_t Visited : 1; + uint32_t Used : 1; + uint32_t Bucket : 4; + + uint8_t* GetData() + { + return (uint8_t*)this + sizeof(*this); + } + + Node* GetPrevNode() + { + return PrevOffs ? (Node*)((uint8_t*)this - PrevOffs) : 0; + } + + Node* GetNextNode() + { + return (Node*)(GetData() + Size); + } + + bool CheckGuard() + { + // Repurpose AllocId as a second guard + return (Guard == (uint32_t)(uintptr_t)this) && (!Used || (AllocId == 0x2adbeef)); + } + }; + + struct FreeNode : Node + { + FreeNode* PrevFree; + FreeNode* NextFree; + }; + +#ifdef GTA_FIVE + static const size_t NumFreeLists = 32; + static const size_t NumPageBits = 65544; +#elif IS_RDR3 + static const size_t NumFreeLists = 288; + static const size_t NumPageBits = 262176; +#endif + + virtual ~sysMemSimpleAllocator() = 0; + + void* Heap; + void* OrigHeap; + + FreeNode* FreeLists[NumFreeLists]; + uint64_t HeapSize; + uint64_t OrigHeapSize; + uint64_t MemoryUsed; + uint64_t MemoryUsedByBucket[16]; + uint64_t MemoryUsedByBucketSnapshot[16]; + uint64_t MemoryAvailable; + uint64_t LeastMemoryAvailable; + uint32_t AllocIdByLayer[8]; + int32_t LayerCount; + bool IsLocked; + bool LockStackTracebacks; + bool OwnHeap; + bool EnableSmallocator; + void* StackFile; + int32_t AllocId; + sysSmallocator SmallAllocator[8]; + void* PageBase; + uint32_t PageArray[(NumPageBits + 31) / 32]; + + uint8_t Flags; + sysCriticalSectionToken Token; + + void Lock() + { + Token.Lock(); + } + + void Unlock() + { + Token.Unlock(); + } + + Node* GetStartNode() + { + return (Node*)Heap; + } + + Node* GetEndNode() + { + return (Node*)((char*)Heap + HeapSize); + } + + enum class SanityCheckReason + { + None, + NodeAlreadyFree, + NodeCorruptThis, + NodeCorruptNext, + }; + + void DoSanityCheck(Node* node, SanityCheckReason reason); + void SanityCheck(); +}; + +} + +static rage::sysMemSimpleAllocator* gameVirtualAllocatorSimple; + +void rage::sysMemSimpleAllocator::SanityCheck() +{ + Lock(); + + if (Heap) + { + DoSanityCheck(nullptr, SanityCheckReason::None); + } + + Unlock(); +} + +void rage::sysMemSimpleAllocator::DoSanityCheck(rage::sysMemSimpleAllocator::Node* node, SanityCheckReason reason) +{ + static int counter = 0; + + const auto error_log = [&](auto format, auto... args) { + auto msg = va(format, args...); + AddCrashometry(va("heap_error_%i", counter++), "%s", msg); + trace("Heap: %s\n", msg); + }; + + Node* const start = GetStartNode(); + Node* const end = GetEndNode(); + + bool found = false; + const char* error = nullptr; + + Node* last_seen[4]{}; + size_t num_seen = 0; + + const auto node_error = [&](auto msg) { + error = msg; + }; + + for (Node *n = start, *prev = nullptr; n != end; prev = n, n = n->GetNextNode()) + { + if (n > end) + { + node_error("Node after end"); + break; + } + + last_seen[num_seen++ % std::size(last_seen)] = n; + + if (n == node) + { + found = true; + } + + if (!n->CheckGuard()) + { + node_error("Corrupt guard"); + break; + } + + if (n->GetPrevNode() != prev) + { + node_error("Corrupt Prev"); + break; + } + + if (!n->Used && (n != node) /* Our node isn't properly freed yet */) + { + if (auto prev_free = static_cast(n)->PrevFree) + { + if (prev_free < start || prev_free >= end) + { + node_error("Invalid PrevFree"); + break; + } + } + + if (auto next_free = static_cast(n)->NextFree) + { + if (next_free < start || next_free >= end) + { + node_error("Invalid NextFree"); + break; + } + } + } + } + + for (FreeNode* n : FreeLists) + { + if (error) + break; + + FreeNode* prev = nullptr; + + for (; n; prev = n, n = n->NextFree) + { + if (n < GetStartNode() || n >= GetEndNode()) + { + node_error("FreeNode out of bounds"); + break; + } + + last_seen[num_seen++ % std::size(last_seen)] = n; + + if (!n->CheckGuard()) + { + node_error("Corrupt guard"); + break; + } + + if (n->PrevFree != prev) + { + node_error("Invalid PrevFree"); + break; + } + } + } + + if (error) + { + error_log("Heap: size=%X, used=%X, start=%p, end=%p %s", HeapSize, MemoryUsed, (void*)start, (void*)end, error); + + for (size_t i = num_seen - std::min(num_seen, std::size(last_seen)); i < num_seen; ++i) + { + auto n = last_seen[i % std::size(last_seen)]; + + std::string data; + + for (int i = -16; i < 48; ++i) + { + if (i == 0) + data.push_back('|'); + + fmt::format_to(std::back_inserter(data), "{:02X}", n->GetData()[i]); + } + + error_log("Node(%p): %4X %s [%s]", (void*)n, n->Size, n->Used ? "used" : "free", data); + } + } + + if (node) + { + error_log("Freeing Node(%p): size=%X, used=%i, prev=%p, next=%p", (void*)node, node->Size, node->Used, (void*)node->GetPrevNode(), (void*)node->GetNextNode()); + + if (!found) + { + error_log("Node not found"); + } + + if (node->CheckGuard()) + { + if (reason == SanityCheckReason::NodeAlreadyFree) + { + error_log("Node already freed"); + } + + if (auto prev = node->GetPrevNode()) + { + if (prev >= node) + { + error_log("Prev node after current"); + } + else if (prev < start) + { + error_log("Prev node before start"); + } + else if (!prev->CheckGuard()) + { + error_log("Corrupt prev guard"); + } + else if (prev->GetNextNode() != node) + { + error_log("Corrupt prev->next"); + } + } + + if (auto next = node->GetNextNode(); next != end) + { + if (next > end) + { + error_log("Next node after end"); + } + else if (!next->CheckGuard()) + { + error_log("Corrupt next guard"); + } + else if (next->GetPrevNode() != node) + { + error_log("Corrupt next->prev"); + } + } + } + else + { + error_log("Corrupt guard"); + } + } + + if (error) + { + FatalError("Game Heap Corruption Detected"); + } +} + +static bool EnableMemoryChecks = false; + +__declspec(dllexport) void ValidateHeaps() +{ + if (EnableMemoryChecks) + { + if (!HeapValidate(GetProcessHeap(), 0, NULL)) + { + FatalError("Process Heap Corruption Detected"); + } + + if (gameVirtualAllocatorSimple && gameVirtualAllocatorSimple->Heap) + { + gameVirtualAllocatorSimple->SanityCheck(); + } + } +} + +static void (*orig_sysMemSimpleAllocator__InitHeap)(rage::sysMemSimpleAllocator* self, void* heap, uint64_t heapSize, bool allowSmallAllocator); + +static void sysMemSimpleAllocator__InitHeap(rage::sysMemSimpleAllocator* self, void* heap, uint64_t heapSize, bool allowSmallAllocator) +{ + allowSmallAllocator = allowSmallAllocator && !EnableMemoryChecks; + orig_sysMemSimpleAllocator__InitHeap(self, heap, heapSize, allowSmallAllocator); + self->AllocId = 0x2adbeef; +} + +static void OnFreeNodeError(rage::sysMemSimpleAllocator* self, rage::sysMemSimpleAllocator::Node* node, rage::sysMemSimpleAllocator::SanityCheckReason reason) +{ + self->DoSanityCheck(node, reason); + + FatalError("Invalid Pointer Free"); +} + +static HookFunction hookFunction([]() +{ + EnableMemoryChecks = GetPrivateProfileInt(L"Game", L"EnableHeapValidation", 0, MakeRelativeCitPath(L"CitizenFX.ini").c_str()) != 0; + + if (EnableMemoryChecks) + { + OnMainGameFrame.Connect([] { + static DWORD next_check = 0; + + if (GetTickCount() >= next_check) + { + ValidateHeaps(); + + next_check = GetTickCount() + 10000; + } + }); + } + +#ifdef GTA_FIVE + gameVirtualAllocatorSimple = hook::get_address(hook::get_pattern("48 8D 0D ? ? ? ? 41 B1 ? 45 33 C0 BA", 3)); + orig_sysMemSimpleAllocator__InitHeap = hook::trampoline(hook::get_pattern("48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 48 83 EC ? 41 8B F0 48 8B DA"), &sysMemSimpleAllocator__InitHeap); +#elif IS_RDR3 + gameVirtualAllocatorSimple = hook::get_address(hook::get_pattern("4C 8D 35 ? ? ? ? 48 8B 0C F8", 3)); + orig_sysMemSimpleAllocator__InitHeap = hook::trampoline(hook::get_pattern("48 89 5C 24 ? 48 89 74 24 ? 57 48 83 EC ? 48 89 51 ? 49 8B F8"), &sysMemSimpleAllocator__InitHeap); +#endif + + using Reason = rage::sysMemSimpleAllocator::SanityCheckReason; + + static struct FreeNodeStub : jitasm::Frontend + { + FreeNodeStub(Reason reason) + : reason(reason) + {} + + Reason reason; + + virtual void InternalMain() override + { + mov(rcx, rdi); // this + mov(rdx, rbx); // node + mov(r8, (uint32_t)reason); // reason + + mov(rax, reinterpret_cast(&OnFreeNodeError)); + jmp(rax); + } + } already_free(Reason::NodeAlreadyFree), corrupt_this(Reason::NodeCorruptThis), corrupt_next(Reason::NodeCorruptNext); + +#ifdef GTA_FIVE + char* ptr = hook::get_pattern("F7 43 ? ? ? ? ? 75 ? B9"); + + hook::call(ptr + 0xE, already_free.GetCode()); + hook::call(ptr + 0x21, corrupt_this.GetCode()); + hook::call(ptr + 0x75, corrupt_next.GetCode()); +#elif IS_RDR3 + char* ptr = hook::get_pattern("F7 46 ? ? ? ? ? 48 8D 5E"); + + hook::call(ptr + 0x18, already_free.GetCode()); + hook::call(ptr + 0x31, corrupt_this.GetCode()); + hook::call(ptr + 0xA1, corrupt_next.GetCode()); +#endif +}); diff --git a/code/components/gta-streaming-five/include/Streaming.h b/code/components/gta-streaming-five/include/Streaming.h index 4269b240ea..cb6d0c64a2 100644 --- a/code/components/gta-streaming-five/include/Streaming.h +++ b/code/components/gta-streaming-five/include/Streaming.h @@ -2,7 +2,7 @@ #include -#ifdef COMPILING_GTA_STREAMING_FIVE +#if defined(COMPILING_GTA_STREAMING_FIVE) || defined(COMPILING_GTA_STREAMING_RDR3) #define STREAMING_EXPORT DLL_EXPORT #else #define STREAMING_EXPORT DLL_IMPORT diff --git a/code/components/gta-streaming-five/src/LoadStreamingFile.cpp b/code/components/gta-streaming-five/src/LoadStreamingFile.cpp index 0250b18a2d..21e060a4bd 100644 --- a/code/components/gta-streaming-five/src/LoadStreamingFile.cpp +++ b/code/components/gta-streaming-five/src/LoadStreamingFile.cpp @@ -747,6 +747,13 @@ class CDataFileMgr }; }; +namespace DataFileType +{ +static int DLC_ITYP_REQUEST; +static int DLC_POP_GROUPS; +static int DLC_WEAPON_PICKUPS; +} + static void* g_dataFileMgr; #ifdef GTA_FIVE @@ -876,28 +883,11 @@ static int LookupDataFileType(const std::string& type) { uint32_t thisHash = HashRageString(boost::to_upper_copy(type).c_str()); -#ifdef GTA_FIVE - int typesCount = 0xC9; - - if (xbr::IsGameBuildOrGreater<2545>()) - { - typesCount = 0xCE; - } - else if (xbr::IsGameBuildOrGreater<2189>()) + for (EnumEntry* i = g_dataFileTypes; (i->hash != 0) || (i->index != 0xFFFFFFFF); ++i) { - typesCount = 0xCB; - } -#elif IS_RDR3 - int typesCount = 0x18B; -#endif - - for (size_t i = 0; i < typesCount; i++) - { - auto entry = &g_dataFileTypes[i]; - - if (entry->hash == thisHash) + if (i->hash == thisHash) { - return entry->index; + return i->index; } } @@ -1326,6 +1316,7 @@ class CfxProxyItypMounter : public CDataFileMountInterface if (*module->FindSlotFromHashKey(&slotId, HashString(baseName.c_str())) != -1) #endif { + // rage::fwAssetStore auto refPool = (atPoolBase*)((char*)module + 56); auto refPtr = refPool->GetAt(slotId); @@ -1350,11 +1341,7 @@ class CfxProxyItypMounter : public CDataFileMountInterface } } -#ifdef GTA_FIVE - CDataFileMount::sm_Interfaces[174]->LoadDataFile(entry); -#elif IS_RDR3 - CDataFileMount::sm_Interfaces[216]->LoadDataFile(entry); -#endif + CDataFileMount::sm_Interfaces[DataFileType::DLC_ITYP_REQUEST]->LoadDataFile(entry); return true; } @@ -1395,11 +1382,7 @@ class CfxProxyItypMounter : public CDataFileMountInterface } } -#ifdef GTA_FIVE - CDataFileMount::sm_Interfaces[174]->UnloadDataFile(entry); -#elif IS_RDR3 - CDataFileMount::sm_Interfaces[216]->UnloadDataFile(entry); -#endif + CDataFileMount::sm_Interfaces[DataFileType::DLC_ITYP_REQUEST]->UnloadDataFile(entry); } }; @@ -1535,17 +1518,12 @@ static CDataFileMountInterface* LookupDataFileMounter(const std::string& type) { return &g_proxyInteriorOrderMounter; } +#endif - if (fileType == 174) // DLC_ITYP_REQUEST + if (fileType == DataFileType::DLC_ITYP_REQUEST) { return &g_proxyDlcItypMounter; } -#elif IS_RDR3 - if (fileType == 216) // DLC_ITYP_REQUEST - { - return &g_proxyDlcItypMounter; - } -#endif return CDataFileMount::sm_Interfaces[fileType]; } @@ -1603,7 +1581,6 @@ inline void HandleDataFileList(const TList& list, const TFn& fn, const char* op } } -#ifdef GTA_FIVE template inline void HandleDataFileListWithTypes(TList& list, const TFn& fn, const std::set& types, const char* op = "loading") { @@ -1621,7 +1598,6 @@ inline void HandleDataFileListWithTypes(TList& list, const TFn& fn, const std::s } } } -#endif enum class LoadType { @@ -1636,12 +1612,7 @@ void LoadStreamingFiles(LoadType loadType = LoadType::AfterSession); static LONG FilterUnmountOperation(CDataFileMgr::DataFile& entry) { - // DLC_ITYP_REQUEST -#ifdef GTA_FIVE - if (entry.type == 174) -#elif IS_RDR3 - if (entry.type == 216) -#endif + if (entry.type == DataFileType::DLC_ITYP_REQUEST) { trace("failed to unload DLC_ITYP_REQUEST %s\n", entry.name); @@ -2678,7 +2649,6 @@ static void UnloadDataFiles() } } -#ifdef GTA_FIVE static void UnloadDataFilesOfTypes(const std::set& types) { HandleDataFileListWithTypes(g_loadedDataFiles, [](CDataFileMountInterface* mounter, CDataFileMgr::DataFile& entry) @@ -2688,12 +2658,6 @@ static void UnloadDataFilesOfTypes(const std::set& types) }, types, "pre-unloading"); } -static hook::cdecl_stub _unloadMultiplayerContent([]() -{ - return hook::get_pattern("01 E8 ? ? ? ? 48 8B 0D ? ? ? ? BA 79", -0x11); -}); -#endif - static const char* NormalizePath(char* out, const char* in, size_t length) { strncpy(out, in, length); @@ -2718,6 +2682,8 @@ struct pgRawStreamer { #ifdef GTA_FIVE char m_pad[24]; +#elif IS_RDR3 + char m_pad[32]; #endif const char* fileName; }; @@ -2728,11 +2694,7 @@ struct pgRawStreamer static const char* pgRawStreamer__GetEntryNameToBuffer(pgRawStreamer* streamer, uint16_t index, char* buffer, int len) { -#ifdef GTA_FIVE const char* fileName = streamer->m_entries[index >> 10][index & 0x3FF].fileName; -#elif IS_RDR3 - const char* fileName = streamer->m_entries[index >> 10][5 * (index & 0x3FF) + 4].fileName; -#endif if (fileName == nullptr) { @@ -2746,7 +2708,6 @@ static const char* pgRawStreamer__GetEntryNameToBuffer(pgRawStreamer* streamer, return buffer; } -#ifdef GTA_FIVE static void DisplayRawStreamerError [[noreturn]] (pgRawStreamer* streamer, uint16_t index, const char* why) { auto streamingMgr = streaming::Manager::GetInstance(); @@ -2814,7 +2775,6 @@ static int64_t pgRawStreamer__GetEntry(pgRawStreamer* streamer, uint16_t index) return g_origGetEntry(streamer, index); } -#endif static bool g_unloadingCfx; @@ -3689,6 +3649,12 @@ static HookFunction hookFunction([]() g_dataFileTypes = hook::get_pattern("61 44 DF 04 00 00 00 00"); +#define X(NAME) DataFileType::NAME = LookupDataFileType(#NAME) + X(DLC_ITYP_REQUEST); + X(DLC_POP_GROUPS); + X(DLC_WEAPON_PICKUPS); +#undef X + rage::OnInitFunctionStart.Connect([](rage::InitFunctionType type) { if (type == rage::INIT_BEFORE_MAP_LOADED) @@ -3734,10 +3700,8 @@ static HookFunction hookFunction([]() g_unloadingCfx = false; -#ifdef GTA_FIVE // unload pre-unloaded data files - UnloadDataFilesOfTypes({ 0xB3 /* DLC_POP_GROUPS */, 166 /* DLC_WEAPON_PICKUPS */ }); -#endif + UnloadDataFilesOfTypes({ DataFileType::DLC_POP_GROUPS, DataFileType::DLC_WEAPON_PICKUPS }); }, 99900); Instance::Get()->OnShutdownSession.Connect([]() @@ -3949,12 +3913,18 @@ static HookFunction hookFunction([]() } #endif - // debug hook for pgRawStreamer::OpenCollectionEntry MH_Initialize(); - MH_CreateHook(hook::get_pattern("8B D5 81 E2", -0x24), pgRawStreamer__OpenCollectionEntry, (void**)&g_origOpenCollectionEntry); - MH_CreateHook(hook::get_pattern("0F B7 C3 48 8B 5C 24 30 8B D0 25 FF", -0x14), pgRawStreamer__GetEntry, (void**)&g_origGetEntry); MH_CreateHook(hook::get_pattern("45 8B E8 4C 8B F1 83 FA FF 0F 84", -0x18), fwStaticBoundsStore__ModifyHierarchyStatus, (void**)&g_orig_fwStaticBoundsStore__ModifyHierarchyStatus); MH_CreateHook(hook::get_pattern("45 33 D2 84 C0 0F 84 ? 01 00 00 4C", -0x28), fwMapDataStore__ModifyHierarchyStatusRecursive, (void**)&g_orig_fwMapDataStore__ModifyHierarchyStatusRecursive); MH_EnableHook(MH_ALL_HOOKS); #endif + + // debug hook for pgRawStreamer::OpenCollectionEntry + +#ifdef GTA_FIVE + g_origOpenCollectionEntry = hook::trampoline(hook::get_pattern("8B D5 81 E2", -0x24), pgRawStreamer__OpenCollectionEntry); + g_origGetEntry = hook::trampoline(hook::get_pattern("0F B7 C3 48 8B 5C 24 30 8B D0 25 FF", -0x14), pgRawStreamer__GetEntry); +#elif IS_RDR3 + g_origOpenCollectionEntry = hook::trampoline(hook::get_pattern("49 8B F0 48 8B 84 C1", -0x2D), pgRawStreamer__OpenCollectionEntry); +#endif });