Patch GPS node multiplier inline

This commit is contained in:
2026-06-26 23:10:22 -05:00
parent 41c758f6a4
commit 9dd0b31e4d
4 changed files with 320 additions and 1 deletions
@@ -0,0 +1,15 @@
2026-06-27 05:03:11.000 +0ms EdgeWeightGPS Main reason=Load handle=0x6ffff0570000 sdk=0x7f9fcf48 runtime=2.3.1
2026-06-27 05:03:11.001 +1ms registered Running game-state callbacks
2026-06-27 05:03:11.008 +8ms hook attach GPSQuerySubmit 0x70a42c target=0x14070a42c rva=0x70a42c result=ok original=0x1018c0480
2026-06-27 05:03:11.015 +15ms hook attach GPSQueryResultFetch 0x7094b8 target=0x1407094b8 rva=0x7094b8 result=ok original=0x1018c04e0
2026-06-27 05:03:11.029 +29ms hook attach GPSNodeMultiplier 0x40bb40 target=0x14040bb40 rva=0x40bb40 result=ok original=0x1018c0540
2026-06-27 05:03:28.861 +16861ms GameState Running OnEnter app=0x31fb80
2026-06-27 05:03:28.887 +16887ms GameState Running OnUpdate app=0x31fb80 count=0
2026-06-27 05:03:28.894 +16894ms GameState Running OnUpdate app=0x31fb80 count=1
2026-06-27 05:03:28.901 +16901ms GameState Running OnUpdate app=0x31fb80 count=2
2026-06-27 05:03:28.907 +16907ms GameState Running OnUpdate app=0x31fb80 count=3
2026-06-27 05:03:28.914 +16914ms GameState Running OnUpdate app=0x31fb80 count=4
2026-06-27 05:04:00.752 +48752ms hook GPSQuerySubmit 0x70a42c call=3 manager=0x1a2e157d0 query=0x79a3fa60 query_f00=0/0x0 query_f04=65535/0xffff query_f08=2052/0x804 query_f0c=0/0x0 query_fcc=5/0x5 query_pos58=(-1569.68,1213.26,17.3737,0) resolveTraceWindowMs=1200 ret_rva=0x8d20d4
2026-06-27 05:04:00.752 +48752ms hook GPSQuerySubmit result call=3 queryId=3 manager_nextIdD4=4/0x4
2026-06-27 05:04:00.752 +48752ms hook GPSQuerySubmit 0x70a42c call=4 manager=0x1a2e157d0 query=0x79a3fa60 query_f00=0/0x0 query_f04=65535/0xffff query_f08=2052/0x804 query_f0c=0/0x0 query_fcc=5/0x5 query_pos58=(-1569.68,1213.26,17.3737,0) resolveTraceWindowMs=1200 ret_rva=0x8d20d4
2026-06-27 05:04:00.752 +48752ms hook GPSQuerySubmit result call=4 queryId=4 manager_nextIdD4=5/0x5
@@ -0,0 +1,145 @@
Registered crash info file...
InternalVersion: 3.0.5294808 P4CL: 9778208 Stream: //R6.Root/R6.Release
!!!CRASHED!!!
Error Reason: Unhandled exception
Expression: EXCEPTION_ACCESS_VIOLATION (0xC0000005)
Message: The thread attempted to read inaccessible data at 0x8C.
File: <Unknown>
Line: 0
"uptimeSeconds":48
"stopThreadID":448
"exceptionCode":0xC0000005
"processID":368
"Engine/CommitFailedErrorCode@1#TID=0":"0"
"ResourceLoaderThrottler/PreviousSpeed@908858#TID=0":"Flood"
"ResourceLoaderThrottler/CurrentSpeed@908859#TID=0":"Stream"
"ResourceLoaderThrottler/NumThrottleTokensAcquired@916696#TID=0":"1"
"ResourceLoaderThrottler/NumThrottleTokensSoftLimit@916695#TID=0":"2"
"Gpu/RayTracing/Enabled@256#TID=0":"false"
"Configuration/Name@2#TID=0":"FINAL"
"Configuration/Name2@3#TID=0":"FINAL"
"Game/Patch@7#TID=0":"2.31"
"Game/TransformAnimator/Components@916123#TID=0":"315"
"Game/TransformAnimator/RunningComponents@916122#TID=0":"271"
"Game/TransformAnimator/AddedRunningComponents@916120#TID=0":"0"
"Game/TransformAnimator/RemovedRunningAnimations@916121#TID=0":"0"
"Game/TransformAnimator/RotatingMeshNodes@916126#TID=0":"120"
"Game/TransformAnimator/AddedRotatingMeshNodes@916124#TID=0":"0"
"Game/TransformAnimator/RemovedRotatingMeshNodes@916125#TID=0":"0"
"Game/SessionDesc/WorldName@276998#TID=0":"03_night_city"
"Game/SessionDesc/IsValid@276999#TID=0":"true"
"Game/SessionDesc/IsLoadingSavedSession@277000#TID=0":"true"
"Game/SessionDesc/IsJoiningRemoteSession@277001#TID=0":"false"
"Game/SessionDesc/HostName@277002#TID=0":""
"Game/SessionDesc/GameRulesConfig@277003#TID=0":"None"
"Game/SessionDesc/SpawnLocalPlayer@277004#TID=0":"false"
"Game/SessionDesc/SpawnPoint@277005#TID=0":"Position=[0, 0, 0, 0] Rotation=[Roll:0, Pitch:0, Yaw:0]"
"Game/SessionDesc/SpawnRecordId@277006#TID=0":"<0x0000001C15982ADF>"
"Game/SessionDesc/Gender@277007#TID=0":"None"
"GameSessionDesc/UseSpecifiedStartPoint@277008#TID=0":"false"
"GameSessionDesc/IsSpectator@277009#TID=0":"false"
"GameSessionDesc/SpawnTags@277010#TID=0":""
"Game/SessionDesc/IsStreamingTestRequested@277011#TID=0":"false"
"Game/SessionDesc/IsBenchmark@277012#TID=0":"false"
"Game/GameEffects/RunningEffects@916520#TID=0":"1"
"Game/GameEffects/AddedEffects@916518#TID=0":"1"
"Game/GameEffects/TerminatingEffects@916521#TID=0":"0"
"Game/Interactions/DialogVisualizerTotalCount@916461#TID=0":"0"
"Game/Interactions/DeviceVisualizerTotalCount@916462#TID=0":"0"
"Game/Interactions/LootVisualizerTotalCount@916463#TID=0":"0"
"Game/Interactions/HotSpots/CurrentCount@916468#TID=0":"405"
"Game/Interactions/HotSpots/MaxCount@916469#TID=0":"528"
"Game/Interactions/HotSpots/LastFrameAddedCount@916464#TID=0":"0"
"Game/Interactions/HotSpots/MaxAddedCount@916465#TID=0":"566"
"Game/Interactions/HotSpots/LastFrameRemovedCount@916466#TID=0":"0"
"Game/Interactions/HotSpots/MaxRemovedCount@916467#TID=0":"128"
"Game/Interactions/UniqueLayersCurrentCount@916470#TID=0":"3310"
"Game/Interactions/UniqueLayersMaxCount@916471#TID=0":"4539"
"Game/Interactions/EventsLastFrameCount@916459#TID=0":"0"
"Game/Interactions/EventsMaxCount@916460#TID=0":"25"
"Game/PlayerStateMachine/numberOfStateMachines@827197#TID=0":"10"
"Game/TransactionSystem/NumberOfItemsInPlayersInventory@422220#TID=0":"620"
"Game/TransactionSystem/NumberOfItemsInPlayersStash@621662#TID=0":"813"
"Game/StatsSystem/NumberOfStatsBundles@915530#TID=0":"2353"
"Game/StatPoolsSystem/MaxNumberOfStatPoolListenersOnObject@822465#TID=0":"10"
"Game/StatsSystem/MaxNumberOfStatModifiersOfStatTypeOnObject@823059#TID=0":"99"
"Game/StatsSystem/NameOfMaxNumberOfStatModifiersOnObjectStatType@823060#TID=0":"StaminaRatio"
"Game/mounting/MountingFacility/numberOfGameplayMounts@826118#TID=0":"1"
"Game/mounting/MountingFacility/numberOfMountEvents@825990#TID=0":"0"
"Game/ItemFactorySystemPool/numberOfPrePooledOwners@422304#TID=0":"1"
"Game/ItemFactorySystemPool/numberOfPrePooledItems@276804#TID=0":"0"
"Game/ItemFactorySystemPool/numberOfPostPooledItems@276802#TID=0":"0"
"Game/ItemFactorySystem/numberOfOwnerDatas@422305#TID=0":"1"
"Game/ItemFactorySystem/numberOfItemsWithNoOwner@276801#TID=0":"0"
"Game/ItemFactorySystem/numberOfCachedOwners@422303#TID=0":"1"
"Game/ItemFactorySystem/numberOfCachedItems@696725#TID=0":"7"
"Game/ItemFactorySystem/numberOfUncachedOwners@913876#TID=0":"106"
"Game/ItemFactorySystem/numberOfUncachedItems@914747#TID=0":"128"
"Game/StatPoolsSystem/MaxNumberOfUnassignedStatPoolListeners@825246#TID=0":"5172"
"Game/StatsSystem/MaxNumberOfStatsListenersOnObject@824938#TID=0":"207"
"Game/StatsSystem/MaxNumberOfStatsCallbackData@817039#TID=0":"267"
"Game/mounting/LowLevelSystem/numberOfAssociations@825781#TID=0":"14"
"Game/ItemFactorySystemLowLevel/numberOfSpawnRequests@914748#TID=0":"0"
"Game/ItemFactorySystemLowLevel/numberOfReleaseRequests@825658#TID=0":"0"
"Game/ItemFactorySystemLowLevel/numberOfCancelRequests@276836#TID=0":"0"
"Game/ItemFactorySystemLowLevel/numberOfAppearanceChangeRequests@840034#TID=0":"0"
"Game/Population/AlwaysSpawned@916330#TID=0":"18"
"Game/Population/Attached@916331#TID=0":"220"
"Game/Population/Registered@916332#TID=0":"4459"
"Game/Population/InBucket@916333#TID=0":"997"
"Game/Population/VisQueries@916334#TID=0":"1994"
"Rendering/AsyncCompute@916665#TID=0":"true"
"Game/LoadingStage@908857#TID=0":"Finished"
"Session/GameDefinitionName@276791#TID=0":""
"Engine/OOM@4#TID=0":"false"
"GlobalMode/IsClosing@5#TID=0":"false"
"GlobalMode/IsGame@17#TID=0":"true"
"Jobs/InitParam/MaxLatentJobs@8#TID=0":"131072"
"Jobs/InitParam/MaxCriticalPathJobs@9#TID=0":"65536"
"Jobs/InitParam/MaxImmediateJobs@10#TID=0":"2048"
"Jobs/InitParam/WorkerThreadStackSizeKB@11#TID=0":"1024"
"Jobs/InitParam/MaxThreads@12#TID=0":"27"
"Jobs/InitParam/AllJobsCriticalPath@14#TID=0":"false"
"Jobs/InitParam/UseJobDebugger@13#TID=0":"false"
"Jobs/Dispatcher/NumDispatcherThreads@15#TID=0":"11"
"Jobs/Dispatcher/MaxHWConcurrency@16#TID=0":"12"
"Gpu/Device/Name@251#TID=0":"AMD Radeon RX 7900 XT (RADV NAVI31)"
"Gpu/Device/Vendor@252#TID=0":"AMD"
"Gpu/Device/DriverVersion@253#TID=0":"Radeon software 99.10.2"
"Gpu/Device/TotalMemoryMB@250#TID=0":"20464"
"Gpu/Device/UsedMemoryMB@916697#TID=0":"5608"
"Engine/SafeInit@47935#TID=0":"Finished"
"Engine/IsHeadless@228#TID=0":"false"
"Engine/VersionWatermark@227#TID=0":"3.0.5294808 P4CL: 9778208 Stream: //R6.Root/R6.Release Aug 27 2025 17:30:27 (FINAL) Total memory: 11711623168 bytes [EP1]"
"Engine/InitParams/InteropStartingPort@213#TID=0":"0"
"Engine/InitParams/RenderPreset@214#TID=0":"1"
"Engine/InitParams/QualityLevelName@215#TID=0":"None"
"Engine/InitParams/TextureQualityLevelName@216#TID=0":"High"
"Engine/InitParams/WatchdogTimeoutSecondsOverride@217#TID=0":"0"
"Engine/InitParams/ScriptsSilentCompilation@218#TID=0":"false"
"Engine/InitParams/ScriptsSilentValidation@219#TID=0":"false"
"Engine/InitParams/TweakDBSilentValidation@220#TID=0":"false"
"Engine/InitParams/PvdDumpToFile@221#TID=0":"false"
"Engine/InitParams/ProfilerEnabled@222#TID=0":"false"
"Engine/InitParams/ShowVersionWatermark@223#TID=0":"true"
"Engine/InitParams/WindowCaption@224#TID=0":""
"Engine/PC/SystemName@225#TID=0":"Windows 10 Pro"
"Engine/PC/SystemVersion@226#TID=0":"10.0.19045"
"Engine/Scripts/PrimaryBlobModifiedDate@235#TID=0":"134020895866810433"
"Engine/Scripts/FallbackBlobModifiedDate@236#TID=0":"0"
"Engine/Scripts/BlobPathUsed@237#TID=0":"Loaded"
"Engine/Scripts/ShouldCompileScripts@233#TID=0":"true"
"Engine/Scripts/CompileScriptsSuccess@234#TID=0":"false"
"Engine/Scripts/Loaded@238#TID=0":"true"
"Telemetry/PlaythroughID@393001#TID=0":"69e857a3ebf988d1"
"Telemetry/VisitID@49233#TID=0":"16cb284d-6d0f-4c08-966d-6e23557e7428"
"Streaming/LastObserverPosition@916499#TID=0":"[-1569, 1213, 18]"
"Streaming/IsLastObserverPositionValid@916500#TID=0":"true"
"Streaming/IsTeleporting@916501#TID=0":"false"
"Streaming/DebugLastTeleportDistance@916502#TID=0":"0.000000"
"Streaming/MountState@392989#TID=0":"Mounted"
"Streaming/Observer/0/LastObserverPosition@916496#TID=0":"[-1569, 1213, 18]"
"Streaming/Observer/0/LastObserverVelocityOffsetPosition@916497#TID=0":"[-1569, 1213, 18]"
"Streaming/Observer/0/UseStreamingOcclusion@916498#TID=0":"false"
"##CrashDump##/DumpCrashDataSeconds":"0"
@@ -0,0 +1,4 @@
Error reason: Unhandled exception
Expression: EXCEPTION_ACCESS_VIOLATION (0xC0000005)
Message: The thread attempted to read inaccessible data at 0x8C.
File: <Unknown>(0)
+156 -1
View File
@@ -99,9 +99,10 @@ constexpr uint32_t kGameStateRunning = 2;
constexpr bool kEnableVerboseJournalListenerHooks = false;
constexpr bool kEnableVerboseRouteObserverHooks = false;
constexpr bool kEnableVerboseMappinRouteState = false;
constexpr bool kEnableGpsMultiplierHooks = true;
constexpr bool kEnableGpsMultiplierHooks = false;
constexpr bool kEnableGpsAuxMultiplierHook = false;
constexpr bool kEnableGpsMultiplierSignatureTrace = false;
constexpr bool kEnableGpsNodeMultiplierInlinePatch = true;
constexpr bool kEnableGpsCostTablePatch = false;
constexpr bool kEnableGpsProviderClassPatch = false;
constexpr bool kEnableGpsProviderClassPatchTrace = false;
@@ -195,6 +196,7 @@ constexpr uintptr_t kGpsBaseCostProviderVtableRva = 0x2AE6120;
constexpr uintptr_t kGpsFilteredCostProviderVtableRva = 0x2AE60F8;
constexpr uintptr_t kGpsAuxMultiplierRva = 0x040BB00;
constexpr uintptr_t kGpsNodeMultiplierRva = 0x040BB40;
constexpr uintptr_t kGpsNodeMultiplierRoadTailRva = 0x040BB98;
constexpr uintptr_t kGpsCostMultiplierTableRva = 0x3154D28;
constexpr uint64_t kGpsResolveTraceWindowMs = 1200;
constexpr uint32_t kGpsResolveSelectedRetLogLimit = 24;
@@ -214,6 +216,7 @@ constexpr uintptr_t kMinimumReadableAddress = 0x10000;
constexpr float kGpsSolverHighwayMultiplier = 0.35f;
constexpr float kGpsSolverGpsOnlyMultiplier = 1.10f;
constexpr float kGpsSolverPavementMultiplier = 1.25f;
constexpr size_t kGpsNodeMultiplierRoadTailPatchSize = 14;
constexpr std::array<float, 8> kGpsSolverNonRoadMultipliers = {
10.0f, 6.0f, 2.0f, 0.0f, 4.0f, 2.5f, 1.5f, 0.0f,
};
@@ -322,6 +325,9 @@ std::array<uint32_t, 4> gGpsResolveHandleRetLogCounts{};
uint64_t gGpsResolveTraceWindowUntilMs = 0;
uint32_t gGpsResolveTraceWindowSerial = 0;
std::array<uint32_t, 64> gGpsEdgeCostClassLogCounts{};
std::array<uint8_t, kGpsNodeMultiplierRoadTailPatchSize> gGpsNodeMultiplierRoadTailOriginal{};
void* gGpsNodeMultiplierRoadTailCave = nullptr;
bool gGpsNodeMultiplierRoadTailPatchApplied = false;
std::mutex gGpsCostLogMutex;
std::mutex gGpsAsyncLogMutex;
std::mutex gGpsCostTablePatchMutex;
@@ -568,6 +574,8 @@ std::atomic<uint64_t> gGpsSpatialWeightsNextReloadCheckMs = 0;
std::filesystem::file_time_type gGpsSpatialWeightsLastWriteTime{};
bool gGpsSpatialWeightsFileKnown = false;
uintptr_t GetImageBase();
std::filesystem::path GetPluginDirectory()
{
wchar_t modulePath[MAX_PATH]{};
@@ -634,6 +642,149 @@ void LogRed4ext(std::string_view aMessage)
}
}
void WriteInt32(uint8_t* aTarget, int32_t aValue)
{
std::memcpy(aTarget, &aValue, sizeof(aValue));
}
void WriteFloat(uint8_t* aTarget, float aValue)
{
std::memcpy(aTarget, &aValue, sizeof(aValue));
}
bool InstallGpsNodeMultiplierInlinePatch()
{
if (!kEnableGpsNodeMultiplierInlinePatch || gGpsNodeMultiplierRoadTailPatchApplied)
{
return true;
}
constexpr size_t kCaveSize = 64;
auto* cave = static_cast<uint8_t*>(
VirtualAlloc(nullptr, kCaveSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE));
if (!cave)
{
LogRed4ext("gps-node-multiplier-inline-patch skipped reason=alloc-failed");
return false;
}
// Replaces the road-tail at 0x40bb98. This preserves the vanilla node93
// multiplier, then applies the highway multiplier before returning.
size_t offset = 0;
const auto emit = [&](std::initializer_list<uint8_t> aBytes) {
for (auto byte : aBytes)
{
cave[offset++] = byte;
}
};
emit({0x80, 0xBA, 0x93, 0x00, 0x00, 0x00, 0x01}); // cmp byte ptr [rdx+0x93], 1
emit({0x75, 0x08}); // jne +8
const auto mulNode93Offset = offset;
emit({0xF3, 0x0F, 0x59, 0x05, 0x00, 0x00, 0x00, 0x00}); // mulss xmm0, [rip+disp32]
emit({0x66, 0xF7, 0x82, 0x88, 0x00, 0x00, 0x00, 0x00, 0x40}); // test word ptr [rdx+0x88], 0x4000
emit({0x74, 0x08}); // je +8
const auto mulHighwayOffset = offset;
emit({0xF3, 0x0F, 0x59, 0x05, 0x00, 0x00, 0x00, 0x00}); // mulss xmm0, [rip+disp32]
emit({0xC3}); // ret
while (offset < 40)
{
cave[offset++] = 0x90;
}
const auto node93ConstantOffset = offset;
WriteFloat(cave + offset, 1.1f);
offset += sizeof(float);
const auto highwayConstantOffset = offset;
WriteFloat(cave + offset, kGpsSolverHighwayMultiplier);
offset += sizeof(float);
const auto caveAddress = reinterpret_cast<uintptr_t>(cave);
WriteInt32(cave + mulNode93Offset + 4,
static_cast<int32_t>((caveAddress + node93ConstantOffset) - (caveAddress + mulNode93Offset + 8)));
WriteInt32(cave + mulHighwayOffset + 4,
static_cast<int32_t>((caveAddress + highwayConstantOffset) - (caveAddress + mulHighwayOffset + 8)));
auto* target = reinterpret_cast<uint8_t*>(GetImageBase() + kGpsNodeMultiplierRoadTailRva);
std::memcpy(gGpsNodeMultiplierRoadTailOriginal.data(), target, gGpsNodeMultiplierRoadTailOriginal.size());
uint8_t patch[kGpsNodeMultiplierRoadTailPatchSize]{};
size_t patchOffset = 0;
patch[patchOffset++] = 0xFF; // jmp qword ptr [rip+0]
patch[patchOffset++] = 0x25;
patch[patchOffset++] = 0x00;
patch[patchOffset++] = 0x00;
patch[patchOffset++] = 0x00;
patch[patchOffset++] = 0x00;
const auto cavePointer = reinterpret_cast<uintptr_t>(cave);
std::memcpy(patch + patchOffset, &cavePointer, sizeof(cavePointer));
patchOffset += sizeof(cavePointer);
while (patchOffset < sizeof(patch))
{
patch[patchOffset++] = 0x90;
}
DWORD oldProtect = 0;
if (!VirtualProtect(target, sizeof(patch), PAGE_EXECUTE_READWRITE, &oldProtect))
{
VirtualFree(cave, 0, MEM_RELEASE);
LogRed4ext("gps-node-multiplier-inline-patch skipped reason=protect-failed");
return false;
}
std::memcpy(target, patch, sizeof(patch));
FlushInstructionCache(GetCurrentProcess(), target, sizeof(patch));
DWORD ignoredProtect = 0;
VirtualProtect(target, sizeof(patch), oldProtect, &ignoredProtect);
gGpsNodeMultiplierRoadTailCave = cave;
gGpsNodeMultiplierRoadTailPatchApplied = true;
std::ostringstream line;
line << "gps-node-multiplier-inline-patch applied target_rva=0x" << std::hex
<< kGpsNodeMultiplierRoadTailRva << " cave=" << static_cast<void*>(cave)
<< " highwayMultiplier=" << std::dec << kGpsSolverHighwayMultiplier;
LogRed4ext(line.str());
return true;
}
void RemoveGpsNodeMultiplierInlinePatch()
{
if (!gGpsNodeMultiplierRoadTailPatchApplied)
{
return;
}
auto* target = reinterpret_cast<uint8_t*>(GetImageBase() + kGpsNodeMultiplierRoadTailRva);
DWORD oldProtect = 0;
if (VirtualProtect(target, gGpsNodeMultiplierRoadTailOriginal.size(), PAGE_EXECUTE_READWRITE, &oldProtect))
{
std::memcpy(target, gGpsNodeMultiplierRoadTailOriginal.data(), gGpsNodeMultiplierRoadTailOriginal.size());
FlushInstructionCache(GetCurrentProcess(), target, gGpsNodeMultiplierRoadTailOriginal.size());
DWORD ignoredProtect = 0;
VirtualProtect(target, gGpsNodeMultiplierRoadTailOriginal.size(), oldProtect, &ignoredProtect);
LogRed4ext("gps-node-multiplier-inline-patch restored");
}
else
{
LogRed4ext("gps-node-multiplier-inline-patch restore-failed reason=protect-failed");
}
if (gGpsNodeMultiplierRoadTailCave)
{
VirtualFree(gGpsNodeMultiplierRoadTailCave, 0, MEM_RELEASE);
gGpsNodeMultiplierRoadTailCave = nullptr;
}
gGpsNodeMultiplierRoadTailPatchApplied = false;
}
bool IsUsableSpatialMultiplier(float aValue)
{
return std::isfinite(aValue) && aValue > 0.0f && aValue < 20.0f;
@@ -5269,6 +5420,8 @@ RED4EXT_C_EXPORT bool RED4EXT_CALL Main(RED4ext::v1::PluginHandle aHandle, RED4e
LogGpsSpatialMultipliers("spatial weights defaults", values);
}
InstallGpsNodeMultiplierInlinePatch();
if (kEnableGpsQueryLifecycleHooks)
{
AttachProbeHook("RunGPSQuery body 0x29bd128", kRunGpsQueryBodyRva,
@@ -5497,6 +5650,8 @@ RED4EXT_C_EXPORT bool RED4EXT_CALL Main(RED4ext::v1::PluginHandle aHandle, RED4e
if (aReason == RED4ext::v1::EMainReason::Unload)
{
RemoveGpsNodeMultiplierInlinePatch();
if (kEnableGpsQueryLifecycleHooks)
{
DetachProbeHook("RunGPSQuery body 0x29bd128", kRunGpsQueryBodyRva);