Trace GPS provider filters

This commit is contained in:
2026-06-20 21:12:39 -05:00
parent 1c54bd453d
commit 64ddf7af62
3 changed files with 173 additions and 6 deletions
+12
View File
@@ -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`
+3
View File
@@ -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:
+158 -6
View File
@@ -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<uint32_t, 64> passClasses{};
std::array<uint32_t, 64> failClasses{};
uint32_t passInvalidClass = 0;
uint32_t failInvalidClass = 0;
std::array<uint32_t, 16> passMaskBits{};
std::array<uint32_t, 16> 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<GpsMultiplierSignature, 256> 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<uint32_t, 16>& 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<void*>(&DetourRunGpsQueryBody),
@@ -4252,6 +4396,12 @@ RED4EXT_C_EXPORT bool RED4EXT_CALL Main(RED4ext::v1::PluginHandle aHandle, RED4e
reinterpret_cast<void**>(&gOriginalGpsRouteProducer));
AttachProbeHook("GPSEdgeCost 0x44f838", kGpsEdgeCostRva, reinterpret_cast<void*>(&DetourGpsEdgeCost),
reinterpret_cast<void**>(&gOriginalGpsEdgeCost));
AttachProbeHook("GPSFilteredProviderFilter 0x44ff68", kGpsFilteredProviderFilterRva,
reinterpret_cast<void*>(&DetourGpsFilteredProviderFilter),
reinterpret_cast<void**>(&gOriginalGpsFilteredProviderFilter));
AttachProbeHook("GPSBaseProviderFilter 0x450b08", kGpsBaseProviderFilterRva,
reinterpret_cast<void*>(&DetourGpsBaseProviderFilter),
reinterpret_cast<void**>(&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)
{