#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace RED4ext::v1 { struct Logger { void (*Trace)(PluginHandle aHandle, const char* aMessage); void (*TraceF)(PluginHandle aHandle, const char* aFormat, ...); void (*TraceW)(PluginHandle aHandle, const wchar_t* aMessage); void (*TraceWF)(PluginHandle aHandle, const wchar_t* aFormat, ...); void (*Debug)(PluginHandle aHandle, const char* aMessage); void (*DebugF)(PluginHandle aHandle, const char* aFormat, ...); void (*DebugW)(PluginHandle aHandle, const wchar_t* aMessage); void (*DebugWF)(PluginHandle aHandle, const wchar_t* aFormat, ...); void (*Info)(PluginHandle aHandle, const char* aMessage); void (*InfoF)(PluginHandle aHandle, const char* aFormat, ...); void (*InfoW)(PluginHandle aHandle, const wchar_t* aMessage); void (*InfoWF)(PluginHandle aHandle, const wchar_t* aFormat, ...); void (*Warn)(PluginHandle aHandle, const char* aMessage); void (*WarnF)(PluginHandle aHandle, const char* aFormat, ...); void (*WarnW)(PluginHandle aHandle, const wchar_t* aMessage); void (*WarnWF)(PluginHandle aHandle, const wchar_t* aFormat, ...); void (*Error)(PluginHandle aHandle, const char* aMessage); void (*ErrorF)(PluginHandle aHandle, const char* aFormat, ...); void (*ErrorW)(PluginHandle aHandle, const wchar_t* aMessage); void (*ErrorWF)(PluginHandle aHandle, const wchar_t* aFormat, ...); void (*Critical)(PluginHandle aHandle, const char* aMessage); void (*CriticalF)(PluginHandle aHandle, const char* aFormat, ...); void (*CriticalW)(PluginHandle aHandle, const wchar_t* aMessage); void (*CriticalWF)(PluginHandle aHandle, const wchar_t* aFormat, ...); }; struct GameState { bool (*OnEnter)(void* aApp); bool (*OnUpdate)(void* aApp); bool (*OnExit)(void* aApp); }; struct GameStates { bool (*Add)(PluginHandle aHandle, uint32_t aType, GameState* aState); }; struct Hooking { bool (*Attach)(PluginHandle aHandle, void* aTarget, void* aDetour, void** aOriginal); bool (*Detach)(PluginHandle aHandle, void* aTarget); }; struct Scripts { bool (*Add)(PluginHandle aHandle, const wchar_t* aPath); bool (*RegisterNeverRefType)(const char* aType); bool (*RegisterMixedRefType)(const char* aType); }; struct Sdk; struct Sdk { SemVer* runtime; Logger* logger; Hooking* hooking; GameStates* gameStates; Scripts* scripts; }; } namespace { constexpr uint32_t kGameStateRunning = 2; constexpr bool kEnableVerboseJournalListenerHooks = false; constexpr bool kEnableVerboseRouteObserverHooks = false; constexpr bool kEnableVerboseMappinRouteState = false; constexpr uintptr_t kGpsSystemTickRva = 0x0E6B62C; constexpr uintptr_t kJournalTrackEntryImplRva = 0x05944FC; constexpr uintptr_t kJournalListenerArrayOffset = 0x210; constexpr uintptr_t kJournalListenerCountOffset = 0x21C; constexpr uintptr_t kMappinSystemResolverRva = 0x02BB840; constexpr uintptr_t kFrameMappinPathWrapperRva = 0x27C4314; constexpr uintptr_t kFrameMappinPathCoreRva = 0x27BC1EC; constexpr uintptr_t kSetSelectedMappinWrapperRva = 0x27C4A38; constexpr uintptr_t kSetSelectedMappinByIdWrapperRva = 0x27C4944; constexpr uintptr_t kSetSelectedMappinByPositionWrapperRva = 0x27C49C4; constexpr uintptr_t kSetSelectedMappinCoreRva = 0x27C1684; constexpr uintptr_t kTrackCustomPositionMappinWrapperRva = 0x27C4AAC; constexpr uintptr_t kTrackCustomPositionMappinCoreRva = 0x27C2318; constexpr uintptr_t kUntrackCustomPositionMappinWrapperRva = 0x27C4BA0; constexpr uintptr_t kTrackMappinWrapperRva = 0x27C4AB4; constexpr uintptr_t kTrackMappinCoreRva = 0x27C11D0; constexpr uintptr_t kSetMappinTrackingAlternativeWrapperRva = 0x1246794; constexpr uintptr_t kMappinUpdateCustomPositionSlotOffset = 0x1F0; constexpr uintptr_t kMappinSetTrackedSlotOffset = 0x220; constexpr uintptr_t kMappinClearTrackedSlotOffset = 0x228; constexpr uintptr_t kMappinFramePathSlotOffset = 0x280; constexpr uintptr_t kMappinPositionQuerySlotOffset = 0x2E0; constexpr uintptr_t kMappinCreateCustomPositionSlotOffset = 0x2F0; constexpr uintptr_t kMappinReleaseQuerySlotOffset = 0x368; constexpr uintptr_t kMappinActiveRouteSlotOffset = 0x3A8; constexpr uintptr_t kJournalListenerObjectiveVt28Rva = 0x0EA89A8; constexpr uintptr_t kJournalListenerObjectiveVt30Rva = 0x0EA8958; constexpr uintptr_t kJournalListenerRouteVt28Rva = 0x0E63F80; constexpr uintptr_t kJournalListenerRouteVt30Rva = 0x0E63E6C; constexpr uintptr_t kJournalListenerRouteAltVt28Rva = 0x0E63F00; constexpr uintptr_t kJournalListenerRouteAltVt30Rva = 0x08D136C; constexpr uintptr_t kJournalListenerUiVt28Rva = 0x0431A34; constexpr uintptr_t kJournalListenerUiVt30Rva = 0x055A4E4; constexpr uintptr_t kJournalListenerTrackerVt28Rva = 0x14DE238; constexpr uintptr_t kJournalRouteBridgeRva = 0x0598250; constexpr uintptr_t kMappinRouteEventEnqueueRva = 0x13763D8; constexpr uintptr_t kMappinRouteEventHandlerRva = 0x0AA62D0; constexpr uintptr_t kMappinRouteActivateRva = 0x0AA6330; constexpr uintptr_t kMappinRouteDeactivateRva = 0x27ABD7C; constexpr uintptr_t kRouteBuildCandidateRva = 0x05625A4; constexpr uintptr_t kRouteObserver0ActivateRva = 0x0AA6610; constexpr uintptr_t kRouteObserver1ActivateRva = 0x0AA6628; constexpr uintptr_t kRouteObserver23ActivateRva = 0x0AA63E0; constexpr uintptr_t kRouteObserver0DeactivateRva = 0x27B10C0; constexpr uintptr_t kRouteObserver1DeactivateRva = 0x295D4A0; constexpr uintptr_t kRouteObserver23DeactivateRva = 0x286A85C; constexpr uintptr_t kObserver1SystemTypeGlobalRva = 0x342F6A8; constexpr uintptr_t kObserver1ServiceLookupRva = 0x0287C44; constexpr uintptr_t kObserver1ServiceRouteLookupSlotOffset = 0x220; constexpr uintptr_t kRunGpsQueryBodyRva = 0x29BD128; constexpr uintptr_t kUpdateGpsQueryBodyRva = 0x29BD254; constexpr uintptr_t kGpsQuerySubmitRva = 0x070A42C; constexpr uintptr_t kGpsQueryDispatchRva = 0x070A570; constexpr uintptr_t kGpsQueryResultFetchRva = 0x07094B8; constexpr uintptr_t kGpsQueryStatusRva = 0x0AA5704; HMODULE gModule = nullptr; std::mutex gLogMutex; RED4ext::v1::PluginHandle gHandle = nullptr; const RED4ext::v1::Sdk* gSdk = nullptr; uint32_t gUpdateLogCount = 0; bool gScannedExecutable = false; uint32_t gGpsTickLogCount = 0; uint32_t gJournalTrackEntryLogCount = 0; uint32_t gJournalTrackEntryListenerDumpCount = 0; uint32_t gFrameMappinPathWrapperLogCount = 0; uint32_t gFrameMappinPathCoreLogCount = 0; uint32_t gSetSelectedMappinWrapperLogCount = 0; uint32_t gSetSelectedMappinByIdWrapperLogCount = 0; uint32_t gSetSelectedMappinByPositionWrapperLogCount = 0; uint32_t gSetSelectedMappinCoreLogCount = 0; uint32_t gTrackCustomPositionMappinWrapperLogCount = 0; uint32_t gTrackCustomPositionMappinCoreLogCount = 0; uint32_t gUntrackCustomPositionMappinWrapperLogCount = 0; uint32_t gTrackMappinWrapperLogCount = 0; uint32_t gTrackMappinCoreLogCount = 0; uint32_t gSetMappinTrackingAlternativeLogCount = 0; uint32_t gMappinSystemSlotLogCount = 0; uint32_t gMappinUpdateCustomPositionLogCount = 0; uint32_t gMappinSetTrackedLogCount = 0; uint32_t gMappinClearTrackedLogCount = 0; uint32_t gMappinFramePathLogCount = 0; uint32_t gMappinPositionQueryLogCount = 0; uint32_t gMappinCreateCustomPositionLogCount = 0; uint32_t gMappinReleaseQueryLogCount = 0; uint32_t gMappinActiveRouteLogCount = 0; uint32_t gJournalListenerObjectiveVt28LogCount = 0; uint32_t gJournalListenerObjectiveVt30LogCount = 0; uint32_t gJournalListenerRouteVt28LogCount = 0; uint32_t gJournalListenerRouteVt30LogCount = 0; uint32_t gJournalListenerRouteAltVt28LogCount = 0; uint32_t gJournalListenerRouteAltVt30LogCount = 0; uint32_t gJournalListenerUiVt28LogCount = 0; uint32_t gJournalListenerUiVt30LogCount = 0; uint32_t gJournalListenerTrackerVt28LogCount = 0; uint32_t gJournalRouteBridgeLogCount = 0; uint32_t gMappinRouteEventEnqueueLogCount = 0; uint32_t gMappinRouteEventHandlerLogCount = 0; uint32_t gMappinRouteActivateLogCount = 0; uint32_t gMappinRouteDeactivateLogCount = 0; uint32_t gRouteBuildCandidateLogCount = 0; uint32_t gRouteObserver0ActivateLogCount = 0; uint32_t gRouteObserver1ActivateLogCount = 0; uint32_t gRouteObserver23ActivateLogCount = 0; uint32_t gRouteObserver0DeactivateLogCount = 0; uint32_t gRouteObserver1DeactivateLogCount = 0; uint32_t gRouteObserver23DeactivateLogCount = 0; uint32_t gObserver1ServiceLookupLogCount = 0; uint32_t gObserver1ServiceRouteLookupLogCount = 0; uint32_t gRunGpsQueryBodyLogCount = 0; uint32_t gUpdateGpsQueryBodyLogCount = 0; uint32_t gGpsQuerySubmitLogCount = 0; uint32_t gGpsQueryDispatchLogCount = 0; uint32_t gGpsQueryResultFetchLogCount = 0; uint32_t gGpsQueryStatusLogCount = 0; uint64_t gLogStartTick = 0; uint64_t gLogFrequency = 0; void* gMappinSetTrackedTarget = nullptr; void* gMappinClearTrackedTarget = nullptr; void* gMappinUpdateCustomPositionTarget = nullptr; void* gMappinFramePathTarget = nullptr; void* gMappinPositionQueryTarget = nullptr; void* gMappinCreateCustomPositionTarget = nullptr; void* gMappinReleaseQueryTarget = nullptr; void* gMappinActiveRouteTarget = nullptr; void* gObserver1ServiceRouteLookupTarget = nullptr; bool gMappinSlotHookAttachAttempted = false; bool gObserver1ServiceRouteLookupHookAttachAttempted = false; struct TrackedGpsQuery { bool active = false; uint32_t id = 0; void* manager = nullptr; uintptr_t submitReturnRva = 0; uint32_t submitCall = 0; uint32_t resultFetchCalls = 0; uint32_t statusCalls = 0; }; std::mutex gTrackedGpsQueryMutex; std::array gTrackedGpsQueries{}; uint32_t gTrackedGpsQueryNext = 0; using ProbeFunc = void (*)(void* aThis, void* a2, void* a3, void* a4); using JournalTrackEntryFunc = void (*)(void* aThis, void* aEntryHandle); using ScriptNativeVoidFunc = void (*)(void* aThis, void* aStack, void* aResult); using MappinSystemResolverFunc = void* (*)(void* aThis); using FrameMappinPathCoreFunc = void (*)(void* aThis, uint32_t aPathKind, void* a3, void* aPathRect); using SetSelectedMappinCoreFunc = void (*)(void* aThis, void* aMappinHandle); using TrackCustomPositionMappinCoreFunc = void (*)(void* aThis); using TrackMappinCoreFunc = void (*)(void* aThis, void* aMappinHandle); using MappinUpdateCustomPositionFunc = void (*)(void* aSystem, void* aHandle, void* aPositionData); using MappinSetTrackedFunc = void (*)(void* aSystem, void* aMappinId); using MappinClearTrackedFunc = void (*)(void* aSystem); using MappinFramePathFunc = void (*)(void* aSystem, uint32_t aPathKind, void* aResult); using MappinPositionQueryFunc = void* (*)(void* aSystem, void* a2, void* a3, void* a4, void* a5); using MappinCreateCustomPositionFunc = void* (*)(void* aSystem, void* aName, void* aParams, void* aPositionData); using MappinReleaseQueryFunc = void (*)(void* aSystem, void* aHandle); using MappinActiveRouteFunc = void* (*)(void* aSystem); using JournalListenerCallbackFunc = void (*)(void* aListener, void* aEvent); using JournalRouteBridgeFunc = bool (*)(void* aThis, void* aEntryHandle); using MappinRouteEventEnqueueFunc = void (*)(void* aSystem, uint32_t aRouteId, uint8_t aActive); using MappinRouteEventHandlerFunc = void (*)(void* aEvent, void* aRouteHandle); using MappinRouteToggleFunc = void (*)(void* aSystem, uint64_t aRouteKey); using RouteBuildCandidateFunc = void (*)(void* aThis, uint32_t aRouteId, uint32_t aAltRouteId, uint32_t aPreviousRouteId, void* aRoutePositionData, void* aPrimaryDescriptor, void* aSecondaryDescriptor, void* aRouteData88, void* aRouteData108, void* aRouteData114, uint8_t aFlag, uint32_t aMode); using RouteObserverCallbackFunc = void (*)(void* aObserver, void* aRouteEntry); using Observer1ServiceLookupFunc = void* (*)(void* aServiceOwner, void* aSystemType); using Observer1ServiceRouteLookupFunc = void (*)(void* aService, void* aOut, uint32_t aRouteId); using RunGpsQueryBodyFunc = bool (*)(void* aThis, void* aStart, void* aEnd, int32_t* aOutQueryId); using UpdateGpsQueryBodyFunc = bool (*)(void* aThis, uint32_t aQueryId, void* aOutPoints, void* aOutError); using GpsQuerySubmitFunc = int32_t (*)(void* aManager, void* aQuery); using GpsQueryDispatchFunc = void (*)(void* aRuntimeQuery, void* aTargetPosition, void* aArg3, void* aArg4); using GpsQueryResultFetchFunc = bool (*)(void* aManager, uint32_t aQueryId, void* aOutPath); using GpsQueryStatusFunc = int32_t (*)(void* aManager, uint32_t aQueryId); ProbeFunc gOriginalGpsSystemTick = nullptr; JournalTrackEntryFunc gOriginalJournalTrackEntry = nullptr; ScriptNativeVoidFunc gOriginalFrameMappinPathWrapper = nullptr; FrameMappinPathCoreFunc gOriginalFrameMappinPathCore = nullptr; ScriptNativeVoidFunc gOriginalSetSelectedMappinWrapper = nullptr; ScriptNativeVoidFunc gOriginalSetSelectedMappinByIdWrapper = nullptr; ScriptNativeVoidFunc gOriginalSetSelectedMappinByPositionWrapper = nullptr; SetSelectedMappinCoreFunc gOriginalSetSelectedMappinCore = nullptr; ScriptNativeVoidFunc gOriginalTrackCustomPositionMappinWrapper = nullptr; TrackCustomPositionMappinCoreFunc gOriginalTrackCustomPositionMappinCore = nullptr; ScriptNativeVoidFunc gOriginalUntrackCustomPositionMappinWrapper = nullptr; ScriptNativeVoidFunc gOriginalTrackMappinWrapper = nullptr; TrackMappinCoreFunc gOriginalTrackMappinCore = nullptr; ScriptNativeVoidFunc gOriginalSetMappinTrackingAlternativeWrapper = nullptr; MappinUpdateCustomPositionFunc gOriginalMappinUpdateCustomPosition = nullptr; MappinSetTrackedFunc gOriginalMappinSetTracked = nullptr; MappinClearTrackedFunc gOriginalMappinClearTracked = nullptr; MappinFramePathFunc gOriginalMappinFramePath = nullptr; MappinPositionQueryFunc gOriginalMappinPositionQuery = nullptr; MappinCreateCustomPositionFunc gOriginalMappinCreateCustomPosition = nullptr; MappinReleaseQueryFunc gOriginalMappinReleaseQuery = nullptr; MappinActiveRouteFunc gOriginalMappinActiveRoute = nullptr; JournalListenerCallbackFunc gOriginalJournalListenerObjectiveVt28 = nullptr; JournalListenerCallbackFunc gOriginalJournalListenerObjectiveVt30 = nullptr; JournalListenerCallbackFunc gOriginalJournalListenerRouteVt28 = nullptr; JournalListenerCallbackFunc gOriginalJournalListenerRouteVt30 = nullptr; JournalListenerCallbackFunc gOriginalJournalListenerRouteAltVt28 = nullptr; JournalListenerCallbackFunc gOriginalJournalListenerRouteAltVt30 = nullptr; JournalListenerCallbackFunc gOriginalJournalListenerUiVt28 = nullptr; JournalListenerCallbackFunc gOriginalJournalListenerUiVt30 = nullptr; JournalListenerCallbackFunc gOriginalJournalListenerTrackerVt28 = nullptr; JournalRouteBridgeFunc gOriginalJournalRouteBridge = nullptr; MappinRouteEventEnqueueFunc gOriginalMappinRouteEventEnqueue = nullptr; MappinRouteEventHandlerFunc gOriginalMappinRouteEventHandler = nullptr; MappinRouteToggleFunc gOriginalMappinRouteActivate = nullptr; MappinRouteToggleFunc gOriginalMappinRouteDeactivate = nullptr; RouteBuildCandidateFunc gOriginalRouteBuildCandidate = nullptr; RouteObserverCallbackFunc gOriginalRouteObserver0Activate = nullptr; RouteObserverCallbackFunc gOriginalRouteObserver1Activate = nullptr; RouteObserverCallbackFunc gOriginalRouteObserver23Activate = nullptr; RouteObserverCallbackFunc gOriginalRouteObserver0Deactivate = nullptr; RouteObserverCallbackFunc gOriginalRouteObserver1Deactivate = nullptr; RouteObserverCallbackFunc gOriginalRouteObserver23Deactivate = nullptr; Observer1ServiceLookupFunc gOriginalObserver1ServiceLookup = nullptr; Observer1ServiceRouteLookupFunc gOriginalObserver1ServiceRouteLookup = nullptr; RunGpsQueryBodyFunc gOriginalRunGpsQueryBody = nullptr; UpdateGpsQueryBodyFunc gOriginalUpdateGpsQueryBody = nullptr; GpsQuerySubmitFunc gOriginalGpsQuerySubmit = nullptr; GpsQueryDispatchFunc gOriginalGpsQueryDispatch = nullptr; GpsQueryResultFetchFunc gOriginalGpsQueryResultFetch = nullptr; GpsQueryStatusFunc gOriginalGpsQueryStatus = nullptr; std::filesystem::path GetLogPath() { wchar_t modulePath[MAX_PATH]{}; if (gModule && GetModuleFileNameW(gModule, modulePath, MAX_PATH) > 0) { auto path = std::filesystem::path(modulePath); return path.parent_path() / L"EdgeWeightGPS.log"; } return std::filesystem::path(L"red4ext") / L"plugins" / L"EdgeWeightGPS" / L"EdgeWeightGPS.log"; } void Log(std::string_view aMessage) { std::lock_guard lock(gLogMutex); auto now = std::chrono::system_clock::now(); auto time = std::chrono::system_clock::to_time_t(now); std::tm localTime{}; localtime_s(&localTime, &time); std::ofstream out(GetLogPath(), std::ios::app); if (!out) { return; } uint64_t elapsedMs = 0; if (gLogStartTick && gLogFrequency) { LARGE_INTEGER counter{}; QueryPerformanceCounter(&counter); elapsedMs = static_cast((counter.QuadPart - gLogStartTick) * 1000 / gLogFrequency); } out << std::put_time(&localTime, "%Y-%m-%d %H:%M:%S") << "." << std::setw(3) << std::setfill('0') << elapsedMs % 1000 << std::setfill(' ') << " +" << elapsedMs << "ms " << aMessage << "\n"; } void LogRed4ext(std::string_view aMessage) { Log(aMessage); if (gSdk && gSdk->logger && gSdk->logger->Info && gHandle) { std::string text(aMessage); gSdk->logger->Info(gHandle, text.c_str()); } } uintptr_t GetImageBase() { return reinterpret_cast(GetModuleHandleW(nullptr)); } uintptr_t GetImageEnd() { const auto imageBase = GetImageBase(); if (!imageBase) { return 0; } const auto* dos = reinterpret_cast(imageBase); if (dos->e_magic != IMAGE_DOS_SIGNATURE) { return 0; } const auto* nt = reinterpret_cast(imageBase + dos->e_lfanew); if (nt->Signature != IMAGE_NT_SIGNATURE) { return 0; } return imageBase + nt->OptionalHeader.SizeOfImage; } bool IsInImage(const void* aPointer) { const auto imageBase = GetImageBase(); const auto imageEnd = GetImageEnd(); const auto address = reinterpret_cast(aPointer); return imageBase && imageEnd && address >= imageBase && address < imageEnd; } bool TryGetImageRva(const void* aPointer, uintptr_t& aRva) { if (!IsInImage(aPointer)) { return false; } aRva = reinterpret_cast(aPointer) - GetImageBase(); return true; } void AppendPointerWithRva(std::ostringstream& aLine, const char* aLabel, const void* aPointer) { aLine << " " << aLabel << "=" << aPointer; if (IsInImage(aPointer)) { const auto imageBase = GetImageBase(); const auto address = reinterpret_cast(aPointer); aLine << " " << aLabel << "_rva=0x" << std::hex << (address - imageBase) << std::dec; } } void AppendReturnRva(std::ostringstream& aLine, const void* aReturnAddress) { if (IsInImage(aReturnAddress)) { const auto imageBase = GetImageBase(); const auto address = reinterpret_cast(aReturnAddress); aLine << " ret_rva=0x" << std::hex << (address - imageBase) << std::dec; } } void AppendHexValue(std::ostringstream& aLine, const char* aLabel, uint64_t aValue) { aLine << " " << aLabel << "=0x" << std::hex << aValue << std::dec; } void LogHookCall(const char* aName, uint32_t aCount, void* aThis, void* a2, void* a3, void* a4) { std::ostringstream line; line << "hook " << aName << " call=" << aCount << " this=" << aThis << " a2=" << a2 << " a3=" << a3 << " a4=" << a4; AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); } void LogScriptNativeCall(const char* aName, uint32_t aCount, void* aThis, void* aStack, void* aResult) { std::ostringstream line; line << "hook " << aName << " call=" << aCount << " this=" << aThis << " stack=" << aStack << " result=" << aResult; LogRed4ext(line.str()); } void LogFrameMappinPathCoreCall(uint32_t aCount, void* aThis, uint32_t aPathKind, void* a3, void* aPathRect) { std::ostringstream line; line << "hook FrameMappinPathCore call=" << aCount << " this=" << aThis << " pathKind=" << aPathKind << " a3=" << a3 << " pathRect=" << aPathRect; AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); } void LogSetSelectedMappinCoreCall(uint32_t aCount, void* aThis, void* aMappinHandle) { std::ostringstream line; line << "hook SetSelectedMappinCore call=" << aCount << " this=" << aThis << " mappinHandle=" << aMappinHandle; AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); } void LogTrackCustomPositionMappinCoreCall(uint32_t aCount, void* aThis) { std::ostringstream line; line << "hook TrackCustomPositionMappinCore call=" << aCount << " this=" << aThis; AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); } void LogTrackMappinCoreCall(uint32_t aCount, void* aThis, void* aMappinHandle) { std::ostringstream line; line << "hook TrackMappinCore call=" << aCount << " this=" << aThis << " mappinHandle=" << aMappinHandle; LogRed4ext(line.str()); } void LogMappinSystemSlots(uint32_t aCount, void* aSystem, void* aVtable) { auto* vtable = reinterpret_cast(aVtable); auto readSlot = [vtable](uintptr_t aOffset) -> void* { if (!vtable) { return nullptr; } return *reinterpret_cast(vtable + aOffset); }; std::ostringstream line; line << "mappin-system slots call=" << aCount; AppendPointerWithRva(line, "system", aSystem); AppendPointerWithRva(line, "vtable", aVtable); AppendPointerWithRva(line, "slot1f0", readSlot(kMappinUpdateCustomPositionSlotOffset)); AppendPointerWithRva(line, "slot220", readSlot(kMappinSetTrackedSlotOffset)); AppendPointerWithRva(line, "slot228", readSlot(kMappinClearTrackedSlotOffset)); AppendPointerWithRva(line, "slot250", readSlot(0x250)); AppendPointerWithRva(line, "slot280", readSlot(kMappinFramePathSlotOffset)); AppendPointerWithRva(line, "slot2e0", readSlot(kMappinPositionQuerySlotOffset)); AppendPointerWithRva(line, "slot2f0", readSlot(kMappinCreateCustomPositionSlotOffset)); AppendPointerWithRva(line, "slot368", readSlot(kMappinReleaseQuerySlotOffset)); AppendPointerWithRva(line, "slot3a8", readSlot(kMappinActiveRouteSlotOffset)); LogRed4ext(line.str()); } void InspectAndHookMappinSystem(void* aThis); const char* ReasonToString(RED4ext::v1::EMainReason aReason) { switch (aReason) { case RED4ext::v1::EMainReason::Load: return "Load"; case RED4ext::v1::EMainReason::Unload: return "Unload"; } return "Unknown"; } bool IsReadablePage(DWORD aProtect) { if ((aProtect & PAGE_GUARD) != 0 || (aProtect & PAGE_NOACCESS) != 0) { return false; } const auto baseProtect = aProtect & 0xFF; return baseProtect == PAGE_READONLY || baseProtect == PAGE_READWRITE || baseProtect == PAGE_WRITECOPY || baseProtect == PAGE_EXECUTE_READ || baseProtect == PAGE_EXECUTE_READWRITE || baseProtect == PAGE_EXECUTE_WRITECOPY; } bool IsReadableMemory(const void* aPointer, size_t aSize) { if (!aPointer || aSize == 0) { return false; } const auto begin = reinterpret_cast(aPointer); const auto end = begin + aSize; if (end < begin) { return false; } auto cursor = begin; while (cursor < end) { MEMORY_BASIC_INFORMATION mbi{}; if (!VirtualQuery(reinterpret_cast(cursor), &mbi, sizeof(mbi))) { return false; } if (mbi.State != MEM_COMMIT || !IsReadablePage(mbi.Protect)) { return false; } const auto regionEnd = reinterpret_cast(mbi.BaseAddress) + mbi.RegionSize; if (regionEnd <= cursor) { return false; } cursor = regionEnd; } return true; } template bool TryReadMemory(const void* aPointer, T& aValue) { if (!IsReadableMemory(aPointer, sizeof(T))) { return false; } aValue = *reinterpret_cast(aPointer); return true; } void* ReadPointerField(void* aObject, uintptr_t aOffset) { void* value = nullptr; if (!aObject) { return nullptr; } TryReadMemory(reinterpret_cast(reinterpret_cast(aObject) + aOffset), value); return value; } void* GetObserver1SystemType() { return ReadPointerField(reinterpret_cast(GetImageBase() + kObserver1SystemTypeGlobalRva), 0x00); } void AppendObjectVtableSlots(std::ostringstream& aLine, void* aObject, std::initializer_list aSlots) { auto* vtable = ReadPointerField(aObject, 0); AppendPointerWithRva(aLine, "vtable", vtable); if (!IsInImage(vtable)) { return; } for (const auto slot : aSlots) { auto* target = vtable ? ReadPointerField(vtable, slot) : nullptr; std::ostringstream label; label << "slot" << std::hex << slot; const auto labelText = label.str(); AppendPointerWithRva(aLine, labelText.c_str(), target); } } void AppendPointerField(std::ostringstream& aLine, const char* aLabel, void* aObject, uintptr_t aOffset) { AppendPointerWithRva(aLine, aLabel, ReadPointerField(aObject, aOffset)); } void AppendUint32Field(std::ostringstream& aLine, const char* aLabel, void* aObject, uintptr_t aOffset) { uint32_t value = 0; const auto hasValue = aObject && TryReadMemory(reinterpret_cast(reinterpret_cast(aObject) + aOffset), value); aLine << " " << aLabel << "="; if (hasValue) { aLine << value << "/0x" << std::hex << value << std::dec; } else { aLine << ""; } } void AppendUint16Field(std::ostringstream& aLine, const char* aLabel, void* aObject, uintptr_t aOffset) { uint16_t value = 0; const auto hasValue = aObject && TryReadMemory(reinterpret_cast(reinterpret_cast(aObject) + aOffset), value); aLine << " " << aLabel << "="; if (hasValue) { aLine << value << "/0x" << std::hex << value << std::dec; } else { aLine << ""; } } void AppendUint64Field(std::ostringstream& aLine, const char* aLabel, void* aObject, uintptr_t aOffset) { uint64_t value = 0; const auto hasValue = aObject && TryReadMemory(reinterpret_cast(reinterpret_cast(aObject) + aOffset), value); aLine << " " << aLabel << "="; if (hasValue) { aLine << value << "/0x" << std::hex << value << std::dec; } else { aLine << ""; } } void AppendByteField(std::ostringstream& aLine, const char* aLabel, void* aObject, uintptr_t aOffset) { uint8_t value = 0; const auto hasValue = aObject && TryReadMemory(reinterpret_cast(reinterpret_cast(aObject) + aOffset), value); aLine << " " << aLabel << "="; if (hasValue) { aLine << static_cast(value); } else { aLine << ""; } } void AppendBytesField(std::ostringstream& aLine, const char* aLabel, void* aObject, uintptr_t aOffset, size_t aSize) { const auto* address = aObject ? reinterpret_cast(reinterpret_cast(aObject) + aOffset) : nullptr; aLine << " " << aLabel << "="; if (!address || !IsReadableMemory(address, aSize)) { aLine << ""; return; } const auto flags = aLine.flags(); const auto fill = aLine.fill(); aLine << std::hex << std::setfill('0'); for (size_t index = 0; index < aSize; ++index) { const auto byte = *(reinterpret_cast(address) + index); aLine << std::setw(2) << static_cast(byte); } aLine.flags(flags); aLine.fill(fill); } void AppendBytesPointer(std::ostringstream& aLine, const char* aLabel, const void* aPointer, size_t aSize) { aLine << " " << aLabel << "="; if (!aPointer || reinterpret_cast(aPointer) < 0x10000 || !IsReadableMemory(aPointer, aSize)) { aLine << ""; return; } const auto flags = aLine.flags(); const auto fill = aLine.fill(); aLine << std::hex << std::setfill('0'); for (size_t index = 0; index < aSize; ++index) { const auto byte = *(reinterpret_cast(aPointer) + index); aLine << std::setw(2) << static_cast(byte); } aLine.flags(flags); aLine.fill(fill); } struct Vector4Value { float x; float y; float z; float w; }; struct Vector3Value { float x; float y; float z; }; void AppendVector3Pointer(std::ostringstream& aLine, const char* aLabel, const void* aVector) { Vector3Value value{}; aLine << " " << aLabel << "="; if (aVector && TryReadMemory(aVector, value)) { aLine << "(" << value.x << "," << value.y << "," << value.z << ")"; } else { aLine << ""; } } void AppendVector4Pointer(std::ostringstream& aLine, const char* aLabel, const void* aVector) { Vector4Value value{}; aLine << " " << aLabel << "="; if (aVector && TryReadMemory(aVector, value)) { aLine << "(" << value.x << "," << value.y << "," << value.z << "," << value.w << ")"; } else { aLine << ""; } } void AppendVector4Field(std::ostringstream& aLine, const char* aLabel, void* aObject, uintptr_t aOffset) { if (!aObject) { aLine << " " << aLabel << "="; return; } AppendVector4Pointer(aLine, aLabel, reinterpret_cast(reinterpret_cast(aObject) + aOffset)); } void AppendVector3ListCandidate(std::ostringstream& aLine, const char* aPrefix, void* aObject, uintptr_t aDataOffset, uintptr_t aCountOffset, uintptr_t aStride) { uint32_t count = 0; auto* data = ReadPointerField(aObject, aDataOffset); TryReadMemory(reinterpret_cast(reinterpret_cast(aObject) + aCountOffset), count); const auto dataLabel = std::string(aPrefix) + "_data"; const auto countLabel = std::string(aPrefix) + "_count"; AppendPointerWithRva(aLine, dataLabel.c_str(), data); aLine << " " << countLabel << "=" << count; if (!data || reinterpret_cast(data) < 0x10000 || count == 0 || count > 10000) { return; } AppendVector3Pointer(aLine, (std::string(aPrefix) + "_first3").c_str(), data); if (count > 1) { AppendVector3Pointer(aLine, (std::string(aPrefix) + "_last3").c_str(), reinterpret_cast(reinterpret_cast(data) + static_cast(count - 1) * aStride)); } } void AppendVectorListCandidate(std::ostringstream& aLine, const char* aPrefix, void* aObject, uintptr_t aDataOffset, uintptr_t aCountOffset, uintptr_t aStride) { uint32_t count = 0; auto* data = ReadPointerField(aObject, aDataOffset); TryReadMemory(reinterpret_cast(reinterpret_cast(aObject) + aCountOffset), count); const auto dataLabel = std::string(aPrefix) + "_data"; const auto countLabel = std::string(aPrefix) + "_count"; AppendPointerWithRva(aLine, dataLabel.c_str(), data); aLine << " " << countLabel << "=" << count; if (!data || reinterpret_cast(data) < 0x10000 || count == 0 || count > 10000) { return; } AppendVector4Pointer(aLine, (std::string(aPrefix) + "_first").c_str(), data); if (count > 1) { AppendVector4Pointer(aLine, (std::string(aPrefix) + "_last").c_str(), reinterpret_cast(reinterpret_cast(data) + static_cast(count - 1) * aStride)); } } void AppendPathBufferSummary(std::ostringstream& aLine, const char* aPrefix, void* aPathBuffer) { AppendPointerWithRva(aLine, aPrefix, aPathBuffer); if (!aPathBuffer) { return; } uint32_t count = 0; auto* data = ReadPointerField(aPathBuffer, 0x30); TryReadMemory(reinterpret_cast(reinterpret_cast(aPathBuffer) + 0x3C), count); aLine << " " << aPrefix << "_count=" << count; AppendPointerWithRva(aLine, (std::string(aPrefix) + "_data").c_str(), data); if (!data || count == 0 || count > 100000) { return; } AppendVector4Pointer(aLine, (std::string(aPrefix) + "_first").c_str(), data); if (count > 1) { AppendVector4Pointer(aLine, (std::string(aPrefix) + "_last").c_str(), reinterpret_cast(reinterpret_cast(data) + static_cast(count - 1) * 12)); } } void AppendGpsResultElementArraySummary(std::ostringstream& aLine, const char* aPrefix, void* aResultObject) { uint32_t capacity = 0; uint32_t count = 0; auto* data = ReadPointerField(aResultObject, 0x28); TryReadMemory(reinterpret_cast(reinterpret_cast(aResultObject) + 0x30), capacity); TryReadMemory(reinterpret_cast(reinterpret_cast(aResultObject) + 0x34), count); const auto prefix = std::string(aPrefix); aLine << " " << prefix << "_ptr28_capacity30=" << capacity; aLine << " " << prefix << "_ptr28_count34=" << count; if (!data || reinterpret_cast(data) < 0x10000 || count == 0 || count > 256) { return; } const auto rawSize = std::min(0x80, static_cast(count) * sizeof(void*)); AppendBytesPointer(aLine, (prefix + "_ptr28_ptrsRaw").c_str(), data, rawSize); const auto appendElement = [&](uint32_t aIndex, const char* aLabel) { auto* element = ReadPointerField(data, static_cast(aIndex) * sizeof(void*)); const auto elementPrefix = prefix + "_ptr28_" + aLabel; AppendPointerWithRva(aLine, elementPrefix.c_str(), element); if (!element || reinterpret_cast(element) < 0x10000) { return; } AppendPointerField(aLine, (elementPrefix + "_f68").c_str(), element, 0x68); AppendUint32Field(aLine, (elementPrefix + "_u74").c_str(), element, 0x74); AppendPointerField(aLine, (elementPrefix + "_f90").c_str(), element, 0x90); AppendUint32Field(aLine, (elementPrefix + "_u98").c_str(), element, 0x98); uint32_t sampleCount = 0; auto* samples = ReadPointerField(element, 0x68); TryReadMemory(reinterpret_cast(reinterpret_cast(element) + 0x74), sampleCount); if (samples && reinterpret_cast(samples) >= 0x10000 && sampleCount > 0 && sampleCount <= 4096) { const auto sampleRawSize = std::min(0x20, static_cast(sampleCount) * 0x08); AppendBytesPointer(aLine, (elementPrefix + "_f68raw").c_str(), samples, sampleRawSize); } }; const auto firstLimit = std::min(count, 4); for (uint32_t index = 0; index < firstLimit; ++index) { appendElement(index, ("elem" + std::to_string(index)).c_str()); } if (count > firstLimit) { appendElement(count - 1, "last"); } } void AppendGpsResultObjectSummary(std::ostringstream& aLine, const char* aPrefix, void* aResultObject) { if (!aResultObject) { aLine << " " << aPrefix << "_result="; return; } AppendBytesField(aLine, (std::string(aPrefix) + "_bytes20").c_str(), aResultObject, 0x20, 0x40); AppendUint32Field(aLine, (std::string(aPrefix) + "_u20").c_str(), aResultObject, 0x20); AppendUint32Field(aLine, (std::string(aPrefix) + "_u24").c_str(), aResultObject, 0x24); AppendPointerField(aLine, (std::string(aPrefix) + "_ptr28").c_str(), aResultObject, 0x28); AppendVector3ListCandidate(aLine, (std::string(aPrefix) + "_vec28s12").c_str(), aResultObject, 0x28, 0x30, 12); AppendVectorListCandidate(aLine, (std::string(aPrefix) + "_vec28s16").c_str(), aResultObject, 0x28, 0x30, 16); AppendVectorListCandidate(aLine, (std::string(aPrefix) + "_vec20").c_str(), aResultObject, 0x20, 0x2C, 12); AppendVectorListCandidate(aLine, (std::string(aPrefix) + "_vec20s16").c_str(), aResultObject, 0x20, 0x2C, 16); AppendUint32Field(aLine, (std::string(aPrefix) + "_u2c").c_str(), aResultObject, 0x2C); AppendUint64Field(aLine, (std::string(aPrefix) + "_u30").c_str(), aResultObject, 0x30); AppendUint32Field(aLine, (std::string(aPrefix) + "_u30lo").c_str(), aResultObject, 0x30); AppendUint32Field(aLine, (std::string(aPrefix) + "_u34hi").c_str(), aResultObject, 0x34); AppendGpsResultElementArraySummary(aLine, aPrefix, aResultObject); AppendVectorListCandidate(aLine, (std::string(aPrefix) + "_vec38").c_str(), aResultObject, 0x38, 0x3C, 12); AppendUint16Field(aLine, (std::string(aPrefix) + "_flags40").c_str(), aResultObject, 0x40); AppendByteField(aLine, (std::string(aPrefix) + "_state42").c_str(), aResultObject, 0x42); AppendUint64Field(aLine, (std::string(aPrefix) + "_u48").c_str(), aResultObject, 0x48); AppendUint64Field(aLine, (std::string(aPrefix) + "_u50").c_str(), aResultObject, 0x50); } void TrackGpsQuery(uint32_t aQueryId, void* aManager, uintptr_t aSubmitReturnRva, uint32_t aSubmitCall) { if (aQueryId == UINT32_MAX) { return; } std::lock_guard lock(gTrackedGpsQueryMutex); for (auto& query : gTrackedGpsQueries) { if (query.active && query.id == aQueryId && query.manager == aManager) { query.submitReturnRva = aSubmitReturnRva; query.submitCall = aSubmitCall; return; } } auto& query = gTrackedGpsQueries[gTrackedGpsQueryNext++ % gTrackedGpsQueries.size()]; query.active = true; query.id = aQueryId; query.manager = aManager; query.submitReturnRva = aSubmitReturnRva; query.submitCall = aSubmitCall; query.resultFetchCalls = 0; query.statusCalls = 0; } bool NoteTrackedGpsQueryResultFetch(uint32_t aQueryId, void* aManager, TrackedGpsQuery& aSnapshot, uint32_t& aPerQueryCall) { std::lock_guard lock(gTrackedGpsQueryMutex); for (auto& query : gTrackedGpsQueries) { if (!query.active || query.id != aQueryId || query.manager != aManager) { continue; } aPerQueryCall = query.resultFetchCalls++; aSnapshot = query; return true; } return false; } bool NoteTrackedGpsQueryStatus(uint32_t aQueryId, void* aManager, TrackedGpsQuery& aSnapshot, uint32_t& aPerQueryCall) { std::lock_guard lock(gTrackedGpsQueryMutex); for (auto& query : gTrackedGpsQueries) { if (!query.active || query.id != aQueryId || query.manager != aManager) { continue; } aPerQueryCall = query.statusCalls++; aSnapshot = query; return true; } return false; } void AppendTrackedGpsQuery(std::ostringstream& aLine, const TrackedGpsQuery& aQuery) { aLine << " submitCall=" << aQuery.submitCall << " submitRetRva=0x" << std::hex << aQuery.submitReturnRva << std::dec; } void AppendRouteRefFields(std::ostringstream& aLine, const char* aPrefix, void* aRouteRef) { AppendPointerWithRva(aLine, aPrefix, aRouteRef); if (!aRouteRef) { return; } std::array offsets{0x00, 0x08, 0x10, 0x18, 0x30, 0x40, 0x48}; for (const auto offset : offsets) { std::ostringstream label; label << aPrefix << "_f" << std::hex << offset; const auto labelText = label.str(); AppendPointerField(aLine, labelText.c_str(), aRouteRef, offset); } } void LogMappinRouteCollections(const char* aName, uint32_t aCount, void* aSystem) { if (!kEnableVerboseMappinRouteState) { return; } if (!aSystem) { return; } void* activeRouteMap = nullptr; uint32_t activeRouteCount = 0; uint32_t activeRouteFlags = 0; void* observerBase = nullptr; uint32_t observerCount = 0; TryReadMemory(reinterpret_cast(reinterpret_cast(aSystem) + 0x1A0), activeRouteMap); TryReadMemory(reinterpret_cast(reinterpret_cast(aSystem) + 0x1AC), activeRouteCount); TryReadMemory(reinterpret_cast(reinterpret_cast(aSystem) + 0x1B0), activeRouteFlags); TryReadMemory(reinterpret_cast(reinterpret_cast(aSystem) + 0x288), observerBase); TryReadMemory(reinterpret_cast(reinterpret_cast(aSystem) + 0x294), observerCount); std::ostringstream line; line << "mappin-route-state " << aName << " call=" << aCount; AppendPointerWithRva(line, "system", aSystem); AppendPointerWithRva(line, "activeRouteMap", activeRouteMap); line << " activeRouteCount=" << activeRouteCount << " activeRouteFlags=0x" << std::hex << activeRouteFlags << std::dec; AppendPointerWithRva(line, "observerBase", observerBase); line << " observerCount=" << observerCount; LogRed4ext(line.str()); if (!observerBase || observerCount == 0 || observerCount > 128) { return; } const auto cappedCount = std::min(observerCount, 4u); for (uint32_t index = 0; index < cappedCount; ++index) { auto* observer = ReadPointerField(observerBase, index * 0x10); std::ostringstream observerLine; observerLine << "mappin-route-observer " << aName << " call=" << aCount << " index=" << index; AppendPointerWithRva(observerLine, "observer", observer); AppendObjectVtableSlots(observerLine, observer, {0x48, 0x50}); LogRed4ext(observerLine.str()); } } void LogRouteObserverContext(const char* aName, uint32_t aCount, void* aObserver, void* aRouteEntry) { auto* observerInner = ReadPointerField(aObserver, 0x08); auto* observerInnerServiceOwner = ReadPointerField(observerInner, 0x18); auto* observer1SystemType = GetObserver1SystemType(); auto* routeObject = ReadPointerField(aRouteEntry, 0x00); std::ostringstream line; line << "hook " << aName << " call=" << aCount; AppendPointerWithRva(line, "observer", aObserver); AppendPointerWithRva(line, "observerInner", observerInner); AppendPointerWithRva(line, "observerInnerServiceOwner", observerInnerServiceOwner); AppendPointerWithRva(line, "observer1SystemType342f6a8", observer1SystemType); AppendPointerWithRva(line, "routeEntry", aRouteEntry); AppendPointerWithRva(line, "routeObject", routeObject); AppendPointerField(line, "routeEntry_f08", aRouteEntry, 0x08); AppendPointerField(line, "routeEntry_f10", aRouteEntry, 0x10); AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); std::ostringstream observerLine; observerLine << "route-observer-object " << aName << " call=" << aCount; AppendPointerWithRva(observerLine, "observer", aObserver); AppendObjectVtableSlots(observerLine, aObserver, {0x48, 0x50}); LogRed4ext(observerLine.str()); if (observerInner) { std::ostringstream innerLine; innerLine << "route-observer-inner " << aName << " call=" << aCount; AppendPointerWithRva(innerLine, "observerInner", observerInner); AppendObjectVtableSlots(innerLine, observerInner, {0x08}); AppendPointerField(innerLine, "field18", observerInner, 0x18); AppendPointerField(innerLine, "field180", observerInner, 0x180); AppendPointerField(innerLine, "field1a0", observerInner, 0x1A0); AppendPointerField(innerLine, "field1c0", observerInner, 0x1C0); AppendPointerField(innerLine, "field1e0", observerInner, 0x1E0); AppendPointerField(innerLine, "field1e8", observerInner, 0x1E8); AppendPointerField(innerLine, "field1f0", observerInner, 0x1F0); LogRed4ext(innerLine.str()); } if (observerInnerServiceOwner) { std::ostringstream serviceOwnerLine; serviceOwnerLine << "route-observer-service-owner " << aName << " call=" << aCount; AppendPointerWithRva(serviceOwnerLine, "serviceOwner", observerInnerServiceOwner); AppendObjectVtableSlots(serviceOwnerLine, observerInnerServiceOwner, {0x08}); LogRed4ext(serviceOwnerLine.str()); } if (!routeObject) { return; } std::ostringstream routeLine; routeLine << "route-observer-route-object " << aName << " call=" << aCount; AppendPointerWithRva(routeLine, "routeObject", routeObject); AppendObjectVtableSlots(routeLine, routeObject, {0x140, 0x188, 0x198, 0x1A8}); AppendPointerField(routeLine, "field40", routeObject, 0x40); AppendPointerField(routeLine, "field48", routeObject, 0x48); AppendPointerField(routeLine, "field78", routeObject, 0x78); AppendUint32Field(routeLine, "field80", routeObject, 0x80); AppendByteField(routeLine, "active84", routeObject, 0x84); AppendUint32Field(routeLine, "field8C", routeObject, 0x8C); AppendUint32Field(routeLine, "field90", routeObject, 0x90); AppendPointerField(routeLine, "field98", routeObject, 0x98); AppendPointerField(routeLine, "field88", routeObject, 0x88); AppendPointerField(routeLine, "fieldF8", routeObject, 0xF8); AppendPointerField(routeLine, "field120", routeObject, 0x120); AppendByteField(routeLine, "fieldE2", routeObject, 0xE2); LogRed4ext(routeLine.str()); } void LogRouteEventFields(const char* aName, uint32_t aCount, void* aEvent) { uint32_t routeId = 0; uint8_t active = 0; void* system = nullptr; if (aEvent) { TryReadMemory(reinterpret_cast(reinterpret_cast(aEvent) + 0x18), routeId); TryReadMemory(reinterpret_cast(reinterpret_cast(aEvent) + 0x20), system); TryReadMemory(reinterpret_cast(reinterpret_cast(aEvent) + 0x28), active); } std::ostringstream line; line << "route-event-fields " << aName << " call=" << aCount << " routeId=" << routeId << " active=" << static_cast(active); AppendPointerWithRva(line, "event", aEvent); AppendPointerWithRva(line, "system", system); AppendObjectVtableSlots(line, aEvent, {0x20, 0x28, 0x30}); LogRed4ext(line.str()); } void LogJournalListenerContext(const char* aName, uint32_t aCount, void* aListener, void* aEvent) { std::ostringstream line; line << "hook " << aName << " call=" << aCount; AppendPointerWithRva(line, "listener", aListener); AppendPointerWithRva(line, "event", aEvent); AppendReturnRva(line, __builtin_return_address(0)); auto* field08 = ReadPointerField(aListener, 0x08); auto* field18 = ReadPointerField(aListener, 0x18); auto* field20 = ReadPointerField(aListener, 0x20); auto* field70 = ReadPointerField(aListener, 0x70); auto* field78 = ReadPointerField(aListener, 0x78); AppendPointerWithRva(line, "field08", field08); AppendPointerWithRva(line, "field18", field18); AppendPointerWithRva(line, "field20", field20); AppendPointerWithRva(line, "field70", field70); AppendPointerWithRva(line, "field78", field78); LogRed4ext(line.str()); if (field70) { std::ostringstream owner; owner << "journal-listener-context " << aName << " call=" << aCount; AppendPointerWithRva(owner, "field70", field70); AppendObjectVtableSlots(owner, field70, {0x1C8, 0x218, 0x230, 0x238, 0x240, 0x248, 0x340, 0x348, 0x358}); LogRed4ext(owner.str()); } if (field78 && field78 != field70) { std::ostringstream service; service << "journal-listener-context " << aName << " call=" << aCount; AppendPointerWithRva(service, "field78", field78); AppendObjectVtableSlots(service, field78, {0x1C8, 0x218, 0x220, 0x230, 0x238, 0x240, 0x248, 0x340, 0x348, 0x358}); LogRed4ext(service.str()); } } void ScanRegionForString(const uint8_t* aRegionBegin, size_t aRegionSize, uintptr_t aImageBase, const char* aNeedle, uint32_t& aMatches) { constexpr uint32_t kMaxMatchesPerNeedle = 8; const auto needleSize = std::strlen(aNeedle); if (needleSize == 0 || aRegionSize < needleSize || aMatches >= kMaxMatchesPerNeedle) { return; } const auto* cursor = aRegionBegin; const auto* regionEnd = aRegionBegin + aRegionSize; while (cursor + needleSize <= regionEnd && aMatches < kMaxMatchesPerNeedle) { if (std::memcmp(cursor, aNeedle, needleSize) == 0) { const auto rva = reinterpret_cast(cursor) - aImageBase; std::ostringstream line; line << "exe-string match needle=\"" << aNeedle << "\" rva=0x" << std::hex << rva << std::dec; LogRed4ext(line.str()); ++aMatches; cursor += needleSize; continue; } ++cursor; } } void ScanExecutableStrings() { if (gScannedExecutable) { return; } gScannedExecutable = true; const auto imageBase = reinterpret_cast(GetModuleHandleW(nullptr)); if (!imageBase) { LogRed4ext("exe-string scan skipped: main module unavailable"); return; } const auto* dos = reinterpret_cast(imageBase); if (dos->e_magic != IMAGE_DOS_SIGNATURE) { LogRed4ext("exe-string scan skipped: bad DOS header"); return; } const auto* nt = reinterpret_cast(imageBase + dos->e_lfanew); if (nt->Signature != IMAGE_NT_SIGNATURE) { LogRed4ext("exe-string scan skipped: bad NT header"); return; } const auto imageSize = static_cast(nt->OptionalHeader.SizeOfImage); std::ostringstream header; header << "exe-string scan begin base=0x" << std::hex << imageBase << " size=0x" << imageSize << std::dec; LogRed4ext(header.str()); constexpr std::array kNeedles{ "GPSSystem/Tick", "GPSSystem", "GPSSettings", "RunGPSQuery", "UpdateGPSQuery", "gameuiGPSGameController", "OnSetHasGPSPortal", "IsGPSPortal", "m_cookedGpsData", "gameCookedGpsMappinData", "GetNavigationFunctionalTests", "TrafficSystem_Pathfinding", "TrafficSystem_PathMap", "Pathfinding Algorithm Failed", "No Path Found in Traffic", "PathFindingFailed", "worldTrafficLanePlayerGPSInfo", "worldTrafficPersistentResource", "worldTrafficConnectivityOutLane", }; for (const auto* needle : kNeedles) { uint32_t matches = 0; uintptr_t cursor = imageBase; const uintptr_t end = imageBase + imageSize; while (cursor < end) { MEMORY_BASIC_INFORMATION mbi{}; if (!VirtualQuery(reinterpret_cast(cursor), &mbi, sizeof(mbi))) { break; } const auto regionBegin = reinterpret_cast(mbi.BaseAddress); const auto regionEnd = regionBegin + mbi.RegionSize; if (mbi.State == MEM_COMMIT && mbi.Type == MEM_IMAGE && IsReadablePage(mbi.Protect)) { const auto scanBegin = std::max(regionBegin, imageBase); const auto scanEnd = std::min(regionEnd, end); if (scanBegin < scanEnd) { ScanRegionForString(reinterpret_cast(scanBegin), scanEnd - scanBegin, imageBase, needle, matches); } } if (regionEnd <= cursor) { break; } cursor = regionEnd; } if (matches == 0) { std::ostringstream line; line << "exe-string no match needle=\"" << needle << "\""; LogRed4ext(line.str()); } } LogRed4ext("exe-string scan end"); } void DetourGpsSystemTick(void* aThis, void* a2, void* a3, void* a4) { if (gGpsTickLogCount < 8) { LogHookCall("GPSSystem/Tick", gGpsTickLogCount, aThis, a2, a3, a4); ++gGpsTickLogCount; } if (gOriginalGpsSystemTick) { gOriginalGpsSystemTick(aThis, a2, a3, a4); } } void LogJournalListeners(uint32_t aCallCount, void* aThis) { constexpr uint32_t kMaxListenerDumps = 16; constexpr uint32_t kMaxListenersPerDump = 24; constexpr uint32_t kSuspiciousListenerCount = 512; if (gJournalTrackEntryListenerDumpCount >= kMaxListenerDumps || !aThis) { return; } uintptr_t listenerBase = 0; uint32_t listenerCount = 0; const auto objectAddress = reinterpret_cast(aThis); const auto hasBase = TryReadMemory(reinterpret_cast(objectAddress + kJournalListenerArrayOffset), listenerBase); const auto hasCount = TryReadMemory(reinterpret_cast(objectAddress + kJournalListenerCountOffset), listenerCount); std::ostringstream header; header << "journal-listeners dump=" << gJournalTrackEntryListenerDumpCount << " call=" << aCallCount << " hasBase=" << hasBase << " hasCount=" << hasCount << " count=" << listenerCount; AppendPointerWithRva(header, "base", reinterpret_cast(listenerBase)); LogRed4ext(header.str()); ++gJournalTrackEntryListenerDumpCount; if (!hasBase || !hasCount || !listenerBase || listenerCount > kSuspiciousListenerCount) { return; } const auto cappedCount = std::min(listenerCount, kMaxListenersPerDump); for (uint32_t index = 0; index < cappedCount; ++index) { void* listener = nullptr; void* vtable = nullptr; void* callback28 = nullptr; void* callback30 = nullptr; void* callback50 = nullptr; const auto listenerAddress = reinterpret_cast(listenerBase + index * sizeof(void*)); const auto hasListener = TryReadMemory(listenerAddress, listener); const auto hasVtable = hasListener && TryReadMemory(listener, vtable); if (hasVtable && vtable) { const auto vtableAddress = reinterpret_cast(vtable); TryReadMemory(reinterpret_cast(vtableAddress + 0x28), callback28); TryReadMemory(reinterpret_cast(vtableAddress + 0x30), callback30); TryReadMemory(reinterpret_cast(vtableAddress + 0x50), callback50); } std::ostringstream line; line << "journal-listener call=" << aCallCount << " index=" << index << " hasListener=" << hasListener << " hasVtable=" << hasVtable; AppendPointerWithRva(line, "listener", listener); AppendPointerWithRva(line, "vtable", vtable); AppendPointerWithRva(line, "vt28", callback28); AppendPointerWithRva(line, "vt30", callback30); AppendPointerWithRva(line, "vt50", callback50); LogRed4ext(line.str()); } } void DetourJournalTrackEntry(void* aThis, void* aEntryHandle) { constexpr uint32_t kMaxCalls = 96; const auto callCount = gJournalTrackEntryLogCount; if (callCount < kMaxCalls) { std::ostringstream line; line << "hook JournalManager::TrackEntry impl call=" << callCount; AppendPointerWithRva(line, "this", aThis); AppendPointerWithRva(line, "entryHandle", aEntryHandle); AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); ++gJournalTrackEntryLogCount; } if (gOriginalJournalTrackEntry) { gOriginalJournalTrackEntry(aThis, aEntryHandle); } } void DispatchJournalListenerCallback(const char* aName, uint32_t& aCounter, JournalListenerCallbackFunc aOriginal, void* aListener, void* aEvent) { constexpr uint32_t kMaxCalls = 64; if (aCounter < kMaxCalls) { LogJournalListenerContext(aName, aCounter, aListener, aEvent); ++aCounter; } if (aOriginal) { aOriginal(aListener, aEvent); } } void DetourJournalListenerObjectiveVt28(void* aListener, void* aEvent) { DispatchJournalListenerCallback("JournalListener::Objective vt28", gJournalListenerObjectiveVt28LogCount, gOriginalJournalListenerObjectiveVt28, aListener, aEvent); } void DetourJournalListenerObjectiveVt30(void* aListener, void* aEvent) { DispatchJournalListenerCallback("JournalListener::Objective vt30", gJournalListenerObjectiveVt30LogCount, gOriginalJournalListenerObjectiveVt30, aListener, aEvent); } void DetourJournalListenerRouteVt28(void* aListener, void* aEvent) { DispatchJournalListenerCallback("JournalListener::Route vt28", gJournalListenerRouteVt28LogCount, gOriginalJournalListenerRouteVt28, aListener, aEvent); } void DetourJournalListenerRouteVt30(void* aListener, void* aEvent) { DispatchJournalListenerCallback("JournalListener::Route vt30", gJournalListenerRouteVt30LogCount, gOriginalJournalListenerRouteVt30, aListener, aEvent); } void DetourJournalListenerRouteAltVt28(void* aListener, void* aEvent) { DispatchJournalListenerCallback("JournalListener::RouteAlt vt28", gJournalListenerRouteAltVt28LogCount, gOriginalJournalListenerRouteAltVt28, aListener, aEvent); } void DetourJournalListenerRouteAltVt30(void* aListener, void* aEvent) { DispatchJournalListenerCallback("JournalListener::RouteAlt vt30", gJournalListenerRouteAltVt30LogCount, gOriginalJournalListenerRouteAltVt30, aListener, aEvent); } void DetourJournalListenerUiVt28(void* aListener, void* aEvent) { DispatchJournalListenerCallback("JournalListener::UI vt28", gJournalListenerUiVt28LogCount, gOriginalJournalListenerUiVt28, aListener, aEvent); } void DetourJournalListenerUiVt30(void* aListener, void* aEvent) { DispatchJournalListenerCallback("JournalListener::UI vt30", gJournalListenerUiVt30LogCount, gOriginalJournalListenerUiVt30, aListener, aEvent); } void DetourJournalListenerTrackerVt28(void* aListener, void* aEvent) { DispatchJournalListenerCallback("JournalListener::Tracker vt28", gJournalListenerTrackerVt28LogCount, gOriginalJournalListenerTrackerVt28, aListener, aEvent); } bool DetourJournalRouteBridge(void* aThis, void* aEntryHandle) { constexpr uint32_t kMaxCalls = 64; const auto callCount = gJournalRouteBridgeLogCount; const auto shouldLog = callCount < kMaxCalls; if (shouldLog) { std::ostringstream line; line << "hook JournalRouteBridge::Update candidate call=" << callCount; AppendPointerWithRva(line, "this", aThis); AppendPointerWithRva(line, "entryHandle", aEntryHandle); AppendReturnRva(line, __builtin_return_address(0)); AppendPointerField(line, "field10", aThis, 0x10); AppendPointerField(line, "field18", aThis, 0x18); AppendPointerField(line, "field20", aThis, 0x20); AppendByteField(line, "field30", aThis, 0x30); AppendUint32Field(line, "field34", aThis, 0x34); AppendUint32Field(line, "field38", aThis, 0x38); LogRed4ext(line.str()); ++gJournalRouteBridgeLogCount; } bool result = false; if (gOriginalJournalRouteBridge) { result = gOriginalJournalRouteBridge(aThis, aEntryHandle); } if (shouldLog) { std::ostringstream line; line << "hook JournalRouteBridge::Update candidate result call=" << callCount << " value=" << static_cast(result); LogRed4ext(line.str()); } return result; } void DetourMappinRouteEventEnqueue(void* aSystem, uint32_t aRouteId, uint8_t aActive) { constexpr uint32_t kMaxCalls = 64; if (gMappinRouteEventEnqueueLogCount < kMaxCalls) { const auto callCount = gMappinRouteEventEnqueueLogCount; std::ostringstream line; line << "hook MappinSystem::RouteEventEnqueue slot240 call=" << callCount << " routeId=" << aRouteId << " active=" << static_cast(aActive); AppendPointerWithRva(line, "system", aSystem); AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); LogMappinRouteCollections("enqueue-before", callCount, aSystem); ++gMappinRouteEventEnqueueLogCount; } if (gOriginalMappinRouteEventEnqueue) { gOriginalMappinRouteEventEnqueue(aSystem, aRouteId, aActive); } } void DetourMappinRouteEventHandler(void* aEvent, void* aRouteHandle) { constexpr uint32_t kMaxCalls = 64; if (gMappinRouteEventHandlerLogCount < kMaxCalls) { const auto callCount = gMappinRouteEventHandlerLogCount; void* routeOwner = ReadPointerField(aRouteHandle, 0); const auto routeKey = reinterpret_cast(ReadPointerField(routeOwner, 0x40)); std::ostringstream line; line << "hook MappinRouteEvent::Handle call=" << callCount; AppendPointerWithRva(line, "event", aEvent); AppendPointerWithRva(line, "routeHandle", aRouteHandle); AppendPointerWithRva(line, "routeOwner", routeOwner); AppendHexValue(line, "routeKey", routeKey); AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); LogRouteEventFields("handler-before", callCount, aEvent); ++gMappinRouteEventHandlerLogCount; } if (gOriginalMappinRouteEventHandler) { gOriginalMappinRouteEventHandler(aEvent, aRouteHandle); } } void DetourMappinRouteActivate(void* aSystem, uint64_t aRouteKey) { constexpr uint32_t kMaxCalls = 64; if (gMappinRouteActivateLogCount < kMaxCalls) { const auto callCount = gMappinRouteActivateLogCount; std::ostringstream line; line << "hook MappinSystem::RouteActivate call=" << callCount; AppendPointerWithRva(line, "system", aSystem); AppendHexValue(line, "routeKey", aRouteKey); AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); LogMappinRouteCollections("activate-before", callCount, aSystem); ++gMappinRouteActivateLogCount; } if (gOriginalMappinRouteActivate) { gOriginalMappinRouteActivate(aSystem, aRouteKey); } } void DetourMappinRouteDeactivate(void* aSystem, uint64_t aRouteKey) { constexpr uint32_t kMaxCalls = 64; if (gMappinRouteDeactivateLogCount < kMaxCalls) { const auto callCount = gMappinRouteDeactivateLogCount; std::ostringstream line; line << "hook MappinSystem::RouteDeactivate call=" << callCount; AppendPointerWithRva(line, "system", aSystem); AppendHexValue(line, "routeKey", aRouteKey); AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); LogMappinRouteCollections("deactivate-before", callCount, aSystem); ++gMappinRouteDeactivateLogCount; } if (gOriginalMappinRouteDeactivate) { gOriginalMappinRouteDeactivate(aSystem, aRouteKey); } } void DetourRouteBuildCandidate(void* aThis, uint32_t aRouteId, uint32_t aAltRouteId, uint32_t aPreviousRouteId, void* aRoutePositionData, void* aPrimaryDescriptor, void* aSecondaryDescriptor, void* aRouteData88, void* aRouteData108, void* aRouteData114, uint8_t aFlag, uint32_t aMode) { constexpr uint32_t kMaxCalls = 64; if (gRouteBuildCandidateLogCount < kMaxCalls) { std::ostringstream line; line << "hook RouteBuildCandidate 0x5625a4 call=" << gRouteBuildCandidateLogCount << " routeId=" << aRouteId << " altRouteId=" << aAltRouteId << " previousRouteId=" << aPreviousRouteId << " flag=" << static_cast(aFlag) << " mode=" << aMode; AppendPointerWithRva(line, "this", aThis); AppendPointerWithRva(line, "routePositionData", aRoutePositionData); AppendPointerWithRva(line, "primaryDescriptor", aPrimaryDescriptor); AppendPointerWithRva(line, "secondaryDescriptor", aSecondaryDescriptor); AppendPointerWithRva(line, "routeData88", aRouteData88); AppendPointerWithRva(line, "routeData108", aRouteData108); AppendPointerWithRva(line, "routeData114", aRouteData114); AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); ++gRouteBuildCandidateLogCount; } if (gOriginalRouteBuildCandidate) { gOriginalRouteBuildCandidate(aThis, aRouteId, aAltRouteId, aPreviousRouteId, aRoutePositionData, aPrimaryDescriptor, aSecondaryDescriptor, aRouteData88, aRouteData108, aRouteData114, aFlag, aMode); } } void DispatchRouteObserverCallback(const char* aName, uint32_t& aCounter, RouteObserverCallbackFunc aOriginal, void* aObserver, void* aRouteEntry) { constexpr uint32_t kMaxCalls = 96; if (aCounter < kMaxCalls) { LogRouteObserverContext(aName, aCounter, aObserver, aRouteEntry); ++aCounter; } if (aOriginal) { aOriginal(aObserver, aRouteEntry); } } void DetourRouteObserver0Activate(void* aObserver, void* aRouteEntry) { DispatchRouteObserverCallback("RouteObserver0::Activate slot48", gRouteObserver0ActivateLogCount, gOriginalRouteObserver0Activate, aObserver, aRouteEntry); } void DetourRouteObserver1Activate(void* aObserver, void* aRouteEntry) { DispatchRouteObserverCallback("RouteObserver1::Activate slot48", gRouteObserver1ActivateLogCount, gOriginalRouteObserver1Activate, aObserver, aRouteEntry); } void DetourRouteObserver23Activate(void* aObserver, void* aRouteEntry) { DispatchRouteObserverCallback("RouteObserver23::Activate slot48", gRouteObserver23ActivateLogCount, gOriginalRouteObserver23Activate, aObserver, aRouteEntry); } void DetourRouteObserver0Deactivate(void* aObserver, void* aRouteEntry) { DispatchRouteObserverCallback("RouteObserver0::Deactivate slot50", gRouteObserver0DeactivateLogCount, gOriginalRouteObserver0Deactivate, aObserver, aRouteEntry); } void DetourRouteObserver1Deactivate(void* aObserver, void* aRouteEntry) { DispatchRouteObserverCallback("RouteObserver1::Deactivate slot50", gRouteObserver1DeactivateLogCount, gOriginalRouteObserver1Deactivate, aObserver, aRouteEntry); } void DetourRouteObserver23Deactivate(void* aObserver, void* aRouteEntry) { DispatchRouteObserverCallback("RouteObserver23::Deactivate slot50", gRouteObserver23DeactivateLogCount, gOriginalRouteObserver23Deactivate, aObserver, aRouteEntry); } void DetourFrameMappinPathWrapper(void* aThis, void* aStack, void* aResult) { if (gFrameMappinPathWrapperLogCount < 64) { LogScriptNativeCall("FrameMappinPathWrapper", gFrameMappinPathWrapperLogCount, aThis, aStack, aResult); ++gFrameMappinPathWrapperLogCount; } InspectAndHookMappinSystem(aThis); if (gOriginalFrameMappinPathWrapper) { gOriginalFrameMappinPathWrapper(aThis, aStack, aResult); } } void DetourFrameMappinPathCore(void* aThis, uint32_t aPathKind, void* a3, void* aPathRect) { if (gFrameMappinPathCoreLogCount < 64) { LogFrameMappinPathCoreCall(gFrameMappinPathCoreLogCount, aThis, aPathKind, a3, aPathRect); ++gFrameMappinPathCoreLogCount; } InspectAndHookMappinSystem(aThis); if (gOriginalFrameMappinPathCore) { gOriginalFrameMappinPathCore(aThis, aPathKind, a3, aPathRect); } } void DetourSetSelectedMappinWrapper(void* aThis, void* aStack, void* aResult) { if (gSetSelectedMappinWrapperLogCount < 64) { LogScriptNativeCall("SetSelectedMappinWrapper", gSetSelectedMappinWrapperLogCount, aThis, aStack, aResult); ++gSetSelectedMappinWrapperLogCount; } InspectAndHookMappinSystem(aThis); if (gOriginalSetSelectedMappinWrapper) { gOriginalSetSelectedMappinWrapper(aThis, aStack, aResult); } } void DetourSetSelectedMappinByIdWrapper(void* aThis, void* aStack, void* aResult) { if (gSetSelectedMappinByIdWrapperLogCount < 64) { LogScriptNativeCall("SetSelectedMappinByIdWrapper", gSetSelectedMappinByIdWrapperLogCount, aThis, aStack, aResult); ++gSetSelectedMappinByIdWrapperLogCount; } InspectAndHookMappinSystem(aThis); if (gOriginalSetSelectedMappinByIdWrapper) { gOriginalSetSelectedMappinByIdWrapper(aThis, aStack, aResult); } } void DetourSetSelectedMappinByPositionWrapper(void* aThis, void* aStack, void* aResult) { if (gSetSelectedMappinByPositionWrapperLogCount < 64) { LogScriptNativeCall("SetSelectedMappinByPositionWrapper", gSetSelectedMappinByPositionWrapperLogCount, aThis, aStack, aResult); ++gSetSelectedMappinByPositionWrapperLogCount; } InspectAndHookMappinSystem(aThis); if (gOriginalSetSelectedMappinByPositionWrapper) { gOriginalSetSelectedMappinByPositionWrapper(aThis, aStack, aResult); } } void DetourSetSelectedMappinCore(void* aThis, void* aMappinHandle) { if (gSetSelectedMappinCoreLogCount < 64) { LogSetSelectedMappinCoreCall(gSetSelectedMappinCoreLogCount, aThis, aMappinHandle); ++gSetSelectedMappinCoreLogCount; } InspectAndHookMappinSystem(aThis); if (gOriginalSetSelectedMappinCore) { gOriginalSetSelectedMappinCore(aThis, aMappinHandle); } } void DetourTrackCustomPositionMappinWrapper(void* aThis, void* aStack, void* aResult) { if (gTrackCustomPositionMappinWrapperLogCount < 64) { LogScriptNativeCall("TrackCustomPositionMappinWrapper", gTrackCustomPositionMappinWrapperLogCount, aThis, aStack, aResult); ++gTrackCustomPositionMappinWrapperLogCount; } InspectAndHookMappinSystem(aThis); if (gOriginalTrackCustomPositionMappinWrapper) { gOriginalTrackCustomPositionMappinWrapper(aThis, aStack, aResult); } } void DetourTrackCustomPositionMappinCore(void* aThis) { if (gTrackCustomPositionMappinCoreLogCount < 64) { LogTrackCustomPositionMappinCoreCall(gTrackCustomPositionMappinCoreLogCount, aThis); ++gTrackCustomPositionMappinCoreLogCount; } InspectAndHookMappinSystem(aThis); if (gOriginalTrackCustomPositionMappinCore) { gOriginalTrackCustomPositionMappinCore(aThis); } } void DetourUntrackCustomPositionMappinWrapper(void* aThis, void* aStack, void* aResult) { if (gUntrackCustomPositionMappinWrapperLogCount < 64) { LogScriptNativeCall("UntrackCustomPositionMappinWrapper", gUntrackCustomPositionMappinWrapperLogCount, aThis, aStack, aResult); ++gUntrackCustomPositionMappinWrapperLogCount; } InspectAndHookMappinSystem(aThis); if (gOriginalUntrackCustomPositionMappinWrapper) { gOriginalUntrackCustomPositionMappinWrapper(aThis, aStack, aResult); } } void DetourTrackMappinWrapper(void* aThis, void* aStack, void* aResult) { if (gTrackMappinWrapperLogCount < 64) { LogScriptNativeCall("TrackMappinWrapper", gTrackMappinWrapperLogCount, aThis, aStack, aResult); ++gTrackMappinWrapperLogCount; } if (gOriginalTrackMappinWrapper) { gOriginalTrackMappinWrapper(aThis, aStack, aResult); } } void DetourTrackMappinCore(void* aThis, void* aMappinHandle) { if (gTrackMappinCoreLogCount < 64) { LogTrackMappinCoreCall(gTrackMappinCoreLogCount, aThis, aMappinHandle); ++gTrackMappinCoreLogCount; } InspectAndHookMappinSystem(aThis); if (gOriginalTrackMappinCore) { gOriginalTrackMappinCore(aThis, aMappinHandle); } } void DetourSetMappinTrackingAlternativeWrapper(void* aThis, void* aStack, void* aResult) { if (gSetMappinTrackingAlternativeLogCount < 64) { LogScriptNativeCall("SetMappinTrackingAlternativeWrapper", gSetMappinTrackingAlternativeLogCount, aThis, aStack, aResult); ++gSetMappinTrackingAlternativeLogCount; } if (gOriginalSetMappinTrackingAlternativeWrapper) { gOriginalSetMappinTrackingAlternativeWrapper(aThis, aStack, aResult); } } void DetourMappinUpdateCustomPosition(void* aSystem, void* aHandle, void* aPositionData) { if (gMappinUpdateCustomPositionLogCount < 32) { std::ostringstream line; line << "hook MappinSystem::UpdateCustomPosition slot1f0 call=" << gMappinUpdateCustomPositionLogCount; AppendPointerWithRva(line, "system", aSystem); AppendPointerWithRva(line, "handle", aHandle); AppendPointerWithRva(line, "positionData", aPositionData); AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); ++gMappinUpdateCustomPositionLogCount; } if (gOriginalMappinUpdateCustomPosition) { gOriginalMappinUpdateCustomPosition(aSystem, aHandle, aPositionData); } } void DetourMappinSetTracked(void* aSystem, void* aMappinId) { if (gMappinSetTrackedLogCount < 64) { std::ostringstream line; line << "hook MappinSystem::SetTracked slot220 call=" << gMappinSetTrackedLogCount; AppendPointerWithRva(line, "system", aSystem); AppendPointerWithRva(line, "mappinId", aMappinId); AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); ++gMappinSetTrackedLogCount; } if (gOriginalMappinSetTracked) { gOriginalMappinSetTracked(aSystem, aMappinId); } } void DetourMappinClearTracked(void* aSystem) { if (gMappinClearTrackedLogCount < 64) { std::ostringstream line; line << "hook MappinSystem::ClearTracked slot228 call=" << gMappinClearTrackedLogCount; AppendPointerWithRva(line, "system", aSystem); AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); ++gMappinClearTrackedLogCount; } if (gOriginalMappinClearTracked) { gOriginalMappinClearTracked(aSystem); } } void DetourMappinFramePath(void* aSystem, uint32_t aPathKind, void* aResult) { if (gMappinFramePathLogCount < 32) { std::ostringstream line; line << "hook MappinSystem::FramePath slot280 call=" << gMappinFramePathLogCount << " pathKind=" << aPathKind; AppendPointerWithRva(line, "system", aSystem); AppendPointerWithRva(line, "result", aResult); AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); ++gMappinFramePathLogCount; } if (gOriginalMappinFramePath) { gOriginalMappinFramePath(aSystem, aPathKind, aResult); } } void* DetourMappinPositionQuery(void* aSystem, void* a2, void* a3, void* a4, void* a5) { const auto shouldLog = gMappinPositionQueryLogCount < 32; if (shouldLog) { std::ostringstream line; line << "hook MappinSystem::PositionQuery slot2e0 call=" << gMappinPositionQueryLogCount; AppendPointerWithRva(line, "system", aSystem); AppendPointerWithRva(line, "a2", a2); AppendPointerWithRva(line, "a3", a3); AppendPointerWithRva(line, "a4", a4); AppendPointerWithRva(line, "a5", a5); AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); ++gMappinPositionQueryLogCount; } void* result = nullptr; if (gOriginalMappinPositionQuery) { result = gOriginalMappinPositionQuery(aSystem, a2, a3, a4, a5); } if (shouldLog) { std::ostringstream line; line << "hook MappinSystem::PositionQuery slot2e0 result"; AppendPointerWithRva(line, "value", result); LogRed4ext(line.str()); } return result; } void* DetourMappinCreateCustomPosition(void* aSystem, void* aName, void* aParams, void* aPositionData) { const auto shouldLog = gMappinCreateCustomPositionLogCount < 32; if (shouldLog) { std::ostringstream line; line << "hook MappinSystem::CreateCustomPosition slot2f0 call=" << gMappinCreateCustomPositionLogCount; AppendPointerWithRva(line, "system", aSystem); AppendPointerWithRva(line, "name", aName); AppendPointerWithRva(line, "params", aParams); AppendPointerWithRva(line, "positionData", aPositionData); AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); ++gMappinCreateCustomPositionLogCount; } void* result = nullptr; if (gOriginalMappinCreateCustomPosition) { result = gOriginalMappinCreateCustomPosition(aSystem, aName, aParams, aPositionData); } if (shouldLog) { std::ostringstream line; line << "hook MappinSystem::CreateCustomPosition slot2f0 result"; AppendPointerWithRva(line, "value", result); LogRed4ext(line.str()); } return result; } void DetourMappinReleaseQuery(void* aSystem, void* aHandle) { if (gMappinReleaseQueryLogCount < 32) { std::ostringstream line; line << "hook MappinSystem::ReleaseQuery slot368 call=" << gMappinReleaseQueryLogCount; AppendPointerWithRva(line, "system", aSystem); AppendPointerWithRva(line, "handle", aHandle); AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); ++gMappinReleaseQueryLogCount; } if (gOriginalMappinReleaseQuery) { gOriginalMappinReleaseQuery(aSystem, aHandle); } } void* DetourMappinActiveRoute(void* aSystem) { const auto shouldLog = gMappinActiveRouteLogCount < 32; if (shouldLog) { std::ostringstream line; line << "hook MappinSystem::ActiveRoute slot3a8 call=" << gMappinActiveRouteLogCount; AppendPointerWithRva(line, "system", aSystem); AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); ++gMappinActiveRouteLogCount; } void* result = nullptr; if (gOriginalMappinActiveRoute) { result = gOriginalMappinActiveRoute(aSystem); } if (shouldLog) { std::ostringstream line; line << "hook MappinSystem::ActiveRoute slot3a8 result"; AppendPointerWithRva(line, "value", result); LogRed4ext(line.str()); } return result; } bool DetourRunGpsQueryBody(void* aThis, void* aStart, void* aEnd, int32_t* aOutQueryId) { constexpr uint32_t kMaxCalls = 96; const auto callCount = gRunGpsQueryBodyLogCount; const auto shouldLog = callCount < kMaxCalls; if (shouldLog) { std::ostringstream line; line << "hook RunGPSQuery body 0x29bd128 call=" << callCount; AppendPointerWithRva(line, "this", aThis); AppendPointerWithRva(line, "outQueryId", aOutQueryId); AppendVector4Pointer(line, "start", aStart); AppendVector4Pointer(line, "end", aEnd); AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); ++gRunGpsQueryBodyLogCount; } bool result = false; if (gOriginalRunGpsQueryBody) { result = gOriginalRunGpsQueryBody(aThis, aStart, aEnd, aOutQueryId); } if (shouldLog) { int32_t queryId = 0; const auto hasQueryId = aOutQueryId && TryReadMemory(aOutQueryId, queryId); std::ostringstream line; line << "hook RunGPSQuery body result call=" << callCount << " value=" << static_cast(result) << " hasQueryId=" << hasQueryId; if (hasQueryId) { line << " queryId=" << queryId; } LogRed4ext(line.str()); } return result; } bool DetourUpdateGpsQueryBody(void* aThis, uint32_t aQueryId, void* aOutPoints, void* aOutError) { constexpr uint32_t kMaxCalls = 128; const auto callCount = gUpdateGpsQueryBodyLogCount; const auto shouldLog = callCount < kMaxCalls; if (shouldLog) { std::ostringstream line; line << "hook UpdateGPSQuery body 0x29bd254 call=" << callCount << " queryId=" << aQueryId; AppendPointerWithRva(line, "this", aThis); AppendPointerWithRva(line, "outPoints", aOutPoints); AppendPointerWithRva(line, "outError", aOutError); AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); ++gUpdateGpsQueryBodyLogCount; } bool result = false; if (gOriginalUpdateGpsQueryBody) { result = gOriginalUpdateGpsQueryBody(aThis, aQueryId, aOutPoints, aOutError); } if (shouldLog) { std::ostringstream line; line << "hook UpdateGPSQuery body result call=" << callCount << " value=" << static_cast(result); AppendPathBufferSummary(line, "cachedPath", ReadPointerField(aThis, 0x58)); LogRed4ext(line.str()); } return result; } int32_t DetourGpsQuerySubmit(void* aManager, void* aQuery) { constexpr uint32_t kMaxCalls = 128; const auto returnAddress = __builtin_return_address(0); uintptr_t returnRva = 0; TryGetImageRva(returnAddress, returnRva); const auto callCount = gGpsQuerySubmitLogCount; const auto shouldLog = callCount < kMaxCalls; if (shouldLog) { std::ostringstream line; line << "hook GPSQuerySubmit 0x70a42c call=" << callCount; AppendPointerWithRva(line, "manager", aManager); AppendPointerWithRva(line, "query", aQuery); AppendUint32Field(line, "query_f00", aQuery, 0x00); AppendUint32Field(line, "query_f04", aQuery, 0x04); AppendUint32Field(line, "query_f08", aQuery, 0x08); AppendUint32Field(line, "query_f0c", aQuery, 0x0C); AppendUint32Field(line, "query_fcc", aQuery, 0xCC); AppendVector4Field(line, "query_pos58", aQuery, 0x58); AppendReturnRva(line, returnAddress); LogRed4ext(line.str()); ++gGpsQuerySubmitLogCount; } int32_t result = -1; if (gOriginalGpsQuerySubmit) { result = gOriginalGpsQuerySubmit(aManager, aQuery); } if (result >= 0) { TrackGpsQuery(static_cast(result), aManager, returnRva, callCount); } if (shouldLog) { std::ostringstream line; line << "hook GPSQuerySubmit result call=" << callCount << " queryId=" << result; AppendUint32Field(line, "manager_nextIdD4", aManager, 0xD4); LogRed4ext(line.str()); } return result; } void DetourGpsQueryDispatch(void* aRuntimeQuery, void* aTargetPosition, void* aArg3, void* aArg4) { constexpr uint32_t kMaxCalls = 128; if (gGpsQueryDispatchLogCount < kMaxCalls) { std::ostringstream line; line << "hook GPSQueryDispatch 0x70a570 call=" << gGpsQueryDispatchLogCount; AppendPointerWithRva(line, "runtimeQuery", aRuntimeQuery); AppendPointerWithRva(line, "arg3", aArg3); AppendPointerWithRva(line, "arg4", aArg4); AppendUint32Field(line, "runtimeQuery_state138", aRuntimeQuery, 0x138); AppendUint32Field(line, "runtimeQuery_bank13c", aRuntimeQuery, 0x13C); AppendVector4Pointer(line, "target", aTargetPosition); AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); ++gGpsQueryDispatchLogCount; } if (gOriginalGpsQueryDispatch) { gOriginalGpsQueryDispatch(aRuntimeQuery, aTargetPosition, aArg3, aArg4); } } bool DetourGpsQueryResultFetch(void* aManager, uint32_t aQueryId, void* aOutPath) { constexpr uint32_t kMaxUntrackedSamples = 32; constexpr uint32_t kMaxTrackedSamplesPerQuery = 24; const auto callCount = gGpsQueryResultFetchLogCount++; const auto returnAddress = __builtin_return_address(0); bool result = false; if (gOriginalGpsQueryResultFetch) { result = gOriginalGpsQueryResultFetch(aManager, aQueryId, aOutPath); } TrackedGpsQuery trackedQuery{}; uint32_t perQueryCall = 0; const auto isTracked = NoteTrackedGpsQueryResultFetch(aQueryId, aManager, trackedQuery, perQueryCall); const auto shouldLog = (isTracked && (perQueryCall < kMaxTrackedSamplesPerQuery || result)) || (!isTracked && callCount < kMaxUntrackedSamples && aQueryId < 2); if (shouldLog) { std::ostringstream line; line << "hook GPSQueryResultFetch 0x7094b8 call=" << callCount << " queryId=" << aQueryId << " tracked=" << static_cast(isTracked) << " perQueryCall=" << perQueryCall << " value=" << static_cast(result); if (isTracked) { AppendTrackedGpsQuery(line, trackedQuery); } AppendPointerWithRva(line, "manager", aManager); AppendPointerWithRva(line, "outPath", aOutPath); AppendReturnRva(line, returnAddress); AppendPathBufferSummary(line, "outPath", aOutPath); if (result && isTracked && trackedQuery.submitReturnRva == 0x8D20D4) { AppendGpsResultObjectSummary(line, "gpsResult", aOutPath); } LogRed4ext(line.str()); } return result; } int32_t DetourGpsQueryStatus(void* aManager, uint32_t aQueryId) { constexpr uint32_t kMaxUntrackedSamples = 32; constexpr uint32_t kMaxTrackedSamplesPerQuery = 24; const auto callCount = gGpsQueryStatusLogCount++; const auto returnAddress = __builtin_return_address(0); int32_t result = 0; if (gOriginalGpsQueryStatus) { result = gOriginalGpsQueryStatus(aManager, aQueryId); } TrackedGpsQuery trackedQuery{}; uint32_t perQueryCall = 0; const auto isTracked = NoteTrackedGpsQueryStatus(aQueryId, aManager, trackedQuery, perQueryCall); const auto shouldLog = (isTracked && (perQueryCall < kMaxTrackedSamplesPerQuery || result != 0)) || (!isTracked && callCount < kMaxUntrackedSamples && aQueryId < 2); if (shouldLog) { std::ostringstream line; line << "hook GPSQueryStatus 0xaa5704 call=" << callCount << " queryId=" << aQueryId << " tracked=" << static_cast(isTracked) << " perQueryCall=" << perQueryCall << " value=" << result; if (isTracked) { AppendTrackedGpsQuery(line, trackedQuery); } AppendPointerWithRva(line, "manager", aManager); AppendReturnRva(line, returnAddress); LogRed4ext(line.str()); } return result; } void AttachProbeHook(const char* aName, uintptr_t aRva, void* aDetour, void** aOriginal) { if (!gSdk || !gSdk->hooking || !gSdk->hooking->Attach) { LogRed4ext("hook attach skipped: RED4ext hooking API unavailable"); return; } auto* target = reinterpret_cast(GetImageBase() + aRva); const auto attached = gSdk->hooking->Attach(gHandle, target, aDetour, aOriginal); std::ostringstream line; line << "hook attach " << aName << " target=" << target << " rva=0x" << std::hex << aRva << std::dec << " result=" << (attached ? "ok" : "failed") << " original=" << (aOriginal ? *aOriginal : nullptr); LogRed4ext(line.str()); } void AttachAbsoluteHook(const char* aName, void* aTarget, void* aDetour, void** aOriginal) { if (!gSdk || !gSdk->hooking || !gSdk->hooking->Attach) { LogRed4ext("absolute hook attach skipped: RED4ext hooking API unavailable"); return; } if (!aTarget) { std::ostringstream line; line << "absolute hook attach skipped " << aName << ": target=null"; LogRed4ext(line.str()); return; } const auto attached = gSdk->hooking->Attach(gHandle, aTarget, aDetour, aOriginal); std::ostringstream line; line << "absolute hook attach " << aName; AppendPointerWithRva(line, "target", aTarget); line << " result=" << (attached ? "ok" : "failed"); AppendPointerWithRva(line, "original", aOriginal ? *aOriginal : nullptr); LogRed4ext(line.str()); } void DetachProbeHook(const char* aName, uintptr_t aRva) { if (!gSdk || !gSdk->hooking || !gSdk->hooking->Detach) { return; } auto* target = reinterpret_cast(GetImageBase() + aRva); const auto detached = gSdk->hooking->Detach(gHandle, target); std::ostringstream line; line << "hook detach " << aName << " target=" << target << " result=" << (detached ? "ok" : "failed"); LogRed4ext(line.str()); } void DetachAbsoluteHook(const char* aName, void* aTarget) { if (!gSdk || !gSdk->hooking || !gSdk->hooking->Detach || !aTarget) { return; } const auto detached = gSdk->hooking->Detach(gHandle, aTarget); std::ostringstream line; line << "absolute hook detach " << aName; AppendPointerWithRva(line, "target", aTarget); line << " result=" << (detached ? "ok" : "failed"); LogRed4ext(line.str()); } void DetourObserver1ServiceRouteLookup(void* aService, void* aOut, uint32_t aRouteId) { const auto shouldLog = gObserver1ServiceRouteLookupLogCount < 96; const auto callCount = gObserver1ServiceRouteLookupLogCount; if (shouldLog) { std::ostringstream line; line << "hook Observer1Service::RouteLookup slot220 call=" << callCount << " routeId=" << aRouteId << "/0x" << std::hex << aRouteId << std::dec; AppendPointerWithRva(line, "service", aService); AppendPointerWithRva(line, "out", aOut); AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); ++gObserver1ServiceRouteLookupLogCount; } if (gOriginalObserver1ServiceRouteLookup) { gOriginalObserver1ServiceRouteLookup(aService, aOut, aRouteId); } if (shouldLog) { std::ostringstream line; line << "hook Observer1Service::RouteLookup slot220 result call=" << callCount; AppendPointerField(line, "out_f00", aOut, 0x00); AppendPointerField(line, "out_f08", aOut, 0x08); AppendPointerField(line, "out_f10", aOut, 0x10); auto* routeData = ReadPointerField(aOut, 0x00); if (routeData) { AppendPointerWithRva(line, "routeData", routeData); AppendPointerField(line, "routeData_f40", routeData, 0x40); AppendPointerField(line, "routeData_f48", routeData, 0x48); AppendPointerField(line, "routeData_f50", routeData, 0x50); AppendPointerField(line, "routeData_f58", routeData, 0x58); } LogRed4ext(line.str()); } } void MaybeAttachObserver1ServiceRouteLookup(void* aService) { if (gObserver1ServiceRouteLookupHookAttachAttempted || !aService) { return; } auto* vtable = ReadPointerField(aService, 0x00); if (!IsInImage(vtable)) { return; } gObserver1ServiceRouteLookupTarget = ReadPointerField(vtable, kObserver1ServiceRouteLookupSlotOffset); if (!gObserver1ServiceRouteLookupTarget) { return; } gObserver1ServiceRouteLookupHookAttachAttempted = true; AttachAbsoluteHook("Observer1Service::RouteLookup slot220", gObserver1ServiceRouteLookupTarget, reinterpret_cast(&DetourObserver1ServiceRouteLookup), reinterpret_cast(&gOriginalObserver1ServiceRouteLookup)); } void* DetourObserver1ServiceLookup(void* aServiceOwner, void* aSystemType) { void* result = nullptr; if (gOriginalObserver1ServiceLookup) { result = gOriginalObserver1ServiceLookup(aServiceOwner, aSystemType); } auto* observer1SystemType = GetObserver1SystemType(); if (!observer1SystemType || aSystemType != observer1SystemType) { return result; } if (gObserver1ServiceLookupLogCount < 96) { std::ostringstream line; line << "hook Observer1ServiceLookup 0x287c44 call=" << gObserver1ServiceLookupLogCount; AppendPointerWithRva(line, "serviceOwner", aServiceOwner); AppendPointerWithRva(line, "systemType", aSystemType); AppendPointerWithRva(line, "result", result); AppendObjectVtableSlots(line, result, {0x218, 0x220, 0x228, 0x230, 0x238, 0x240}); AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); ++gObserver1ServiceLookupLogCount; } MaybeAttachObserver1ServiceRouteLookup(result); return result; } void InspectAndHookMappinSystem(void* aThis) { auto* resolver = reinterpret_cast(GetImageBase() + kMappinSystemResolverRva); void* system = resolver ? resolver(aThis) : nullptr; auto* vtable = system ? *reinterpret_cast(system) : nullptr; if (gMappinSystemSlotLogCount < 16) { LogMappinSystemSlots(gMappinSystemSlotLogCount, system, vtable); ++gMappinSystemSlotLogCount; } if (gMappinSlotHookAttachAttempted || !vtable) { return; } gMappinSlotHookAttachAttempted = true; gMappinUpdateCustomPositionTarget = *reinterpret_cast(reinterpret_cast(vtable) + kMappinUpdateCustomPositionSlotOffset); gMappinSetTrackedTarget = *reinterpret_cast(reinterpret_cast(vtable) + kMappinSetTrackedSlotOffset); gMappinClearTrackedTarget = *reinterpret_cast(reinterpret_cast(vtable) + kMappinClearTrackedSlotOffset); gMappinFramePathTarget = *reinterpret_cast(reinterpret_cast(vtable) + kMappinFramePathSlotOffset); gMappinPositionQueryTarget = *reinterpret_cast(reinterpret_cast(vtable) + kMappinPositionQuerySlotOffset); gMappinCreateCustomPositionTarget = *reinterpret_cast(reinterpret_cast(vtable) + kMappinCreateCustomPositionSlotOffset); gMappinReleaseQueryTarget = *reinterpret_cast(reinterpret_cast(vtable) + kMappinReleaseQuerySlotOffset); gMappinActiveRouteTarget = *reinterpret_cast(reinterpret_cast(vtable) + kMappinActiveRouteSlotOffset); AttachAbsoluteHook("MappinSystem::UpdateCustomPosition slot1f0", gMappinUpdateCustomPositionTarget, reinterpret_cast(&DetourMappinUpdateCustomPosition), reinterpret_cast(&gOriginalMappinUpdateCustomPosition)); AttachAbsoluteHook("MappinSystem::SetTracked slot220", gMappinSetTrackedTarget, reinterpret_cast(&DetourMappinSetTracked), reinterpret_cast(&gOriginalMappinSetTracked)); AttachAbsoluteHook("MappinSystem::ClearTracked slot228", gMappinClearTrackedTarget, reinterpret_cast(&DetourMappinClearTracked), reinterpret_cast(&gOriginalMappinClearTracked)); AttachAbsoluteHook("MappinSystem::FramePath slot280", gMappinFramePathTarget, reinterpret_cast(&DetourMappinFramePath), reinterpret_cast(&gOriginalMappinFramePath)); AttachAbsoluteHook("MappinSystem::PositionQuery slot2e0", gMappinPositionQueryTarget, reinterpret_cast(&DetourMappinPositionQuery), reinterpret_cast(&gOriginalMappinPositionQuery)); AttachAbsoluteHook("MappinSystem::CreateCustomPosition slot2f0", gMappinCreateCustomPositionTarget, reinterpret_cast(&DetourMappinCreateCustomPosition), reinterpret_cast(&gOriginalMappinCreateCustomPosition)); AttachAbsoluteHook("MappinSystem::ReleaseQuery slot368", gMappinReleaseQueryTarget, reinterpret_cast(&DetourMappinReleaseQuery), reinterpret_cast(&gOriginalMappinReleaseQuery)); AttachAbsoluteHook("MappinSystem::ActiveRoute slot3a8", gMappinActiveRouteTarget, reinterpret_cast(&DetourMappinActiveRoute), reinterpret_cast(&gOriginalMappinActiveRoute)); } bool OnRunningEnter(void* aApp) { std::ostringstream line; line << "GameState Running OnEnter app=" << aApp; LogRed4ext(line.str()); return true; } bool OnRunningUpdate(void* aApp) { if (gUpdateLogCount < 5) { std::ostringstream line; line << "GameState Running OnUpdate app=" << aApp << " count=" << gUpdateLogCount; LogRed4ext(line.str()); ++gUpdateLogCount; } return false; } bool OnRunningExit(void* aApp) { std::ostringstream line; line << "GameState Running OnExit app=" << aApp; LogRed4ext(line.str()); return true; } RED4ext::v1::GameState gRunningState{ .OnEnter = OnRunningEnter, .OnUpdate = OnRunningUpdate, .OnExit = OnRunningExit, }; } BOOL APIENTRY DllMain(HMODULE aModule, DWORD aReason, LPVOID) { if (aReason == DLL_PROCESS_ATTACH) { gModule = aModule; LARGE_INTEGER frequency{}; LARGE_INTEGER counter{}; QueryPerformanceFrequency(&frequency); QueryPerformanceCounter(&counter); gLogFrequency = static_cast(frequency.QuadPart); gLogStartTick = static_cast(counter.QuadPart); } return TRUE; } RED4EXT_C_EXPORT bool RED4EXT_CALL Main(RED4ext::v1::PluginHandle aHandle, RED4ext::v1::EMainReason aReason, const RED4ext::v1::Sdk* aSdk) { gHandle = aHandle; gSdk = aSdk; std::ostringstream line; line << "EdgeWeightGPS Main reason=" << ReasonToString(aReason) << " handle=" << aHandle << " sdk=" << aSdk; if (aSdk && aSdk->runtime) { line << " runtime=" << static_cast(aSdk->runtime->major) << "." << aSdk->runtime->minor << "." << aSdk->runtime->patch; } LogRed4ext(line.str()); if (aReason == RED4ext::v1::EMainReason::Load && aSdk && aSdk->gameStates && aSdk->gameStates->Add) { const auto added = aSdk->gameStates->Add(aHandle, kGameStateRunning, &gRunningState); LogRed4ext(added ? "registered Running game-state callbacks" : "failed to register Running game-state callbacks"); AttachProbeHook("RunGPSQuery body 0x29bd128", kRunGpsQueryBodyRva, reinterpret_cast(&DetourRunGpsQueryBody), reinterpret_cast(&gOriginalRunGpsQueryBody)); AttachProbeHook("UpdateGPSQuery body 0x29bd254", kUpdateGpsQueryBodyRva, reinterpret_cast(&DetourUpdateGpsQueryBody), reinterpret_cast(&gOriginalUpdateGpsQueryBody)); AttachProbeHook("GPSQuerySubmit 0x70a42c", kGpsQuerySubmitRva, reinterpret_cast(&DetourGpsQuerySubmit), reinterpret_cast(&gOriginalGpsQuerySubmit)); AttachProbeHook("GPSQueryDispatch 0x70a570", kGpsQueryDispatchRva, reinterpret_cast(&DetourGpsQueryDispatch), reinterpret_cast(&gOriginalGpsQueryDispatch)); AttachProbeHook("GPSQueryResultFetch 0x7094b8", kGpsQueryResultFetchRva, reinterpret_cast(&DetourGpsQueryResultFetch), reinterpret_cast(&gOriginalGpsQueryResultFetch)); AttachProbeHook("GPSQueryStatus 0xaa5704", kGpsQueryStatusRva, reinterpret_cast(&DetourGpsQueryStatus), reinterpret_cast(&gOriginalGpsQueryStatus)); AttachProbeHook("GPSSystem/Tick", kGpsSystemTickRva, reinterpret_cast(&DetourGpsSystemTick), reinterpret_cast(&gOriginalGpsSystemTick)); AttachProbeHook("JournalManager::TrackEntry impl", kJournalTrackEntryImplRva, reinterpret_cast(&DetourJournalTrackEntry), reinterpret_cast(&gOriginalJournalTrackEntry)); if (kEnableVerboseJournalListenerHooks) { AttachProbeHook("JournalListener::Objective vt28", kJournalListenerObjectiveVt28Rva, reinterpret_cast(&DetourJournalListenerObjectiveVt28), reinterpret_cast(&gOriginalJournalListenerObjectiveVt28)); AttachProbeHook("JournalListener::Objective vt30", kJournalListenerObjectiveVt30Rva, reinterpret_cast(&DetourJournalListenerObjectiveVt30), reinterpret_cast(&gOriginalJournalListenerObjectiveVt30)); AttachProbeHook("JournalListener::Route vt28", kJournalListenerRouteVt28Rva, reinterpret_cast(&DetourJournalListenerRouteVt28), reinterpret_cast(&gOriginalJournalListenerRouteVt28)); AttachProbeHook("JournalListener::Route vt30", kJournalListenerRouteVt30Rva, reinterpret_cast(&DetourJournalListenerRouteVt30), reinterpret_cast(&gOriginalJournalListenerRouteVt30)); AttachProbeHook("JournalListener::RouteAlt vt28", kJournalListenerRouteAltVt28Rva, reinterpret_cast(&DetourJournalListenerRouteAltVt28), reinterpret_cast(&gOriginalJournalListenerRouteAltVt28)); AttachProbeHook("JournalListener::RouteAlt vt30", kJournalListenerRouteAltVt30Rva, reinterpret_cast(&DetourJournalListenerRouteAltVt30), reinterpret_cast(&gOriginalJournalListenerRouteAltVt30)); AttachProbeHook("JournalListener::UI vt28", kJournalListenerUiVt28Rva, reinterpret_cast(&DetourJournalListenerUiVt28), reinterpret_cast(&gOriginalJournalListenerUiVt28)); AttachProbeHook("JournalListener::UI vt30", kJournalListenerUiVt30Rva, reinterpret_cast(&DetourJournalListenerUiVt30), reinterpret_cast(&gOriginalJournalListenerUiVt30)); AttachProbeHook("JournalListener::Tracker vt28", kJournalListenerTrackerVt28Rva, reinterpret_cast(&DetourJournalListenerTrackerVt28), reinterpret_cast(&gOriginalJournalListenerTrackerVt28)); } AttachProbeHook("JournalRouteBridge::Update candidate", kJournalRouteBridgeRva, reinterpret_cast(&DetourJournalRouteBridge), reinterpret_cast(&gOriginalJournalRouteBridge)); AttachProbeHook("MappinSystem::RouteEventEnqueue slot240", kMappinRouteEventEnqueueRva, reinterpret_cast(&DetourMappinRouteEventEnqueue), reinterpret_cast(&gOriginalMappinRouteEventEnqueue)); AttachProbeHook("MappinRouteEvent::Handle", kMappinRouteEventHandlerRva, reinterpret_cast(&DetourMappinRouteEventHandler), reinterpret_cast(&gOriginalMappinRouteEventHandler)); AttachProbeHook("MappinSystem::RouteActivate", kMappinRouteActivateRva, reinterpret_cast(&DetourMappinRouteActivate), reinterpret_cast(&gOriginalMappinRouteActivate)); AttachProbeHook("MappinSystem::RouteDeactivate", kMappinRouteDeactivateRva, reinterpret_cast(&DetourMappinRouteDeactivate), reinterpret_cast(&gOriginalMappinRouteDeactivate)); AttachProbeHook("RouteBuildCandidate 0x5625a4", kRouteBuildCandidateRva, reinterpret_cast(&DetourRouteBuildCandidate), reinterpret_cast(&gOriginalRouteBuildCandidate)); if (kEnableVerboseRouteObserverHooks) { AttachProbeHook("RouteObserver0::Activate slot48", kRouteObserver0ActivateRva, reinterpret_cast(&DetourRouteObserver0Activate), reinterpret_cast(&gOriginalRouteObserver0Activate)); AttachProbeHook("RouteObserver1::Activate slot48", kRouteObserver1ActivateRva, reinterpret_cast(&DetourRouteObserver1Activate), reinterpret_cast(&gOriginalRouteObserver1Activate)); AttachProbeHook("RouteObserver23::Activate slot48", kRouteObserver23ActivateRva, reinterpret_cast(&DetourRouteObserver23Activate), reinterpret_cast(&gOriginalRouteObserver23Activate)); AttachProbeHook("RouteObserver0::Deactivate slot50", kRouteObserver0DeactivateRva, reinterpret_cast(&DetourRouteObserver0Deactivate), reinterpret_cast(&gOriginalRouteObserver0Deactivate)); AttachProbeHook("RouteObserver1::Deactivate slot50", kRouteObserver1DeactivateRva, reinterpret_cast(&DetourRouteObserver1Deactivate), reinterpret_cast(&gOriginalRouteObserver1Deactivate)); AttachProbeHook("RouteObserver23::Deactivate slot50", kRouteObserver23DeactivateRva, reinterpret_cast(&DetourRouteObserver23Deactivate), reinterpret_cast(&gOriginalRouteObserver23Deactivate)); } AttachProbeHook("FrameMappinPathWrapper", kFrameMappinPathWrapperRva, reinterpret_cast(&DetourFrameMappinPathWrapper), reinterpret_cast(&gOriginalFrameMappinPathWrapper)); AttachProbeHook("FrameMappinPathCore", kFrameMappinPathCoreRva, reinterpret_cast(&DetourFrameMappinPathCore), reinterpret_cast(&gOriginalFrameMappinPathCore)); AttachProbeHook("SetSelectedMappinWrapper", kSetSelectedMappinWrapperRva, reinterpret_cast(&DetourSetSelectedMappinWrapper), reinterpret_cast(&gOriginalSetSelectedMappinWrapper)); AttachProbeHook("SetSelectedMappinByIdWrapper", kSetSelectedMappinByIdWrapperRva, reinterpret_cast(&DetourSetSelectedMappinByIdWrapper), reinterpret_cast(&gOriginalSetSelectedMappinByIdWrapper)); AttachProbeHook("SetSelectedMappinByPositionWrapper", kSetSelectedMappinByPositionWrapperRva, reinterpret_cast(&DetourSetSelectedMappinByPositionWrapper), reinterpret_cast(&gOriginalSetSelectedMappinByPositionWrapper)); AttachProbeHook("SetSelectedMappinCore", kSetSelectedMappinCoreRva, reinterpret_cast(&DetourSetSelectedMappinCore), reinterpret_cast(&gOriginalSetSelectedMappinCore)); AttachProbeHook("TrackCustomPositionMappinWrapper", kTrackCustomPositionMappinWrapperRva, reinterpret_cast(&DetourTrackCustomPositionMappinWrapper), reinterpret_cast(&gOriginalTrackCustomPositionMappinWrapper)); AttachProbeHook("TrackCustomPositionMappinCore", kTrackCustomPositionMappinCoreRva, reinterpret_cast(&DetourTrackCustomPositionMappinCore), reinterpret_cast(&gOriginalTrackCustomPositionMappinCore)); AttachProbeHook("UntrackCustomPositionMappinWrapper", kUntrackCustomPositionMappinWrapperRva, reinterpret_cast(&DetourUntrackCustomPositionMappinWrapper), reinterpret_cast(&gOriginalUntrackCustomPositionMappinWrapper)); AttachProbeHook("TrackMappinWrapper", kTrackMappinWrapperRva, reinterpret_cast(&DetourTrackMappinWrapper), reinterpret_cast(&gOriginalTrackMappinWrapper)); AttachProbeHook("TrackMappinCore", kTrackMappinCoreRva, reinterpret_cast(&DetourTrackMappinCore), reinterpret_cast(&gOriginalTrackMappinCore)); AttachProbeHook("SetMappinTrackingAlternativeWrapper", kSetMappinTrackingAlternativeWrapperRva, reinterpret_cast(&DetourSetMappinTrackingAlternativeWrapper), reinterpret_cast(&gOriginalSetMappinTrackingAlternativeWrapper)); } if (aReason == RED4ext::v1::EMainReason::Unload) { DetachProbeHook("RunGPSQuery body 0x29bd128", kRunGpsQueryBodyRva); DetachProbeHook("UpdateGPSQuery body 0x29bd254", kUpdateGpsQueryBodyRva); DetachProbeHook("GPSQuerySubmit 0x70a42c", kGpsQuerySubmitRva); DetachProbeHook("GPSQueryDispatch 0x70a570", kGpsQueryDispatchRva); DetachProbeHook("GPSQueryResultFetch 0x7094b8", kGpsQueryResultFetchRva); DetachProbeHook("GPSQueryStatus 0xaa5704", kGpsQueryStatusRva); DetachProbeHook("GPSSystem/Tick", kGpsSystemTickRva); DetachProbeHook("JournalManager::TrackEntry impl", kJournalTrackEntryImplRva); if (kEnableVerboseJournalListenerHooks) { DetachProbeHook("JournalListener::Objective vt28", kJournalListenerObjectiveVt28Rva); DetachProbeHook("JournalListener::Objective vt30", kJournalListenerObjectiveVt30Rva); DetachProbeHook("JournalListener::Route vt28", kJournalListenerRouteVt28Rva); DetachProbeHook("JournalListener::Route vt30", kJournalListenerRouteVt30Rva); DetachProbeHook("JournalListener::RouteAlt vt28", kJournalListenerRouteAltVt28Rva); DetachProbeHook("JournalListener::RouteAlt vt30", kJournalListenerRouteAltVt30Rva); DetachProbeHook("JournalListener::UI vt28", kJournalListenerUiVt28Rva); DetachProbeHook("JournalListener::UI vt30", kJournalListenerUiVt30Rva); DetachProbeHook("JournalListener::Tracker vt28", kJournalListenerTrackerVt28Rva); } DetachProbeHook("JournalRouteBridge::Update candidate", kJournalRouteBridgeRva); DetachProbeHook("MappinSystem::RouteEventEnqueue slot240", kMappinRouteEventEnqueueRva); DetachProbeHook("MappinRouteEvent::Handle", kMappinRouteEventHandlerRva); DetachProbeHook("MappinSystem::RouteActivate", kMappinRouteActivateRva); DetachProbeHook("MappinSystem::RouteDeactivate", kMappinRouteDeactivateRva); DetachProbeHook("RouteBuildCandidate 0x5625a4", kRouteBuildCandidateRva); if (kEnableVerboseRouteObserverHooks) { DetachProbeHook("RouteObserver0::Activate slot48", kRouteObserver0ActivateRva); DetachProbeHook("RouteObserver1::Activate slot48", kRouteObserver1ActivateRva); DetachProbeHook("RouteObserver23::Activate slot48", kRouteObserver23ActivateRva); DetachProbeHook("RouteObserver0::Deactivate slot50", kRouteObserver0DeactivateRva); DetachProbeHook("RouteObserver1::Deactivate slot50", kRouteObserver1DeactivateRva); DetachProbeHook("RouteObserver23::Deactivate slot50", kRouteObserver23DeactivateRva); } DetachProbeHook("FrameMappinPathWrapper", kFrameMappinPathWrapperRva); DetachProbeHook("FrameMappinPathCore", kFrameMappinPathCoreRva); DetachProbeHook("SetSelectedMappinWrapper", kSetSelectedMappinWrapperRva); DetachProbeHook("SetSelectedMappinByIdWrapper", kSetSelectedMappinByIdWrapperRva); DetachProbeHook("SetSelectedMappinByPositionWrapper", kSetSelectedMappinByPositionWrapperRva); DetachProbeHook("SetSelectedMappinCore", kSetSelectedMappinCoreRva); DetachProbeHook("TrackCustomPositionMappinWrapper", kTrackCustomPositionMappinWrapperRva); DetachProbeHook("TrackCustomPositionMappinCore", kTrackCustomPositionMappinCoreRva); DetachProbeHook("UntrackCustomPositionMappinWrapper", kUntrackCustomPositionMappinWrapperRva); DetachProbeHook("TrackMappinWrapper", kTrackMappinWrapperRva); DetachProbeHook("TrackMappinCore", kTrackMappinCoreRva); DetachProbeHook("SetMappinTrackingAlternativeWrapper", kSetMappinTrackingAlternativeWrapperRva); DetachAbsoluteHook("MappinSystem::UpdateCustomPosition slot1f0", gMappinUpdateCustomPositionTarget); DetachAbsoluteHook("MappinSystem::SetTracked slot220", gMappinSetTrackedTarget); DetachAbsoluteHook("MappinSystem::ClearTracked slot228", gMappinClearTrackedTarget); DetachAbsoluteHook("MappinSystem::FramePath slot280", gMappinFramePathTarget); DetachAbsoluteHook("MappinSystem::PositionQuery slot2e0", gMappinPositionQueryTarget); DetachAbsoluteHook("MappinSystem::CreateCustomPosition slot2f0", gMappinCreateCustomPositionTarget); DetachAbsoluteHook("MappinSystem::ReleaseQuery slot368", gMappinReleaseQueryTarget); DetachAbsoluteHook("MappinSystem::ActiveRoute slot3a8", gMappinActiveRouteTarget); DetachAbsoluteHook("Observer1Service::RouteLookup slot220", gObserver1ServiceRouteLookupTarget); } return true; } RED4EXT_C_EXPORT void RED4EXT_CALL Query(RED4ext::v1::PluginInfo* aInfo) { aInfo->name = L"EdgeWeightGPS"; aInfo->author = L"salt+Codex"; aInfo->version = RED4ext::v1::SemVer{0, 1, 0, {0, 0}}; aInfo->runtime = RED4ext::v1::FileVer{3, 0, 80, 51928}; aInfo->sdk = RED4ext::v1::SemVer{0, 5, 0, {0, 0}}; } RED4EXT_C_EXPORT uint32_t RED4EXT_CALL Supports() { return RED4EXT_API_VERSION_1_COMPAT_0; }