From 9dd0b31e4da64b713590d3fd3931f1fd020a9187 Mon Sep 17 00:00:00 2001 From: Jacob Babor Date: Fri, 26 Jun 2026 23:10:22 -0500 Subject: [PATCH] Patch GPS node multiplier inline --- ...GPS_fullsolver_notrampoline_crash_0503.log | 15 ++ ...lsolver_notrampoline_crash_report_0503.txt | 145 ++++++++++++++++ ...ver_notrampoline_crash_stacktrace_0503.txt | 4 + red4ext/EdgeWeightGPS/src/Main.cpp | 157 +++++++++++++++++- 4 files changed, 320 insertions(+), 1 deletion(-) create mode 100644 logs/EdgeWeightGPS_fullsolver_notrampoline_crash_0503.log create mode 100644 logs/EdgeWeightGPS_fullsolver_notrampoline_crash_report_0503.txt create mode 100644 logs/EdgeWeightGPS_fullsolver_notrampoline_crash_stacktrace_0503.txt diff --git a/logs/EdgeWeightGPS_fullsolver_notrampoline_crash_0503.log b/logs/EdgeWeightGPS_fullsolver_notrampoline_crash_0503.log new file mode 100644 index 0000000..baf3789 --- /dev/null +++ b/logs/EdgeWeightGPS_fullsolver_notrampoline_crash_0503.log @@ -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 diff --git a/logs/EdgeWeightGPS_fullsolver_notrampoline_crash_report_0503.txt b/logs/EdgeWeightGPS_fullsolver_notrampoline_crash_report_0503.txt new file mode 100644 index 0000000..7e3c876 --- /dev/null +++ b/logs/EdgeWeightGPS_fullsolver_notrampoline_crash_report_0503.txt @@ -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: +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" diff --git a/logs/EdgeWeightGPS_fullsolver_notrampoline_crash_stacktrace_0503.txt b/logs/EdgeWeightGPS_fullsolver_notrampoline_crash_stacktrace_0503.txt new file mode 100644 index 0000000..5a4521f --- /dev/null +++ b/logs/EdgeWeightGPS_fullsolver_notrampoline_crash_stacktrace_0503.txt @@ -0,0 +1,4 @@ +Error reason: Unhandled exception +Expression: EXCEPTION_ACCESS_VIOLATION (0xC0000005) +Message: The thread attempted to read inaccessible data at 0x8C. +File: (0) diff --git a/red4ext/EdgeWeightGPS/src/Main.cpp b/red4ext/EdgeWeightGPS/src/Main.cpp index 139f231..5d991be 100644 --- a/red4ext/EdgeWeightGPS/src/Main.cpp +++ b/red4ext/EdgeWeightGPS/src/Main.cpp @@ -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 kGpsSolverNonRoadMultipliers = { 10.0f, 6.0f, 2.0f, 0.0f, 4.0f, 2.5f, 1.5f, 0.0f, }; @@ -322,6 +325,9 @@ std::array gGpsResolveHandleRetLogCounts{}; uint64_t gGpsResolveTraceWindowUntilMs = 0; uint32_t gGpsResolveTraceWindowSerial = 0; std::array gGpsEdgeCostClassLogCounts{}; +std::array gGpsNodeMultiplierRoadTailOriginal{}; +void* gGpsNodeMultiplierRoadTailCave = nullptr; +bool gGpsNodeMultiplierRoadTailPatchApplied = false; std::mutex gGpsCostLogMutex; std::mutex gGpsAsyncLogMutex; std::mutex gGpsCostTablePatchMutex; @@ -568,6 +574,8 @@ std::atomic 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( + 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 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(cave); + WriteInt32(cave + mulNode93Offset + 4, + static_cast((caveAddress + node93ConstantOffset) - (caveAddress + mulNode93Offset + 8))); + WriteInt32(cave + mulHighwayOffset + 4, + static_cast((caveAddress + highwayConstantOffset) - (caveAddress + mulHighwayOffset + 8))); + + auto* target = reinterpret_cast(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(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(cave) + << " highwayMultiplier=" << std::dec << kGpsSolverHighwayMultiplier; + LogRed4ext(line.str()); + return true; +} + +void RemoveGpsNodeMultiplierInlinePatch() +{ + if (!gGpsNodeMultiplierRoadTailPatchApplied) + { + return; + } + + auto* target = reinterpret_cast(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);