#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 bool kEnableGpsMultiplierHooks = false; constexpr bool kEnableGpsCostTablePatch = false; constexpr bool kEnableGpsProviderClassPatch = true; constexpr bool kEnableGpsProviderClassPatchTrace = true; constexpr bool kEnableGpsTraceHooks = false; constexpr bool kEnableGpsRouteJobStartTrace = false; constexpr bool kEnableGpsLocalSearchHooks = false; constexpr bool kEnableRouteUiTraceHooks = 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; constexpr uintptr_t kGpsDispatchAdvanceRva = 0x08D17D8; constexpr uintptr_t kGpsAsyncWorkerTickRva = 0x0126B28; constexpr uintptr_t kGpsRouteJobStartRva = 0x0818928; constexpr uintptr_t kGpsRouteJobPollRva = 0x0883CD8; constexpr uintptr_t kGpsRouteFinishRva = 0x11F5F60; constexpr uintptr_t kGpsRouteJobBuildRva = 0x0818BA8; constexpr uintptr_t kGpsRouteProducerRva = 0x044CC7C; constexpr uintptr_t kGpsSearchRva = 0x044F054; constexpr uintptr_t kGpsEdgeCostRva = 0x044F838; constexpr uintptr_t kGpsBaseCostProviderVtableRva = 0x2AE6120; constexpr uintptr_t kGpsFilteredCostProviderVtableRva = 0x2AE60F8; constexpr uintptr_t kGpsAuxMultiplierRva = 0x040BB00; constexpr uintptr_t kGpsNodeMultiplierRva = 0x040BB40; constexpr uintptr_t kGpsCostMultiplierTableRva = 0x3154D28; constexpr std::array kGpsPatchedCostMultipliers = { 12.0f, 7.0f, 2.5f, 0.0f, 3.0f, 2.0f, 1.25f, }; constexpr size_t kGpsProviderClassCount = 64; struct GpsProviderClassOverride { size_t classId; float multiplier; }; constexpr std::array kGpsProviderClassOverrides = {{ {1, 0.70f}, {3, 1.45f}, {4, 1.25f}, {5, 1.75f}, {15, 0.80f}, }}; constexpr std::array kGpsProviderClassProbeIds = {1, 3, 4, 5, 14, 15}; 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; uint32_t gGpsDispatchAdvanceLogCount = 0; uint32_t gGpsAsyncWorkerTickLogCount = 0; uint32_t gGpsRouteJobStartLogCount = 0; uint32_t gGpsRouteJobPollLogCount = 0; uint32_t gGpsRouteFinishLogCount = 0; uint32_t gGpsRouteJobBuildLogCount = 0; uint32_t gGpsRouteProducerLogCount = 0; uint32_t gGpsSearchLogCount = 0; uint32_t gGpsEdgeCostLogCount = 0; uint32_t gGpsAuxMultiplierLogCount = 0; uint32_t gGpsNodeMultiplierLogCount = 0; uint32_t gGpsCostTablePatchLogCount = 0; uint32_t gGpsProviderClassPatchLogCount = 0; std::array gGpsEdgeCostClassLogCounts{}; std::mutex gGpsCostLogMutex; std::mutex gGpsAsyncLogMutex; std::mutex gGpsCostTablePatchMutex; 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; struct GpsEdgeCostSample { uint64_t currentHandle = 0; uint64_t neighborHandle = 0; uintptr_t returnRva = 0; float cost = 0.0f; uint8_t sourceClass = 0xFF; uint8_t neighborClass = 0xFF; }; struct GpsEdgeCostStats { uint32_t total = 0; uint32_t invalidSourceClass = 0; uint32_t invalidNeighborClass = 0; float costSum = 0.0f; float minCost = 0.0f; float maxCost = 0.0f; std::array sourceClasses{}; std::array neighborClasses{}; std::array returnRvas{}; std::array returnRvaCounts{}; uint32_t otherReturnRvas = 0; std::array samples{}; uint32_t sampleCount = 0; }; struct GpsMultiplierSignature { bool active = false; bool auxiliary = false; uint32_t mode = 0; uint32_t retry = 0; uint32_t flagsC0 = 0; uint8_t c8 = 0; uint16_t node86 = 0; uint16_t flags88 = 0; uint16_t node8c = 0; uint16_t node8e = 0; uint8_t node90 = 0; uint8_t node93 = 0; uint32_t valueBits = 0; uint32_t count = 0; }; thread_local GpsEdgeCostStats* gActiveGpsEdgeCostStats = nullptr; thread_local uint32_t gActiveGpsRouteProducerCall = UINT32_MAX; std::array gGpsMultiplierSignatures{}; uint32_t gGpsMultiplierOverflowCount = 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); using GpsDispatchAdvanceFunc = void (*)(void* aRouteSystem, void* aRuntimeQuery, void* aOutTicket, void* aTargetPosition, void* aSourcePosition); using GpsAsyncWorkerTickFunc = void (*)(void* aRouteSystem); using GpsRouteJobStartFunc = bool (*)(void* aRouteSystem, void* aActiveEntry, float aDelta, void* aOutLocal); using GpsRouteJobPollFunc = bool (*)(void* aRouteSystem, void* aActiveEntry); using GpsRouteFinishFunc = void* (*)(void* aResultQueue, void* aOutEntry, void* aActiveEntry, void* aLocalResult); using GpsRouteJobBuildFunc = void* (*)(void* aJob, void* aOut, void* aRequest); using GpsRouteProducerFunc = int32_t (*)(void* aGraph, void* aQuery, void* aOutResult); using GpsSearchFunc = int32_t (*)(void* aGraph, uint64_t aStartHandle, uint64_t aTargetHandle, void* aStartPoint, void* aTargetPoint, void* aCostProvider, void* aOutPath, uint32_t* aOutCount); using GpsEdgeCostFunc = float (*)(void* aCostProvider, void* aCurrentState, void* aNeighborState, uint64_t aPreviousHandle, void* aPreviousSegment, void* aPreviousPoint, uint64_t aCurrentHandle, void* aCurrentSegment, void* aCurrentPoint, uint64_t aNeighborHandle, void* aNeighborSegment, void* aNeighborPoint); using GpsMultiplierFunc = float (*)(void* aJob, void* aNode); 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; GpsDispatchAdvanceFunc gOriginalGpsDispatchAdvance = nullptr; GpsAsyncWorkerTickFunc gOriginalGpsAsyncWorkerTick = nullptr; GpsRouteJobStartFunc gOriginalGpsRouteJobStart = nullptr; GpsRouteJobPollFunc gOriginalGpsRouteJobPoll = nullptr; GpsRouteFinishFunc gOriginalGpsRouteFinish = nullptr; GpsRouteJobBuildFunc gOriginalGpsRouteJobBuild = nullptr; GpsRouteProducerFunc gOriginalGpsRouteProducer = nullptr; GpsSearchFunc gOriginalGpsSearch = nullptr; GpsEdgeCostFunc gOriginalGpsEdgeCost = nullptr; GpsMultiplierFunc gOriginalGpsAuxMultiplier = nullptr; GpsMultiplierFunc gOriginalGpsNodeMultiplier = 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; } float FloatFromBits(uint32_t aBits) { float value = 0.0f; std::memcpy(&value, &aBits, sizeof(value)); return value; } uint32_t FloatToBits(float aValue) { uint32_t bits = 0; std::memcpy(&bits, &aValue, sizeof(bits)); return bits; } 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)); } bool TryReadFloatField(void* aObject, uintptr_t aOffset, float& aValue) { return aObject && TryReadMemory(reinterpret_cast(reinterpret_cast(aObject) + aOffset), aValue); } void AppendFloatField(std::ostringstream& aLine, const char* aLabel, void* aObject, uintptr_t aOffset) { float value = 0.0f; const auto hasValue = TryReadFloatField(aObject, aOffset, value); aLine << " " << aLabel << "="; if (hasValue) { const auto flags = aLine.flags(); const auto precision = aLine.precision(); aLine << std::fixed << std::setprecision(3) << value; aLine.flags(flags); aLine.precision(precision); } else { aLine << ""; } } uint8_t ReadGpsPointClass(void* aPoint) { uint8_t value = 0xFF; if (!aPoint || !TryReadMemory(reinterpret_cast(reinterpret_cast(aPoint) + 0x13), value)) { return 0xFF; } return value & 0x3F; } uint16_t ReadGpsPointMask(void* aPoint) { uint16_t value = 0; if (!aPoint) { return 0; } TryReadMemory(reinterpret_cast(reinterpret_cast(aPoint) + 0x10), value); return value; } void AppendGpsPointSummary(std::ostringstream& aLine, const char* aPrefix, void* aPoint) { const auto prefix = std::string(aPrefix); AppendPointerWithRva(aLine, prefix.c_str(), aPoint); if (!aPoint) { return; } uint32_t adjacency = 0; uint16_t mask = 0; uint8_t byte12 = 0; uint8_t byte13 = 0; TryReadMemory(aPoint, adjacency); TryReadMemory(reinterpret_cast(reinterpret_cast(aPoint) + 0x10), mask); TryReadMemory(reinterpret_cast(reinterpret_cast(aPoint) + 0x12), byte12); TryReadMemory(reinterpret_cast(reinterpret_cast(aPoint) + 0x13), byte13); aLine << " " << prefix << "_adj=" << adjacency; aLine << " " << prefix << "_mask10=0x" << std::hex << mask << std::dec; aLine << " " << prefix << "_b12=0x" << std::hex << static_cast(byte12); aLine << " " << prefix << "_class13=" << std::dec << static_cast(byte13 & 0x3F); aLine << " " << prefix << "_raw13=0x" << std::hex << static_cast(byte13) << std::dec; } void AppendGpsCostProviderSummary(std::ostringstream& aLine, const char* aPrefix, void* aProvider) { const auto prefix = std::string(aPrefix); AppendPointerWithRva(aLine, prefix.c_str(), aProvider); if (!aProvider) { return; } auto* vtable = ReadPointerField(aProvider, 0x00); AppendPointerWithRva(aLine, (prefix + "_vtable").c_str(), vtable); if (IsInImage(vtable)) { AppendPointerWithRva(aLine, (prefix + "_slot08").c_str(), ReadPointerField(vtable, 0x08)); AppendPointerWithRva(aLine, (prefix + "_slot10").c_str(), ReadPointerField(vtable, 0x10)); } AppendUint16Field(aLine, (prefix + "_mask108").c_str(), aProvider, 0x108); AppendUint16Field(aLine, (prefix + "_mask10a").c_str(), aProvider, 0x10A); const auto flags = aLine.flags(); const auto precision = aLine.precision(); aLine << " " << prefix << "_mul=["; for (uint32_t index = 0; index < 64; ++index) { float value = 0.0f; const auto hasValue = TryReadFloatField(aProvider, 0x08 + static_cast(index) * sizeof(float), value); if (index > 0) { aLine << ","; } aLine << index << ":"; if (hasValue) { aLine << std::fixed << std::setprecision(3) << value; } else { aLine << "?"; } } aLine << "]"; aLine.flags(flags); aLine.precision(precision); } void AppendGpsCostProviderCompact(std::ostringstream& aLine, const char* aPrefix, void* aProvider) { const auto prefix = std::string(aPrefix); AppendPointerWithRva(aLine, prefix.c_str(), aProvider); if (!aProvider) { return; } auto* vtable = ReadPointerField(aProvider, 0x00); AppendPointerWithRva(aLine, (prefix + "_vtable").c_str(), vtable); if (IsInImage(vtable)) { AppendPointerWithRva(aLine, (prefix + "_slot08").c_str(), ReadPointerField(vtable, 0x08)); AppendPointerWithRva(aLine, (prefix + "_slot10").c_str(), ReadPointerField(vtable, 0x10)); } AppendUint16Field(aLine, (prefix + "_mask108").c_str(), aProvider, 0x108); AppendUint16Field(aLine, (prefix + "_mask10a").c_str(), aProvider, 0x10A); const auto flags = aLine.flags(); const auto precision = aLine.precision(); aLine << " " << prefix << "_nonunitMul=["; bool first = true; for (uint32_t index = 0; index < 64; ++index) { float value = 0.0f; if (!TryReadFloatField(aProvider, 0x08 + static_cast(index) * sizeof(float), value)) { continue; } if (value > 0.999f && value < 1.001f) { continue; } if (!first) { aLine << ","; } first = false; aLine << index << ":" << std::fixed << std::setprecision(3) << value; } if (first) { aLine << "none"; } aLine << "]"; aLine.flags(flags); aLine.precision(precision); } void AppendClassCounts(std::ostringstream& aLine, const char* aLabel, const std::array& aCounts, uint32_t aInvalidCount) { aLine << " " << aLabel << "=["; bool first = true; for (uint32_t index = 0; index < aCounts.size(); ++index) { if (aCounts[index] == 0) { continue; } if (!first) { aLine << ","; } first = false; aLine << index << ":" << aCounts[index]; } if (aInvalidCount > 0) { if (!first) { aLine << ","; } first = false; aLine << "invalid:" << aInvalidCount; } if (first) { aLine << "none"; } aLine << "]"; } void AppendGpsEdgeCostStats(std::ostringstream& aLine, const GpsEdgeCostStats& aStats) { aLine << " edgeCostCalls=" << aStats.total; if (aStats.total > 0) { const auto flags = aLine.flags(); const auto precision = aLine.precision(); aLine << " edgeCostMin=" << std::fixed << std::setprecision(3) << aStats.minCost; aLine << " edgeCostMax=" << std::fixed << std::setprecision(3) << aStats.maxCost; aLine << " edgeCostAvg=" << std::fixed << std::setprecision(3) << (aStats.costSum / static_cast(aStats.total)); aLine.flags(flags); aLine.precision(precision); } AppendClassCounts(aLine, "edgeSrcClasses", aStats.sourceClasses, aStats.invalidSourceClass); AppendClassCounts(aLine, "edgeDstClasses", aStats.neighborClasses, aStats.invalidNeighborClass); aLine << " edgeRetRvas=["; bool first = true; for (uint32_t index = 0; index < aStats.returnRvas.size(); ++index) { if (aStats.returnRvas[index] == 0) { continue; } if (!first) { aLine << ","; } first = false; aLine << "0x" << std::hex << aStats.returnRvas[index] << std::dec << ":" << aStats.returnRvaCounts[index]; } if (aStats.otherReturnRvas > 0) { if (!first) { aLine << ","; } first = false; aLine << "other:" << aStats.otherReturnRvas; } if (first) { aLine << "none"; } aLine << "]"; aLine << " edgeSamples=["; const auto sampleCount = std::min(aStats.sampleCount, static_cast(aStats.samples.size())); for (uint32_t index = 0; index < sampleCount; ++index) { const auto& sample = aStats.samples[index]; if (index > 0) { aLine << ";"; } const auto flags = aLine.flags(); const auto precision = aLine.precision(); aLine << index << ":" << static_cast(sample.sourceClass) << ">" << static_cast(sample.neighborClass) << " cost=" << std::fixed << std::setprecision(3) << sample.cost << " ret=0x" << std::hex << sample.returnRva << " cur=0x" << sample.currentHandle << " dst=0x" << sample.neighborHandle << std::dec; aLine.flags(flags); aLine.precision(precision); } if (sampleCount == 0) { aLine << "none"; } aLine << "]"; } void NoteGpsEdgeCostStats(GpsEdgeCostStats& aStats, uint8_t aSourceClass, uint8_t aNeighborClass, float aCost, uintptr_t aReturnRva, uint64_t aCurrentHandle, uint64_t aNeighborHandle) { ++aStats.total; aStats.costSum += aCost; if (aStats.total == 1) { aStats.minCost = aCost; aStats.maxCost = aCost; } else { aStats.minCost = std::min(aStats.minCost, aCost); aStats.maxCost = std::max(aStats.maxCost, aCost); } if (aSourceClass < aStats.sourceClasses.size()) { ++aStats.sourceClasses[aSourceClass]; } else { ++aStats.invalidSourceClass; } if (aNeighborClass < aStats.neighborClasses.size()) { ++aStats.neighborClasses[aNeighborClass]; } else { ++aStats.invalidNeighborClass; } if (aReturnRva != 0) { bool recorded = false; for (uint32_t index = 0; index < aStats.returnRvas.size(); ++index) { if (aStats.returnRvas[index] == aReturnRva) { ++aStats.returnRvaCounts[index]; recorded = true; break; } if (aStats.returnRvas[index] == 0) { aStats.returnRvas[index] = aReturnRva; aStats.returnRvaCounts[index] = 1; recorded = true; break; } } if (!recorded) { ++aStats.otherReturnRvas; } } if (aStats.sampleCount < aStats.samples.size()) { auto& sample = aStats.samples[aStats.sampleCount++]; sample.currentHandle = aCurrentHandle; sample.neighborHandle = aNeighborHandle; sample.returnRva = aReturnRva; sample.cost = aCost; sample.sourceClass = aSourceClass; sample.neighborClass = aNeighborClass; } } bool ReadGpsMultiplierSignature(bool aAuxiliary, void* aJob, void* aNode, float aValue, GpsMultiplierSignature& aSignature) { if (!aJob || !aNode) { return false; } aSignature = {}; aSignature.active = true; aSignature.auxiliary = aAuxiliary; aSignature.valueBits = FloatToBits(aValue); const auto jobAddress = reinterpret_cast(aJob); const auto nodeAddress = reinterpret_cast(aNode); if (!TryReadMemory(reinterpret_cast(jobAddress + 0xC4), aSignature.mode) || aSignature.mode != 2) { return false; } TryReadMemory(reinterpret_cast(jobAddress + 0x54), aSignature.retry); TryReadMemory(reinterpret_cast(jobAddress + 0xC0), aSignature.flagsC0); TryReadMemory(reinterpret_cast(jobAddress + 0xC8), aSignature.c8); TryReadMemory(reinterpret_cast(nodeAddress + 0x86), aSignature.node86); TryReadMemory(reinterpret_cast(nodeAddress + 0x88), aSignature.flags88); TryReadMemory(reinterpret_cast(nodeAddress + 0x8C), aSignature.node8c); TryReadMemory(reinterpret_cast(nodeAddress + 0x8E), aSignature.node8e); TryReadMemory(reinterpret_cast(nodeAddress + 0x90), aSignature.node90); TryReadMemory(reinterpret_cast(nodeAddress + 0x93), aSignature.node93); return true; } bool SameGpsMultiplierSignature(const GpsMultiplierSignature& aLeft, const GpsMultiplierSignature& aRight) { return aLeft.active == aRight.active && aLeft.auxiliary == aRight.auxiliary && aLeft.mode == aRight.mode && aLeft.retry == aRight.retry && aLeft.flagsC0 == aRight.flagsC0 && aLeft.c8 == aRight.c8 && aLeft.node86 == aRight.node86 && aLeft.flags88 == aRight.flags88 && aLeft.node8c == aRight.node8c && aLeft.node8e == aRight.node8e && aLeft.node90 == aRight.node90 && aLeft.node93 == aRight.node93 && aLeft.valueBits == aRight.valueBits; } bool NoteGpsMultiplierSignature(GpsMultiplierSignature aSignature, uint32_t& aCallCount, uint32_t& aSignatureCount) { std::lock_guard lock(gGpsCostLogMutex); auto& logCount = aSignature.auxiliary ? gGpsAuxMultiplierLogCount : gGpsNodeMultiplierLogCount; aCallCount = logCount++; for (auto& existing : gGpsMultiplierSignatures) { if (!existing.active || !SameGpsMultiplierSignature(existing, aSignature)) { continue; } aSignatureCount = ++existing.count; return false; } for (auto& existing : gGpsMultiplierSignatures) { if (existing.active) { continue; } existing = aSignature; existing.count = 1; aSignatureCount = 1; return true; } ++gGpsMultiplierOverflowCount; return false; } void AppendGpsMultiplierSignature(std::ostringstream& aLine, const GpsMultiplierSignature& aSignature) { aLine << " mode=" << aSignature.mode << " retry=" << aSignature.retry << " flagsC0=0x" << std::hex << aSignature.flagsC0 << " c8=0x" << static_cast(aSignature.c8) << " node86=0x" << aSignature.node86 << " flags88=0x" << aSignature.flags88 << " node8c=0x" << aSignature.node8c << " node8e=0x" << aSignature.node8e << " node90=0x" << static_cast(aSignature.node90) << " node93=0x" << static_cast(aSignature.node93) << std::dec << " value=" << FloatFromBits(aSignature.valueBits); } void AppendGpsRouteProducerQuerySummary(std::ostringstream& aLine, void* aQuery) { AppendVector4Field(aLine, "query_start00", aQuery, 0x00); AppendVector4Field(aLine, "query_end10", aQuery, 0x10); AppendFloatField(aLine, "query_f68", aQuery, 0x68); AppendFloatField(aLine, "query_f78", aQuery, 0x78); AppendFloatField(aLine, "query_f88", aQuery, 0x88); AppendUint32Field(aLine, "query_f8c", aQuery, 0x8C); AppendUint32Field(aLine, "query_f90", aQuery, 0x90); AppendUint32Field(aLine, "query_f94", aQuery, 0x94); AppendPointerField(aLine, "query_ptr98", aQuery, 0x98); AppendPointerField(aLine, "query_ptra0", aQuery, 0xA0); AppendUint32Field(aLine, "query_flagsa8", aQuery, 0xA8); } 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; } constexpr uintptr_t kRouteRecordStride = 0x28; const auto rawSize = std::min(0xA0, static_cast(count) * kRouteRecordStride); AppendBytesPointer(aLine, (prefix + "_ptr28_rec40raw").c_str(), data, rawSize); const auto flags = aLine.flags(); const auto fill = aLine.fill(); aLine << " " << prefix << "_ptr28_rec40=["; for (uint32_t index = 0; index < count; ++index) { const auto recordAddress = reinterpret_cast(data) + static_cast(index) * kRouteRecordStride; const auto* record = reinterpret_cast(recordAddress); uint64_t h00 = 0; uint32_t u08 = 0; uint32_t u0c = 0; uint32_t u10 = 0; uint32_t u14 = 0; uint64_t h18 = 0; uint64_t h20 = 0; const auto hasRecord = TryReadMemory(record, h00) && TryReadMemory(reinterpret_cast(recordAddress + 0x08), u08) && TryReadMemory(reinterpret_cast(recordAddress + 0x0C), u0c) && TryReadMemory(reinterpret_cast(recordAddress + 0x10), u10) && TryReadMemory(reinterpret_cast(recordAddress + 0x14), u14) && TryReadMemory(reinterpret_cast(recordAddress + 0x18), h18) && TryReadMemory(reinterpret_cast(recordAddress + 0x20), h20); if (index > 0) { aLine << ";"; } aLine << index << ":"; if (!hasRecord) { aLine << ""; continue; } const auto h18Lo = static_cast(h18); const auto h18Hi = static_cast(h18 >> 32); aLine << std::hex << h00 << ",0x" << std::setw(8) << std::setfill('0') << u08 << std::setfill(fill) << std::dec << "," << u0c << "," << u10 << "," << u14 << "," << std::hex << h18 << std::dec << "," << FloatFromBits(h18Lo) << "," << FloatFromBits(h18Hi) << "," << std::hex << h20 << std::dec; } aLine << "]"; aLine.flags(flags); aLine.fill(fill); } 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); 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); } uint32_t NextGpsAsyncLogCall(uint32_t& aCounter) { std::lock_guard lock(gGpsAsyncLogMutex); return aCounter++; } bool TryReadUint32FieldValue(void* aObject, uintptr_t aOffset, uint32_t& aValue) { return aObject && TryReadMemory(reinterpret_cast(reinterpret_cast(aObject) + aOffset), aValue); } bool IsDrivingRouteJob(void* aRouteSystem, void* aActiveEntry) { uint32_t mode = 0; if (TryReadUint32FieldValue(aActiveEntry, 0x80, mode) && mode == 2) { return true; } auto* job = ReadPointerField(aRouteSystem, 0x90D0); return TryReadUint32FieldValue(job, 0xC4, mode) && mode == 2; } void AppendFloatArray(std::ostringstream& aLine, const char* aLabel, const float* aValues, size_t aCount) { const auto flags = aLine.flags(); const auto precision = aLine.precision(); aLine << " " << aLabel << "=["; for (size_t index = 0; index < aCount; ++index) { if (index > 0) { aLine << ","; } aLine << std::fixed << std::setprecision(2) << aValues[index]; } aLine << "]"; aLine.flags(flags); aLine.precision(precision); } void AppendGpsProviderClassSamples(std::ostringstream& aLine, const char* aLabel, const float* aValues, size_t aCount) { const auto flags = aLine.flags(); const auto precision = aLine.precision(); aLine << " " << aLabel << "=["; bool first = true; for (const auto classId : kGpsProviderClassProbeIds) { if (classId >= aCount) { continue; } if (!first) { aLine << ","; } first = false; aLine << classId << ":" << std::fixed << std::setprecision(2) << aValues[classId]; } aLine << "]"; aLine.flags(flags); aLine.precision(precision); } void AppendGpsProviderClassOverrides(std::ostringstream& aLine) { const auto flags = aLine.flags(); const auto precision = aLine.precision(); aLine << " overrides=["; for (size_t index = 0; index < kGpsProviderClassOverrides.size(); ++index) { if (index > 0) { aLine << ","; } const auto& item = kGpsProviderClassOverrides[index]; aLine << item.classId << ":" << std::fixed << std::setprecision(2) << item.multiplier; } aLine << "]"; aLine.flags(flags); aLine.precision(precision); } class ScopedGpsCostTablePatch { public: ScopedGpsCostTablePatch(bool aShouldPatch, uint32_t aRouteJobStartCall) : m_routeJobStartCall(aRouteJobStartCall) { if (!kEnableGpsCostTablePatch || !aShouldPatch) { return; } m_table = reinterpret_cast(GetImageBase() + kGpsCostMultiplierTableRva); if (!IsReadableMemory(m_table, sizeof(m_original))) { LogPatch("gps-cost-table-patch skipped", "reason=unreadable-table"); return; } std::memcpy(m_original.data(), m_table, sizeof(m_original)); DWORD oldProtect = 0; if (!VirtualProtect(m_table, sizeof(m_original), PAGE_EXECUTE_READWRITE, &oldProtect)) { LogPatch("gps-cost-table-patch skipped", "reason=protect-failed"); return; } std::memcpy(m_table, kGpsPatchedCostMultipliers.data(), sizeof(m_original)); DWORD ignoredProtect = 0; VirtualProtect(m_table, sizeof(m_original), oldProtect, &ignoredProtect); FlushInstructionCache(GetCurrentProcess(), m_table, sizeof(m_original)); m_active = true; LogPatch("gps-cost-table-patch applied", nullptr); } ~ScopedGpsCostTablePatch() { if (!m_active || !m_table) { return; } DWORD oldProtect = 0; if (!VirtualProtect(m_table, sizeof(m_original), PAGE_EXECUTE_READWRITE, &oldProtect)) { LogPatch("gps-cost-table-patch restore-failed", "reason=protect-failed"); return; } std::memcpy(m_table, m_original.data(), sizeof(m_original)); DWORD ignoredProtect = 0; VirtualProtect(m_table, sizeof(m_original), oldProtect, &ignoredProtect); FlushInstructionCache(GetCurrentProcess(), m_table, sizeof(m_original)); LogPatch("gps-cost-table-patch restored", nullptr); } private: void LogPatch(const char* aMessage, const char* aReason) { const auto patchLogCall = NextGpsAsyncLogCall(gGpsCostTablePatchLogCount); if (patchLogCall >= 16) { return; } std::ostringstream line; line << aMessage << " patchCall=" << patchLogCall << " routeJobStartCall=" << m_routeJobStartCall; AppendPointerWithRva(line, "table", m_table); if (aReason) { line << " " << aReason; } if (m_table && IsReadableMemory(m_table, sizeof(m_original))) { AppendFloatArray(line, "current", m_table, m_original.size()); } AppendFloatArray(line, "original", m_original.data(), m_original.size()); AppendFloatArray(line, "patched", kGpsPatchedCostMultipliers.data(), kGpsPatchedCostMultipliers.size()); LogRed4ext(line.str()); } uint32_t m_routeJobStartCall = 0; bool m_active = false; float* m_table = nullptr; std::array m_original{}; }; bool IsKnownGpsCostProvider(void* aProvider, uintptr_t& aVtableRva) { aVtableRva = 0; auto* vtable = ReadPointerField(aProvider, 0x00); if (!TryGetImageRva(vtable, aVtableRva)) { return false; } return aVtableRva == kGpsBaseCostProviderVtableRva || aVtableRva == kGpsFilteredCostProviderVtableRva; } class ScopedGpsProviderClassPatch { public: ScopedGpsProviderClassPatch(void* aProvider, uint32_t aSearchCall) : m_provider(aProvider) , m_searchCall(aSearchCall) { if (!kEnableGpsProviderClassPatch || !m_provider) { return; } if (!IsKnownGpsCostProvider(m_provider, m_vtableRva)) { LogPatch("gps-provider-class-patch skipped", "reason=unknown-provider"); return; } m_table = reinterpret_cast(reinterpret_cast(m_provider) + 0x08); if (!IsReadableMemory(m_table, sizeof(m_original))) { LogPatch("gps-provider-class-patch skipped", "reason=unreadable-table"); return; } std::memcpy(m_original.data(), m_table, sizeof(m_original)); for (const auto& item : kGpsProviderClassOverrides) { if (item.classId < m_original.size()) { m_table[item.classId] = item.multiplier; } } m_active = true; LogPatch("gps-provider-class-patch applied", nullptr); } ~ScopedGpsProviderClassPatch() { if (!m_active || !m_table) { return; } std::memcpy(m_table, m_original.data(), sizeof(m_original)); LogPatch("gps-provider-class-patch restored", nullptr); } private: void LogPatch(const char* aMessage, const char* aReason) { if (!kEnableGpsProviderClassPatchTrace) { return; } const auto patchLogCall = NextGpsAsyncLogCall(gGpsProviderClassPatchLogCount); if (patchLogCall >= 128) { return; } std::ostringstream line; line << aMessage << " patchCall=" << patchLogCall << " searchCall=" << m_searchCall; AppendPointerWithRva(line, "provider", m_provider); line << " provider_vtable_rva=0x" << std::hex << m_vtableRva << std::dec; if (aReason) { line << " " << aReason; } if (m_table && IsReadableMemory(m_table, sizeof(m_original))) { AppendGpsProviderClassSamples(line, "current", m_table, m_original.size()); } AppendGpsProviderClassSamples(line, "original", m_original.data(), m_original.size()); AppendGpsProviderClassOverrides(line); LogRed4ext(line.str()); } void* m_provider = nullptr; float* m_table = nullptr; uint32_t m_searchCall = 0; uintptr_t m_vtableRva = 0; bool m_active = false; std::array m_original{}; }; void AppendRouteJobSummary(std::ostringstream& aLine, const char* aPrefix, void* aJob) { const auto prefix = std::string(aPrefix); AppendPointerWithRva(aLine, prefix.c_str(), aJob); if (!aJob) { return; } AppendUint32Field(aLine, (prefix + "_type10").c_str(), aJob, 0x10); AppendPointerField(aLine, (prefix + "_result18").c_str(), aJob, 0x18); AppendPointerField(aLine, (prefix + "_result30").c_str(), aJob, 0x30); AppendUint32Field(aLine, (prefix + "_u4c").c_str(), aJob, 0x4C); AppendUint32Field(aLine, (prefix + "_count50").c_str(), aJob, 0x50); AppendUint32Field(aLine, (prefix + "_retry54").c_str(), aJob, 0x54); AppendUint32Field(aLine, (prefix + "_flagsc0").c_str(), aJob, 0xC0); AppendUint32Field(aLine, (prefix + "_statec4").c_str(), aJob, 0xC4); AppendPointerField(aLine, (prefix + "_chunkse0").c_str(), aJob, 0xE0); AppendUint32Field(aLine, (prefix + "_chunks_cap_e8").c_str(), aJob, 0xE8); AppendUint32Field(aLine, (prefix + "_chunks_count_ec").c_str(), aJob, 0xEC); AppendPointerField(aLine, (prefix + "_nodesf8").c_str(), aJob, 0xF8); } void AppendRouteSystemSummary(std::ostringstream& aLine, const char* aPrefix, void* aRouteSystem) { const auto prefix = std::string(aPrefix); AppendPointerWithRva(aLine, prefix.c_str(), aRouteSystem); if (!aRouteSystem) { return; } AppendUint32Field(aLine, (prefix + "_queued8fc0").c_str(), aRouteSystem, 0x8FC0); AppendUint32Field(aLine, (prefix + "_active8fd0").c_str(), aRouteSystem, 0x8FD0); AppendPointerField(aLine, (prefix + "_job90d0").c_str(), aRouteSystem, 0x90D0); auto* job = ReadPointerField(aRouteSystem, 0x90D0); if (!job) { return; } AppendRouteJobSummary(aLine, (prefix + "_job").c_str(), job); } bool HasPendingRouteWork(void* aRouteSystem) { uint32_t queued = 0; uint32_t active = 0; TryReadUint32FieldValue(aRouteSystem, 0x8FC0, queued); TryReadUint32FieldValue(aRouteSystem, 0x8FD0, active); return queued != 0 || active != 0 || ReadPointerField(aRouteSystem, 0x90D0) != nullptr; } void AppendRouteRequestFields(std::ostringstream& aLine, const char* aPrefix, void* aObject, uintptr_t aOffset) { const auto prefix = std::string(aPrefix); auto* base = aObject ? reinterpret_cast(reinterpret_cast(aObject) + aOffset) : nullptr; AppendPointerWithRva(aLine, prefix.c_str(), base); if (!base) { return; } AppendBytesPointer(aLine, (prefix + "_raw40").c_str(), base, 0x40); AppendVector4Field(aLine, (prefix + "_vec00").c_str(), base, 0x00); AppendVector4Field(aLine, (prefix + "_vec10").c_str(), base, 0x10); AppendUint32Field(aLine, (prefix + "_u00").c_str(), base, 0x00); AppendUint32Field(aLine, (prefix + "_u08").c_str(), base, 0x08); AppendUint32Field(aLine, (prefix + "_u1c").c_str(), base, 0x1C); AppendUint16Field(aLine, (prefix + "_u1e").c_str(), base, 0x1E); AppendUint32Field(aLine, (prefix + "_u20").c_str(), base, 0x20); AppendUint16Field(aLine, (prefix + "_u20w").c_str(), base, 0x20); AppendUint16Field(aLine, (prefix + "_u24").c_str(), base, 0x24); AppendUint16Field(aLine, (prefix + "_u26").c_str(), base, 0x26); AppendUint16Field(aLine, (prefix + "_u28").c_str(), base, 0x28); AppendUint32Field(aLine, (prefix + "_u70").c_str(), base, 0x70); AppendByteField(aLine, (prefix + "_b74").c_str(), base, 0x74); AppendByteField(aLine, (prefix + "_b75").c_str(), base, 0x75); AppendByteField(aLine, (prefix + "_b76").c_str(), base, 0x76); AppendByteField(aLine, (prefix + "_b77").c_str(), base, 0x77); } void AppendRouteQueueEntrySummary(std::ostringstream& aLine, const char* aPrefix, void* aEntry) { const auto prefix = std::string(aPrefix); AppendPointerWithRva(aLine, prefix.c_str(), aEntry); if (!aEntry) { return; } AppendUint32Field(aLine, (prefix + "_id00").c_str(), aEntry, 0x00); AppendRouteRequestFields(aLine, (prefix + "_payload10").c_str(), aEntry, 0x10); AppendPointerField(aLine, (prefix + "_vtable40").c_str(), aEntry, 0x40); AppendPointerField(aLine, (prefix + "_self78").c_str(), aEntry, 0x78); AppendUint32Field(aLine, (prefix + "_u80").c_str(), aEntry, 0x80); AppendByteField(aLine, (prefix + "_b84").c_str(), aEntry, 0x84); AppendByteField(aLine, (prefix + "_b85").c_str(), aEntry, 0x85); AppendByteField(aLine, (prefix + "_b86").c_str(), aEntry, 0x86); AppendByteField(aLine, (prefix + "_b87").c_str(), aEntry, 0x87); } void AppendRouteOutputPointerSummary(std::ostringstream& aLine, const char* aPrefix, void* aOut) { const auto prefix = std::string(aPrefix); AppendPointerWithRva(aLine, prefix.c_str(), aOut); if (!aOut) { return; } auto* value = ReadPointerField(aOut, 0x00); AppendPointerWithRva(aLine, (prefix + "_value").c_str(), value); if (value && IsReadableMemory(value, 0x58)) { AppendGpsResultObjectSummary(aLine, (prefix + "_result").c_str(), value); } } 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 DetourGpsDispatchAdvance(void* aRouteSystem, void* aRuntimeQuery, void* aOutTicket, void* aTargetPosition, void* aSourcePosition) { constexpr uint32_t kMaxCalls = 128; const auto callCount = NextGpsAsyncLogCall(gGpsDispatchAdvanceLogCount); const auto shouldLog = callCount < kMaxCalls; if (shouldLog) { std::ostringstream line; line << "hook GPSDispatchAdvance 0x8d17d8 call=" << callCount; AppendRouteSystemSummary(line, "routeSystem", aRouteSystem); AppendPointerWithRva(line, "runtimeQuery", aRuntimeQuery); AppendPointerWithRva(line, "outTicket", aOutTicket); AppendUint32Field(line, "runtimeQuery_state138", aRuntimeQuery, 0x138); AppendUint32Field(line, "runtimeQuery_bank13c", aRuntimeQuery, 0x13C); AppendVector4Pointer(line, "target", aTargetPosition); AppendVector4Pointer(line, "source", aSourcePosition); AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); } if (gOriginalGpsDispatchAdvance) { gOriginalGpsDispatchAdvance(aRouteSystem, aRuntimeQuery, aOutTicket, aTargetPosition, aSourcePosition); } if (shouldLog) { std::ostringstream line; line << "hook GPSDispatchAdvance result call=" << callCount; AppendUint32Field(line, "outTicket_value", aOutTicket, 0x00); AppendRouteSystemSummary(line, "routeSystem", aRouteSystem); LogRed4ext(line.str()); } } void DetourGpsAsyncWorkerTick(void* aRouteSystem) { constexpr uint32_t kMaxCalls = 192; const auto callCount = NextGpsAsyncLogCall(gGpsAsyncWorkerTickLogCount); const auto shouldLogBefore = callCount < 16 || (callCount < kMaxCalls && HasPendingRouteWork(aRouteSystem)); if (shouldLogBefore) { std::ostringstream line; line << "hook GPSAsyncWorkerTick 0x126b28 call=" << callCount; AppendRouteSystemSummary(line, "routeSystem", aRouteSystem); AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); } if (gOriginalGpsAsyncWorkerTick) { gOriginalGpsAsyncWorkerTick(aRouteSystem); } const auto shouldLogAfter = shouldLogBefore || (callCount < kMaxCalls && HasPendingRouteWork(aRouteSystem)); if (shouldLogAfter) { std::ostringstream line; line << "hook GPSAsyncWorkerTick result call=" << callCount; AppendRouteSystemSummary(line, "routeSystem", aRouteSystem); LogRed4ext(line.str()); } } bool DetourGpsRouteJobStart(void* aRouteSystem, void* aActiveEntry, float aDelta, void* aOutLocal) { constexpr uint32_t kMaxCalls = 128; const auto callCount = NextGpsAsyncLogCall(gGpsRouteJobStartLogCount); const auto shouldLog = kEnableGpsRouteJobStartTrace && callCount < kMaxCalls; if (shouldLog) { std::ostringstream line; line << "hook GPSRouteJobStart 0x818928 call=" << callCount << " dt=" << aDelta; AppendRouteSystemSummary(line, "routeSystem", aRouteSystem); AppendRouteQueueEntrySummary(line, "activeEntry", aActiveEntry); AppendRouteOutputPointerSummary(line, "outLocal", aOutLocal); AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); } bool result = false; if (gOriginalGpsRouteJobStart) { if (kEnableGpsCostTablePatch && IsDrivingRouteJob(aRouteSystem, aActiveEntry)) { std::lock_guard patchLock(gGpsCostTablePatchMutex); ScopedGpsCostTablePatch patch(true, callCount); result = gOriginalGpsRouteJobStart(aRouteSystem, aActiveEntry, aDelta, aOutLocal); } else { result = gOriginalGpsRouteJobStart(aRouteSystem, aActiveEntry, aDelta, aOutLocal); } } if (shouldLog) { std::ostringstream line; line << "hook GPSRouteJobStart result call=" << callCount << " value=" << static_cast(result); AppendRouteSystemSummary(line, "routeSystem", aRouteSystem); AppendRouteQueueEntrySummary(line, "activeEntry", aActiveEntry); AppendRouteOutputPointerSummary(line, "outLocal", aOutLocal); LogRed4ext(line.str()); } return result; } bool DetourGpsRouteJobPoll(void* aRouteSystem, void* aActiveEntry) { constexpr uint32_t kMaxCalls = 128; const auto callCount = NextGpsAsyncLogCall(gGpsRouteJobPollLogCount); const auto shouldLog = callCount < kMaxCalls; if (shouldLog) { std::ostringstream line; line << "hook GPSRouteJobPoll 0x883cd8 call=" << callCount; AppendRouteSystemSummary(line, "routeSystem", aRouteSystem); AppendRouteQueueEntrySummary(line, "activeEntry", aActiveEntry); AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); } bool result = false; if (gOriginalGpsRouteJobPoll) { result = gOriginalGpsRouteJobPoll(aRouteSystem, aActiveEntry); } if (shouldLog || result) { std::ostringstream line; line << "hook GPSRouteJobPoll result call=" << callCount << " value=" << static_cast(result); AppendRouteSystemSummary(line, "routeSystem", aRouteSystem); AppendRouteQueueEntrySummary(line, "activeEntry", aActiveEntry); LogRed4ext(line.str()); } return result; } void* DetourGpsRouteFinish(void* aResultQueue, void* aOutEntry, void* aActiveEntry, void* aLocalResult) { constexpr uint32_t kMaxCalls = 128; const auto callCount = NextGpsAsyncLogCall(gGpsRouteFinishLogCount); const auto shouldLog = callCount < kMaxCalls; if (shouldLog) { std::ostringstream line; line << "hook GPSRouteFinish 0x11f5f60 call=" << callCount; AppendPointerWithRva(line, "resultQueue", aResultQueue); AppendRouteQueueEntrySummary(line, "activeEntry", aActiveEntry); AppendRouteOutputPointerSummary(line, "localResult", aLocalResult); AppendPointerWithRva(line, "outEntry", aOutEntry); AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); } void* result = nullptr; if (gOriginalGpsRouteFinish) { result = gOriginalGpsRouteFinish(aResultQueue, aOutEntry, aActiveEntry, aLocalResult); } if (shouldLog) { std::ostringstream line; line << "hook GPSRouteFinish result call=" << callCount; AppendPointerWithRva(line, "value", result); AppendRouteOutputPointerSummary(line, "localResult", aLocalResult); AppendPointerWithRva(line, "outEntry", aOutEntry); LogRed4ext(line.str()); } return result; } void* DetourGpsRouteJobBuild(void* aJob, void* aOut, void* aRequest) { constexpr uint32_t kMaxCalls = 128; const auto callCount = NextGpsAsyncLogCall(gGpsRouteJobBuildLogCount); const auto shouldLog = callCount < kMaxCalls; if (shouldLog) { std::ostringstream line; line << "hook GPSRouteJobBuild 0x818ba8 call=" << callCount; AppendRouteJobSummary(line, "job", aJob); AppendRouteOutputPointerSummary(line, "out", aOut); AppendRouteRequestFields(line, "request", aRequest, 0x00); AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); } void* result = nullptr; if (gOriginalGpsRouteJobBuild) { result = gOriginalGpsRouteJobBuild(aJob, aOut, aRequest); } if (shouldLog) { std::ostringstream line; line << "hook GPSRouteJobBuild result call=" << callCount; AppendPointerWithRva(line, "value", result); AppendRouteOutputPointerSummary(line, "out", aOut); AppendRouteJobSummary(line, "job", aJob); LogRed4ext(line.str()); } return result; } int32_t DetourGpsRouteProducer(void* aGraph, void* aQuery, void* aOutResult) { constexpr uint32_t kMaxCalls = 512; uint32_t callCount = 0; bool shouldLog = false; { std::lock_guard lock(gGpsCostLogMutex); callCount = gGpsRouteProducerLogCount++; shouldLog = callCount < kMaxCalls; } if (shouldLog) { std::ostringstream line; line << "hook GPSRouteProducer 0x44cc7c call=" << callCount; AppendPointerWithRva(line, "graph", aGraph); AppendPointerWithRva(line, "query", aQuery); AppendPointerWithRva(line, "outResult", aOutResult); AppendGpsRouteProducerQuerySummary(line, aQuery); AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); } GpsEdgeCostStats stats{}; auto* previousStats = gActiveGpsEdgeCostStats; const auto previousRouteProducerCall = gActiveGpsRouteProducerCall; gActiveGpsEdgeCostStats = &stats; gActiveGpsRouteProducerCall = callCount; int32_t result = 0x80000008; if (gOriginalGpsRouteProducer) { result = gOriginalGpsRouteProducer(aGraph, aQuery, aOutResult); } gActiveGpsEdgeCostStats = previousStats; gActiveGpsRouteProducerCall = previousRouteProducerCall; if (shouldLog) { std::ostringstream line; line << "hook GPSRouteProducer result call=" << callCount << " value=0x" << std::hex << static_cast(result) << std::dec; AppendUint32Field(line, "out_u0c", aOutResult, 0x0C); AppendUint32Field(line, "out_u1c", aOutResult, 0x1C); AppendUint32Field(line, "out_u20", aOutResult, 0x20); AppendUint32Field(line, "out_u24", aOutResult, 0x24); AppendUint32Field(line, "out_u2c", aOutResult, 0x2C); AppendUint32Field(line, "out_u30lo", aOutResult, 0x30); AppendUint32Field(line, "out_u34hi", aOutResult, 0x34); AppendUint16Field(line, "out_flags40", aOutResult, 0x40); AppendByteField(line, "out_state42", aOutResult, 0x42); AppendGpsEdgeCostStats(line, stats); LogRed4ext(line.str()); } return result; } int32_t DetourGpsSearch(void* aGraph, uint64_t aStartHandle, uint64_t aTargetHandle, void* aStartPoint, void* aTargetPoint, void* aCostProvider, void* aOutPath, uint32_t* aOutCount) { constexpr uint32_t kMaxCalls = 384; uint32_t callCount = 0; bool shouldLog = false; { std::lock_guard lock(gGpsCostLogMutex); callCount = gGpsSearchLogCount++; shouldLog = kEnableGpsTraceHooks && callCount < kMaxCalls; } if (shouldLog) { std::ostringstream line; line << "hook GPSSearch 0x44f054 call=" << callCount << " startHandle=0x" << std::hex << aStartHandle << " targetHandle=0x" << aTargetHandle << std::dec; AppendPointerWithRva(line, "graph", aGraph); AppendVector3Pointer(line, "startPoint", aStartPoint); AppendVector3Pointer(line, "targetPoint", aTargetPoint); AppendPointerWithRva(line, "outPath", aOutPath); AppendPointerWithRva(line, "outCount", aOutCount); AppendGpsCostProviderCompact(line, "provider", aCostProvider); if (gActiveGpsRouteProducerCall != UINT32_MAX) { line << " routeProducerCall=" << gActiveGpsRouteProducerCall; } AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); } int32_t result = 0x80000008; if (gOriginalGpsSearch) { ScopedGpsProviderClassPatch providerPatch(aCostProvider, callCount); result = gOriginalGpsSearch(aGraph, aStartHandle, aTargetHandle, aStartPoint, aTargetPoint, aCostProvider, aOutPath, aOutCount); } if (shouldLog) { uint32_t outCount = 0; const auto hasOutCount = aOutCount && TryReadMemory(aOutCount, outCount); std::ostringstream line; line << "hook GPSSearch result call=" << callCount << " value=0x" << std::hex << static_cast(result) << std::dec << " hasOutCount=" << static_cast(hasOutCount); if (hasOutCount) { line << " outCount=" << outCount; } LogRed4ext(line.str()); } return result; } float DetourGpsEdgeCost(void* aCostProvider, void* aCurrentState, void* aNeighborState, uint64_t aPreviousHandle, void* aPreviousSegment, void* aPreviousPoint, uint64_t aCurrentHandle, void* aCurrentSegment, void* aCurrentPoint, uint64_t aNeighborHandle, void* aNeighborSegment, void* aNeighborPoint) { const auto returnAddress = __builtin_return_address(0); uintptr_t returnRva = 0; TryGetImageRva(returnAddress, returnRva); float result = 0.0f; if (gOriginalGpsEdgeCost) { result = gOriginalGpsEdgeCost(aCostProvider, aCurrentState, aNeighborState, aPreviousHandle, aPreviousSegment, aPreviousPoint, aCurrentHandle, aCurrentSegment, aCurrentPoint, aNeighborHandle, aNeighborSegment, aNeighborPoint); } const auto sourceClass = ReadGpsPointClass(aCurrentPoint); const auto neighborClass = ReadGpsPointClass(aNeighborPoint); if (gActiveGpsEdgeCostStats) { NoteGpsEdgeCostStats(*gActiveGpsEdgeCostStats, sourceClass, neighborClass, result, returnRva, aCurrentHandle, aNeighborHandle); } float multiplier = 0.0f; const auto hasMultiplier = sourceClass < 64 && TryReadFloatField(aCostProvider, 0x08 + static_cast(sourceClass) * sizeof(float), multiplier); uint32_t callCount = 0; uint32_t classCallCount = 0; bool shouldLog = false; { std::lock_guard lock(gGpsCostLogMutex); callCount = gGpsEdgeCostLogCount++; if (sourceClass < gGpsEdgeCostClassLogCounts.size()) { classCallCount = gGpsEdgeCostClassLogCounts[sourceClass]++; } shouldLog = callCount < 32 || (sourceClass < 64 && classCallCount < 1); } if (shouldLog) { const auto baseCost = hasMultiplier && multiplier != 0.0f ? result / multiplier : 0.0f; std::ostringstream line; line << "hook GPSEdgeCost 0x44f838 call=" << callCount << " classCall=" << classCallCount << " sourceClass=" << static_cast(sourceClass) << " neighborClass=" << static_cast(neighborClass) << " value=" << result; if (hasMultiplier) { line << " multiplier=" << multiplier << " baseCost=" << baseCost; } else { line << " multiplier="; } line << " previousHandle=0x" << std::hex << aPreviousHandle << " currentHandle=0x" << aCurrentHandle << " neighborHandle=0x" << aNeighborHandle << std::dec; AppendPointerWithRva(line, "provider", aCostProvider); AppendVector3Pointer(line, "currentState", aCurrentState); AppendVector3Pointer(line, "neighborState", aNeighborState); AppendPointerWithRva(line, "previousSegment", aPreviousSegment); AppendGpsPointSummary(line, "previousPoint", aPreviousPoint); AppendPointerWithRva(line, "currentSegment", aCurrentSegment); AppendGpsPointSummary(line, "currentPoint", aCurrentPoint); AppendPointerWithRva(line, "neighborSegment", aNeighborSegment); AppendGpsPointSummary(line, "neighborPoint", aNeighborPoint); AppendReturnRva(line, returnAddress); LogRed4ext(line.str()); } return result; } void LogGpsMultiplierSignature(const char* aName, uintptr_t aRva, bool aAuxiliary, void* aJob, void* aNode, float aValue, const void* aReturnAddress) { GpsMultiplierSignature signature{}; if (!ReadGpsMultiplierSignature(aAuxiliary, aJob, aNode, aValue, signature)) { return; } uint32_t callCount = 0; uint32_t signatureCount = 0; const auto isNew = NoteGpsMultiplierSignature(signature, callCount, signatureCount); const auto shouldLog = isNew || signatureCount == 8 || signatureCount == 64 || signatureCount == 512; if (!shouldLog) { return; } std::ostringstream line; line << "hook " << aName << " 0x" << std::hex << aRva << std::dec << " call=" << callCount << " signatureCount=" << signatureCount << " new=" << static_cast(isNew); AppendPointerWithRva(line, "job", aJob); AppendPointerWithRva(line, "node", aNode); AppendGpsMultiplierSignature(line, signature); AppendReturnRva(line, aReturnAddress); LogRed4ext(line.str()); } float DetourGpsAuxMultiplier(void* aJob, void* aNode) { float result = 1.0f; if (gOriginalGpsAuxMultiplier) { result = gOriginalGpsAuxMultiplier(aJob, aNode); } LogGpsMultiplierSignature("GPSAuxMultiplier", kGpsAuxMultiplierRva, true, aJob, aNode, result, __builtin_return_address(0)); return result; } float DetourGpsNodeMultiplier(void* aJob, void* aNode) { float result = 1.0f; if (gOriginalGpsNodeMultiplier) { result = gOriginalGpsNodeMultiplier(aJob, aNode); } LogGpsMultiplierSignature("GPSNodeMultiplier", kGpsNodeMultiplierRva, false, aJob, aNode, result, __builtin_return_address(0)); 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"); if (kEnableGpsTraceHooks) { 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("GPSDispatchAdvance 0x8d17d8", kGpsDispatchAdvanceRva, reinterpret_cast(&DetourGpsDispatchAdvance), reinterpret_cast(&gOriginalGpsDispatchAdvance)); AttachProbeHook("GPSAsyncWorkerTick 0x126b28", kGpsAsyncWorkerTickRva, reinterpret_cast(&DetourGpsAsyncWorkerTick), reinterpret_cast(&gOriginalGpsAsyncWorkerTick)); AttachProbeHook("GPSRouteJobPoll 0x883cd8", kGpsRouteJobPollRva, reinterpret_cast(&DetourGpsRouteJobPoll), reinterpret_cast(&gOriginalGpsRouteJobPoll)); AttachProbeHook("GPSRouteFinish 0x11f5f60", kGpsRouteFinishRva, reinterpret_cast(&DetourGpsRouteFinish), reinterpret_cast(&gOriginalGpsRouteFinish)); AttachProbeHook("GPSRouteJobBuild 0x818ba8", kGpsRouteJobBuildRva, reinterpret_cast(&DetourGpsRouteJobBuild), reinterpret_cast(&gOriginalGpsRouteJobBuild)); } if (kEnableGpsCostTablePatch || kEnableGpsRouteJobStartTrace) { AttachProbeHook("GPSRouteJobStart 0x818928", kGpsRouteJobStartRva, reinterpret_cast(&DetourGpsRouteJobStart), reinterpret_cast(&gOriginalGpsRouteJobStart)); } if (kEnableGpsProviderClassPatch || (kEnableGpsTraceHooks && kEnableGpsLocalSearchHooks)) { AttachProbeHook("GPSSearch 0x44f054", kGpsSearchRva, reinterpret_cast(&DetourGpsSearch), reinterpret_cast(&gOriginalGpsSearch)); } if (kEnableGpsTraceHooks && kEnableGpsLocalSearchHooks) { AttachProbeHook("GPSRouteProducer 0x44cc7c", kGpsRouteProducerRva, reinterpret_cast(&DetourGpsRouteProducer), reinterpret_cast(&gOriginalGpsRouteProducer)); AttachProbeHook("GPSEdgeCost 0x44f838", kGpsEdgeCostRva, reinterpret_cast(&DetourGpsEdgeCost), reinterpret_cast(&gOriginalGpsEdgeCost)); } if (kEnableGpsMultiplierHooks) { AttachProbeHook("GPSAuxMultiplier 0x40bb00", kGpsAuxMultiplierRva, reinterpret_cast(&DetourGpsAuxMultiplier), reinterpret_cast(&gOriginalGpsAuxMultiplier)); AttachProbeHook("GPSNodeMultiplier 0x40bb40", kGpsNodeMultiplierRva, reinterpret_cast(&DetourGpsNodeMultiplier), reinterpret_cast(&gOriginalGpsNodeMultiplier)); } if (kEnableRouteUiTraceHooks) { 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) { if (kEnableGpsTraceHooks) { 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("GPSDispatchAdvance 0x8d17d8", kGpsDispatchAdvanceRva); DetachProbeHook("GPSAsyncWorkerTick 0x126b28", kGpsAsyncWorkerTickRva); DetachProbeHook("GPSRouteJobPoll 0x883cd8", kGpsRouteJobPollRva); DetachProbeHook("GPSRouteFinish 0x11f5f60", kGpsRouteFinishRva); DetachProbeHook("GPSRouteJobBuild 0x818ba8", kGpsRouteJobBuildRva); } if (kEnableGpsCostTablePatch || kEnableGpsRouteJobStartTrace) { DetachProbeHook("GPSRouteJobStart 0x818928", kGpsRouteJobStartRva); } if (kEnableGpsProviderClassPatch || (kEnableGpsTraceHooks && kEnableGpsLocalSearchHooks)) { DetachProbeHook("GPSSearch 0x44f054", kGpsSearchRva); } if (kEnableGpsTraceHooks && kEnableGpsLocalSearchHooks) { DetachProbeHook("GPSRouteProducer 0x44cc7c", kGpsRouteProducerRva); DetachProbeHook("GPSEdgeCost 0x44f838", kGpsEdgeCostRva); } if (kEnableGpsMultiplierHooks) { DetachProbeHook("GPSAuxMultiplier 0x40bb00", kGpsAuxMultiplierRva); DetachProbeHook("GPSNodeMultiplier 0x40bb40", kGpsNodeMultiplierRva); } if (kEnableRouteUiTraceHooks) { 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; }