Trace GPS provider filters
This commit is contained in:
@@ -64,6 +64,18 @@ Current behavior:
|
|||||||
- Resolves the native mappin system when one of those paths fires, logs relevant
|
- Resolves the native mappin system when one of those paths fires, logs relevant
|
||||||
vtable slot addresses, and temporarily hooks the route-adjacent slots.
|
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:
|
Current route-probe focus:
|
||||||
|
|
||||||
- `FrameMappinPath` wrapper/core: `0x27c4314`, `0x27bc1ec`
|
- `FrameMappinPath` wrapper/core: `0x27c4314`, `0x27bc1ec`
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ Installed inside that toolbox:
|
|||||||
- GCC/G++, CMake, make
|
- GCC/G++, CMake, make
|
||||||
- `jq`, `ripgrep`
|
- `jq`, `ripgrep`
|
||||||
- WolvenKit CLI NuGet tool `wolvenkit.cli` 8.18.1
|
- 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:
|
The WolvenKit command is:
|
||||||
|
|
||||||
|
|||||||
@@ -97,11 +97,12 @@ constexpr bool kEnableVerboseRouteObserverHooks = false;
|
|||||||
constexpr bool kEnableVerboseMappinRouteState = false;
|
constexpr bool kEnableVerboseMappinRouteState = false;
|
||||||
constexpr bool kEnableGpsMultiplierHooks = false;
|
constexpr bool kEnableGpsMultiplierHooks = false;
|
||||||
constexpr bool kEnableGpsCostTablePatch = false;
|
constexpr bool kEnableGpsCostTablePatch = false;
|
||||||
constexpr bool kEnableGpsProviderClassPatch = true;
|
constexpr bool kEnableGpsProviderClassPatch = false;
|
||||||
constexpr bool kEnableGpsProviderClassPatchTrace = true;
|
constexpr bool kEnableGpsProviderClassPatchTrace = false;
|
||||||
constexpr bool kEnableGpsTraceHooks = false;
|
constexpr bool kEnableGpsTraceHooks = true;
|
||||||
|
constexpr bool kEnableGpsQueryLifecycleHooks = false;
|
||||||
constexpr bool kEnableGpsRouteJobStartTrace = false;
|
constexpr bool kEnableGpsRouteJobStartTrace = false;
|
||||||
constexpr bool kEnableGpsLocalSearchHooks = false;
|
constexpr bool kEnableGpsLocalSearchHooks = true;
|
||||||
constexpr bool kEnableRouteUiTraceHooks = false;
|
constexpr bool kEnableRouteUiTraceHooks = false;
|
||||||
constexpr uintptr_t kGpsSystemTickRva = 0x0E6B62C;
|
constexpr uintptr_t kGpsSystemTickRva = 0x0E6B62C;
|
||||||
constexpr uintptr_t kJournalTrackEntryImplRva = 0x05944FC;
|
constexpr uintptr_t kJournalTrackEntryImplRva = 0x05944FC;
|
||||||
@@ -167,6 +168,8 @@ constexpr uintptr_t kGpsRouteJobBuildRva = 0x0818BA8;
|
|||||||
constexpr uintptr_t kGpsRouteProducerRva = 0x044CC7C;
|
constexpr uintptr_t kGpsRouteProducerRva = 0x044CC7C;
|
||||||
constexpr uintptr_t kGpsSearchRva = 0x044F054;
|
constexpr uintptr_t kGpsSearchRva = 0x044F054;
|
||||||
constexpr uintptr_t kGpsEdgeCostRva = 0x044F838;
|
constexpr uintptr_t kGpsEdgeCostRva = 0x044F838;
|
||||||
|
constexpr uintptr_t kGpsFilteredProviderFilterRva = 0x044FF68;
|
||||||
|
constexpr uintptr_t kGpsBaseProviderFilterRva = 0x0450B08;
|
||||||
constexpr uintptr_t kGpsBaseCostProviderVtableRva = 0x2AE6120;
|
constexpr uintptr_t kGpsBaseCostProviderVtableRva = 0x2AE6120;
|
||||||
constexpr uintptr_t kGpsFilteredCostProviderVtableRva = 0x2AE60F8;
|
constexpr uintptr_t kGpsFilteredCostProviderVtableRva = 0x2AE60F8;
|
||||||
constexpr uintptr_t kGpsAuxMultiplierRva = 0x040BB00;
|
constexpr uintptr_t kGpsAuxMultiplierRva = 0x040BB00;
|
||||||
@@ -325,6 +328,21 @@ struct GpsEdgeCostStats
|
|||||||
uint32_t sampleCount = 0;
|
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
|
struct GpsMultiplierSignature
|
||||||
{
|
{
|
||||||
bool active = false;
|
bool active = false;
|
||||||
@@ -344,6 +362,7 @@ struct GpsMultiplierSignature
|
|||||||
};
|
};
|
||||||
|
|
||||||
thread_local GpsEdgeCostStats* gActiveGpsEdgeCostStats = nullptr;
|
thread_local GpsEdgeCostStats* gActiveGpsEdgeCostStats = nullptr;
|
||||||
|
thread_local GpsProviderFilterStats* gActiveGpsProviderFilterStats = nullptr;
|
||||||
thread_local uint32_t gActiveGpsRouteProducerCall = UINT32_MAX;
|
thread_local uint32_t gActiveGpsRouteProducerCall = UINT32_MAX;
|
||||||
std::array<GpsMultiplierSignature, 256> gGpsMultiplierSignatures{};
|
std::array<GpsMultiplierSignature, 256> gGpsMultiplierSignatures{};
|
||||||
uint32_t gGpsMultiplierOverflowCount = 0;
|
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 aPreviousHandle, void* aPreviousSegment, void* aPreviousPoint,
|
||||||
uint64_t aCurrentHandle, void* aCurrentSegment, void* aCurrentPoint,
|
uint64_t aCurrentHandle, void* aCurrentSegment, void* aCurrentPoint,
|
||||||
uint64_t aNeighborHandle, void* aNeighborSegment, void* aNeighborPoint);
|
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);
|
using GpsMultiplierFunc = float (*)(void* aJob, void* aNode);
|
||||||
ProbeFunc gOriginalGpsSystemTick = nullptr;
|
ProbeFunc gOriginalGpsSystemTick = nullptr;
|
||||||
JournalTrackEntryFunc gOriginalJournalTrackEntry = nullptr;
|
JournalTrackEntryFunc gOriginalJournalTrackEntry = nullptr;
|
||||||
@@ -458,6 +478,8 @@ GpsRouteJobBuildFunc gOriginalGpsRouteJobBuild = nullptr;
|
|||||||
GpsRouteProducerFunc gOriginalGpsRouteProducer = nullptr;
|
GpsRouteProducerFunc gOriginalGpsRouteProducer = nullptr;
|
||||||
GpsSearchFunc gOriginalGpsSearch = nullptr;
|
GpsSearchFunc gOriginalGpsSearch = nullptr;
|
||||||
GpsEdgeCostFunc gOriginalGpsEdgeCost = nullptr;
|
GpsEdgeCostFunc gOriginalGpsEdgeCost = nullptr;
|
||||||
|
GpsProviderFilterFunc gOriginalGpsFilteredProviderFilter = nullptr;
|
||||||
|
GpsProviderFilterFunc gOriginalGpsBaseProviderFilter = nullptr;
|
||||||
GpsMultiplierFunc gOriginalGpsAuxMultiplier = nullptr;
|
GpsMultiplierFunc gOriginalGpsAuxMultiplier = nullptr;
|
||||||
GpsMultiplierFunc gOriginalGpsNodeMultiplier = nullptr;
|
GpsMultiplierFunc gOriginalGpsNodeMultiplier = nullptr;
|
||||||
|
|
||||||
@@ -1170,6 +1192,44 @@ void AppendClassCounts(std::ostringstream& aLine, const char* aLabel, const std:
|
|||||||
aLine << "]";
|
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)
|
void AppendGpsEdgeCostStats(std::ostringstream& aLine, const GpsEdgeCostStats& aStats)
|
||||||
{
|
{
|
||||||
aLine << " edgeCostCalls=" << aStats.total;
|
aLine << " edgeCostCalls=" << aStats.total;
|
||||||
@@ -1245,6 +1305,49 @@ void AppendGpsEdgeCostStats(std::ostringstream& aLine, const GpsEdgeCostStats& a
|
|||||||
aLine << "]";
|
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,
|
void NoteGpsEdgeCostStats(GpsEdgeCostStats& aStats, uint8_t aSourceClass, uint8_t aNeighborClass, float aCost,
|
||||||
uintptr_t aReturnRva, uint64_t aCurrentHandle, uint64_t aNeighborHandle)
|
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());
|
LogRed4ext(line.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GpsProviderFilterStats filterStats{};
|
||||||
|
auto* previousFilterStats = gActiveGpsProviderFilterStats;
|
||||||
|
gActiveGpsProviderFilterStats = &filterStats;
|
||||||
|
|
||||||
int32_t result = 0x80000008;
|
int32_t result = 0x80000008;
|
||||||
if (gOriginalGpsSearch)
|
if (gOriginalGpsSearch)
|
||||||
{
|
{
|
||||||
@@ -3741,6 +3848,8 @@ int32_t DetourGpsSearch(void* aGraph, uint64_t aStartHandle, uint64_t aTargetHan
|
|||||||
aOutPath, aOutCount);
|
aOutPath, aOutCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gActiveGpsProviderFilterStats = previousFilterStats;
|
||||||
|
|
||||||
if (shouldLog)
|
if (shouldLog)
|
||||||
{
|
{
|
||||||
uint32_t outCount = 0;
|
uint32_t outCount = 0;
|
||||||
@@ -3753,12 +3862,47 @@ int32_t DetourGpsSearch(void* aGraph, uint64_t aStartHandle, uint64_t aTargetHan
|
|||||||
{
|
{
|
||||||
line << " outCount=" << outCount;
|
line << " outCount=" << outCount;
|
||||||
}
|
}
|
||||||
|
AppendGpsProviderFilterStats(line, filterStats);
|
||||||
LogRed4ext(line.str());
|
LogRed4ext(line.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
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,
|
float DetourGpsEdgeCost(void* aCostProvider, void* aCurrentState, void* aNeighborState, uint64_t aPreviousHandle,
|
||||||
void* aPreviousSegment, void* aPreviousPoint, uint64_t aCurrentHandle, void* aCurrentSegment,
|
void* aPreviousSegment, void* aPreviousPoint, uint64_t aCurrentHandle, void* aCurrentSegment,
|
||||||
void* aCurrentPoint, uint64_t aNeighborHandle, void* aNeighborSegment, void* aNeighborPoint)
|
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);
|
const auto added = aSdk->gameStates->Add(aHandle, kGameStateRunning, &gRunningState);
|
||||||
LogRed4ext(added ? "registered Running game-state callbacks" : "failed to register Running game-state callbacks");
|
LogRed4ext(added ? "registered Running game-state callbacks" : "failed to register Running game-state callbacks");
|
||||||
|
|
||||||
if (kEnableGpsTraceHooks)
|
if (kEnableGpsQueryLifecycleHooks)
|
||||||
{
|
{
|
||||||
AttachProbeHook("RunGPSQuery body 0x29bd128", kRunGpsQueryBodyRva,
|
AttachProbeHook("RunGPSQuery body 0x29bd128", kRunGpsQueryBodyRva,
|
||||||
reinterpret_cast<void*>(&DetourRunGpsQueryBody),
|
reinterpret_cast<void*>(&DetourRunGpsQueryBody),
|
||||||
@@ -4252,6 +4396,12 @@ RED4EXT_C_EXPORT bool RED4EXT_CALL Main(RED4ext::v1::PluginHandle aHandle, RED4e
|
|||||||
reinterpret_cast<void**>(&gOriginalGpsRouteProducer));
|
reinterpret_cast<void**>(&gOriginalGpsRouteProducer));
|
||||||
AttachProbeHook("GPSEdgeCost 0x44f838", kGpsEdgeCostRva, reinterpret_cast<void*>(&DetourGpsEdgeCost),
|
AttachProbeHook("GPSEdgeCost 0x44f838", kGpsEdgeCostRva, reinterpret_cast<void*>(&DetourGpsEdgeCost),
|
||||||
reinterpret_cast<void**>(&gOriginalGpsEdgeCost));
|
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)
|
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 (aReason == RED4ext::v1::EMainReason::Unload)
|
||||||
{
|
{
|
||||||
if (kEnableGpsTraceHooks)
|
if (kEnableGpsQueryLifecycleHooks)
|
||||||
{
|
{
|
||||||
DetachProbeHook("RunGPSQuery body 0x29bd128", kRunGpsQueryBodyRva);
|
DetachProbeHook("RunGPSQuery body 0x29bd128", kRunGpsQueryBodyRva);
|
||||||
DetachProbeHook("UpdateGPSQuery body 0x29bd254", kUpdateGpsQueryBodyRva);
|
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("GPSRouteProducer 0x44cc7c", kGpsRouteProducerRva);
|
||||||
DetachProbeHook("GPSEdgeCost 0x44f838", kGpsEdgeCostRva);
|
DetachProbeHook("GPSEdgeCost 0x44f838", kGpsEdgeCostRva);
|
||||||
|
DetachProbeHook("GPSFilteredProviderFilter 0x44ff68", kGpsFilteredProviderFilterRva);
|
||||||
|
DetachProbeHook("GPSBaseProviderFilter 0x450b08", kGpsBaseProviderFilterRva);
|
||||||
}
|
}
|
||||||
if (kEnableGpsMultiplierHooks)
|
if (kEnableGpsMultiplierHooks)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user