From 2a5d4a647373ccb4e2885ae550e8a2eb0e2f41bd Mon Sep 17 00:00:00 2001 From: Jacob Babor Date: Sat, 20 Jun 2026 14:56:36 -0500 Subject: [PATCH] Probe GPS route cost multipliers --- red4ext/EdgeWeightGPS/src/Main.cpp | 186 +++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) diff --git a/red4ext/EdgeWeightGPS/src/Main.cpp b/red4ext/EdgeWeightGPS/src/Main.cpp index e386a99..8d25c37 100644 --- a/red4ext/EdgeWeightGPS/src/Main.cpp +++ b/red4ext/EdgeWeightGPS/src/Main.cpp @@ -159,6 +159,8 @@ constexpr uintptr_t kGpsRouteJobBuildRva = 0x0818BA8; constexpr uintptr_t kGpsRouteProducerRva = 0x044CC7C; constexpr uintptr_t kGpsSearchRva = 0x044F054; constexpr uintptr_t kGpsEdgeCostRva = 0x044F838; +constexpr uintptr_t kGpsAuxMultiplierRva = 0x040BB00; +constexpr uintptr_t kGpsNodeMultiplierRva = 0x040BB40; HMODULE gModule = nullptr; std::mutex gLogMutex; @@ -228,6 +230,8 @@ uint32_t gGpsRouteJobBuildLogCount = 0; uint32_t gGpsRouteProducerLogCount = 0; uint32_t gGpsSearchLogCount = 0; uint32_t gGpsEdgeCostLogCount = 0; +uint32_t gGpsAuxMultiplierLogCount = 0; +uint32_t gGpsNodeMultiplierLogCount = 0; std::array gGpsEdgeCostClassLogCounts{}; std::mutex gGpsCostLogMutex; std::mutex gGpsAsyncLogMutex; @@ -287,8 +291,28 @@ struct GpsEdgeCostStats 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); @@ -339,6 +363,7 @@ using GpsEdgeCostFunc = float (*)(void* aCostProvider, void* aCurrentState, void 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; @@ -399,6 +424,8 @@ GpsRouteJobBuildFunc gOriginalGpsRouteJobBuild = nullptr; GpsRouteProducerFunc gOriginalGpsRouteProducer = nullptr; GpsSearchFunc gOriginalGpsSearch = nullptr; GpsEdgeCostFunc gOriginalGpsEdgeCost = nullptr; +GpsMultiplierFunc gOriginalGpsAuxMultiplier = nullptr; +GpsMultiplierFunc gOriginalGpsNodeMultiplier = nullptr; std::filesystem::path GetLogPath() { @@ -531,6 +558,13 @@ float FloatFromBits(uint32_t aBits) 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; @@ -1250,6 +1284,96 @@ void NoteGpsEdgeCostStats(GpsEdgeCostStats& aStats, uint8_t aSourceClass, uint8_ } } +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); @@ -3403,6 +3527,60 @@ float DetourGpsEdgeCost(void* aCostProvider, void* aCurrentState, void* aNeighbo 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) @@ -3754,6 +3932,12 @@ RED4EXT_C_EXPORT bool RED4EXT_CALL Main(RED4ext::v1::PluginHandle aHandle, RED4e reinterpret_cast(&gOriginalGpsSearch)); AttachProbeHook("GPSEdgeCost 0x44f838", kGpsEdgeCostRva, reinterpret_cast(&DetourGpsEdgeCost), reinterpret_cast(&gOriginalGpsEdgeCost)); + AttachProbeHook("GPSAuxMultiplier 0x40bb00", kGpsAuxMultiplierRva, + reinterpret_cast(&DetourGpsAuxMultiplier), + reinterpret_cast(&gOriginalGpsAuxMultiplier)); + AttachProbeHook("GPSNodeMultiplier 0x40bb40", kGpsNodeMultiplierRva, + reinterpret_cast(&DetourGpsNodeMultiplier), + reinterpret_cast(&gOriginalGpsNodeMultiplier)); AttachProbeHook("GPSSystem/Tick", kGpsSystemTickRva, reinterpret_cast(&DetourGpsSystemTick), reinterpret_cast(&gOriginalGpsSystemTick)); AttachProbeHook("JournalManager::TrackEntry impl", kJournalTrackEntryImplRva, @@ -3876,6 +4060,8 @@ RED4EXT_C_EXPORT bool RED4EXT_CALL Main(RED4ext::v1::PluginHandle aHandle, RED4e DetachProbeHook("GPSRouteProducer 0x44cc7c", kGpsRouteProducerRva); DetachProbeHook("GPSSearch 0x44f054", kGpsSearchRva); DetachProbeHook("GPSEdgeCost 0x44f838", kGpsEdgeCostRva); + DetachProbeHook("GPSAuxMultiplier 0x40bb00", kGpsAuxMultiplierRva); + DetachProbeHook("GPSNodeMultiplier 0x40bb40", kGpsNodeMultiplierRva); DetachProbeHook("GPSSystem/Tick", kGpsSystemTickRva); DetachProbeHook("JournalManager::TrackEntry impl", kJournalTrackEntryImplRva); if (kEnableVerboseJournalListenerHooks)