diff --git a/docs/red4ext-logging-shim.md b/docs/red4ext-logging-shim.md index a76dc9d..75b2ff1 100644 --- a/docs/red4ext-logging-shim.md +++ b/docs/red4ext-logging-shim.md @@ -64,6 +64,18 @@ Current behavior: - Resolves the native mappin system when one of those paths fires, logs relevant vtable slot addresses, and temporarily hooks the route-adjacent slots. +Active build note: + +- The currently installed build is read-only. It disables provider class + multiplier overrides and enables local GPS search tracing. +- It hooks `GPSSearch` (`0x44f054`), `GPSRouteProducer` (`0x44cc7c`), + `GPSEdgeCost` (`0x44f838`), and the two cost-provider filters + `0x44ff68`/`0x450b08`. +- The filter probe records pass/fail counts by point class and by the + `point + 0x10` mask bits tested against provider masks at `+0x108`/`+0x10a`. + This is meant to identify whether class/mask filtering, rather than the edge + multiplier table, is dominating route choice. + Current route-probe focus: - `FrameMappinPath` wrapper/core: `0x27c4314`, `0x27bc1ec` diff --git a/docs/tooling.md b/docs/tooling.md index 7217b9b..2ce468e 100644 --- a/docs/tooling.md +++ b/docs/tooling.md @@ -8,6 +8,9 @@ Installed inside that toolbox: - GCC/G++, CMake, make - `jq`, `ripgrep` - WolvenKit CLI NuGet tool `wolvenkit.cli` 8.18.1 +- `rizin` 0.7.4 +- `python3-capstone` 5.0.5 +- `python3-pefile` 2024.8.26 The WolvenKit command is: diff --git a/red4ext/EdgeWeightGPS/src/Main.cpp b/red4ext/EdgeWeightGPS/src/Main.cpp index cc18f66..1b5bba3 100644 --- a/red4ext/EdgeWeightGPS/src/Main.cpp +++ b/red4ext/EdgeWeightGPS/src/Main.cpp @@ -97,11 +97,12 @@ 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 kEnableGpsProviderClassPatch = false; +constexpr bool kEnableGpsProviderClassPatchTrace = false; +constexpr bool kEnableGpsTraceHooks = true; +constexpr bool kEnableGpsQueryLifecycleHooks = false; constexpr bool kEnableGpsRouteJobStartTrace = false; -constexpr bool kEnableGpsLocalSearchHooks = false; +constexpr bool kEnableGpsLocalSearchHooks = true; constexpr bool kEnableRouteUiTraceHooks = false; constexpr uintptr_t kGpsSystemTickRva = 0x0E6B62C; constexpr uintptr_t kJournalTrackEntryImplRva = 0x05944FC; @@ -167,6 +168,8 @@ constexpr uintptr_t kGpsRouteJobBuildRva = 0x0818BA8; constexpr uintptr_t kGpsRouteProducerRva = 0x044CC7C; constexpr uintptr_t kGpsSearchRva = 0x044F054; constexpr uintptr_t kGpsEdgeCostRva = 0x044F838; +constexpr uintptr_t kGpsFilteredProviderFilterRva = 0x044FF68; +constexpr uintptr_t kGpsBaseProviderFilterRva = 0x0450B08; constexpr uintptr_t kGpsBaseCostProviderVtableRva = 0x2AE6120; constexpr uintptr_t kGpsFilteredCostProviderVtableRva = 0x2AE60F8; constexpr uintptr_t kGpsAuxMultiplierRva = 0x040BB00; @@ -325,6 +328,21 @@ struct GpsEdgeCostStats uint32_t sampleCount = 0; }; +struct GpsProviderFilterStats +{ + uint32_t total = 0; + uint32_t passed = 0; + uint32_t failed = 0; + uint32_t baseTotal = 0; + uint32_t filteredTotal = 0; + std::array passClasses{}; + std::array failClasses{}; + uint32_t passInvalidClass = 0; + uint32_t failInvalidClass = 0; + std::array passMaskBits{}; + std::array failMaskBits{}; +}; + struct GpsMultiplierSignature { bool active = false; @@ -344,6 +362,7 @@ struct GpsMultiplierSignature }; thread_local GpsEdgeCostStats* gActiveGpsEdgeCostStats = nullptr; +thread_local GpsProviderFilterStats* gActiveGpsProviderFilterStats = nullptr; thread_local uint32_t gActiveGpsRouteProducerCall = UINT32_MAX; std::array gGpsMultiplierSignatures{}; uint32_t gGpsMultiplierOverflowCount = 0; @@ -397,6 +416,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 GpsProviderFilterFunc = bool (*)(void* aCostProvider, uint64_t aHandle, void* aSegment, void* aPoint); using GpsMultiplierFunc = float (*)(void* aJob, void* aNode); ProbeFunc gOriginalGpsSystemTick = nullptr; JournalTrackEntryFunc gOriginalJournalTrackEntry = nullptr; @@ -458,6 +478,8 @@ GpsRouteJobBuildFunc gOriginalGpsRouteJobBuild = nullptr; GpsRouteProducerFunc gOriginalGpsRouteProducer = nullptr; GpsSearchFunc gOriginalGpsSearch = nullptr; GpsEdgeCostFunc gOriginalGpsEdgeCost = nullptr; +GpsProviderFilterFunc gOriginalGpsFilteredProviderFilter = nullptr; +GpsProviderFilterFunc gOriginalGpsBaseProviderFilter = nullptr; GpsMultiplierFunc gOriginalGpsAuxMultiplier = nullptr; GpsMultiplierFunc gOriginalGpsNodeMultiplier = nullptr; @@ -1170,6 +1192,44 @@ void AppendClassCounts(std::ostringstream& aLine, const char* aLabel, const std: aLine << "]"; } +void AppendMaskBitCounts(std::ostringstream& aLine, const char* aLabel, const std::array& aCounts) +{ + 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 (first) + { + aLine << "none"; + } + + aLine << "]"; +} + +void AppendGpsProviderFilterStats(std::ostringstream& aLine, const GpsProviderFilterStats& aStats) +{ + aLine << " filterCalls=" << aStats.total << " filterPass=" << aStats.passed + << " filterFail=" << aStats.failed << " filterBase=" << aStats.baseTotal + << " filterFiltered=" << aStats.filteredTotal; + AppendClassCounts(aLine, "filterPassClasses", aStats.passClasses, aStats.passInvalidClass); + AppendClassCounts(aLine, "filterFailClasses", aStats.failClasses, aStats.failInvalidClass); + AppendMaskBitCounts(aLine, "filterPassMaskBits", aStats.passMaskBits); + AppendMaskBitCounts(aLine, "filterFailMaskBits", aStats.failMaskBits); +} + void AppendGpsEdgeCostStats(std::ostringstream& aLine, const GpsEdgeCostStats& aStats) { aLine << " edgeCostCalls=" << aStats.total; @@ -1245,6 +1305,49 @@ void AppendGpsEdgeCostStats(std::ostringstream& aLine, const GpsEdgeCostStats& a aLine << "]"; } +void NoteGpsProviderFilterStats(GpsProviderFilterStats& aStats, bool aFilteredProvider, bool aPassed, + uint8_t aPointClass, uint16_t aPointMask) +{ + ++aStats.total; + if (aPassed) + { + ++aStats.passed; + } + else + { + ++aStats.failed; + } + + if (aFilteredProvider) + { + ++aStats.filteredTotal; + } + else + { + ++aStats.baseTotal; + } + + auto& classCounts = aPassed ? aStats.passClasses : aStats.failClasses; + auto& invalidClassCount = aPassed ? aStats.passInvalidClass : aStats.failInvalidClass; + if (aPointClass < classCounts.size()) + { + ++classCounts[aPointClass]; + } + else + { + ++invalidClassCount; + } + + auto& maskCounts = aPassed ? aStats.passMaskBits : aStats.failMaskBits; + for (uint32_t bit = 0; bit < maskCounts.size(); ++bit) + { + if ((aPointMask & (1u << bit)) != 0) + { + ++maskCounts[bit]; + } + } +} + void NoteGpsEdgeCostStats(GpsEdgeCostStats& aStats, uint8_t aSourceClass, uint8_t aNeighborClass, float aCost, uintptr_t aReturnRva, uint64_t aCurrentHandle, uint64_t aNeighborHandle) { @@ -3733,6 +3836,10 @@ int32_t DetourGpsSearch(void* aGraph, uint64_t aStartHandle, uint64_t aTargetHan LogRed4ext(line.str()); } + GpsProviderFilterStats filterStats{}; + auto* previousFilterStats = gActiveGpsProviderFilterStats; + gActiveGpsProviderFilterStats = &filterStats; + int32_t result = 0x80000008; if (gOriginalGpsSearch) { @@ -3741,6 +3848,8 @@ int32_t DetourGpsSearch(void* aGraph, uint64_t aStartHandle, uint64_t aTargetHan aOutPath, aOutCount); } + gActiveGpsProviderFilterStats = previousFilterStats; + if (shouldLog) { uint32_t outCount = 0; @@ -3753,12 +3862,47 @@ int32_t DetourGpsSearch(void* aGraph, uint64_t aStartHandle, uint64_t aTargetHan { line << " outCount=" << outCount; } + AppendGpsProviderFilterStats(line, filterStats); LogRed4ext(line.str()); } return result; } +bool DetourGpsFilteredProviderFilter(void* aCostProvider, uint64_t aHandle, void* aSegment, void* aPoint) +{ + bool result = false; + if (gOriginalGpsFilteredProviderFilter) + { + result = gOriginalGpsFilteredProviderFilter(aCostProvider, aHandle, aSegment, aPoint); + } + + if (gActiveGpsProviderFilterStats) + { + NoteGpsProviderFilterStats(*gActiveGpsProviderFilterStats, true, result, ReadGpsPointClass(aPoint), + ReadGpsPointMask(aPoint)); + } + + return result; +} + +bool DetourGpsBaseProviderFilter(void* aCostProvider, uint64_t aHandle, void* aSegment, void* aPoint) +{ + bool result = false; + if (gOriginalGpsBaseProviderFilter) + { + result = gOriginalGpsBaseProviderFilter(aCostProvider, aHandle, aSegment, aPoint); + } + + if (gActiveGpsProviderFilterStats) + { + NoteGpsProviderFilterStats(*gActiveGpsProviderFilterStats, false, result, ReadGpsPointClass(aPoint), + ReadGpsPointMask(aPoint)); + } + + 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) @@ -4198,7 +4342,7 @@ RED4EXT_C_EXPORT bool RED4EXT_CALL Main(RED4ext::v1::PluginHandle aHandle, RED4e 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) + if (kEnableGpsQueryLifecycleHooks) { AttachProbeHook("RunGPSQuery body 0x29bd128", kRunGpsQueryBodyRva, reinterpret_cast(&DetourRunGpsQueryBody), @@ -4252,6 +4396,12 @@ RED4EXT_C_EXPORT bool RED4EXT_CALL Main(RED4ext::v1::PluginHandle aHandle, RED4e reinterpret_cast(&gOriginalGpsRouteProducer)); AttachProbeHook("GPSEdgeCost 0x44f838", kGpsEdgeCostRva, reinterpret_cast(&DetourGpsEdgeCost), reinterpret_cast(&gOriginalGpsEdgeCost)); + AttachProbeHook("GPSFilteredProviderFilter 0x44ff68", kGpsFilteredProviderFilterRva, + reinterpret_cast(&DetourGpsFilteredProviderFilter), + reinterpret_cast(&gOriginalGpsFilteredProviderFilter)); + AttachProbeHook("GPSBaseProviderFilter 0x450b08", kGpsBaseProviderFilterRva, + reinterpret_cast(&DetourGpsBaseProviderFilter), + reinterpret_cast(&gOriginalGpsBaseProviderFilter)); } if (kEnableGpsMultiplierHooks) { @@ -4378,7 +4528,7 @@ RED4EXT_C_EXPORT bool RED4EXT_CALL Main(RED4ext::v1::PluginHandle aHandle, RED4e if (aReason == RED4ext::v1::EMainReason::Unload) { - if (kEnableGpsTraceHooks) + if (kEnableGpsQueryLifecycleHooks) { DetachProbeHook("RunGPSQuery body 0x29bd128", kRunGpsQueryBodyRva); DetachProbeHook("UpdateGPSQuery body 0x29bd254", kUpdateGpsQueryBodyRva); @@ -4404,6 +4554,8 @@ RED4EXT_C_EXPORT bool RED4EXT_CALL Main(RED4ext::v1::PluginHandle aHandle, RED4e { DetachProbeHook("GPSRouteProducer 0x44cc7c", kGpsRouteProducerRva); DetachProbeHook("GPSEdgeCost 0x44f838", kGpsEdgeCostRva); + DetachProbeHook("GPSFilteredProviderFilter 0x44ff68", kGpsFilteredProviderFilterRva); + DetachProbeHook("GPSBaseProviderFilter 0x450b08", kGpsBaseProviderFilterRva); } if (kEnableGpsMultiplierHooks) {