#include <RED4ext/Api/ApiVersion.hpp>
#include <RED4ext/Api/v1/EMainReason.hpp>
#include <RED4ext/Api/v1/PluginHandle.hpp>
#include <RED4ext/Api/v1/PluginInfo.hpp>
#include <RED4ext/Api/v1/Runtime.hpp>
#include <RED4ext/Api/v1/Version.hpp>
#include <RED4ext/Common.hpp>

#include <windows.h>

#include <algorithm>
#include <array>
#include <atomic>
#include <chrono>
#include <cmath>
#include <cstring>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <initializer_list>
#include <mutex>
#include <sstream>
#include <string>
#include <string_view>

#include "GeneratedSpatialRoadGrid.hpp"

namespace RED4ext::v1
{
struct Logger
{
    void (*Trace)(PluginHandle aHandle, const char* aMessage);
    void (*TraceF)(PluginHandle aHandle, const char* aFormat, ...);
    void (*TraceW)(PluginHandle aHandle, const wchar_t* aMessage);
    void (*TraceWF)(PluginHandle aHandle, const wchar_t* aFormat, ...);
    void (*Debug)(PluginHandle aHandle, const char* aMessage);
    void (*DebugF)(PluginHandle aHandle, const char* aFormat, ...);
    void (*DebugW)(PluginHandle aHandle, const wchar_t* aMessage);
    void (*DebugWF)(PluginHandle aHandle, const wchar_t* aFormat, ...);
    void (*Info)(PluginHandle aHandle, const char* aMessage);
    void (*InfoF)(PluginHandle aHandle, const char* aFormat, ...);
    void (*InfoW)(PluginHandle aHandle, const wchar_t* aMessage);
    void (*InfoWF)(PluginHandle aHandle, const wchar_t* aFormat, ...);
    void (*Warn)(PluginHandle aHandle, const char* aMessage);
    void (*WarnF)(PluginHandle aHandle, const char* aFormat, ...);
    void (*WarnW)(PluginHandle aHandle, const wchar_t* aMessage);
    void (*WarnWF)(PluginHandle aHandle, const wchar_t* aFormat, ...);
    void (*Error)(PluginHandle aHandle, const char* aMessage);
    void (*ErrorF)(PluginHandle aHandle, const char* aFormat, ...);
    void (*ErrorW)(PluginHandle aHandle, const wchar_t* aMessage);
    void (*ErrorWF)(PluginHandle aHandle, const wchar_t* aFormat, ...);
    void (*Critical)(PluginHandle aHandle, const char* aMessage);
    void (*CriticalF)(PluginHandle aHandle, const char* aFormat, ...);
    void (*CriticalW)(PluginHandle aHandle, const wchar_t* aMessage);
    void (*CriticalWF)(PluginHandle aHandle, const wchar_t* aFormat, ...);
};

struct GameState
{
    bool (*OnEnter)(void* aApp);
    bool (*OnUpdate)(void* aApp);
    bool (*OnExit)(void* aApp);
};

struct GameStates
{
    bool (*Add)(PluginHandle aHandle, uint32_t aType, GameState* aState);
};

struct Hooking
{
    bool (*Attach)(PluginHandle aHandle, void* aTarget, void* aDetour, void** aOriginal);
    bool (*Detach)(PluginHandle aHandle, void* aTarget);
};

struct Scripts
{
    bool (*Add)(PluginHandle aHandle, const wchar_t* aPath);
    bool (*RegisterNeverRefType)(const char* aType);
    bool (*RegisterMixedRefType)(const char* aType);
};

struct Sdk;

struct Sdk
{
    SemVer* runtime;
    Logger* logger;
    Hooking* hooking;
    GameStates* gameStates;
    Scripts* scripts;
};
}

namespace
{
constexpr uint32_t kGameStateRunning = 2;
constexpr bool kEnableVerboseJournalListenerHooks = false;
constexpr bool kEnableVerboseRouteObserverHooks = false;
constexpr bool kEnableVerboseMappinRouteState = false;
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;
constexpr bool kEnableGpsTraceHooks = true;
constexpr bool kEnableGpsQueryLifecycleHooks = false;
constexpr bool kEnableGpsQueryResultTraceHooks = true;
constexpr bool kEnableGpsAsyncTrafficProbe = false;
constexpr bool kEnableGpsTrafficEdgeWeightPatch = false;
constexpr bool kEnableGpsSpatialEdgeWeightPatch = false;
constexpr bool kEnableGpsSpatialEdgeWeightPatchTrace = false;
constexpr bool kEnableGpsSpatialEdgeWeightPatchStats = true;
constexpr bool kEnableGpsRouteJobLifecycleHooks = false;
constexpr bool kEnableGpsRouteJobStartTrace = false;
constexpr bool kEnableGpsLocalSearchHooks = false;
constexpr bool kEnableRouteUiTraceHooks = false;
constexpr bool kEnableGpsLocalSearchTraceHooks = kEnableGpsTraceHooks && kEnableGpsLocalSearchHooks;
constexpr bool kEnableGpsRouteProducerHook = kEnableGpsLocalSearchTraceHooks || kEnableGpsSpatialEdgeWeightPatch;
constexpr bool kEnableGpsEdgeCostHook = kEnableGpsLocalSearchTraceHooks || kEnableGpsSpatialEdgeWeightPatch;
constexpr uintptr_t kGpsSystemTickRva = 0x0E6B62C;
constexpr uintptr_t kJournalTrackEntryImplRva = 0x05944FC;
constexpr uintptr_t kJournalListenerArrayOffset = 0x210;
constexpr uintptr_t kJournalListenerCountOffset = 0x21C;
constexpr uintptr_t kMappinSystemResolverRva = 0x02BB840;
constexpr uintptr_t kFrameMappinPathWrapperRva = 0x27C4314;
constexpr uintptr_t kFrameMappinPathCoreRva = 0x27BC1EC;
constexpr uintptr_t kSetSelectedMappinWrapperRva = 0x27C4A38;
constexpr uintptr_t kSetSelectedMappinByIdWrapperRva = 0x27C4944;
constexpr uintptr_t kSetSelectedMappinByPositionWrapperRva = 0x27C49C4;
constexpr uintptr_t kSetSelectedMappinCoreRva = 0x27C1684;
constexpr uintptr_t kTrackCustomPositionMappinWrapperRva = 0x27C4AAC;
constexpr uintptr_t kTrackCustomPositionMappinCoreRva = 0x27C2318;
constexpr uintptr_t kUntrackCustomPositionMappinWrapperRva = 0x27C4BA0;
constexpr uintptr_t kTrackMappinWrapperRva = 0x27C4AB4;
constexpr uintptr_t kTrackMappinCoreRva = 0x27C11D0;
constexpr uintptr_t kSetMappinTrackingAlternativeWrapperRva = 0x1246794;
constexpr uintptr_t kMappinUpdateCustomPositionSlotOffset = 0x1F0;
constexpr uintptr_t kMappinSetTrackedSlotOffset = 0x220;
constexpr uintptr_t kMappinClearTrackedSlotOffset = 0x228;
constexpr uintptr_t kMappinFramePathSlotOffset = 0x280;
constexpr uintptr_t kMappinPositionQuerySlotOffset = 0x2E0;
constexpr uintptr_t kMappinCreateCustomPositionSlotOffset = 0x2F0;
constexpr uintptr_t kMappinReleaseQuerySlotOffset = 0x368;
constexpr uintptr_t kMappinActiveRouteSlotOffset = 0x3A8;
constexpr uintptr_t kJournalListenerObjectiveVt28Rva = 0x0EA89A8;
constexpr uintptr_t kJournalListenerObjectiveVt30Rva = 0x0EA8958;
constexpr uintptr_t kJournalListenerRouteVt28Rva = 0x0E63F80;
constexpr uintptr_t kJournalListenerRouteVt30Rva = 0x0E63E6C;
constexpr uintptr_t kJournalListenerRouteAltVt28Rva = 0x0E63F00;
constexpr uintptr_t kJournalListenerRouteAltVt30Rva = 0x08D136C;
constexpr uintptr_t kJournalListenerUiVt28Rva = 0x0431A34;
constexpr uintptr_t kJournalListenerUiVt30Rva = 0x055A4E4;
constexpr uintptr_t kJournalListenerTrackerVt28Rva = 0x14DE238;
constexpr uintptr_t kJournalRouteBridgeRva = 0x0598250;
constexpr uintptr_t kMappinRouteEventEnqueueRva = 0x13763D8;
constexpr uintptr_t kMappinRouteEventHandlerRva = 0x0AA62D0;
constexpr uintptr_t kMappinRouteActivateRva = 0x0AA6330;
constexpr uintptr_t kMappinRouteDeactivateRva = 0x27ABD7C;
constexpr uintptr_t kRouteBuildCandidateRva = 0x05625A4;
constexpr uintptr_t kRouteObserver0ActivateRva = 0x0AA6610;
constexpr uintptr_t kRouteObserver1ActivateRva = 0x0AA6628;
constexpr uintptr_t kRouteObserver23ActivateRva = 0x0AA63E0;
constexpr uintptr_t kRouteObserver0DeactivateRva = 0x27B10C0;
constexpr uintptr_t kRouteObserver1DeactivateRva = 0x295D4A0;
constexpr uintptr_t kRouteObserver23DeactivateRva = 0x286A85C;
constexpr uintptr_t kObserver1SystemTypeGlobalRva = 0x342F6A8;
constexpr uintptr_t kObserver1ServiceLookupRva = 0x0287C44;
constexpr uintptr_t kObserver1ServiceRouteLookupSlotOffset = 0x220;
constexpr uintptr_t kRunGpsQueryBodyRva = 0x29BD128;
constexpr uintptr_t kUpdateGpsQueryBodyRva = 0x29BD254;
constexpr uintptr_t kGpsQuerySubmitRva = 0x070A42C;
constexpr uintptr_t kGpsQueryDispatchRva = 0x070A570;
constexpr uintptr_t kGpsQueryResultFetchRva = 0x07094B8;
constexpr uintptr_t kGpsQueryStatusRva = 0x0AA5704;
constexpr uintptr_t kGpsMapRouteSubmitReturnRva = 0x08D20D4;
constexpr uintptr_t kGpsDispatchAdvanceRva = 0x08D17D8;
constexpr uintptr_t kGpsTrafficResultTakeRva = 0x0709D5C;
constexpr uintptr_t kGpsRoutePostprocessRva = 0x044830C;
constexpr uintptr_t kGpsTrafficEdgeScoreRva = 0x08D46CC;
constexpr uintptr_t kGpsAsyncWorkerTickRva = 0x0126B28;
constexpr uintptr_t kGpsRouteJobStartRva = 0x0818928;
constexpr uintptr_t kGpsRouteJobPollRva = 0x0883CD8;
constexpr uintptr_t kGpsRouteFinishRva = 0x11F5F60;
constexpr uintptr_t kGpsRouteJobBuildRva = 0x0818BA8;
constexpr uintptr_t kGpsRouteProducerRva = 0x044CC7C;
constexpr uintptr_t kGpsSearchRva = 0x044F054;
constexpr uintptr_t kGpsResolveHandleRva = 0x044E1A8;
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;
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;
constexpr uint32_t kGpsResolveStartupLogLimit = 8;
constexpr uint32_t kGpsResolveVerboseDumpLimit = 4;
constexpr std::array<float, 7> kGpsPatchedCostMultipliers = {
    12.0f, 7.0f, 2.5f, 0.0f, 3.0f, 2.0f, 1.25f,
};
constexpr size_t kGpsProviderClassCount = 64;
constexpr uint16_t kTrafficLaneFlagPavement = 0x0008;
constexpr uint16_t kTrafficLaneFlagRoad = 0x0010;
constexpr uint16_t kTrafficLaneFlagGpsOnly = 0x0200;
constexpr uint16_t kTrafficLaneFlagNoAiDriving = 0x2000;
constexpr uint16_t kTrafficLaneFlagHighway = 0x4000;
constexpr uint16_t kTrafficLaneFlagNoAutodrive = 0x8000;
constexpr uintptr_t kMinimumReadableAddress = 0x10000;
constexpr float kGpsSolverHighwayMultiplierDefault = 0.80f;
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,
};
constexpr bool kGpsSpatialPatchRequiresRouteProducer = true;
constexpr float kGpsSpatialHighwayMultiplier = 0.62f;
constexpr float kGpsSpatialRoadMultiplier = 1.0f;
constexpr float kGpsSpatialGpsOnlyMultiplier = 1.20f;
constexpr float kGpsSpatialPavementMultiplier = 1.35f;
constexpr float kGpsSpatialUnknownMultiplier = 1.05f;
constexpr uint64_t kGpsSolverWeightsReloadIntervalMs = 500;
constexpr uint64_t kGpsSpatialWeightsReloadIntervalMs = 500;

struct GpsProviderClassOverride
{
    size_t classId;
    float multiplier;
};

constexpr std::array<GpsProviderClassOverride, 5> kGpsProviderClassOverrides = {{
    {1, 0.70f},
    {3, 1.45f},
    {4, 1.25f},
    {5, 1.75f},
    {15, 0.80f},
}};

constexpr std::array<size_t, 6> kGpsProviderClassProbeIds = {1, 3, 4, 5, 14, 15};

HMODULE gModule = nullptr;
std::mutex gLogMutex;
RED4ext::v1::PluginHandle gHandle = nullptr;
const RED4ext::v1::Sdk* gSdk = nullptr;
uint32_t gUpdateLogCount = 0;
bool gScannedExecutable = false;
uint32_t gGpsTickLogCount = 0;
uint32_t gJournalTrackEntryLogCount = 0;
uint32_t gJournalTrackEntryListenerDumpCount = 0;
uint32_t gFrameMappinPathWrapperLogCount = 0;
uint32_t gFrameMappinPathCoreLogCount = 0;
uint32_t gSetSelectedMappinWrapperLogCount = 0;
uint32_t gSetSelectedMappinByIdWrapperLogCount = 0;
uint32_t gSetSelectedMappinByPositionWrapperLogCount = 0;
uint32_t gSetSelectedMappinCoreLogCount = 0;
uint32_t gTrackCustomPositionMappinWrapperLogCount = 0;
uint32_t gTrackCustomPositionMappinCoreLogCount = 0;
uint32_t gUntrackCustomPositionMappinWrapperLogCount = 0;
uint32_t gTrackMappinWrapperLogCount = 0;
uint32_t gTrackMappinCoreLogCount = 0;
uint32_t gSetMappinTrackingAlternativeLogCount = 0;
uint32_t gMappinSystemSlotLogCount = 0;
uint32_t gMappinUpdateCustomPositionLogCount = 0;
uint32_t gMappinSetTrackedLogCount = 0;
uint32_t gMappinClearTrackedLogCount = 0;
uint32_t gMappinFramePathLogCount = 0;
uint32_t gMappinPositionQueryLogCount = 0;
uint32_t gMappinCreateCustomPositionLogCount = 0;
uint32_t gMappinReleaseQueryLogCount = 0;
uint32_t gMappinActiveRouteLogCount = 0;
uint32_t gJournalListenerObjectiveVt28LogCount = 0;
uint32_t gJournalListenerObjectiveVt30LogCount = 0;
uint32_t gJournalListenerRouteVt28LogCount = 0;
uint32_t gJournalListenerRouteVt30LogCount = 0;
uint32_t gJournalListenerRouteAltVt28LogCount = 0;
uint32_t gJournalListenerRouteAltVt30LogCount = 0;
uint32_t gJournalListenerUiVt28LogCount = 0;
uint32_t gJournalListenerUiVt30LogCount = 0;
uint32_t gJournalListenerTrackerVt28LogCount = 0;
uint32_t gJournalRouteBridgeLogCount = 0;
uint32_t gMappinRouteEventEnqueueLogCount = 0;
uint32_t gMappinRouteEventHandlerLogCount = 0;
uint32_t gMappinRouteActivateLogCount = 0;
uint32_t gMappinRouteDeactivateLogCount = 0;
uint32_t gRouteBuildCandidateLogCount = 0;
uint32_t gRouteObserver0ActivateLogCount = 0;
uint32_t gRouteObserver1ActivateLogCount = 0;
uint32_t gRouteObserver23ActivateLogCount = 0;
uint32_t gRouteObserver0DeactivateLogCount = 0;
uint32_t gRouteObserver1DeactivateLogCount = 0;
uint32_t gRouteObserver23DeactivateLogCount = 0;
uint32_t gObserver1ServiceLookupLogCount = 0;
uint32_t gObserver1ServiceRouteLookupLogCount = 0;
uint32_t gRunGpsQueryBodyLogCount = 0;
uint32_t gUpdateGpsQueryBodyLogCount = 0;
uint32_t gGpsQuerySubmitLogCount = 0;
uint32_t gGpsQueryDispatchLogCount = 0;
uint32_t gGpsQueryResultFetchLogCount = 0;
uint32_t gGpsQueryStatusLogCount = 0;
uint32_t gGpsDispatchAdvanceLogCount = 0;
uint32_t gGpsTrafficResultTakeLogCount = 0;
uint32_t gGpsRoutePostprocessLogCount = 0;
LONG gGpsTrafficEdgeScoreLogCount = 0;
uint32_t gGpsAsyncWorkerTickLogCount = 0;
uint32_t gGpsRouteJobStartLogCount = 0;
uint32_t gGpsRouteJobPollLogCount = 0;
uint32_t gGpsRouteFinishLogCount = 0;
uint32_t gGpsRouteJobBuildLogCount = 0;
uint32_t gGpsRouteProducerLogCount = 0;
uint32_t gGpsSearchLogCount = 0;
uint32_t gGpsResolveHandleLogCount = 0;
uint32_t gGpsEdgeCostLogCount = 0;
uint32_t gGpsAuxMultiplierLogCount = 0;
uint32_t gGpsNodeMultiplierLogCount = 0;
LONG gGpsSolverNodeWeightPatchLogCount = 0;
uint32_t gGpsCostTablePatchLogCount = 0;
uint32_t gGpsProviderClassPatchLogCount = 0;
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;
uint8_t* gGpsNodeMultiplierRoadTailHighwayConstant = nullptr;
bool gGpsNodeMultiplierRoadTailPatchApplied = false;
std::mutex gGpsCostLogMutex;
std::mutex gGpsAsyncLogMutex;
std::mutex gGpsCostTablePatchMutex;
std::mutex gGpsSolverWeightsMutex;
uint64_t gLogStartTick = 0;
uint64_t gLogFrequency = 0;
void* gMappinSetTrackedTarget = nullptr;
void* gMappinClearTrackedTarget = nullptr;
void* gMappinUpdateCustomPositionTarget = nullptr;
void* gMappinFramePathTarget = nullptr;
void* gMappinPositionQueryTarget = nullptr;
void* gMappinCreateCustomPositionTarget = nullptr;
void* gMappinReleaseQueryTarget = nullptr;
void* gMappinActiveRouteTarget = nullptr;
void* gObserver1ServiceRouteLookupTarget = nullptr;
bool gMappinSlotHookAttachAttempted = false;
bool gObserver1ServiceRouteLookupHookAttachAttempted = false;

struct TrackedGpsQuery
{
    bool active = false;
    uint32_t id = 0;
    void* manager = nullptr;
    uintptr_t submitReturnRva = 0;
    uint32_t submitCall = 0;
    uint32_t resultFetchCalls = 0;
    uint32_t statusCalls = 0;
};

std::mutex gTrackedGpsQueryMutex;
std::array<TrackedGpsQuery, 32> gTrackedGpsQueries{};
uint32_t gTrackedGpsQueryNext = 0;

struct GpsEdgeCostSample
{
    uint64_t currentHandle = 0;
    uint64_t neighborHandle = 0;
    uintptr_t returnRva = 0;
    float cost = 0.0f;
    float originalCost = 0.0f;
    float spatialMultiplier = 1.0f;
    uint8_t sourceClass = 0xFF;
    uint8_t neighborClass = 0xFF;
    char spatialClass = '.';
    bool spatialPatchAllowed = false;
    bool spatialLookupOk = false;
    bool spatialAdjusted = false;
};

struct GpsEdgeCostStats
{
    uint32_t total = 0;
    uint32_t invalidSourceClass = 0;
    uint32_t invalidNeighborClass = 0;
    float costSum = 0.0f;
    float minCost = 0.0f;
    float maxCost = 0.0f;
    std::array<uint32_t, 64> sourceClasses{};
    std::array<uint32_t, 64> neighborClasses{};
    uint32_t spatialPatchAllowed = 0;
    uint32_t spatialLookupOk = 0;
    uint32_t spatialLookupFailed = 0;
    uint32_t spatialAdjusted = 0;
    std::array<uint32_t, 5> spatialClasses{};
    std::array<float, 5> spatialMultiplierSums{};
    std::array<uintptr_t, 8> returnRvas{};
    std::array<uint32_t, 8> returnRvaCounts{};
    uint32_t otherReturnRvas = 0;
    std::array<GpsEdgeCostSample, 8> samples{};
    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;
    bool auxiliary = false;
    uint32_t mode = 0;
    uint32_t retry = 0;
    uint32_t flagsC0 = 0;
    uint8_t c8 = 0;
    uint16_t node86 = 0;
    uint16_t flags88 = 0;
    uint16_t node8c = 0;
    uint16_t node8e = 0;
    uint8_t node90 = 0;
    uint8_t node93 = 0;
    uint32_t valueBits = 0;
    uint32_t count = 0;
};

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;

using ProbeFunc = void (*)(void* aThis, void* a2, void* a3, void* a4);
using JournalTrackEntryFunc = void (*)(void* aThis, void* aEntryHandle);
using ScriptNativeVoidFunc = void (*)(void* aThis, void* aStack, void* aResult);
using MappinSystemResolverFunc = void* (*)(void* aThis);
using FrameMappinPathCoreFunc = void (*)(void* aThis, uint32_t aPathKind, void* a3, void* aPathRect);
using SetSelectedMappinCoreFunc = void (*)(void* aThis, void* aMappinHandle);
using TrackCustomPositionMappinCoreFunc = void (*)(void* aThis);
using TrackMappinCoreFunc = void (*)(void* aThis, void* aMappinHandle);
using MappinUpdateCustomPositionFunc = void (*)(void* aSystem, void* aHandle, void* aPositionData);
using MappinSetTrackedFunc = void (*)(void* aSystem, void* aMappinId);
using MappinClearTrackedFunc = void (*)(void* aSystem);
using MappinFramePathFunc = void (*)(void* aSystem, uint32_t aPathKind, void* aResult);
using MappinPositionQueryFunc = void* (*)(void* aSystem, void* a2, void* a3, void* a4, void* a5);
using MappinCreateCustomPositionFunc = void* (*)(void* aSystem, void* aName, void* aParams, void* aPositionData);
using MappinReleaseQueryFunc = void (*)(void* aSystem, void* aHandle);
using MappinActiveRouteFunc = void* (*)(void* aSystem);
using JournalListenerCallbackFunc = void (*)(void* aListener, void* aEvent);
using JournalRouteBridgeFunc = bool (*)(void* aThis, void* aEntryHandle);
using MappinRouteEventEnqueueFunc = void (*)(void* aSystem, uint32_t aRouteId, uint8_t aActive);
using MappinRouteEventHandlerFunc = void (*)(void* aEvent, void* aRouteHandle);
using MappinRouteToggleFunc = void (*)(void* aSystem, uint64_t aRouteKey);
using RouteBuildCandidateFunc = void (*)(void* aThis, uint32_t aRouteId, uint32_t aAltRouteId,
                                         uint32_t aPreviousRouteId, void* aRoutePositionData,
                                         void* aPrimaryDescriptor, void* aSecondaryDescriptor, void* aRouteData88,
                                         void* aRouteData108, void* aRouteData114, uint8_t aFlag,
                                         uint32_t aMode);
using RouteObserverCallbackFunc = void (*)(void* aObserver, void* aRouteEntry);
using Observer1ServiceLookupFunc = void* (*)(void* aServiceOwner, void* aSystemType);
using Observer1ServiceRouteLookupFunc = void (*)(void* aService, void* aOut, uint32_t aRouteId);
using RunGpsQueryBodyFunc = bool (*)(void* aThis, void* aStart, void* aEnd, int32_t* aOutQueryId);
using UpdateGpsQueryBodyFunc = bool (*)(void* aThis, uint32_t aQueryId, void* aOutPoints, void* aOutError);
using GpsQuerySubmitFunc = int32_t (*)(void* aManager, void* aQuery);
using GpsQueryDispatchFunc = void (*)(void* aRuntimeQuery, void* aTargetPosition, void* aArg3, void* aArg4);
using GpsQueryResultFetchFunc = bool (*)(void* aManager, uint32_t aQueryId, void* aOutPath);
using GpsQueryStatusFunc = int32_t (*)(void* aManager, uint32_t aQueryId);
using GpsDispatchAdvanceFunc = void (*)(void* aRouteSystem, void* aRuntimeQuery, void* aOutTicket,
                                        void* aTargetPosition, void* aSourcePosition);
using GpsTrafficResultTakeFunc = void* (*)(void* aTrafficService, void* aOutResult, uint32_t aTicket,
                                           void* aOutMeta);
using GpsRoutePostprocessFunc = void (*)(void* aOutPath, void* aTrafficResult, void* aRouteRecords, float aScale);
using GpsTrafficEdgeScoreFunc = float (*)(void* aSearchContext, void* aCandidate, void* aReferencePoint);
using GpsAsyncWorkerTickFunc = void (*)(void* aRouteSystem);
using GpsRouteJobStartFunc = bool (*)(void* aRouteSystem, void* aActiveEntry, float aDelta, void* aOutLocal);
using GpsRouteJobPollFunc = bool (*)(void* aRouteSystem, void* aActiveEntry);
using GpsRouteFinishFunc = void* (*)(void* aResultQueue, void* aOutEntry, void* aActiveEntry, void* aLocalResult);
using GpsRouteJobBuildFunc = void* (*)(void* aJob, void* aOut, void* aRequest);
using GpsRouteProducerFunc = int32_t (*)(void* aGraph, void* aQuery, void* aOutResult);
using GpsSearchFunc = int32_t (*)(void* aGraph, uint64_t aStartHandle, uint64_t aTargetHandle, void* aStartPoint,
                                  void* aTargetPoint, void* aCostProvider, void* aOutPath, uint32_t* aOutCount);
using GpsResolveHandleFunc = int32_t (*)(void* aGraph, uint64_t aHandle, void* aOutSegment, void* aOutPoint);
using GpsEdgeCostFunc = float (*)(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);
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;
ScriptNativeVoidFunc gOriginalFrameMappinPathWrapper = nullptr;
FrameMappinPathCoreFunc gOriginalFrameMappinPathCore = nullptr;
ScriptNativeVoidFunc gOriginalSetSelectedMappinWrapper = nullptr;
ScriptNativeVoidFunc gOriginalSetSelectedMappinByIdWrapper = nullptr;
ScriptNativeVoidFunc gOriginalSetSelectedMappinByPositionWrapper = nullptr;
SetSelectedMappinCoreFunc gOriginalSetSelectedMappinCore = nullptr;
ScriptNativeVoidFunc gOriginalTrackCustomPositionMappinWrapper = nullptr;
TrackCustomPositionMappinCoreFunc gOriginalTrackCustomPositionMappinCore = nullptr;
ScriptNativeVoidFunc gOriginalUntrackCustomPositionMappinWrapper = nullptr;
ScriptNativeVoidFunc gOriginalTrackMappinWrapper = nullptr;
TrackMappinCoreFunc gOriginalTrackMappinCore = nullptr;
ScriptNativeVoidFunc gOriginalSetMappinTrackingAlternativeWrapper = nullptr;
MappinUpdateCustomPositionFunc gOriginalMappinUpdateCustomPosition = nullptr;
MappinSetTrackedFunc gOriginalMappinSetTracked = nullptr;
MappinClearTrackedFunc gOriginalMappinClearTracked = nullptr;
MappinFramePathFunc gOriginalMappinFramePath = nullptr;
MappinPositionQueryFunc gOriginalMappinPositionQuery = nullptr;
MappinCreateCustomPositionFunc gOriginalMappinCreateCustomPosition = nullptr;
MappinReleaseQueryFunc gOriginalMappinReleaseQuery = nullptr;
MappinActiveRouteFunc gOriginalMappinActiveRoute = nullptr;
JournalListenerCallbackFunc gOriginalJournalListenerObjectiveVt28 = nullptr;
JournalListenerCallbackFunc gOriginalJournalListenerObjectiveVt30 = nullptr;
JournalListenerCallbackFunc gOriginalJournalListenerRouteVt28 = nullptr;
JournalListenerCallbackFunc gOriginalJournalListenerRouteVt30 = nullptr;
JournalListenerCallbackFunc gOriginalJournalListenerRouteAltVt28 = nullptr;
JournalListenerCallbackFunc gOriginalJournalListenerRouteAltVt30 = nullptr;
JournalListenerCallbackFunc gOriginalJournalListenerUiVt28 = nullptr;
JournalListenerCallbackFunc gOriginalJournalListenerUiVt30 = nullptr;
JournalListenerCallbackFunc gOriginalJournalListenerTrackerVt28 = nullptr;
JournalRouteBridgeFunc gOriginalJournalRouteBridge = nullptr;
MappinRouteEventEnqueueFunc gOriginalMappinRouteEventEnqueue = nullptr;
MappinRouteEventHandlerFunc gOriginalMappinRouteEventHandler = nullptr;
MappinRouteToggleFunc gOriginalMappinRouteActivate = nullptr;
MappinRouteToggleFunc gOriginalMappinRouteDeactivate = nullptr;
RouteBuildCandidateFunc gOriginalRouteBuildCandidate = nullptr;
RouteObserverCallbackFunc gOriginalRouteObserver0Activate = nullptr;
RouteObserverCallbackFunc gOriginalRouteObserver1Activate = nullptr;
RouteObserverCallbackFunc gOriginalRouteObserver23Activate = nullptr;
RouteObserverCallbackFunc gOriginalRouteObserver0Deactivate = nullptr;
RouteObserverCallbackFunc gOriginalRouteObserver1Deactivate = nullptr;
RouteObserverCallbackFunc gOriginalRouteObserver23Deactivate = nullptr;
Observer1ServiceLookupFunc gOriginalObserver1ServiceLookup = nullptr;
Observer1ServiceRouteLookupFunc gOriginalObserver1ServiceRouteLookup = nullptr;
RunGpsQueryBodyFunc gOriginalRunGpsQueryBody = nullptr;
UpdateGpsQueryBodyFunc gOriginalUpdateGpsQueryBody = nullptr;
GpsQuerySubmitFunc gOriginalGpsQuerySubmit = nullptr;
GpsQueryDispatchFunc gOriginalGpsQueryDispatch = nullptr;
GpsQueryResultFetchFunc gOriginalGpsQueryResultFetch = nullptr;
GpsQueryStatusFunc gOriginalGpsQueryStatus = nullptr;
GpsDispatchAdvanceFunc gOriginalGpsDispatchAdvance = nullptr;
GpsTrafficResultTakeFunc gOriginalGpsTrafficResultTake = nullptr;
GpsRoutePostprocessFunc gOriginalGpsRoutePostprocess = nullptr;
GpsTrafficEdgeScoreFunc gOriginalGpsTrafficEdgeScore = nullptr;
GpsAsyncWorkerTickFunc gOriginalGpsAsyncWorkerTick = nullptr;
GpsRouteJobStartFunc gOriginalGpsRouteJobStart = nullptr;
GpsRouteJobPollFunc gOriginalGpsRouteJobPoll = nullptr;
GpsRouteFinishFunc gOriginalGpsRouteFinish = nullptr;
GpsRouteJobBuildFunc gOriginalGpsRouteJobBuild = nullptr;
GpsRouteProducerFunc gOriginalGpsRouteProducer = nullptr;
GpsSearchFunc gOriginalGpsSearch = nullptr;
GpsResolveHandleFunc gOriginalGpsResolveHandle = nullptr;
GpsEdgeCostFunc gOriginalGpsEdgeCost = nullptr;
GpsProviderFilterFunc gOriginalGpsFilteredProviderFilter = nullptr;
GpsProviderFilterFunc gOriginalGpsBaseProviderFilter = nullptr;
GpsMultiplierFunc gOriginalGpsAuxMultiplier = nullptr;
GpsMultiplierFunc gOriginalGpsNodeMultiplier = nullptr;
std::array<std::atomic<float>, 5> gGpsSpatialMultipliers = {
    kGpsSpatialHighwayMultiplier,
    kGpsSpatialRoadMultiplier,
    kGpsSpatialGpsOnlyMultiplier,
    kGpsSpatialPavementMultiplier,
    kGpsSpatialUnknownMultiplier,
};
std::atomic<float> gGpsSolverHighwayMultiplier{kGpsSolverHighwayMultiplierDefault};
std::mutex gGpsSpatialWeightsMutex;
std::atomic<uint64_t> gGpsSolverWeightsNextReloadCheckMs = 0;
std::atomic<uint64_t> gGpsSpatialWeightsNextReloadCheckMs = 0;
std::filesystem::file_time_type gGpsSolverWeightsLastWriteTime{};
std::filesystem::file_time_type gGpsSpatialWeightsLastWriteTime{};
bool gGpsSolverWeightsFileKnown = false;
bool gGpsSpatialWeightsFileKnown = false;

uintptr_t GetImageBase();

std::filesystem::path GetPluginDirectory()
{
    wchar_t modulePath[MAX_PATH]{};
    if (gModule && GetModuleFileNameW(gModule, modulePath, MAX_PATH) > 0)
    {
        auto path = std::filesystem::path(modulePath);
        return path.parent_path();
    }

    return std::filesystem::path(L"red4ext") / L"plugins" / L"EdgeWeightGPS";
}

std::filesystem::path GetLogPath()
{
    return GetPluginDirectory() / L"EdgeWeightGPS.log";
}

std::filesystem::path GetSpatialWeightsPath()
{
    return GetPluginDirectory() / L"spatial_weights.bin";
}

std::filesystem::path GetSolverWeightsPath()
{
    return GetPluginDirectory() / L"solver_weights.bin";
}

uint64_t GetElapsedMs()
{
    if (!gLogStartTick || !gLogFrequency)
    {
        return 0;
    }

    LARGE_INTEGER counter{};
    QueryPerformanceCounter(&counter);
    return static_cast<uint64_t>((counter.QuadPart - gLogStartTick) * 1000 / gLogFrequency);
}

void Log(std::string_view aMessage)
{
    std::lock_guard lock(gLogMutex);

    auto now = std::chrono::system_clock::now();
    auto time = std::chrono::system_clock::to_time_t(now);
    std::tm localTime{};
    localtime_s(&localTime, &time);

    std::ofstream out(GetLogPath(), std::ios::app);
    if (!out)
    {
        return;
    }

    const auto elapsedMs = GetElapsedMs();

    out << std::put_time(&localTime, "%Y-%m-%d %H:%M:%S") << "." << std::setw(3) << std::setfill('0')
        << elapsedMs % 1000 << std::setfill(' ') << " +" << elapsedMs << "ms " << aMessage << "\n";
}

void LogRed4ext(std::string_view aMessage)
{
    Log(aMessage);

    if (gSdk && gSdk->logger && gSdk->logger->Info && gHandle)
    {
        std::string text(aMessage);
        gSdk->logger->Info(gHandle, text.c_str());
    }
}

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 IsUsableSolverMultiplier(float aValue)
{
    return std::isfinite(aValue) && aValue > 0.0f && aValue < 20.0f;
}

void StoreGpsSolverHighwayMultiplier(float aValue)
{
    gGpsSolverHighwayMultiplier.store(aValue, std::memory_order_relaxed);

    if (gGpsNodeMultiplierRoadTailHighwayConstant)
    {
        WriteFloat(gGpsNodeMultiplierRoadTailHighwayConstant, aValue);
        FlushInstructionCache(GetCurrentProcess(), gGpsNodeMultiplierRoadTailHighwayConstant, sizeof(aValue));
    }
}

void LogGpsSolverHighwayMultiplier(std::string_view aPrefix, float aValue)
{
    std::ostringstream line;
    line << aPrefix << " highway=" << aValue;
    LogRed4ext(line.str());
}

void MaybeReloadGpsSolverWeights(bool aForce = false)
{
    if (!kEnableGpsNodeMultiplierInlinePatch)
    {
        return;
    }

    const auto nowMs = GetElapsedMs();
    if (!aForce && nowMs < gGpsSolverWeightsNextReloadCheckMs.load(std::memory_order_relaxed))
    {
        return;
    }

    std::lock_guard lock(gGpsSolverWeightsMutex);
    if (!aForce && nowMs < gGpsSolverWeightsNextReloadCheckMs.load(std::memory_order_relaxed))
    {
        return;
    }
    gGpsSolverWeightsNextReloadCheckMs.store(nowMs + kGpsSolverWeightsReloadIntervalMs, std::memory_order_relaxed);

    const auto path = GetSolverWeightsPath();
    std::error_code error;
    const auto writeTime = std::filesystem::last_write_time(path, error);
    if (error)
    {
        if (aForce || gGpsSolverWeightsFileKnown)
        {
            gGpsSolverWeightsFileKnown = false;
            std::ostringstream line;
            line << "solver weights file unavailable path=" << path.string()
                 << " using highway=" << gGpsSolverHighwayMultiplier.load(std::memory_order_relaxed);
            LogRed4ext(line.str());
        }
        return;
    }

    if (!aForce && gGpsSolverWeightsFileKnown && writeTime == gGpsSolverWeightsLastWriteTime)
    {
        return;
    }

    float highwayMultiplier = 0.0f;
    std::ifstream input(path, std::ios::binary);
    input.read(reinterpret_cast<char*>(&highwayMultiplier), static_cast<std::streamsize>(sizeof(highwayMultiplier)));
    if (input.gcount() != static_cast<std::streamsize>(sizeof(highwayMultiplier)))
    {
        std::ostringstream line;
        line << "solver weights reload skipped path=" << path.string() << " bytes=" << input.gcount()
             << " expected=" << sizeof(highwayMultiplier);
        LogRed4ext(line.str());
        gGpsSolverWeightsFileKnown = true;
        gGpsSolverWeightsLastWriteTime = writeTime;
        return;
    }

    if (!IsUsableSolverMultiplier(highwayMultiplier))
    {
        std::ostringstream line;
        line << "solver weights reload skipped path=" << path.string() << " invalidHighway=" << highwayMultiplier;
        LogRed4ext(line.str());
        gGpsSolverWeightsFileKnown = true;
        gGpsSolverWeightsLastWriteTime = writeTime;
        return;
    }

    StoreGpsSolverHighwayMultiplier(highwayMultiplier);
    gGpsSolverWeightsFileKnown = true;
    gGpsSolverWeightsLastWriteTime = writeTime;

    std::ostringstream line;
    line << "solver weights reloaded path=" << path.string() << " highway=" << highwayMultiplier;
    LogRed4ext(line.str());
}

bool InstallGpsNodeMultiplierInlinePatch()
{
    if (!kEnableGpsNodeMultiplierInlinePatch || gGpsNodeMultiplierRoadTailPatchApplied)
    {
        return true;
    }

    constexpr size_t kCaveSize = 80;
    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. Reaching this block already means
    // vanilla selected one of the road-style solver modes (0/2/4), so keep
    // the change broad enough for manual GPS and autodrive-like callers.
    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 % alignof(float)) != 0)
    {
        cave[offset++] = 0x90;
    }

    const auto node93ConstantOffset = offset;
    WriteFloat(cave + offset, 1.1f);
    offset += sizeof(float);
    const auto highwayConstantOffset = offset;
    const auto highwayMultiplier = gGpsSolverHighwayMultiplier.load(std::memory_order_relaxed);
    WriteFloat(cave + offset, highwayMultiplier);
    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;
    gGpsNodeMultiplierRoadTailHighwayConstant = cave + highwayConstantOffset;
    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 << highwayMultiplier << " modes=road-tail";
    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;
    }

    gGpsNodeMultiplierRoadTailHighwayConstant = nullptr;
    gGpsNodeMultiplierRoadTailPatchApplied = false;
}

bool IsUsableSpatialMultiplier(float aValue)
{
    return std::isfinite(aValue) && aValue > 0.0f && aValue < 20.0f;
}

void StoreGpsSpatialMultipliers(const std::array<float, 5>& aValues)
{
    for (size_t index = 0; index < aValues.size(); ++index)
    {
        gGpsSpatialMultipliers[index].store(aValues[index], std::memory_order_relaxed);
    }
}

void LogGpsSpatialMultipliers(std::string_view aPrefix, const std::array<float, 5>& aValues)
{
    std::ostringstream line;
    line << aPrefix << " highway=" << aValues[0] << " road=" << aValues[1] << " gpsonly=" << aValues[2]
         << " pavement=" << aValues[3] << " unknown=" << aValues[4];
    LogRed4ext(line.str());
}

void MaybeReloadGpsSpatialWeights()
{
    if (!kEnableGpsSpatialEdgeWeightPatch)
    {
        return;
    }

    const auto nowMs = GetElapsedMs();
    if (nowMs < gGpsSpatialWeightsNextReloadCheckMs.load(std::memory_order_relaxed))
    {
        return;
    }

    std::lock_guard lock(gGpsSpatialWeightsMutex);
    if (nowMs < gGpsSpatialWeightsNextReloadCheckMs.load(std::memory_order_relaxed))
    {
        return;
    }
    gGpsSpatialWeightsNextReloadCheckMs.store(nowMs + kGpsSpatialWeightsReloadIntervalMs, std::memory_order_relaxed);

    const auto path = GetSpatialWeightsPath();
    std::error_code error;
    const auto writeTime = std::filesystem::last_write_time(path, error);
    if (error)
    {
        if (gGpsSpatialWeightsFileKnown)
        {
            gGpsSpatialWeightsFileKnown = false;
            std::ostringstream line;
            line << "spatial weights file unavailable path=" << path.string() << " using current/default values";
            LogRed4ext(line.str());
        }
        return;
    }

    if (gGpsSpatialWeightsFileKnown && writeTime == gGpsSpatialWeightsLastWriteTime)
    {
        return;
    }

    std::array<float, 5> values{};
    std::ifstream input(path, std::ios::binary);
    input.read(reinterpret_cast<char*>(values.data()), static_cast<std::streamsize>(values.size() * sizeof(float)));
    if (input.gcount() != static_cast<std::streamsize>(values.size() * sizeof(float)))
    {
        std::ostringstream line;
        line << "spatial weights reload skipped path=" << path.string() << " bytes=" << input.gcount()
             << " expected=" << values.size() * sizeof(float);
        LogRed4ext(line.str());
        gGpsSpatialWeightsFileKnown = true;
        gGpsSpatialWeightsLastWriteTime = writeTime;
        return;
    }

    for (const auto value : values)
    {
        if (!IsUsableSpatialMultiplier(value))
        {
            std::ostringstream line;
            line << "spatial weights reload skipped path=" << path.string() << " invalidValue=" << value;
            LogRed4ext(line.str());
            gGpsSpatialWeightsFileKnown = true;
            gGpsSpatialWeightsLastWriteTime = writeTime;
            return;
        }
    }

    StoreGpsSpatialMultipliers(values);
    gGpsSpatialWeightsFileKnown = true;
    gGpsSpatialWeightsLastWriteTime = writeTime;
    LogGpsSpatialMultipliers("spatial weights reloaded", values);
}

uintptr_t GetImageBase()
{
    return reinterpret_cast<uintptr_t>(GetModuleHandleW(nullptr));
}

uintptr_t GetImageEnd()
{
    const auto imageBase = GetImageBase();
    if (!imageBase)
    {
        return 0;
    }

    const auto* dos = reinterpret_cast<const IMAGE_DOS_HEADER*>(imageBase);
    if (dos->e_magic != IMAGE_DOS_SIGNATURE)
    {
        return 0;
    }

    const auto* nt = reinterpret_cast<const IMAGE_NT_HEADERS*>(imageBase + dos->e_lfanew);
    if (nt->Signature != IMAGE_NT_SIGNATURE)
    {
        return 0;
    }

    return imageBase + nt->OptionalHeader.SizeOfImage;
}

bool IsInImage(const void* aPointer)
{
    const auto imageBase = GetImageBase();
    const auto imageEnd = GetImageEnd();
    const auto address = reinterpret_cast<uintptr_t>(aPointer);
    return imageBase && imageEnd && address >= imageBase && address < imageEnd;
}

bool TryGetImageRva(const void* aPointer, uintptr_t& aRva)
{
    if (!IsInImage(aPointer))
    {
        return false;
    }

    aRva = reinterpret_cast<uintptr_t>(aPointer) - GetImageBase();
    return true;
}

void AppendPointerWithRva(std::ostringstream& aLine, const char* aLabel, const void* aPointer)
{
    aLine << " " << aLabel << "=" << aPointer;

    if (IsInImage(aPointer))
    {
        const auto imageBase = GetImageBase();
        const auto address = reinterpret_cast<uintptr_t>(aPointer);
        aLine << " " << aLabel << "_rva=0x" << std::hex << (address - imageBase) << std::dec;
    }
}

void AppendReturnRva(std::ostringstream& aLine, const void* aReturnAddress)
{
    if (IsInImage(aReturnAddress))
    {
        const auto imageBase = GetImageBase();
        const auto address = reinterpret_cast<uintptr_t>(aReturnAddress);
        aLine << " ret_rva=0x" << std::hex << (address - imageBase) << std::dec;
    }
}

void AppendHexValue(std::ostringstream& aLine, const char* aLabel, uint64_t aValue)
{
    aLine << " " << aLabel << "=0x" << std::hex << aValue << std::dec;
}

float FloatFromBits(uint32_t aBits)
{
    float value = 0.0f;
    std::memcpy(&value, &aBits, sizeof(value));
    return value;
}

uint32_t FloatToBits(float aValue)
{
    uint32_t bits = 0;
    std::memcpy(&bits, &aValue, sizeof(bits));
    return bits;
}

void LogHookCall(const char* aName, uint32_t aCount, void* aThis, void* a2, void* a3, void* a4)
{
    std::ostringstream line;
    line << "hook " << aName << " call=" << aCount << " this=" << aThis << " a2=" << a2 << " a3=" << a3
         << " a4=" << a4;
    AppendReturnRva(line, __builtin_return_address(0));
    LogRed4ext(line.str());
}

void LogScriptNativeCall(const char* aName, uint32_t aCount, void* aThis, void* aStack, void* aResult)
{
    std::ostringstream line;
    line << "hook " << aName << " call=" << aCount << " this=" << aThis << " stack=" << aStack
         << " result=" << aResult;
    LogRed4ext(line.str());
}

void LogFrameMappinPathCoreCall(uint32_t aCount, void* aThis, uint32_t aPathKind, void* a3, void* aPathRect)
{
    std::ostringstream line;
    line << "hook FrameMappinPathCore call=" << aCount << " this=" << aThis << " pathKind=" << aPathKind
         << " a3=" << a3 << " pathRect=" << aPathRect;
    AppendReturnRva(line, __builtin_return_address(0));
    LogRed4ext(line.str());
}

void LogSetSelectedMappinCoreCall(uint32_t aCount, void* aThis, void* aMappinHandle)
{
    std::ostringstream line;
    line << "hook SetSelectedMappinCore call=" << aCount << " this=" << aThis
         << " mappinHandle=" << aMappinHandle;
    AppendReturnRva(line, __builtin_return_address(0));
    LogRed4ext(line.str());
}

void LogTrackCustomPositionMappinCoreCall(uint32_t aCount, void* aThis)
{
    std::ostringstream line;
    line << "hook TrackCustomPositionMappinCore call=" << aCount << " this=" << aThis;
    AppendReturnRva(line, __builtin_return_address(0));
    LogRed4ext(line.str());
}

void LogTrackMappinCoreCall(uint32_t aCount, void* aThis, void* aMappinHandle)
{
    std::ostringstream line;
    line << "hook TrackMappinCore call=" << aCount << " this=" << aThis << " mappinHandle=" << aMappinHandle;
    LogRed4ext(line.str());
}

void LogMappinSystemSlots(uint32_t aCount, void* aSystem, void* aVtable)
{
    auto* vtable = reinterpret_cast<uint8_t*>(aVtable);
    auto readSlot = [vtable](uintptr_t aOffset) -> void* {
        if (!vtable)
        {
            return nullptr;
        }

        return *reinterpret_cast<void**>(vtable + aOffset);
    };

    std::ostringstream line;
    line << "mappin-system slots call=" << aCount;
    AppendPointerWithRva(line, "system", aSystem);
    AppendPointerWithRva(line, "vtable", aVtable);
    AppendPointerWithRva(line, "slot1f0", readSlot(kMappinUpdateCustomPositionSlotOffset));
    AppendPointerWithRva(line, "slot220", readSlot(kMappinSetTrackedSlotOffset));
    AppendPointerWithRva(line, "slot228", readSlot(kMappinClearTrackedSlotOffset));
    AppendPointerWithRva(line, "slot250", readSlot(0x250));
    AppendPointerWithRva(line, "slot280", readSlot(kMappinFramePathSlotOffset));
    AppendPointerWithRva(line, "slot2e0", readSlot(kMappinPositionQuerySlotOffset));
    AppendPointerWithRva(line, "slot2f0", readSlot(kMappinCreateCustomPositionSlotOffset));
    AppendPointerWithRva(line, "slot368", readSlot(kMappinReleaseQuerySlotOffset));
    AppendPointerWithRva(line, "slot3a8", readSlot(kMappinActiveRouteSlotOffset));
    LogRed4ext(line.str());
}

void InspectAndHookMappinSystem(void* aThis);

const char* ReasonToString(RED4ext::v1::EMainReason aReason)
{
    switch (aReason)
    {
    case RED4ext::v1::EMainReason::Load:
        return "Load";
    case RED4ext::v1::EMainReason::Unload:
        return "Unload";
    }

    return "Unknown";
}

bool IsReadablePage(DWORD aProtect)
{
    if ((aProtect & PAGE_GUARD) != 0 || (aProtect & PAGE_NOACCESS) != 0)
    {
        return false;
    }

    const auto baseProtect = aProtect & 0xFF;
    return baseProtect == PAGE_READONLY || baseProtect == PAGE_READWRITE || baseProtect == PAGE_WRITECOPY ||
           baseProtect == PAGE_EXECUTE_READ || baseProtect == PAGE_EXECUTE_READWRITE ||
           baseProtect == PAGE_EXECUTE_WRITECOPY;
}

bool IsReadableMemory(const void* aPointer, size_t aSize)
{
    if (!aPointer || aSize == 0)
    {
        return false;
    }

    const auto begin = reinterpret_cast<uintptr_t>(aPointer);
    const auto end = begin + aSize;
    if (begin < kMinimumReadableAddress || end < begin)
    {
        return false;
    }

    auto cursor = begin;
    while (cursor < end)
    {
        MEMORY_BASIC_INFORMATION mbi{};
        if (!VirtualQuery(reinterpret_cast<const void*>(cursor), &mbi, sizeof(mbi)))
        {
            return false;
        }

        if (mbi.State != MEM_COMMIT || !IsReadablePage(mbi.Protect))
        {
            return false;
        }

        const auto regionEnd = reinterpret_cast<uintptr_t>(mbi.BaseAddress) + mbi.RegionSize;
        if (regionEnd <= cursor)
        {
            return false;
        }

        cursor = regionEnd;
    }

    return true;
}

template<typename T>
bool TryReadMemory(const void* aPointer, T& aValue)
{
    if (!IsReadableMemory(aPointer, sizeof(T)))
    {
        return false;
    }

    aValue = *reinterpret_cast<const T*>(aPointer);
    return true;
}

void* ReadPointerField(void* aObject, uintptr_t aOffset)
{
    void* value = nullptr;
    if (!aObject)
    {
        return nullptr;
    }

    TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aObject) + aOffset), value);
    return value;
}

void* GetObserver1SystemType()
{
    return ReadPointerField(reinterpret_cast<void*>(GetImageBase() + kObserver1SystemTypeGlobalRva), 0x00);
}

void AppendObjectVtableSlots(std::ostringstream& aLine, void* aObject, std::initializer_list<uintptr_t> aSlots)
{
    auto* vtable = ReadPointerField(aObject, 0);
    AppendPointerWithRva(aLine, "vtable", vtable);
    if (!IsInImage(vtable))
    {
        return;
    }

    for (const auto slot : aSlots)
    {
        auto* target = vtable ? ReadPointerField(vtable, slot) : nullptr;
        std::ostringstream label;
        label << "slot" << std::hex << slot;
        const auto labelText = label.str();
        AppendPointerWithRva(aLine, labelText.c_str(), target);
    }
}

void AppendPointerField(std::ostringstream& aLine, const char* aLabel, void* aObject, uintptr_t aOffset)
{
    AppendPointerWithRva(aLine, aLabel, ReadPointerField(aObject, aOffset));
}

void AppendUint32Field(std::ostringstream& aLine, const char* aLabel, void* aObject, uintptr_t aOffset)
{
    uint32_t value = 0;
    const auto hasValue = aObject && TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aObject) + aOffset),
                                                  value);
    aLine << " " << aLabel << "=";
    if (hasValue)
    {
        aLine << value << "/0x" << std::hex << value << std::dec;
    }
    else
    {
        aLine << "<unreadable>";
    }
}

void AppendUint16Field(std::ostringstream& aLine, const char* aLabel, void* aObject, uintptr_t aOffset)
{
    uint16_t value = 0;
    const auto hasValue = aObject && TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aObject) + aOffset),
                                                  value);
    aLine << " " << aLabel << "=";
    if (hasValue)
    {
        aLine << value << "/0x" << std::hex << value << std::dec;
    }
    else
    {
        aLine << "<unreadable>";
    }
}

void AppendUint64Field(std::ostringstream& aLine, const char* aLabel, void* aObject, uintptr_t aOffset)
{
    uint64_t value = 0;
    const auto hasValue = aObject && TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aObject) + aOffset),
                                                  value);
    aLine << " " << aLabel << "=";
    if (hasValue)
    {
        aLine << value << "/0x" << std::hex << value << std::dec;
    }
    else
    {
        aLine << "<unreadable>";
    }
}

void AppendByteField(std::ostringstream& aLine, const char* aLabel, void* aObject, uintptr_t aOffset)
{
    uint8_t value = 0;
    const auto hasValue = aObject && TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aObject) + aOffset),
                                                  value);
    aLine << " " << aLabel << "=";
    if (hasValue)
    {
        aLine << static_cast<uint32_t>(value);
    }
    else
    {
        aLine << "<unreadable>";
    }
}

void AppendBytesField(std::ostringstream& aLine, const char* aLabel, void* aObject, uintptr_t aOffset, size_t aSize)
{
    const auto* address = aObject ? reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aObject) + aOffset) : nullptr;
    aLine << " " << aLabel << "=";
    if (!address || !IsReadableMemory(address, aSize))
    {
        aLine << "<unreadable>";
        return;
    }

    const auto flags = aLine.flags();
    const auto fill = aLine.fill();
    aLine << std::hex << std::setfill('0');
    for (size_t index = 0; index < aSize; ++index)
    {
        const auto byte = *(reinterpret_cast<const uint8_t*>(address) + index);
        aLine << std::setw(2) << static_cast<uint32_t>(byte);
    }
    aLine.flags(flags);
    aLine.fill(fill);
}

void AppendBytesPointer(std::ostringstream& aLine, const char* aLabel, const void* aPointer, size_t aSize)
{
    aLine << " " << aLabel << "=";
    if (!aPointer || reinterpret_cast<uintptr_t>(aPointer) < 0x10000 || !IsReadableMemory(aPointer, aSize))
    {
        aLine << "<unreadable>";
        return;
    }

    const auto flags = aLine.flags();
    const auto fill = aLine.fill();
    aLine << std::hex << std::setfill('0');
    for (size_t index = 0; index < aSize; ++index)
    {
        const auto byte = *(reinterpret_cast<const uint8_t*>(aPointer) + index);
        aLine << std::setw(2) << static_cast<uint32_t>(byte);
    }
    aLine.flags(flags);
    aLine.fill(fill);
}

struct Vector4Value
{
    float x;
    float y;
    float z;
    float w;
};

struct Vector3Value
{
    float x;
    float y;
    float z;
};

int GpsSpatialRoadClassRank(char aCode)
{
    switch (aCode)
    {
    case 'H':
        return 4;
    case 'R':
        return 3;
    case 'G':
        return 2;
    case 'P':
        return 1;
    default:
        return 0;
    }
}

uint32_t GpsSpatialRoadClassIndex(char aCode)
{
    switch (aCode)
    {
    case 'H':
        return 0;
    case 'R':
        return 1;
    case 'G':
        return 2;
    case 'P':
        return 3;
    default:
        return 4;
    }
}

const char* GpsSpatialRoadClassName(char aCode)
{
    switch (aCode)
    {
    case 'H':
        return "highway";
    case 'R':
        return "road";
    case 'G':
        return "gpsonly";
    case 'P':
        return "pavement";
    default:
        return "unknown";
    }
}

float GpsSpatialRoadClassMultiplier(char aCode)
{
    switch (aCode)
    {
    case 'H':
        return gGpsSpatialMultipliers[0].load(std::memory_order_relaxed);
    case 'R':
        return gGpsSpatialMultipliers[1].load(std::memory_order_relaxed);
    case 'G':
        return gGpsSpatialMultipliers[2].load(std::memory_order_relaxed);
    case 'P':
        return gGpsSpatialMultipliers[3].load(std::memory_order_relaxed);
    default:
        return gGpsSpatialMultipliers[4].load(std::memory_order_relaxed);
    }
}

char LookupGpsSpatialRoadClass(float aX, float aMapY)
{
    using namespace EdgeWeightGPS::Generated;

    if (!std::isfinite(aX) || !std::isfinite(aMapY))
    {
        return '.';
    }

    const auto columnFloat = (aX - kSpatialRoadGridMinX) / kSpatialRoadGridCellSize;
    const auto rowFloat = (aMapY - kSpatialRoadGridMinY) / kSpatialRoadGridCellSize;
    if (columnFloat < 0.0f || rowFloat < 0.0f)
    {
        return '.';
    }

    const auto column = static_cast<int64_t>(std::floor(columnFloat));
    const auto row = static_cast<int64_t>(std::floor(rowFloat));
    if (column < 0 || row < 0 || column >= static_cast<int64_t>(kSpatialRoadGridWidth) ||
        row >= static_cast<int64_t>(kSpatialRoadGridHeight))
    {
        return '.';
    }

    const auto& rowData = kSpatialRoadGridRows[static_cast<size_t>(row)];
    if (static_cast<size_t>(column) >= rowData.size())
    {
        return '.';
    }

    return rowData[static_cast<size_t>(column)];
}

bool TryLookupGpsSpatialEdgeClass(void* aCurrentState, void* aNeighborState, char& aCode)
{
    Vector3Value current{};
    Vector3Value neighbor{};
    if (!aCurrentState || !aNeighborState || !TryReadMemory(aCurrentState, current) ||
        !TryReadMemory(aNeighborState, neighbor))
    {
        return false;
    }

    const auto midpointX = (current.x + neighbor.x) * 0.5f;
    const auto midpointMapY = (current.z + neighbor.z) * 0.5f;

    // Search states are (world X, height, world Y); route producer query vectors use (X, Y, Z, W).
    const auto midpointClass = LookupGpsSpatialRoadClass(midpointX, midpointMapY);
    if (midpointClass != '.')
    {
        aCode = midpointClass;
        return true;
    }

    aCode = '.';
    const std::array candidates = {
        LookupGpsSpatialRoadClass(current.x, current.z),
        LookupGpsSpatialRoadClass(neighbor.x, neighbor.z),
    };
    for (const auto candidate : candidates)
    {
        if (GpsSpatialRoadClassRank(candidate) > GpsSpatialRoadClassRank(aCode))
        {
            aCode = candidate;
        }
    }

    return true;
}

void AppendVector3Pointer(std::ostringstream& aLine, const char* aLabel, const void* aVector)
{
    Vector3Value value{};
    aLine << " " << aLabel << "=";
    if (aVector && TryReadMemory(aVector, value))
    {
        aLine << "(" << value.x << "," << value.y << "," << value.z << ")";
    }
    else
    {
        aLine << "<unreadable>";
    }
}

void AppendVector4Pointer(std::ostringstream& aLine, const char* aLabel, const void* aVector)
{
    Vector4Value value{};
    aLine << " " << aLabel << "=";
    if (aVector && TryReadMemory(aVector, value))
    {
        aLine << "(" << value.x << "," << value.y << "," << value.z << "," << value.w << ")";
    }
    else
    {
        aLine << "<unreadable>";
    }
}

void AppendVector4Field(std::ostringstream& aLine, const char* aLabel, void* aObject, uintptr_t aOffset)
{
    if (!aObject)
    {
        aLine << " " << aLabel << "=<null>";
        return;
    }

    AppendVector4Pointer(aLine, aLabel, reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aObject) + aOffset));
}

bool TryReadFloatField(void* aObject, uintptr_t aOffset, float& aValue)
{
    return aObject && TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aObject) + aOffset),
                                    aValue);
}

void AppendFloatField(std::ostringstream& aLine, const char* aLabel, void* aObject, uintptr_t aOffset)
{
    float value = 0.0f;
    const auto hasValue = TryReadFloatField(aObject, aOffset, value);
    aLine << " " << aLabel << "=";
    if (hasValue)
    {
        const auto flags = aLine.flags();
        const auto precision = aLine.precision();
        aLine << std::fixed << std::setprecision(3) << value;
        aLine.flags(flags);
        aLine.precision(precision);
    }
    else
    {
        aLine << "<unreadable>";
    }
}

uint8_t ReadGpsPointClass(void* aPoint)
{
    uint8_t value = 0xFF;
    if (!aPoint || !TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aPoint) + 0x13), value))
    {
        return 0xFF;
    }

    return value & 0x3F;
}

uint16_t ReadGpsPointMask(void* aPoint)
{
    uint16_t value = 0;
    if (!aPoint)
    {
        return 0;
    }

    TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aPoint) + 0x10), value);
    return value;
}

void AppendGpsPointSummary(std::ostringstream& aLine, const char* aPrefix, void* aPoint)
{
    const auto prefix = std::string(aPrefix);
    AppendPointerWithRva(aLine, prefix.c_str(), aPoint);
    if (!aPoint)
    {
        return;
    }

    uint32_t adjacency = 0;
    uint16_t mask = 0;
    uint8_t byte12 = 0;
    uint8_t byte13 = 0;
    TryReadMemory(aPoint, adjacency);
    TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aPoint) + 0x10), mask);
    TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aPoint) + 0x12), byte12);
    TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aPoint) + 0x13), byte13);

    aLine << " " << prefix << "_adj=" << adjacency;
    aLine << " " << prefix << "_mask10=0x" << std::hex << mask << std::dec;
    aLine << " " << prefix << "_b12=0x" << std::hex << static_cast<uint32_t>(byte12);
    aLine << " " << prefix << "_class13=" << std::dec << static_cast<uint32_t>(byte13 & 0x3F);
    aLine << " " << prefix << "_raw13=0x" << std::hex << static_cast<uint32_t>(byte13) << std::dec;
}

void AppendGpsPackedHandleSummary(std::ostringstream& aLine, const char* aPrefix, uint64_t aHandle)
{
    const auto pointIndex = aHandle & 0xFFFFF;
    const auto segmentIndex = (aHandle >> 20) & 0x0FFFFFFF;
    const auto generation = aHandle >> 48;

    aLine << " " << aPrefix << "_pointIndex=" << pointIndex;
    aLine << " " << aPrefix << "_segmentIndex=" << segmentIndex;
    aLine << " " << aPrefix << "_generation=" << generation;
}

bool IsSelectedGpsResolveReturnRva(uintptr_t aReturnRva)
{
    constexpr std::array<uintptr_t, 2> kSelectedReturnRvas = {
        0x44CB9E,
        0x44DD97,
    };

    return std::find(kSelectedReturnRvas.begin(), kSelectedReturnRvas.end(), aReturnRva) !=
           kSelectedReturnRvas.end();
}

size_t GetGpsResolveReturnBucket(uintptr_t aReturnRva)
{
    switch (aReturnRva)
    {
    case 0x44CB9E:
        return 0;
    case 0x44DD97:
        return 1;
    default:
        return gGpsResolveHandleRetLogCounts.size();
    }
}

void ExtendGpsResolveTraceWindow(uint64_t aNowMs)
{
    std::lock_guard lock(gGpsCostLogMutex);
    const auto untilMs = aNowMs + kGpsResolveTraceWindowMs;
    std::fill(gGpsResolveHandleRetLogCounts.begin(), gGpsResolveHandleRetLogCounts.end(), 0);
    ++gGpsResolveTraceWindowSerial;
    if (untilMs > gGpsResolveTraceWindowUntilMs)
    {
        gGpsResolveTraceWindowUntilMs = untilMs;
    }
}

void AppendGpsCostProviderSummary(std::ostringstream& aLine, const char* aPrefix, void* aProvider)
{
    const auto prefix = std::string(aPrefix);
    AppendPointerWithRva(aLine, prefix.c_str(), aProvider);
    if (!aProvider)
    {
        return;
    }

    auto* vtable = ReadPointerField(aProvider, 0x00);
    AppendPointerWithRva(aLine, (prefix + "_vtable").c_str(), vtable);
    if (IsInImage(vtable))
    {
        AppendPointerWithRva(aLine, (prefix + "_slot08").c_str(), ReadPointerField(vtable, 0x08));
        AppendPointerWithRva(aLine, (prefix + "_slot10").c_str(), ReadPointerField(vtable, 0x10));
    }

    AppendUint16Field(aLine, (prefix + "_mask108").c_str(), aProvider, 0x108);
    AppendUint16Field(aLine, (prefix + "_mask10a").c_str(), aProvider, 0x10A);

    const auto flags = aLine.flags();
    const auto precision = aLine.precision();
    aLine << " " << prefix << "_mul=[";
    for (uint32_t index = 0; index < 64; ++index)
    {
        float value = 0.0f;
        const auto hasValue = TryReadFloatField(aProvider, 0x08 + static_cast<uintptr_t>(index) * sizeof(float), value);
        if (index > 0)
        {
            aLine << ",";
        }

        aLine << index << ":";
        if (hasValue)
        {
            aLine << std::fixed << std::setprecision(3) << value;
        }
        else
        {
            aLine << "?";
        }
    }
    aLine << "]";
    aLine.flags(flags);
    aLine.precision(precision);
}

void AppendGpsCostProviderCompact(std::ostringstream& aLine, const char* aPrefix, void* aProvider)
{
    const auto prefix = std::string(aPrefix);
    AppendPointerWithRva(aLine, prefix.c_str(), aProvider);
    if (!aProvider)
    {
        return;
    }

    auto* vtable = ReadPointerField(aProvider, 0x00);
    AppendPointerWithRva(aLine, (prefix + "_vtable").c_str(), vtable);
    if (IsInImage(vtable))
    {
        AppendPointerWithRva(aLine, (prefix + "_slot08").c_str(), ReadPointerField(vtable, 0x08));
        AppendPointerWithRva(aLine, (prefix + "_slot10").c_str(), ReadPointerField(vtable, 0x10));
    }

    AppendUint16Field(aLine, (prefix + "_mask108").c_str(), aProvider, 0x108);
    AppendUint16Field(aLine, (prefix + "_mask10a").c_str(), aProvider, 0x10A);

    const auto flags = aLine.flags();
    const auto precision = aLine.precision();
    aLine << " " << prefix << "_nonunitMul=[";
    bool first = true;
    for (uint32_t index = 0; index < 64; ++index)
    {
        float value = 0.0f;
        if (!TryReadFloatField(aProvider, 0x08 + static_cast<uintptr_t>(index) * sizeof(float), value))
        {
            continue;
        }

        if (value > 0.999f && value < 1.001f)
        {
            continue;
        }

        if (!first)
        {
            aLine << ",";
        }
        first = false;
        aLine << index << ":" << std::fixed << std::setprecision(3) << value;
    }
    if (first)
    {
        aLine << "none";
    }
    aLine << "]";
    aLine.flags(flags);
    aLine.precision(precision);
}

void AppendClassCounts(std::ostringstream& aLine, const char* aLabel, const std::array<uint32_t, 64>& aCounts,
                       uint32_t aInvalidCount)
{
    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 (aInvalidCount > 0)
    {
        if (!first)
        {
            aLine << ",";
        }
        first = false;
        aLine << "invalid:" << aInvalidCount;
    }

    if (first)
    {
        aLine << "none";
    }

    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;
    if (aStats.total > 0)
    {
        const auto flags = aLine.flags();
        const auto precision = aLine.precision();
        aLine << " edgeCostMin=" << std::fixed << std::setprecision(3) << aStats.minCost;
        aLine << " edgeCostMax=" << std::fixed << std::setprecision(3) << aStats.maxCost;
        aLine << " edgeCostAvg=" << std::fixed << std::setprecision(3)
              << (aStats.costSum / static_cast<float>(aStats.total));
        aLine.flags(flags);
        aLine.precision(precision);
    }

    AppendClassCounts(aLine, "edgeSrcClasses", aStats.sourceClasses, aStats.invalidSourceClass);
    AppendClassCounts(aLine, "edgeDstClasses", aStats.neighborClasses, aStats.invalidNeighborClass);
    aLine << " spatialAllowed=" << aStats.spatialPatchAllowed << " spatialLookupOk=" << aStats.spatialLookupOk
          << " spatialLookupFailed=" << aStats.spatialLookupFailed << " spatialAdjusted=" << aStats.spatialAdjusted;

    aLine << " spatialClasses=[";
    constexpr std::array<char, 5> spatialClassCodes = {'H', 'R', 'G', 'P', '.'};
    bool firstSpatialClass = true;
    for (uint32_t index = 0; index < aStats.spatialClasses.size(); ++index)
    {
        const auto count = aStats.spatialClasses[index];
        if (count == 0)
        {
            continue;
        }

        if (!firstSpatialClass)
        {
            aLine << ",";
        }
        firstSpatialClass = false;
        aLine << spatialClassCodes[index] << ":" << count;
        const auto flags = aLine.flags();
        const auto precision = aLine.precision();
        aLine << ":avgMult=" << std::fixed << std::setprecision(3)
              << (aStats.spatialMultiplierSums[index] / static_cast<float>(count));
        aLine.flags(flags);
        aLine.precision(precision);
    }
    if (firstSpatialClass)
    {
        aLine << "none";
    }
    aLine << "]";

    aLine << " edgeRetRvas=[";
    bool first = true;
    for (uint32_t index = 0; index < aStats.returnRvas.size(); ++index)
    {
        if (aStats.returnRvas[index] == 0)
        {
            continue;
        }

        if (!first)
        {
            aLine << ",";
        }
        first = false;
        aLine << "0x" << std::hex << aStats.returnRvas[index] << std::dec << ":" << aStats.returnRvaCounts[index];
    }
    if (aStats.otherReturnRvas > 0)
    {
        if (!first)
        {
            aLine << ",";
        }
        first = false;
        aLine << "other:" << aStats.otherReturnRvas;
    }
    if (first)
    {
        aLine << "none";
    }
    aLine << "]";

    aLine << " edgeSamples=[";
    const auto sampleCount = std::min<uint32_t>(aStats.sampleCount, static_cast<uint32_t>(aStats.samples.size()));
    for (uint32_t index = 0; index < sampleCount; ++index)
    {
        const auto& sample = aStats.samples[index];
        if (index > 0)
        {
            aLine << ";";
        }

        const auto flags = aLine.flags();
        const auto precision = aLine.precision();
        aLine << index << ":" << static_cast<uint32_t>(sample.sourceClass) << ">"
              << static_cast<uint32_t>(sample.neighborClass) << " cost=" << std::fixed << std::setprecision(3)
              << sample.cost << " original=" << sample.originalCost << " spatial=" << sample.spatialClass
              << " mult=" << sample.spatialMultiplier << " allowed=" << static_cast<uint32_t>(sample.spatialPatchAllowed)
              << " lookup=" << static_cast<uint32_t>(sample.spatialLookupOk)
              << " adjusted=" << static_cast<uint32_t>(sample.spatialAdjusted) << " ret=0x" << std::hex
              << sample.returnRva << " cur=0x" << sample.currentHandle << " dst=0x" << sample.neighborHandle
              << std::dec;
        aLine.flags(flags);
        aLine.precision(precision);
    }
    if (sampleCount == 0)
    {
        aLine << "none";
    }
    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,
                          float aOriginalCost, uintptr_t aReturnRva, uint64_t aCurrentHandle,
                          uint64_t aNeighborHandle, char aSpatialClass, float aSpatialMultiplier,
                          bool aSpatialPatchAllowed, bool aSpatialLookupOk, bool aSpatialAdjusted)
{
    ++aStats.total;
    aStats.costSum += aCost;
    if (aStats.total == 1)
    {
        aStats.minCost = aCost;
        aStats.maxCost = aCost;
    }
    else
    {
        aStats.minCost = std::min(aStats.minCost, aCost);
        aStats.maxCost = std::max(aStats.maxCost, aCost);
    }

    if (aSourceClass < aStats.sourceClasses.size())
    {
        ++aStats.sourceClasses[aSourceClass];
    }
    else
    {
        ++aStats.invalidSourceClass;
    }

    if (aNeighborClass < aStats.neighborClasses.size())
    {
        ++aStats.neighborClasses[aNeighborClass];
    }
    else
    {
        ++aStats.invalidNeighborClass;
    }

    if (aSpatialPatchAllowed)
    {
        ++aStats.spatialPatchAllowed;
        if (aSpatialLookupOk)
        {
            ++aStats.spatialLookupOk;
            const auto spatialIndex = GpsSpatialRoadClassIndex(aSpatialClass);
            if (spatialIndex < aStats.spatialClasses.size())
            {
                ++aStats.spatialClasses[spatialIndex];
                aStats.spatialMultiplierSums[spatialIndex] += aSpatialMultiplier;
            }
        }
        else
        {
            ++aStats.spatialLookupFailed;
        }

        if (aSpatialAdjusted)
        {
            ++aStats.spatialAdjusted;
        }
    }

    if (aReturnRva != 0)
    {
        bool recorded = false;
        for (uint32_t index = 0; index < aStats.returnRvas.size(); ++index)
        {
            if (aStats.returnRvas[index] == aReturnRva)
            {
                ++aStats.returnRvaCounts[index];
                recorded = true;
                break;
            }

            if (aStats.returnRvas[index] == 0)
            {
                aStats.returnRvas[index] = aReturnRva;
                aStats.returnRvaCounts[index] = 1;
                recorded = true;
                break;
            }
        }

        if (!recorded)
        {
            ++aStats.otherReturnRvas;
        }
    }

    if (aStats.sampleCount < aStats.samples.size())
    {
        auto& sample = aStats.samples[aStats.sampleCount++];
        sample.currentHandle = aCurrentHandle;
        sample.neighborHandle = aNeighborHandle;
        sample.returnRva = aReturnRva;
        sample.cost = aCost;
        sample.originalCost = aOriginalCost;
        sample.spatialMultiplier = aSpatialMultiplier;
        sample.sourceClass = aSourceClass;
        sample.neighborClass = aNeighborClass;
        sample.spatialClass = aSpatialClass;
        sample.spatialPatchAllowed = aSpatialPatchAllowed;
        sample.spatialLookupOk = aSpatialLookupOk;
        sample.spatialAdjusted = aSpatialAdjusted;
    }
}

bool ReadGpsMultiplierSignature(bool aAuxiliary, void* aJob, void* aNode, float aValue,
                                GpsMultiplierSignature& aSignature)
{
    if (!aJob || !aNode)
    {
        return false;
    }

    aSignature = {};
    aSignature.active = true;
    aSignature.auxiliary = aAuxiliary;
    aSignature.valueBits = FloatToBits(aValue);

    const auto jobAddress = reinterpret_cast<uintptr_t>(aJob);
    const auto nodeAddress = reinterpret_cast<uintptr_t>(aNode);
    if (!TryReadMemory(reinterpret_cast<const void*>(jobAddress + 0xC4), aSignature.mode) ||
        aSignature.mode != 2)
    {
        return false;
    }

    TryReadMemory(reinterpret_cast<const void*>(jobAddress + 0x54), aSignature.retry);
    TryReadMemory(reinterpret_cast<const void*>(jobAddress + 0xC0), aSignature.flagsC0);
    TryReadMemory(reinterpret_cast<const void*>(jobAddress + 0xC8), aSignature.c8);
    TryReadMemory(reinterpret_cast<const void*>(nodeAddress + 0x86), aSignature.node86);
    TryReadMemory(reinterpret_cast<const void*>(nodeAddress + 0x88), aSignature.flags88);
    TryReadMemory(reinterpret_cast<const void*>(nodeAddress + 0x8C), aSignature.node8c);
    TryReadMemory(reinterpret_cast<const void*>(nodeAddress + 0x8E), aSignature.node8e);
    TryReadMemory(reinterpret_cast<const void*>(nodeAddress + 0x90), aSignature.node90);
    TryReadMemory(reinterpret_cast<const void*>(nodeAddress + 0x93), aSignature.node93);

    return true;
}

bool SameGpsMultiplierSignature(const GpsMultiplierSignature& aLeft, const GpsMultiplierSignature& aRight)
{
    return aLeft.active == aRight.active && aLeft.auxiliary == aRight.auxiliary &&
           aLeft.mode == aRight.mode && aLeft.retry == aRight.retry && aLeft.flagsC0 == aRight.flagsC0 &&
           aLeft.c8 == aRight.c8 && aLeft.node86 == aRight.node86 && aLeft.flags88 == aRight.flags88 &&
           aLeft.node8c == aRight.node8c && aLeft.node8e == aRight.node8e &&
           aLeft.node90 == aRight.node90 && aLeft.node93 == aRight.node93 &&
           aLeft.valueBits == aRight.valueBits;
}

bool NoteGpsMultiplierSignature(GpsMultiplierSignature aSignature, uint32_t& aCallCount, uint32_t& aSignatureCount)
{
    std::lock_guard lock(gGpsCostLogMutex);

    auto& logCount = aSignature.auxiliary ? gGpsAuxMultiplierLogCount : gGpsNodeMultiplierLogCount;
    aCallCount = logCount++;

    for (auto& existing : gGpsMultiplierSignatures)
    {
        if (!existing.active || !SameGpsMultiplierSignature(existing, aSignature))
        {
            continue;
        }

        aSignatureCount = ++existing.count;
        return false;
    }

    for (auto& existing : gGpsMultiplierSignatures)
    {
        if (existing.active)
        {
            continue;
        }

        existing = aSignature;
        existing.count = 1;
        aSignatureCount = 1;
        return true;
    }

    ++gGpsMultiplierOverflowCount;
    return false;
}

void AppendGpsMultiplierSignature(std::ostringstream& aLine, const GpsMultiplierSignature& aSignature)
{
    aLine << " mode=" << aSignature.mode << " retry=" << aSignature.retry << " flagsC0=0x" << std::hex
          << aSignature.flagsC0 << " c8=0x" << static_cast<uint32_t>(aSignature.c8)
          << " node86=0x" << aSignature.node86 << " flags88=0x" << aSignature.flags88
          << " node8c=0x" << aSignature.node8c << " node8e=0x" << aSignature.node8e
          << " node90=0x" << static_cast<uint32_t>(aSignature.node90)
          << " node93=0x" << static_cast<uint32_t>(aSignature.node93) << std::dec
          << " value=" << FloatFromBits(aSignature.valueBits);
}

void AppendGpsRouteProducerQuerySummary(std::ostringstream& aLine, void* aQuery)
{
    AppendVector4Field(aLine, "query_start00", aQuery, 0x00);
    AppendVector4Field(aLine, "query_end10", aQuery, 0x10);
    AppendFloatField(aLine, "query_f68", aQuery, 0x68);
    AppendFloatField(aLine, "query_f78", aQuery, 0x78);
    AppendFloatField(aLine, "query_f88", aQuery, 0x88);
    AppendUint32Field(aLine, "query_f8c", aQuery, 0x8C);
    AppendUint32Field(aLine, "query_f90", aQuery, 0x90);
    AppendUint32Field(aLine, "query_f94", aQuery, 0x94);
    AppendPointerField(aLine, "query_ptr98", aQuery, 0x98);
    AppendPointerField(aLine, "query_ptra0", aQuery, 0xA0);
    AppendUint32Field(aLine, "query_flagsa8", aQuery, 0xA8);
}

bool ShouldLogGpsRouteProducerStats(void* aQuery)
{
    if (!aQuery)
    {
        return false;
    }

    Vector4Value start{};
    Vector4Value end{};
    if (!TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aQuery) + 0x00), start) ||
        !TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aQuery) + 0x10), end))
    {
        return false;
    }

    float radius = 0.0f;
    TryReadFloatField(aQuery, 0x78, radius);
    const auto* routeContext = ReadPointerField(aQuery, 0x98);
    const auto deltaX = end.x - start.x;
    const auto deltaY = end.y - start.y;
    const auto deltaZ = end.z - start.z;
    const auto distanceSquared = deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;

    return distanceSquared >= 4.0f || radius != 0.0f || routeContext != nullptr;
}

void AppendVector3ListCandidate(std::ostringstream& aLine, const char* aPrefix, void* aObject, uintptr_t aDataOffset,
                                uintptr_t aCountOffset, uintptr_t aStride)
{
    uint32_t count = 0;
    auto* data = ReadPointerField(aObject, aDataOffset);
    TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aObject) + aCountOffset), count);

    const auto dataLabel = std::string(aPrefix) + "_data";
    const auto countLabel = std::string(aPrefix) + "_count";
    AppendPointerWithRva(aLine, dataLabel.c_str(), data);
    aLine << " " << countLabel << "=" << count;

    if (!data || reinterpret_cast<uintptr_t>(data) < 0x10000 || count == 0 || count > 10000)
    {
        return;
    }

    AppendVector3Pointer(aLine, (std::string(aPrefix) + "_first3").c_str(), data);
    if (count > 1)
    {
        AppendVector3Pointer(aLine, (std::string(aPrefix) + "_last3").c_str(),
                             reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(data) +
                                                           static_cast<uintptr_t>(count - 1) * aStride));
    }
}

void AppendVectorListCandidate(std::ostringstream& aLine, const char* aPrefix, void* aObject, uintptr_t aDataOffset,
                               uintptr_t aCountOffset, uintptr_t aStride)
{
    uint32_t count = 0;
    auto* data = ReadPointerField(aObject, aDataOffset);
    TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aObject) + aCountOffset), count);

    const auto dataLabel = std::string(aPrefix) + "_data";
    const auto countLabel = std::string(aPrefix) + "_count";
    AppendPointerWithRva(aLine, dataLabel.c_str(), data);
    aLine << " " << countLabel << "=" << count;

    if (!data || reinterpret_cast<uintptr_t>(data) < 0x10000 || count == 0 || count > 10000)
    {
        return;
    }

    AppendVector4Pointer(aLine, (std::string(aPrefix) + "_first").c_str(), data);
    if (count > 1)
    {
        AppendVector4Pointer(aLine, (std::string(aPrefix) + "_last").c_str(),
                             reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(data) +
                                                           static_cast<uintptr_t>(count - 1) * aStride));
    }
}

void AppendPathBufferSummary(std::ostringstream& aLine, const char* aPrefix, void* aPathBuffer)
{
    AppendPointerWithRva(aLine, aPrefix, aPathBuffer);
    if (!aPathBuffer)
    {
        return;
    }

    uint32_t count = 0;
    auto* data = ReadPointerField(aPathBuffer, 0x30);
    TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aPathBuffer) + 0x3C), count);

    aLine << " " << aPrefix << "_count=" << count;
    AppendPointerWithRva(aLine, (std::string(aPrefix) + "_data").c_str(), data);

    if (!data || count == 0 || count > 100000)
    {
        return;
    }

    AppendVector4Pointer(aLine, (std::string(aPrefix) + "_first").c_str(), data);
    if (count > 1)
    {
        AppendVector4Pointer(aLine, (std::string(aPrefix) + "_last").c_str(),
                             reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(data) +
                                                           static_cast<uintptr_t>(count - 1) * 12));
    }
}

void AppendGpsResultElementArraySummary(std::ostringstream& aLine, const char* aPrefix, void* aResultObject)
{
    uint32_t capacity = 0;
    uint32_t count = 0;
    auto* data = ReadPointerField(aResultObject, 0x28);
    TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aResultObject) + 0x30), capacity);
    TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aResultObject) + 0x34), count);

    const auto prefix = std::string(aPrefix);
    aLine << " " << prefix << "_ptr28_capacity30=" << capacity;
    aLine << " " << prefix << "_ptr28_count34=" << count;

    if (!data || reinterpret_cast<uintptr_t>(data) < 0x10000 || count == 0 || count > 256)
    {
        return;
    }

    constexpr uintptr_t kRouteRecordStride = 0x28;
    const auto rawSize = std::min<size_t>(0xA0, static_cast<size_t>(count) * kRouteRecordStride);
    AppendBytesPointer(aLine, (prefix + "_ptr28_rec40raw").c_str(), data, rawSize);

    const auto flags = aLine.flags();
    const auto fill = aLine.fill();
    aLine << " " << prefix << "_ptr28_rec40=[";
    for (uint32_t index = 0; index < count; ++index)
    {
        const auto recordAddress = reinterpret_cast<uintptr_t>(data) + static_cast<uintptr_t>(index) * kRouteRecordStride;
        const auto* record = reinterpret_cast<const void*>(recordAddress);

        uint64_t h00 = 0;
        uint32_t u08 = 0;
        uint32_t u0c = 0;
        uint32_t u10 = 0;
        uint32_t u14 = 0;
        uint64_t h18 = 0;
        uint64_t h20 = 0;
        const auto hasRecord = TryReadMemory(record, h00) &&
                               TryReadMemory(reinterpret_cast<const void*>(recordAddress + 0x08), u08) &&
                               TryReadMemory(reinterpret_cast<const void*>(recordAddress + 0x0C), u0c) &&
                               TryReadMemory(reinterpret_cast<const void*>(recordAddress + 0x10), u10) &&
                               TryReadMemory(reinterpret_cast<const void*>(recordAddress + 0x14), u14) &&
                               TryReadMemory(reinterpret_cast<const void*>(recordAddress + 0x18), h18) &&
                               TryReadMemory(reinterpret_cast<const void*>(recordAddress + 0x20), h20);

        if (index > 0)
        {
            aLine << ";";
        }

        aLine << index << ":";
        if (!hasRecord)
        {
            aLine << "<unreadable>";
            continue;
        }

        const auto h18Lo = static_cast<uint32_t>(h18);
        const auto h18Hi = static_cast<uint32_t>(h18 >> 32);
        aLine << std::hex << h00 << ",0x" << std::setw(8) << std::setfill('0') << u08 << std::setfill(fill)
              << std::dec << "," << u0c << "," << u10 << "," << u14 << "," << std::hex << h18 << std::dec
              << "," << FloatFromBits(h18Lo) << "," << FloatFromBits(h18Hi) << "," << std::hex << h20
              << std::dec;
    }
    aLine << "]";
    aLine.flags(flags);
    aLine.fill(fill);
}

void AppendGpsResultObjectSummary(std::ostringstream& aLine, const char* aPrefix, void* aResultObject)
{
    if (!aResultObject)
    {
        aLine << " " << aPrefix << "_result=<null>";
        return;
    }

    AppendBytesField(aLine, (std::string(aPrefix) + "_bytes20").c_str(), aResultObject, 0x20, 0x40);
    AppendUint32Field(aLine, (std::string(aPrefix) + "_u20").c_str(), aResultObject, 0x20);
    AppendUint32Field(aLine, (std::string(aPrefix) + "_u24").c_str(), aResultObject, 0x24);
    AppendPointerField(aLine, (std::string(aPrefix) + "_ptr28").c_str(), aResultObject, 0x28);
    AppendVectorListCandidate(aLine, (std::string(aPrefix) + "_vec20").c_str(), aResultObject, 0x20, 0x2C, 12);
    AppendVectorListCandidate(aLine, (std::string(aPrefix) + "_vec20s16").c_str(), aResultObject, 0x20, 0x2C, 16);
    AppendUint32Field(aLine, (std::string(aPrefix) + "_u2c").c_str(), aResultObject, 0x2C);
    AppendUint64Field(aLine, (std::string(aPrefix) + "_u30").c_str(), aResultObject, 0x30);
    AppendUint32Field(aLine, (std::string(aPrefix) + "_u30lo").c_str(), aResultObject, 0x30);
    AppendUint32Field(aLine, (std::string(aPrefix) + "_u34hi").c_str(), aResultObject, 0x34);
    AppendGpsResultElementArraySummary(aLine, aPrefix, aResultObject);
    AppendVectorListCandidate(aLine, (std::string(aPrefix) + "_vec38").c_str(), aResultObject, 0x38, 0x3C, 12);
    AppendUint16Field(aLine, (std::string(aPrefix) + "_flags40").c_str(), aResultObject, 0x40);
    AppendByteField(aLine, (std::string(aPrefix) + "_state42").c_str(), aResultObject, 0x42);
    AppendUint64Field(aLine, (std::string(aPrefix) + "_u48").c_str(), aResultObject, 0x48);
    AppendUint64Field(aLine, (std::string(aPrefix) + "_u50").c_str(), aResultObject, 0x50);
}

uint32_t NextGpsAsyncLogCall(uint32_t& aCounter)
{
    std::lock_guard lock(gGpsAsyncLogMutex);
    return aCounter++;
}

bool TryReadUint32FieldValue(void* aObject, uintptr_t aOffset, uint32_t& aValue)
{
    return aObject && TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aObject) + aOffset),
                                    aValue);
}

bool IsGpsRoadStyleMode(uint32_t aMode)
{
    return aMode == 0 || aMode == 2 || aMode == 4;
}

bool IsDrivingRouteJob(void* aRouteSystem, void* aActiveEntry)
{
    uint32_t mode = 0;
    if (TryReadUint32FieldValue(aActiveEntry, 0x80, mode) && IsGpsRoadStyleMode(mode))
    {
        return true;
    }

    auto* job = ReadPointerField(aRouteSystem, 0x90D0);
    return TryReadUint32FieldValue(job, 0xC4, mode) && IsGpsRoadStyleMode(mode);
}

void AppendFloatArray(std::ostringstream& aLine, const char* aLabel, const float* aValues, size_t aCount)
{
    const auto flags = aLine.flags();
    const auto precision = aLine.precision();
    aLine << " " << aLabel << "=[";
    for (size_t index = 0; index < aCount; ++index)
    {
        if (index > 0)
        {
            aLine << ",";
        }
        aLine << std::fixed << std::setprecision(2) << aValues[index];
    }
    aLine << "]";
    aLine.flags(flags);
    aLine.precision(precision);
}

void AppendGpsProviderClassSamples(std::ostringstream& aLine, const char* aLabel, const float* aValues,
                                   size_t aCount)
{
    const auto flags = aLine.flags();
    const auto precision = aLine.precision();
    aLine << " " << aLabel << "=[";
    bool first = true;
    for (const auto classId : kGpsProviderClassProbeIds)
    {
        if (classId >= aCount)
        {
            continue;
        }

        if (!first)
        {
            aLine << ",";
        }
        first = false;
        aLine << classId << ":" << std::fixed << std::setprecision(2) << aValues[classId];
    }
    aLine << "]";
    aLine.flags(flags);
    aLine.precision(precision);
}

void AppendGpsProviderClassOverrides(std::ostringstream& aLine)
{
    const auto flags = aLine.flags();
    const auto precision = aLine.precision();
    aLine << " overrides=[";
    for (size_t index = 0; index < kGpsProviderClassOverrides.size(); ++index)
    {
        if (index > 0)
        {
            aLine << ",";
        }

        const auto& item = kGpsProviderClassOverrides[index];
        aLine << item.classId << ":" << std::fixed << std::setprecision(2) << item.multiplier;
    }
    aLine << "]";
    aLine.flags(flags);
    aLine.precision(precision);
}

class ScopedGpsCostTablePatch
{
public:
    ScopedGpsCostTablePatch(bool aShouldPatch, uint32_t aRouteJobStartCall)
        : m_routeJobStartCall(aRouteJobStartCall)
    {
        if (!kEnableGpsCostTablePatch || !aShouldPatch)
        {
            return;
        }

        m_table = reinterpret_cast<float*>(GetImageBase() + kGpsCostMultiplierTableRva);
        if (!IsReadableMemory(m_table, sizeof(m_original)))
        {
            LogPatch("gps-cost-table-patch skipped", "reason=unreadable-table");
            return;
        }

        std::memcpy(m_original.data(), m_table, sizeof(m_original));

        DWORD oldProtect = 0;
        if (!VirtualProtect(m_table, sizeof(m_original), PAGE_EXECUTE_READWRITE, &oldProtect))
        {
            LogPatch("gps-cost-table-patch skipped", "reason=protect-failed");
            return;
        }

        std::memcpy(m_table, kGpsPatchedCostMultipliers.data(), sizeof(m_original));

        DWORD ignoredProtect = 0;
        VirtualProtect(m_table, sizeof(m_original), oldProtect, &ignoredProtect);
        FlushInstructionCache(GetCurrentProcess(), m_table, sizeof(m_original));

        m_active = true;
        LogPatch("gps-cost-table-patch applied", nullptr);
    }

    ~ScopedGpsCostTablePatch()
    {
        if (!m_active || !m_table)
        {
            return;
        }

        DWORD oldProtect = 0;
        if (!VirtualProtect(m_table, sizeof(m_original), PAGE_EXECUTE_READWRITE, &oldProtect))
        {
            LogPatch("gps-cost-table-patch restore-failed", "reason=protect-failed");
            return;
        }

        std::memcpy(m_table, m_original.data(), sizeof(m_original));

        DWORD ignoredProtect = 0;
        VirtualProtect(m_table, sizeof(m_original), oldProtect, &ignoredProtect);
        FlushInstructionCache(GetCurrentProcess(), m_table, sizeof(m_original));
        LogPatch("gps-cost-table-patch restored", nullptr);
    }

private:
    void LogPatch(const char* aMessage, const char* aReason)
    {
        const auto patchLogCall = NextGpsAsyncLogCall(gGpsCostTablePatchLogCount);
        if (patchLogCall >= 16)
        {
            return;
        }

        std::ostringstream line;
        line << aMessage << " patchCall=" << patchLogCall << " routeJobStartCall=" << m_routeJobStartCall;
        AppendPointerWithRva(line, "table", m_table);
        if (aReason)
        {
            line << " " << aReason;
        }
        if (m_table && IsReadableMemory(m_table, sizeof(m_original)))
        {
            AppendFloatArray(line, "current", m_table, m_original.size());
        }
        AppendFloatArray(line, "original", m_original.data(), m_original.size());
        AppendFloatArray(line, "patched", kGpsPatchedCostMultipliers.data(), kGpsPatchedCostMultipliers.size());
        LogRed4ext(line.str());
    }

    uint32_t m_routeJobStartCall = 0;
    bool m_active = false;
    float* m_table = nullptr;
    std::array<float, kGpsPatchedCostMultipliers.size()> m_original{};
};

bool IsKnownGpsCostProvider(void* aProvider, uintptr_t& aVtableRva)
{
    aVtableRva = 0;
    auto* vtable = ReadPointerField(aProvider, 0x00);
    if (!TryGetImageRva(vtable, aVtableRva))
    {
        return false;
    }

    return aVtableRva == kGpsBaseCostProviderVtableRva || aVtableRva == kGpsFilteredCostProviderVtableRva;
}

class ScopedGpsProviderClassPatch
{
public:
    ScopedGpsProviderClassPatch(void* aProvider, uint32_t aSearchCall)
        : m_provider(aProvider)
        , m_searchCall(aSearchCall)
    {
        if (!kEnableGpsProviderClassPatch || !m_provider)
        {
            return;
        }

        if (!IsKnownGpsCostProvider(m_provider, m_vtableRva))
        {
            LogPatch("gps-provider-class-patch skipped", "reason=unknown-provider");
            return;
        }

        m_table = reinterpret_cast<float*>(reinterpret_cast<uintptr_t>(m_provider) + 0x08);
        if (!IsReadableMemory(m_table, sizeof(m_original)))
        {
            LogPatch("gps-provider-class-patch skipped", "reason=unreadable-table");
            return;
        }

        std::memcpy(m_original.data(), m_table, sizeof(m_original));
        for (const auto& item : kGpsProviderClassOverrides)
        {
            if (item.classId < m_original.size())
            {
                m_table[item.classId] = item.multiplier;
            }
        }
        m_active = true;
        LogPatch("gps-provider-class-patch applied", nullptr);
    }

    ~ScopedGpsProviderClassPatch()
    {
        if (!m_active || !m_table)
        {
            return;
        }

        std::memcpy(m_table, m_original.data(), sizeof(m_original));
        LogPatch("gps-provider-class-patch restored", nullptr);
    }

private:
    void LogPatch(const char* aMessage, const char* aReason)
    {
        if (!kEnableGpsProviderClassPatchTrace)
        {
            return;
        }

        const auto patchLogCall = NextGpsAsyncLogCall(gGpsProviderClassPatchLogCount);
        if (patchLogCall >= 128)
        {
            return;
        }

        std::ostringstream line;
        line << aMessage << " patchCall=" << patchLogCall << " searchCall=" << m_searchCall;
        AppendPointerWithRva(line, "provider", m_provider);
        line << " provider_vtable_rva=0x" << std::hex << m_vtableRva << std::dec;
        if (aReason)
        {
            line << " " << aReason;
        }
        if (m_table && IsReadableMemory(m_table, sizeof(m_original)))
        {
            AppendGpsProviderClassSamples(line, "current", m_table, m_original.size());
        }
        AppendGpsProviderClassSamples(line, "original", m_original.data(), m_original.size());
        AppendGpsProviderClassOverrides(line);
        LogRed4ext(line.str());
    }

    void* m_provider = nullptr;
    float* m_table = nullptr;
    uint32_t m_searchCall = 0;
    uintptr_t m_vtableRva = 0;
    bool m_active = false;
    std::array<float, kGpsProviderClassCount> m_original{};
};

void AppendRouteJobSummary(std::ostringstream& aLine, const char* aPrefix, void* aJob)
{
    const auto prefix = std::string(aPrefix);
    AppendPointerWithRva(aLine, prefix.c_str(), aJob);
    if (!aJob)
    {
        return;
    }

    AppendUint32Field(aLine, (prefix + "_type10").c_str(), aJob, 0x10);
    AppendPointerField(aLine, (prefix + "_result18").c_str(), aJob, 0x18);
    AppendPointerField(aLine, (prefix + "_result30").c_str(), aJob, 0x30);
    AppendUint32Field(aLine, (prefix + "_u4c").c_str(), aJob, 0x4C);
    AppendUint32Field(aLine, (prefix + "_count50").c_str(), aJob, 0x50);
    AppendUint32Field(aLine, (prefix + "_retry54").c_str(), aJob, 0x54);
    AppendUint32Field(aLine, (prefix + "_flagsc0").c_str(), aJob, 0xC0);
    AppendUint32Field(aLine, (prefix + "_statec4").c_str(), aJob, 0xC4);
    AppendPointerField(aLine, (prefix + "_chunkse0").c_str(), aJob, 0xE0);
    AppendUint32Field(aLine, (prefix + "_chunks_cap_e8").c_str(), aJob, 0xE8);
    AppendUint32Field(aLine, (prefix + "_chunks_count_ec").c_str(), aJob, 0xEC);
    AppendPointerField(aLine, (prefix + "_nodesf8").c_str(), aJob, 0xF8);
}

void AppendRouteSystemSummary(std::ostringstream& aLine, const char* aPrefix, void* aRouteSystem)
{
    const auto prefix = std::string(aPrefix);
    AppendPointerWithRva(aLine, prefix.c_str(), aRouteSystem);
    if (!aRouteSystem)
    {
        return;
    }

    AppendUint32Field(aLine, (prefix + "_queued8fc0").c_str(), aRouteSystem, 0x8FC0);
    AppendUint32Field(aLine, (prefix + "_active8fd0").c_str(), aRouteSystem, 0x8FD0);
    AppendPointerField(aLine, (prefix + "_job90d0").c_str(), aRouteSystem, 0x90D0);

    auto* job = ReadPointerField(aRouteSystem, 0x90D0);
    if (!job)
    {
        return;
    }

    AppendRouteJobSummary(aLine, (prefix + "_job").c_str(), job);
}

bool HasPendingRouteWork(void* aRouteSystem)
{
    uint32_t queued = 0;
    uint32_t active = 0;
    TryReadUint32FieldValue(aRouteSystem, 0x8FC0, queued);
    TryReadUint32FieldValue(aRouteSystem, 0x8FD0, active);
    return queued != 0 || active != 0 || ReadPointerField(aRouteSystem, 0x90D0) != nullptr;
}

void AppendRouteRequestFields(std::ostringstream& aLine, const char* aPrefix, void* aObject, uintptr_t aOffset)
{
    const auto prefix = std::string(aPrefix);
    auto* base = aObject ? reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(aObject) + aOffset) : nullptr;
    AppendPointerWithRva(aLine, prefix.c_str(), base);
    if (!base)
    {
        return;
    }

    AppendBytesPointer(aLine, (prefix + "_raw40").c_str(), base, 0x40);
    AppendVector4Field(aLine, (prefix + "_vec00").c_str(), base, 0x00);
    AppendVector4Field(aLine, (prefix + "_vec10").c_str(), base, 0x10);
    AppendUint32Field(aLine, (prefix + "_u00").c_str(), base, 0x00);
    AppendUint32Field(aLine, (prefix + "_u08").c_str(), base, 0x08);
    AppendUint32Field(aLine, (prefix + "_u1c").c_str(), base, 0x1C);
    AppendUint16Field(aLine, (prefix + "_u1e").c_str(), base, 0x1E);
    AppendUint32Field(aLine, (prefix + "_u20").c_str(), base, 0x20);
    AppendUint16Field(aLine, (prefix + "_u20w").c_str(), base, 0x20);
    AppendUint16Field(aLine, (prefix + "_u24").c_str(), base, 0x24);
    AppendUint16Field(aLine, (prefix + "_u26").c_str(), base, 0x26);
    AppendUint16Field(aLine, (prefix + "_u28").c_str(), base, 0x28);
    AppendUint32Field(aLine, (prefix + "_u70").c_str(), base, 0x70);
    AppendByteField(aLine, (prefix + "_b74").c_str(), base, 0x74);
    AppendByteField(aLine, (prefix + "_b75").c_str(), base, 0x75);
    AppendByteField(aLine, (prefix + "_b76").c_str(), base, 0x76);
    AppendByteField(aLine, (prefix + "_b77").c_str(), base, 0x77);
}

void AppendRouteQueueEntrySummary(std::ostringstream& aLine, const char* aPrefix, void* aEntry)
{
    const auto prefix = std::string(aPrefix);
    AppendPointerWithRva(aLine, prefix.c_str(), aEntry);
    if (!aEntry)
    {
        return;
    }

    AppendUint32Field(aLine, (prefix + "_id00").c_str(), aEntry, 0x00);
    AppendRouteRequestFields(aLine, (prefix + "_payload10").c_str(), aEntry, 0x10);
    AppendPointerField(aLine, (prefix + "_vtable40").c_str(), aEntry, 0x40);
    AppendPointerField(aLine, (prefix + "_self78").c_str(), aEntry, 0x78);
    AppendUint32Field(aLine, (prefix + "_u80").c_str(), aEntry, 0x80);
    AppendByteField(aLine, (prefix + "_b84").c_str(), aEntry, 0x84);
    AppendByteField(aLine, (prefix + "_b85").c_str(), aEntry, 0x85);
    AppendByteField(aLine, (prefix + "_b86").c_str(), aEntry, 0x86);
    AppendByteField(aLine, (prefix + "_b87").c_str(), aEntry, 0x87);
}

void AppendRouteOutputPointerSummary(std::ostringstream& aLine, const char* aPrefix, void* aOut)
{
    const auto prefix = std::string(aPrefix);
    AppendPointerWithRva(aLine, prefix.c_str(), aOut);
    if (!aOut)
    {
        return;
    }

    auto* value = ReadPointerField(aOut, 0x00);
    AppendPointerWithRva(aLine, (prefix + "_value").c_str(), value);
    if (value && IsReadableMemory(value, 0x58))
    {
        AppendGpsResultObjectSummary(aLine, (prefix + "_result").c_str(), value);
    }
}

void AppendTrafficResultSummary(std::ostringstream& aLine, const char* aPrefix, void* aResult)
{
    const auto prefix = std::string(aPrefix);
    AppendPointerWithRva(aLine, prefix.c_str(), aResult);
    if (!aResult)
    {
        return;
    }

    AppendUint32Field(aLine, (prefix + "_count0c").c_str(), aResult, 0x0C);
    AppendPointerField(aLine, (prefix + "_records10").c_str(), aResult, 0x10);
    AppendUint32Field(aLine, (prefix + "_u2c").c_str(), aResult, 0x2C);
    AppendUint32Field(aLine, (prefix + "_u30").c_str(), aResult, 0x30);
    AppendUint32Field(aLine, (prefix + "_u34").c_str(), aResult, 0x34);

    uint32_t count = 0;
    auto* records = ReadPointerField(aResult, 0x10);
    if (TryReadUint32FieldValue(aResult, 0x0C, count) && records)
    {
        const auto sampleCount = std::min<uint32_t>(count, 4);
        AppendBytesPointer(aLine, (prefix + "_records10_raw").c_str(), records,
                           static_cast<size_t>(sampleCount) * 0x10);
    }
}

void AppendTrafficResultHolderSummary(std::ostringstream& aLine, const char* aPrefix, void* aOut)
{
    const auto prefix = std::string(aPrefix);
    AppendPointerWithRva(aLine, prefix.c_str(), aOut);
    if (!aOut)
    {
        return;
    }

    auto* value = ReadPointerField(aOut, 0x00);
    AppendTrafficResultSummary(aLine, (prefix + "_value").c_str(), value);
}

void TrackGpsQuery(uint32_t aQueryId, void* aManager, uintptr_t aSubmitReturnRva, uint32_t aSubmitCall)
{
    if (aQueryId == UINT32_MAX)
    {
        return;
    }

    std::lock_guard lock(gTrackedGpsQueryMutex);

    for (auto& query : gTrackedGpsQueries)
    {
        if (query.active && query.id == aQueryId && query.manager == aManager)
        {
            query.submitReturnRva = aSubmitReturnRva;
            query.submitCall = aSubmitCall;
            return;
        }
    }

    auto& query = gTrackedGpsQueries[gTrackedGpsQueryNext++ % gTrackedGpsQueries.size()];
    query.active = true;
    query.id = aQueryId;
    query.manager = aManager;
    query.submitReturnRva = aSubmitReturnRva;
    query.submitCall = aSubmitCall;
    query.resultFetchCalls = 0;
    query.statusCalls = 0;
}

bool NoteTrackedGpsQueryResultFetch(uint32_t aQueryId, void* aManager, TrackedGpsQuery& aSnapshot,
                                    uint32_t& aPerQueryCall)
{
    std::lock_guard lock(gTrackedGpsQueryMutex);

    for (auto& query : gTrackedGpsQueries)
    {
        if (!query.active || query.id != aQueryId || query.manager != aManager)
        {
            continue;
        }

        aPerQueryCall = query.resultFetchCalls++;
        aSnapshot = query;
        return true;
    }

    return false;
}

bool NoteTrackedGpsQueryStatus(uint32_t aQueryId, void* aManager, TrackedGpsQuery& aSnapshot,
                               uint32_t& aPerQueryCall)
{
    std::lock_guard lock(gTrackedGpsQueryMutex);

    for (auto& query : gTrackedGpsQueries)
    {
        if (!query.active || query.id != aQueryId || query.manager != aManager)
        {
            continue;
        }

        aPerQueryCall = query.statusCalls++;
        aSnapshot = query;
        return true;
    }

    return false;
}

void AppendTrackedGpsQuery(std::ostringstream& aLine, const TrackedGpsQuery& aQuery)
{
    aLine << " submitCall=" << aQuery.submitCall << " submitRetRva=0x" << std::hex << aQuery.submitReturnRva
          << std::dec;
}

void AppendRouteRefFields(std::ostringstream& aLine, const char* aPrefix, void* aRouteRef)
{
    AppendPointerWithRva(aLine, aPrefix, aRouteRef);
    if (!aRouteRef)
    {
        return;
    }

    std::array<uintptr_t, 7> offsets{0x00, 0x08, 0x10, 0x18, 0x30, 0x40, 0x48};
    for (const auto offset : offsets)
    {
        std::ostringstream label;
        label << aPrefix << "_f" << std::hex << offset;
        const auto labelText = label.str();
        AppendPointerField(aLine, labelText.c_str(), aRouteRef, offset);
    }
}

void LogMappinRouteCollections(const char* aName, uint32_t aCount, void* aSystem)
{
    if (!kEnableVerboseMappinRouteState)
    {
        return;
    }

    if (!aSystem)
    {
        return;
    }

    void* activeRouteMap = nullptr;
    uint32_t activeRouteCount = 0;
    uint32_t activeRouteFlags = 0;
    void* observerBase = nullptr;
    uint32_t observerCount = 0;
    TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aSystem) + 0x1A0), activeRouteMap);
    TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aSystem) + 0x1AC), activeRouteCount);
    TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aSystem) + 0x1B0), activeRouteFlags);
    TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aSystem) + 0x288), observerBase);
    TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aSystem) + 0x294), observerCount);

    std::ostringstream line;
    line << "mappin-route-state " << aName << " call=" << aCount;
    AppendPointerWithRva(line, "system", aSystem);
    AppendPointerWithRva(line, "activeRouteMap", activeRouteMap);
    line << " activeRouteCount=" << activeRouteCount << " activeRouteFlags=0x" << std::hex << activeRouteFlags
         << std::dec;
    AppendPointerWithRva(line, "observerBase", observerBase);
    line << " observerCount=" << observerCount;
    LogRed4ext(line.str());

    if (!observerBase || observerCount == 0 || observerCount > 128)
    {
        return;
    }

    const auto cappedCount = std::min(observerCount, 4u);
    for (uint32_t index = 0; index < cappedCount; ++index)
    {
        auto* observer = ReadPointerField(observerBase, index * 0x10);
        std::ostringstream observerLine;
        observerLine << "mappin-route-observer " << aName << " call=" << aCount << " index=" << index;
        AppendPointerWithRva(observerLine, "observer", observer);
        AppendObjectVtableSlots(observerLine, observer, {0x48, 0x50});
        LogRed4ext(observerLine.str());
    }
}

void LogRouteObserverContext(const char* aName, uint32_t aCount, void* aObserver, void* aRouteEntry)
{
    auto* observerInner = ReadPointerField(aObserver, 0x08);
    auto* observerInnerServiceOwner = ReadPointerField(observerInner, 0x18);
    auto* observer1SystemType = GetObserver1SystemType();
    auto* routeObject = ReadPointerField(aRouteEntry, 0x00);

    std::ostringstream line;
    line << "hook " << aName << " call=" << aCount;
    AppendPointerWithRva(line, "observer", aObserver);
    AppendPointerWithRva(line, "observerInner", observerInner);
    AppendPointerWithRva(line, "observerInnerServiceOwner", observerInnerServiceOwner);
    AppendPointerWithRva(line, "observer1SystemType342f6a8", observer1SystemType);
    AppendPointerWithRva(line, "routeEntry", aRouteEntry);
    AppendPointerWithRva(line, "routeObject", routeObject);
    AppendPointerField(line, "routeEntry_f08", aRouteEntry, 0x08);
    AppendPointerField(line, "routeEntry_f10", aRouteEntry, 0x10);
    AppendReturnRva(line, __builtin_return_address(0));
    LogRed4ext(line.str());

    std::ostringstream observerLine;
    observerLine << "route-observer-object " << aName << " call=" << aCount;
    AppendPointerWithRva(observerLine, "observer", aObserver);
    AppendObjectVtableSlots(observerLine, aObserver, {0x48, 0x50});
    LogRed4ext(observerLine.str());

    if (observerInner)
    {
        std::ostringstream innerLine;
        innerLine << "route-observer-inner " << aName << " call=" << aCount;
        AppendPointerWithRva(innerLine, "observerInner", observerInner);
        AppendObjectVtableSlots(innerLine, observerInner, {0x08});
        AppendPointerField(innerLine, "field18", observerInner, 0x18);
        AppendPointerField(innerLine, "field180", observerInner, 0x180);
        AppendPointerField(innerLine, "field1a0", observerInner, 0x1A0);
        AppendPointerField(innerLine, "field1c0", observerInner, 0x1C0);
        AppendPointerField(innerLine, "field1e0", observerInner, 0x1E0);
        AppendPointerField(innerLine, "field1e8", observerInner, 0x1E8);
        AppendPointerField(innerLine, "field1f0", observerInner, 0x1F0);
        LogRed4ext(innerLine.str());
    }

    if (observerInnerServiceOwner)
    {
        std::ostringstream serviceOwnerLine;
        serviceOwnerLine << "route-observer-service-owner " << aName << " call=" << aCount;
        AppendPointerWithRva(serviceOwnerLine, "serviceOwner", observerInnerServiceOwner);
        AppendObjectVtableSlots(serviceOwnerLine, observerInnerServiceOwner, {0x08});
        LogRed4ext(serviceOwnerLine.str());
    }

    if (!routeObject)
    {
        return;
    }

    std::ostringstream routeLine;
    routeLine << "route-observer-route-object " << aName << " call=" << aCount;
    AppendPointerWithRva(routeLine, "routeObject", routeObject);
    AppendObjectVtableSlots(routeLine, routeObject, {0x140, 0x188, 0x198, 0x1A8});
    AppendPointerField(routeLine, "field40", routeObject, 0x40);
    AppendPointerField(routeLine, "field48", routeObject, 0x48);
    AppendPointerField(routeLine, "field78", routeObject, 0x78);
    AppendUint32Field(routeLine, "field80", routeObject, 0x80);
    AppendByteField(routeLine, "active84", routeObject, 0x84);
    AppendUint32Field(routeLine, "field8C", routeObject, 0x8C);
    AppendUint32Field(routeLine, "field90", routeObject, 0x90);
    AppendPointerField(routeLine, "field98", routeObject, 0x98);
    AppendPointerField(routeLine, "field88", routeObject, 0x88);
    AppendPointerField(routeLine, "fieldF8", routeObject, 0xF8);
    AppendPointerField(routeLine, "field120", routeObject, 0x120);
    AppendByteField(routeLine, "fieldE2", routeObject, 0xE2);
    LogRed4ext(routeLine.str());
}

void LogRouteEventFields(const char* aName, uint32_t aCount, void* aEvent)
{
    uint32_t routeId = 0;
    uint8_t active = 0;
    void* system = nullptr;
    if (aEvent)
    {
        TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aEvent) + 0x18), routeId);
        TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aEvent) + 0x20), system);
        TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aEvent) + 0x28), active);
    }

    std::ostringstream line;
    line << "route-event-fields " << aName << " call=" << aCount << " routeId=" << routeId << " active="
         << static_cast<uint32_t>(active);
    AppendPointerWithRva(line, "event", aEvent);
    AppendPointerWithRva(line, "system", system);
    AppendObjectVtableSlots(line, aEvent, {0x20, 0x28, 0x30});
    LogRed4ext(line.str());
}

void LogJournalListenerContext(const char* aName, uint32_t aCount, void* aListener, void* aEvent)
{
    std::ostringstream line;
    line << "hook " << aName << " call=" << aCount;
    AppendPointerWithRva(line, "listener", aListener);
    AppendPointerWithRva(line, "event", aEvent);
    AppendReturnRva(line, __builtin_return_address(0));

    auto* field08 = ReadPointerField(aListener, 0x08);
    auto* field18 = ReadPointerField(aListener, 0x18);
    auto* field20 = ReadPointerField(aListener, 0x20);
    auto* field70 = ReadPointerField(aListener, 0x70);
    auto* field78 = ReadPointerField(aListener, 0x78);
    AppendPointerWithRva(line, "field08", field08);
    AppendPointerWithRva(line, "field18", field18);
    AppendPointerWithRva(line, "field20", field20);
    AppendPointerWithRva(line, "field70", field70);
    AppendPointerWithRva(line, "field78", field78);
    LogRed4ext(line.str());

    if (field70)
    {
        std::ostringstream owner;
        owner << "journal-listener-context " << aName << " call=" << aCount;
        AppendPointerWithRva(owner, "field70", field70);
        AppendObjectVtableSlots(owner, field70, {0x1C8, 0x218, 0x230, 0x238, 0x240, 0x248, 0x340, 0x348, 0x358});
        LogRed4ext(owner.str());
    }

    if (field78 && field78 != field70)
    {
        std::ostringstream service;
        service << "journal-listener-context " << aName << " call=" << aCount;
        AppendPointerWithRva(service, "field78", field78);
        AppendObjectVtableSlots(service, field78,
                                {0x1C8, 0x218, 0x220, 0x230, 0x238, 0x240, 0x248, 0x340, 0x348, 0x358});
        LogRed4ext(service.str());
    }
}

void ScanRegionForString(const uint8_t* aRegionBegin, size_t aRegionSize, uintptr_t aImageBase, const char* aNeedle,
                         uint32_t& aMatches)
{
    constexpr uint32_t kMaxMatchesPerNeedle = 8;

    const auto needleSize = std::strlen(aNeedle);
    if (needleSize == 0 || aRegionSize < needleSize || aMatches >= kMaxMatchesPerNeedle)
    {
        return;
    }

    const auto* cursor = aRegionBegin;
    const auto* regionEnd = aRegionBegin + aRegionSize;

    while (cursor + needleSize <= regionEnd && aMatches < kMaxMatchesPerNeedle)
    {
        if (std::memcmp(cursor, aNeedle, needleSize) == 0)
        {
            const auto rva = reinterpret_cast<uintptr_t>(cursor) - aImageBase;
            std::ostringstream line;
            line << "exe-string match needle=\"" << aNeedle << "\" rva=0x" << std::hex << rva << std::dec;
            LogRed4ext(line.str());
            ++aMatches;
            cursor += needleSize;
            continue;
        }

        ++cursor;
    }
}

void ScanExecutableStrings()
{
    if (gScannedExecutable)
    {
        return;
    }
    gScannedExecutable = true;

    const auto imageBase = reinterpret_cast<uintptr_t>(GetModuleHandleW(nullptr));
    if (!imageBase)
    {
        LogRed4ext("exe-string scan skipped: main module unavailable");
        return;
    }

    const auto* dos = reinterpret_cast<const IMAGE_DOS_HEADER*>(imageBase);
    if (dos->e_magic != IMAGE_DOS_SIGNATURE)
    {
        LogRed4ext("exe-string scan skipped: bad DOS header");
        return;
    }

    const auto* nt = reinterpret_cast<const IMAGE_NT_HEADERS*>(imageBase + dos->e_lfanew);
    if (nt->Signature != IMAGE_NT_SIGNATURE)
    {
        LogRed4ext("exe-string scan skipped: bad NT header");
        return;
    }

    const auto imageSize = static_cast<size_t>(nt->OptionalHeader.SizeOfImage);
    std::ostringstream header;
    header << "exe-string scan begin base=0x" << std::hex << imageBase << " size=0x" << imageSize << std::dec;
    LogRed4ext(header.str());

    constexpr std::array<const char*, 21> kNeedles{
        "GPSSystem/Tick",
        "GPSSystem",
        "GPSSettings",
        "RunGPSQuery",
        "UpdateGPSQuery",
        "gameuiGPSGameController",
        "OnSetHasGPSPortal",
        "IsGPSPortal",
        "m_cookedGpsData",
        "gameCookedGpsMappinData",
        "GetNavigationFunctionalTests",
        "TrafficSystem_Pathfinding",
        "TrafficSystem_PathMap",
        "Pathfinding Algorithm Failed",
        "No Path Found in Traffic",
        "PathFindingFailed",
        "worldTrafficLanePlayerGPSInfo",
        "worldTrafficPersistentResource",
        "worldTrafficConnectivityOutLane",
    };

    for (const auto* needle : kNeedles)
    {
        uint32_t matches = 0;
        uintptr_t cursor = imageBase;
        const uintptr_t end = imageBase + imageSize;

        while (cursor < end)
        {
            MEMORY_BASIC_INFORMATION mbi{};
            if (!VirtualQuery(reinterpret_cast<const void*>(cursor), &mbi, sizeof(mbi)))
            {
                break;
            }

            const auto regionBegin = reinterpret_cast<uintptr_t>(mbi.BaseAddress);
            const auto regionEnd = regionBegin + mbi.RegionSize;
            if (mbi.State == MEM_COMMIT && mbi.Type == MEM_IMAGE && IsReadablePage(mbi.Protect))
            {
                const auto scanBegin = std::max(regionBegin, imageBase);
                const auto scanEnd = std::min(regionEnd, end);
                if (scanBegin < scanEnd)
                {
                    ScanRegionForString(reinterpret_cast<const uint8_t*>(scanBegin), scanEnd - scanBegin, imageBase,
                                        needle, matches);
                }
            }

            if (regionEnd <= cursor)
            {
                break;
            }
            cursor = regionEnd;
        }

        if (matches == 0)
        {
            std::ostringstream line;
            line << "exe-string no match needle=\"" << needle << "\"";
            LogRed4ext(line.str());
        }
    }

    LogRed4ext("exe-string scan end");
}

void DetourGpsSystemTick(void* aThis, void* a2, void* a3, void* a4)
{
    if (gGpsTickLogCount < 8)
    {
        LogHookCall("GPSSystem/Tick", gGpsTickLogCount, aThis, a2, a3, a4);
        ++gGpsTickLogCount;
    }

    if (gOriginalGpsSystemTick)
    {
        gOriginalGpsSystemTick(aThis, a2, a3, a4);
    }
}

void LogJournalListeners(uint32_t aCallCount, void* aThis)
{
    constexpr uint32_t kMaxListenerDumps = 16;
    constexpr uint32_t kMaxListenersPerDump = 24;
    constexpr uint32_t kSuspiciousListenerCount = 512;

    if (gJournalTrackEntryListenerDumpCount >= kMaxListenerDumps || !aThis)
    {
        return;
    }

    uintptr_t listenerBase = 0;
    uint32_t listenerCount = 0;
    const auto objectAddress = reinterpret_cast<uintptr_t>(aThis);
    const auto hasBase = TryReadMemory(reinterpret_cast<const void*>(objectAddress + kJournalListenerArrayOffset),
                                       listenerBase);
    const auto hasCount = TryReadMemory(reinterpret_cast<const void*>(objectAddress + kJournalListenerCountOffset),
                                        listenerCount);

    std::ostringstream header;
    header << "journal-listeners dump=" << gJournalTrackEntryListenerDumpCount << " call=" << aCallCount
           << " hasBase=" << hasBase << " hasCount=" << hasCount << " count=" << listenerCount;
    AppendPointerWithRva(header, "base", reinterpret_cast<const void*>(listenerBase));
    LogRed4ext(header.str());

    ++gJournalTrackEntryListenerDumpCount;

    if (!hasBase || !hasCount || !listenerBase || listenerCount > kSuspiciousListenerCount)
    {
        return;
    }

    const auto cappedCount = std::min(listenerCount, kMaxListenersPerDump);
    for (uint32_t index = 0; index < cappedCount; ++index)
    {
        void* listener = nullptr;
        void* vtable = nullptr;
        void* callback28 = nullptr;
        void* callback30 = nullptr;
        void* callback50 = nullptr;

        const auto listenerAddress = reinterpret_cast<const void*>(listenerBase + index * sizeof(void*));
        const auto hasListener = TryReadMemory(listenerAddress, listener);
        const auto hasVtable = hasListener && TryReadMemory(listener, vtable);
        if (hasVtable && vtable)
        {
            const auto vtableAddress = reinterpret_cast<uintptr_t>(vtable);
            TryReadMemory(reinterpret_cast<const void*>(vtableAddress + 0x28), callback28);
            TryReadMemory(reinterpret_cast<const void*>(vtableAddress + 0x30), callback30);
            TryReadMemory(reinterpret_cast<const void*>(vtableAddress + 0x50), callback50);
        }

        std::ostringstream line;
        line << "journal-listener call=" << aCallCount << " index=" << index << " hasListener=" << hasListener
             << " hasVtable=" << hasVtable;
        AppendPointerWithRva(line, "listener", listener);
        AppendPointerWithRva(line, "vtable", vtable);
        AppendPointerWithRva(line, "vt28", callback28);
        AppendPointerWithRva(line, "vt30", callback30);
        AppendPointerWithRva(line, "vt50", callback50);
        LogRed4ext(line.str());
    }
}

void DetourJournalTrackEntry(void* aThis, void* aEntryHandle)
{
    constexpr uint32_t kMaxCalls = 96;

    const auto callCount = gJournalTrackEntryLogCount;
    if (callCount < kMaxCalls)
    {
        std::ostringstream line;
        line << "hook JournalManager::TrackEntry impl call=" << callCount;
        AppendPointerWithRva(line, "this", aThis);
        AppendPointerWithRva(line, "entryHandle", aEntryHandle);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());

        ++gJournalTrackEntryLogCount;
    }

    if (gOriginalJournalTrackEntry)
    {
        gOriginalJournalTrackEntry(aThis, aEntryHandle);
    }
}

void DispatchJournalListenerCallback(const char* aName, uint32_t& aCounter, JournalListenerCallbackFunc aOriginal,
                                     void* aListener, void* aEvent)
{
    constexpr uint32_t kMaxCalls = 64;

    if (aCounter < kMaxCalls)
    {
        LogJournalListenerContext(aName, aCounter, aListener, aEvent);
        ++aCounter;
    }

    if (aOriginal)
    {
        aOriginal(aListener, aEvent);
    }
}

void DetourJournalListenerObjectiveVt28(void* aListener, void* aEvent)
{
    DispatchJournalListenerCallback("JournalListener::Objective vt28", gJournalListenerObjectiveVt28LogCount,
                                    gOriginalJournalListenerObjectiveVt28, aListener, aEvent);
}

void DetourJournalListenerObjectiveVt30(void* aListener, void* aEvent)
{
    DispatchJournalListenerCallback("JournalListener::Objective vt30", gJournalListenerObjectiveVt30LogCount,
                                    gOriginalJournalListenerObjectiveVt30, aListener, aEvent);
}

void DetourJournalListenerRouteVt28(void* aListener, void* aEvent)
{
    DispatchJournalListenerCallback("JournalListener::Route vt28", gJournalListenerRouteVt28LogCount,
                                    gOriginalJournalListenerRouteVt28, aListener, aEvent);
}

void DetourJournalListenerRouteVt30(void* aListener, void* aEvent)
{
    DispatchJournalListenerCallback("JournalListener::Route vt30", gJournalListenerRouteVt30LogCount,
                                    gOriginalJournalListenerRouteVt30, aListener, aEvent);
}

void DetourJournalListenerRouteAltVt28(void* aListener, void* aEvent)
{
    DispatchJournalListenerCallback("JournalListener::RouteAlt vt28", gJournalListenerRouteAltVt28LogCount,
                                    gOriginalJournalListenerRouteAltVt28, aListener, aEvent);
}

void DetourJournalListenerRouteAltVt30(void* aListener, void* aEvent)
{
    DispatchJournalListenerCallback("JournalListener::RouteAlt vt30", gJournalListenerRouteAltVt30LogCount,
                                    gOriginalJournalListenerRouteAltVt30, aListener, aEvent);
}

void DetourJournalListenerUiVt28(void* aListener, void* aEvent)
{
    DispatchJournalListenerCallback("JournalListener::UI vt28", gJournalListenerUiVt28LogCount,
                                    gOriginalJournalListenerUiVt28, aListener, aEvent);
}

void DetourJournalListenerUiVt30(void* aListener, void* aEvent)
{
    DispatchJournalListenerCallback("JournalListener::UI vt30", gJournalListenerUiVt30LogCount,
                                    gOriginalJournalListenerUiVt30, aListener, aEvent);
}

void DetourJournalListenerTrackerVt28(void* aListener, void* aEvent)
{
    DispatchJournalListenerCallback("JournalListener::Tracker vt28", gJournalListenerTrackerVt28LogCount,
                                    gOriginalJournalListenerTrackerVt28, aListener, aEvent);
}

bool DetourJournalRouteBridge(void* aThis, void* aEntryHandle)
{
    constexpr uint32_t kMaxCalls = 64;

    const auto callCount = gJournalRouteBridgeLogCount;
    const auto shouldLog = callCount < kMaxCalls;
    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook JournalRouteBridge::Update candidate call=" << callCount;
        AppendPointerWithRva(line, "this", aThis);
        AppendPointerWithRva(line, "entryHandle", aEntryHandle);
        AppendReturnRva(line, __builtin_return_address(0));
        AppendPointerField(line, "field10", aThis, 0x10);
        AppendPointerField(line, "field18", aThis, 0x18);
        AppendPointerField(line, "field20", aThis, 0x20);
        AppendByteField(line, "field30", aThis, 0x30);
        AppendUint32Field(line, "field34", aThis, 0x34);
        AppendUint32Field(line, "field38", aThis, 0x38);
        LogRed4ext(line.str());
        ++gJournalRouteBridgeLogCount;
    }

    bool result = false;
    if (gOriginalJournalRouteBridge)
    {
        result = gOriginalJournalRouteBridge(aThis, aEntryHandle);
    }

    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook JournalRouteBridge::Update candidate result call=" << callCount << " value="
             << static_cast<uint32_t>(result);
        LogRed4ext(line.str());
    }

    return result;
}

void DetourMappinRouteEventEnqueue(void* aSystem, uint32_t aRouteId, uint8_t aActive)
{
    constexpr uint32_t kMaxCalls = 64;

    if (gMappinRouteEventEnqueueLogCount < kMaxCalls)
    {
        const auto callCount = gMappinRouteEventEnqueueLogCount;
        std::ostringstream line;
        line << "hook MappinSystem::RouteEventEnqueue slot240 call=" << callCount << " routeId=" << aRouteId
             << " active=" << static_cast<uint32_t>(aActive);
        AppendPointerWithRva(line, "system", aSystem);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
        LogMappinRouteCollections("enqueue-before", callCount, aSystem);
        ++gMappinRouteEventEnqueueLogCount;
    }

    if (gOriginalMappinRouteEventEnqueue)
    {
        gOriginalMappinRouteEventEnqueue(aSystem, aRouteId, aActive);
    }
}

void DetourMappinRouteEventHandler(void* aEvent, void* aRouteHandle)
{
    constexpr uint32_t kMaxCalls = 64;

    if (gMappinRouteEventHandlerLogCount < kMaxCalls)
    {
        const auto callCount = gMappinRouteEventHandlerLogCount;
        void* routeOwner = ReadPointerField(aRouteHandle, 0);
        const auto routeKey = reinterpret_cast<uint64_t>(ReadPointerField(routeOwner, 0x40));

        std::ostringstream line;
        line << "hook MappinRouteEvent::Handle call=" << callCount;
        AppendPointerWithRva(line, "event", aEvent);
        AppendPointerWithRva(line, "routeHandle", aRouteHandle);
        AppendPointerWithRva(line, "routeOwner", routeOwner);
        AppendHexValue(line, "routeKey", routeKey);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
        LogRouteEventFields("handler-before", callCount, aEvent);
        ++gMappinRouteEventHandlerLogCount;
    }

    if (gOriginalMappinRouteEventHandler)
    {
        gOriginalMappinRouteEventHandler(aEvent, aRouteHandle);
    }
}

void DetourMappinRouteActivate(void* aSystem, uint64_t aRouteKey)
{
    constexpr uint32_t kMaxCalls = 64;

    if (gMappinRouteActivateLogCount < kMaxCalls)
    {
        const auto callCount = gMappinRouteActivateLogCount;
        std::ostringstream line;
        line << "hook MappinSystem::RouteActivate call=" << callCount;
        AppendPointerWithRva(line, "system", aSystem);
        AppendHexValue(line, "routeKey", aRouteKey);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
        LogMappinRouteCollections("activate-before", callCount, aSystem);
        ++gMappinRouteActivateLogCount;
    }

    if (gOriginalMappinRouteActivate)
    {
        gOriginalMappinRouteActivate(aSystem, aRouteKey);
    }
}

void DetourMappinRouteDeactivate(void* aSystem, uint64_t aRouteKey)
{
    constexpr uint32_t kMaxCalls = 64;

    if (gMappinRouteDeactivateLogCount < kMaxCalls)
    {
        const auto callCount = gMappinRouteDeactivateLogCount;
        std::ostringstream line;
        line << "hook MappinSystem::RouteDeactivate call=" << callCount;
        AppendPointerWithRva(line, "system", aSystem);
        AppendHexValue(line, "routeKey", aRouteKey);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
        LogMappinRouteCollections("deactivate-before", callCount, aSystem);
        ++gMappinRouteDeactivateLogCount;
    }

    if (gOriginalMappinRouteDeactivate)
    {
        gOriginalMappinRouteDeactivate(aSystem, aRouteKey);
    }
}

void DetourRouteBuildCandidate(void* aThis, uint32_t aRouteId, uint32_t aAltRouteId, uint32_t aPreviousRouteId,
                               void* aRoutePositionData, void* aPrimaryDescriptor, void* aSecondaryDescriptor,
                               void* aRouteData88, void* aRouteData108, void* aRouteData114, uint8_t aFlag,
                               uint32_t aMode)
{
    constexpr uint32_t kMaxCalls = 64;

    if (gRouteBuildCandidateLogCount < kMaxCalls)
    {
        std::ostringstream line;
        line << "hook RouteBuildCandidate 0x5625a4 call=" << gRouteBuildCandidateLogCount
             << " routeId=" << aRouteId << " altRouteId=" << aAltRouteId
             << " previousRouteId=" << aPreviousRouteId << " flag=" << static_cast<uint32_t>(aFlag)
             << " mode=" << aMode;
        AppendPointerWithRva(line, "this", aThis);
        AppendPointerWithRva(line, "routePositionData", aRoutePositionData);
        AppendPointerWithRva(line, "primaryDescriptor", aPrimaryDescriptor);
        AppendPointerWithRva(line, "secondaryDescriptor", aSecondaryDescriptor);
        AppendPointerWithRva(line, "routeData88", aRouteData88);
        AppendPointerWithRva(line, "routeData108", aRouteData108);
        AppendPointerWithRva(line, "routeData114", aRouteData114);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
        ++gRouteBuildCandidateLogCount;
    }

    if (gOriginalRouteBuildCandidate)
    {
        gOriginalRouteBuildCandidate(aThis, aRouteId, aAltRouteId, aPreviousRouteId, aRoutePositionData,
                                     aPrimaryDescriptor, aSecondaryDescriptor, aRouteData88, aRouteData108,
                                     aRouteData114, aFlag, aMode);
    }
}

void DispatchRouteObserverCallback(const char* aName, uint32_t& aCounter, RouteObserverCallbackFunc aOriginal,
                                   void* aObserver, void* aRouteEntry)
{
    constexpr uint32_t kMaxCalls = 96;

    if (aCounter < kMaxCalls)
    {
        LogRouteObserverContext(aName, aCounter, aObserver, aRouteEntry);
        ++aCounter;
    }

    if (aOriginal)
    {
        aOriginal(aObserver, aRouteEntry);
    }
}

void DetourRouteObserver0Activate(void* aObserver, void* aRouteEntry)
{
    DispatchRouteObserverCallback("RouteObserver0::Activate slot48", gRouteObserver0ActivateLogCount,
                                  gOriginalRouteObserver0Activate, aObserver, aRouteEntry);
}

void DetourRouteObserver1Activate(void* aObserver, void* aRouteEntry)
{
    DispatchRouteObserverCallback("RouteObserver1::Activate slot48", gRouteObserver1ActivateLogCount,
                                  gOriginalRouteObserver1Activate, aObserver, aRouteEntry);
}

void DetourRouteObserver23Activate(void* aObserver, void* aRouteEntry)
{
    DispatchRouteObserverCallback("RouteObserver23::Activate slot48", gRouteObserver23ActivateLogCount,
                                  gOriginalRouteObserver23Activate, aObserver, aRouteEntry);
}

void DetourRouteObserver0Deactivate(void* aObserver, void* aRouteEntry)
{
    DispatchRouteObserverCallback("RouteObserver0::Deactivate slot50", gRouteObserver0DeactivateLogCount,
                                  gOriginalRouteObserver0Deactivate, aObserver, aRouteEntry);
}

void DetourRouteObserver1Deactivate(void* aObserver, void* aRouteEntry)
{
    DispatchRouteObserverCallback("RouteObserver1::Deactivate slot50", gRouteObserver1DeactivateLogCount,
                                  gOriginalRouteObserver1Deactivate, aObserver, aRouteEntry);
}

void DetourRouteObserver23Deactivate(void* aObserver, void* aRouteEntry)
{
    DispatchRouteObserverCallback("RouteObserver23::Deactivate slot50", gRouteObserver23DeactivateLogCount,
                                  gOriginalRouteObserver23Deactivate, aObserver, aRouteEntry);
}

void DetourFrameMappinPathWrapper(void* aThis, void* aStack, void* aResult)
{
    if (gFrameMappinPathWrapperLogCount < 64)
    {
        LogScriptNativeCall("FrameMappinPathWrapper", gFrameMappinPathWrapperLogCount, aThis, aStack, aResult);
        ++gFrameMappinPathWrapperLogCount;
    }

    InspectAndHookMappinSystem(aThis);

    if (gOriginalFrameMappinPathWrapper)
    {
        gOriginalFrameMappinPathWrapper(aThis, aStack, aResult);
    }
}

void DetourFrameMappinPathCore(void* aThis, uint32_t aPathKind, void* a3, void* aPathRect)
{
    if (gFrameMappinPathCoreLogCount < 64)
    {
        LogFrameMappinPathCoreCall(gFrameMappinPathCoreLogCount, aThis, aPathKind, a3, aPathRect);
        ++gFrameMappinPathCoreLogCount;
    }

    InspectAndHookMappinSystem(aThis);

    if (gOriginalFrameMappinPathCore)
    {
        gOriginalFrameMappinPathCore(aThis, aPathKind, a3, aPathRect);
    }
}

void DetourSetSelectedMappinWrapper(void* aThis, void* aStack, void* aResult)
{
    if (gSetSelectedMappinWrapperLogCount < 64)
    {
        LogScriptNativeCall("SetSelectedMappinWrapper", gSetSelectedMappinWrapperLogCount, aThis, aStack, aResult);
        ++gSetSelectedMappinWrapperLogCount;
    }

    InspectAndHookMappinSystem(aThis);

    if (gOriginalSetSelectedMappinWrapper)
    {
        gOriginalSetSelectedMappinWrapper(aThis, aStack, aResult);
    }
}

void DetourSetSelectedMappinByIdWrapper(void* aThis, void* aStack, void* aResult)
{
    if (gSetSelectedMappinByIdWrapperLogCount < 64)
    {
        LogScriptNativeCall("SetSelectedMappinByIdWrapper", gSetSelectedMappinByIdWrapperLogCount, aThis, aStack,
                            aResult);
        ++gSetSelectedMappinByIdWrapperLogCount;
    }

    InspectAndHookMappinSystem(aThis);

    if (gOriginalSetSelectedMappinByIdWrapper)
    {
        gOriginalSetSelectedMappinByIdWrapper(aThis, aStack, aResult);
    }
}

void DetourSetSelectedMappinByPositionWrapper(void* aThis, void* aStack, void* aResult)
{
    if (gSetSelectedMappinByPositionWrapperLogCount < 64)
    {
        LogScriptNativeCall("SetSelectedMappinByPositionWrapper", gSetSelectedMappinByPositionWrapperLogCount, aThis,
                            aStack, aResult);
        ++gSetSelectedMappinByPositionWrapperLogCount;
    }

    InspectAndHookMappinSystem(aThis);

    if (gOriginalSetSelectedMappinByPositionWrapper)
    {
        gOriginalSetSelectedMappinByPositionWrapper(aThis, aStack, aResult);
    }
}

void DetourSetSelectedMappinCore(void* aThis, void* aMappinHandle)
{
    if (gSetSelectedMappinCoreLogCount < 64)
    {
        LogSetSelectedMappinCoreCall(gSetSelectedMappinCoreLogCount, aThis, aMappinHandle);
        ++gSetSelectedMappinCoreLogCount;
    }

    InspectAndHookMappinSystem(aThis);

    if (gOriginalSetSelectedMappinCore)
    {
        gOriginalSetSelectedMappinCore(aThis, aMappinHandle);
    }
}

void DetourTrackCustomPositionMappinWrapper(void* aThis, void* aStack, void* aResult)
{
    if (gTrackCustomPositionMappinWrapperLogCount < 64)
    {
        LogScriptNativeCall("TrackCustomPositionMappinWrapper", gTrackCustomPositionMappinWrapperLogCount, aThis,
                            aStack, aResult);
        ++gTrackCustomPositionMappinWrapperLogCount;
    }

    InspectAndHookMappinSystem(aThis);

    if (gOriginalTrackCustomPositionMappinWrapper)
    {
        gOriginalTrackCustomPositionMappinWrapper(aThis, aStack, aResult);
    }
}

void DetourTrackCustomPositionMappinCore(void* aThis)
{
    if (gTrackCustomPositionMappinCoreLogCount < 64)
    {
        LogTrackCustomPositionMappinCoreCall(gTrackCustomPositionMappinCoreLogCount, aThis);
        ++gTrackCustomPositionMappinCoreLogCount;
    }

    InspectAndHookMappinSystem(aThis);

    if (gOriginalTrackCustomPositionMappinCore)
    {
        gOriginalTrackCustomPositionMappinCore(aThis);
    }
}

void DetourUntrackCustomPositionMappinWrapper(void* aThis, void* aStack, void* aResult)
{
    if (gUntrackCustomPositionMappinWrapperLogCount < 64)
    {
        LogScriptNativeCall("UntrackCustomPositionMappinWrapper", gUntrackCustomPositionMappinWrapperLogCount, aThis,
                            aStack, aResult);
        ++gUntrackCustomPositionMappinWrapperLogCount;
    }

    InspectAndHookMappinSystem(aThis);

    if (gOriginalUntrackCustomPositionMappinWrapper)
    {
        gOriginalUntrackCustomPositionMappinWrapper(aThis, aStack, aResult);
    }
}

void DetourTrackMappinWrapper(void* aThis, void* aStack, void* aResult)
{
    if (gTrackMappinWrapperLogCount < 64)
    {
        LogScriptNativeCall("TrackMappinWrapper", gTrackMappinWrapperLogCount, aThis, aStack, aResult);
        ++gTrackMappinWrapperLogCount;
    }

    if (gOriginalTrackMappinWrapper)
    {
        gOriginalTrackMappinWrapper(aThis, aStack, aResult);
    }
}

void DetourTrackMappinCore(void* aThis, void* aMappinHandle)
{
    if (gTrackMappinCoreLogCount < 64)
    {
        LogTrackMappinCoreCall(gTrackMappinCoreLogCount, aThis, aMappinHandle);
        ++gTrackMappinCoreLogCount;
    }

    InspectAndHookMappinSystem(aThis);

    if (gOriginalTrackMappinCore)
    {
        gOriginalTrackMappinCore(aThis, aMappinHandle);
    }
}

void DetourSetMappinTrackingAlternativeWrapper(void* aThis, void* aStack, void* aResult)
{
    if (gSetMappinTrackingAlternativeLogCount < 64)
    {
        LogScriptNativeCall("SetMappinTrackingAlternativeWrapper", gSetMappinTrackingAlternativeLogCount, aThis,
                            aStack, aResult);
        ++gSetMappinTrackingAlternativeLogCount;
    }

    if (gOriginalSetMappinTrackingAlternativeWrapper)
    {
        gOriginalSetMappinTrackingAlternativeWrapper(aThis, aStack, aResult);
    }
}

void DetourMappinUpdateCustomPosition(void* aSystem, void* aHandle, void* aPositionData)
{
    if (gMappinUpdateCustomPositionLogCount < 32)
    {
        std::ostringstream line;
        line << "hook MappinSystem::UpdateCustomPosition slot1f0 call=" << gMappinUpdateCustomPositionLogCount;
        AppendPointerWithRva(line, "system", aSystem);
        AppendPointerWithRva(line, "handle", aHandle);
        AppendPointerWithRva(line, "positionData", aPositionData);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
        ++gMappinUpdateCustomPositionLogCount;
    }

    if (gOriginalMappinUpdateCustomPosition)
    {
        gOriginalMappinUpdateCustomPosition(aSystem, aHandle, aPositionData);
    }
}

void DetourMappinSetTracked(void* aSystem, void* aMappinId)
{
    if (gMappinSetTrackedLogCount < 64)
    {
        std::ostringstream line;
        line << "hook MappinSystem::SetTracked slot220 call=" << gMappinSetTrackedLogCount;
        AppendPointerWithRva(line, "system", aSystem);
        AppendPointerWithRva(line, "mappinId", aMappinId);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
        ++gMappinSetTrackedLogCount;
    }

    if (gOriginalMappinSetTracked)
    {
        gOriginalMappinSetTracked(aSystem, aMappinId);
    }
}

void DetourMappinClearTracked(void* aSystem)
{
    if (gMappinClearTrackedLogCount < 64)
    {
        std::ostringstream line;
        line << "hook MappinSystem::ClearTracked slot228 call=" << gMappinClearTrackedLogCount;
        AppendPointerWithRva(line, "system", aSystem);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
        ++gMappinClearTrackedLogCount;
    }

    if (gOriginalMappinClearTracked)
    {
        gOriginalMappinClearTracked(aSystem);
    }
}

void DetourMappinFramePath(void* aSystem, uint32_t aPathKind, void* aResult)
{
    if (gMappinFramePathLogCount < 32)
    {
        std::ostringstream line;
        line << "hook MappinSystem::FramePath slot280 call=" << gMappinFramePathLogCount
             << " pathKind=" << aPathKind;
        AppendPointerWithRva(line, "system", aSystem);
        AppendPointerWithRva(line, "result", aResult);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
        ++gMappinFramePathLogCount;
    }

    if (gOriginalMappinFramePath)
    {
        gOriginalMappinFramePath(aSystem, aPathKind, aResult);
    }
}

void* DetourMappinPositionQuery(void* aSystem, void* a2, void* a3, void* a4, void* a5)
{
    const auto shouldLog = gMappinPositionQueryLogCount < 32;
    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook MappinSystem::PositionQuery slot2e0 call=" << gMappinPositionQueryLogCount;
        AppendPointerWithRva(line, "system", aSystem);
        AppendPointerWithRva(line, "a2", a2);
        AppendPointerWithRva(line, "a3", a3);
        AppendPointerWithRva(line, "a4", a4);
        AppendPointerWithRva(line, "a5", a5);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
        ++gMappinPositionQueryLogCount;
    }

    void* result = nullptr;
    if (gOriginalMappinPositionQuery)
    {
        result = gOriginalMappinPositionQuery(aSystem, a2, a3, a4, a5);
    }

    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook MappinSystem::PositionQuery slot2e0 result";
        AppendPointerWithRva(line, "value", result);
        LogRed4ext(line.str());
    }

    return result;
}

void* DetourMappinCreateCustomPosition(void* aSystem, void* aName, void* aParams, void* aPositionData)
{
    const auto shouldLog = gMappinCreateCustomPositionLogCount < 32;
    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook MappinSystem::CreateCustomPosition slot2f0 call=" << gMappinCreateCustomPositionLogCount;
        AppendPointerWithRva(line, "system", aSystem);
        AppendPointerWithRva(line, "name", aName);
        AppendPointerWithRva(line, "params", aParams);
        AppendPointerWithRva(line, "positionData", aPositionData);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
        ++gMappinCreateCustomPositionLogCount;
    }

    void* result = nullptr;
    if (gOriginalMappinCreateCustomPosition)
    {
        result = gOriginalMappinCreateCustomPosition(aSystem, aName, aParams, aPositionData);
    }

    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook MappinSystem::CreateCustomPosition slot2f0 result";
        AppendPointerWithRva(line, "value", result);
        LogRed4ext(line.str());
    }

    return result;
}

void DetourMappinReleaseQuery(void* aSystem, void* aHandle)
{
    if (gMappinReleaseQueryLogCount < 32)
    {
        std::ostringstream line;
        line << "hook MappinSystem::ReleaseQuery slot368 call=" << gMappinReleaseQueryLogCount;
        AppendPointerWithRva(line, "system", aSystem);
        AppendPointerWithRva(line, "handle", aHandle);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
        ++gMappinReleaseQueryLogCount;
    }

    if (gOriginalMappinReleaseQuery)
    {
        gOriginalMappinReleaseQuery(aSystem, aHandle);
    }
}

void* DetourMappinActiveRoute(void* aSystem)
{
    const auto shouldLog = gMappinActiveRouteLogCount < 32;
    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook MappinSystem::ActiveRoute slot3a8 call=" << gMappinActiveRouteLogCount;
        AppendPointerWithRva(line, "system", aSystem);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
        ++gMappinActiveRouteLogCount;
    }

    void* result = nullptr;
    if (gOriginalMappinActiveRoute)
    {
        result = gOriginalMappinActiveRoute(aSystem);
    }

    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook MappinSystem::ActiveRoute slot3a8 result";
        AppendPointerWithRva(line, "value", result);
        LogRed4ext(line.str());
    }

    return result;
}

bool DetourRunGpsQueryBody(void* aThis, void* aStart, void* aEnd, int32_t* aOutQueryId)
{
    constexpr uint32_t kMaxCalls = 96;

    const auto callCount = gRunGpsQueryBodyLogCount;
    const auto shouldLog = callCount < kMaxCalls;
    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook RunGPSQuery body 0x29bd128 call=" << callCount;
        AppendPointerWithRva(line, "this", aThis);
        AppendPointerWithRva(line, "outQueryId", aOutQueryId);
        AppendVector4Pointer(line, "start", aStart);
        AppendVector4Pointer(line, "end", aEnd);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
        ++gRunGpsQueryBodyLogCount;
    }

    bool result = false;
    if (gOriginalRunGpsQueryBody)
    {
        result = gOriginalRunGpsQueryBody(aThis, aStart, aEnd, aOutQueryId);
    }

    if (shouldLog)
    {
        int32_t queryId = 0;
        const auto hasQueryId = aOutQueryId && TryReadMemory(aOutQueryId, queryId);

        std::ostringstream line;
        line << "hook RunGPSQuery body result call=" << callCount << " value=" << static_cast<uint32_t>(result)
             << " hasQueryId=" << hasQueryId;
        if (hasQueryId)
        {
            line << " queryId=" << queryId;
        }
        LogRed4ext(line.str());
    }

    return result;
}

bool DetourUpdateGpsQueryBody(void* aThis, uint32_t aQueryId, void* aOutPoints, void* aOutError)
{
    constexpr uint32_t kMaxCalls = 128;

    const auto callCount = gUpdateGpsQueryBodyLogCount;
    const auto shouldLog = callCount < kMaxCalls;
    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook UpdateGPSQuery body 0x29bd254 call=" << callCount << " queryId=" << aQueryId;
        AppendPointerWithRva(line, "this", aThis);
        AppendPointerWithRva(line, "outPoints", aOutPoints);
        AppendPointerWithRva(line, "outError", aOutError);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
        ++gUpdateGpsQueryBodyLogCount;
    }

    bool result = false;
    if (gOriginalUpdateGpsQueryBody)
    {
        result = gOriginalUpdateGpsQueryBody(aThis, aQueryId, aOutPoints, aOutError);
    }

    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook UpdateGPSQuery body result call=" << callCount << " value=" << static_cast<uint32_t>(result);
        AppendPathBufferSummary(line, "cachedPath", ReadPointerField(aThis, 0x58));
        LogRed4ext(line.str());
    }

    return result;
}

int32_t DetourGpsQuerySubmit(void* aManager, void* aQuery)
{
    constexpr uint32_t kMaxCalls = 128;

    const auto returnAddress = __builtin_return_address(0);
    uintptr_t returnRva = 0;
    TryGetImageRva(returnAddress, returnRva);

    const auto callCount = gGpsQuerySubmitLogCount++;
    uint32_t queryKind = 0;
    const auto hasQueryKind = aQuery && TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aQuery) + 0xCC),
                                                     queryKind);
    const auto shouldTraceResolver =
        returnRva == kGpsMapRouteSubmitReturnRva && hasQueryKind && queryKind == 5;
    const auto shouldLog =
        callCount < kMaxCalls &&
        (kEnableGpsQueryLifecycleHooks || (returnRva == kGpsMapRouteSubmitReturnRva && hasQueryKind && queryKind == 5));
    if (shouldTraceResolver)
    {
        ExtendGpsResolveTraceWindow(GetElapsedMs());
    }

    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook GPSQuerySubmit 0x70a42c call=" << callCount;
        AppendPointerWithRva(line, "manager", aManager);
        AppendPointerWithRva(line, "query", aQuery);
        AppendUint32Field(line, "query_f00", aQuery, 0x00);
        AppendUint32Field(line, "query_f04", aQuery, 0x04);
        AppendUint32Field(line, "query_f08", aQuery, 0x08);
        AppendUint32Field(line, "query_f0c", aQuery, 0x0C);
        AppendUint32Field(line, "query_fcc", aQuery, 0xCC);
        AppendVector4Field(line, "query_pos58", aQuery, 0x58);
        if (shouldTraceResolver)
        {
            line << " resolveTraceWindowMs=" << kGpsResolveTraceWindowMs;
        }
        AppendReturnRva(line, returnAddress);
        LogRed4ext(line.str());
    }

    int32_t result = -1;
    if (gOriginalGpsQuerySubmit)
    {
        result = gOriginalGpsQuerySubmit(aManager, aQuery);
    }

    if (result >= 0)
    {
        TrackGpsQuery(static_cast<uint32_t>(result), aManager, returnRva, callCount);
    }

    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook GPSQuerySubmit result call=" << callCount << " queryId=" << result;
        AppendUint32Field(line, "manager_nextIdD4", aManager, 0xD4);
        LogRed4ext(line.str());
    }

    return result;
}

void DetourGpsQueryDispatch(void* aRuntimeQuery, void* aTargetPosition, void* aArg3, void* aArg4)
{
    constexpr uint32_t kMaxCalls = 128;

    if (gGpsQueryDispatchLogCount < kMaxCalls)
    {
        std::ostringstream line;
        line << "hook GPSQueryDispatch 0x70a570 call=" << gGpsQueryDispatchLogCount;
        AppendPointerWithRva(line, "runtimeQuery", aRuntimeQuery);
        AppendPointerWithRva(line, "arg3", aArg3);
        AppendPointerWithRva(line, "arg4", aArg4);
        AppendUint32Field(line, "runtimeQuery_state138", aRuntimeQuery, 0x138);
        AppendUint32Field(line, "runtimeQuery_bank13c", aRuntimeQuery, 0x13C);
        AppendVector4Pointer(line, "target", aTargetPosition);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
        ++gGpsQueryDispatchLogCount;
    }

    if (gOriginalGpsQueryDispatch)
    {
        gOriginalGpsQueryDispatch(aRuntimeQuery, aTargetPosition, aArg3, aArg4);
    }
}

bool DetourGpsQueryResultFetch(void* aManager, uint32_t aQueryId, void* aOutPath)
{
    constexpr uint32_t kMaxUntrackedSamples = 32;
    constexpr uint32_t kMaxTrackedSamplesPerQuery = 24;

    const auto callCount = gGpsQueryResultFetchLogCount++;
    const auto returnAddress = __builtin_return_address(0);

    bool result = false;
    if (gOriginalGpsQueryResultFetch)
    {
        result = gOriginalGpsQueryResultFetch(aManager, aQueryId, aOutPath);
    }

    TrackedGpsQuery trackedQuery{};
    uint32_t perQueryCall = 0;
    const auto isTracked = NoteTrackedGpsQueryResultFetch(aQueryId, aManager, trackedQuery, perQueryCall);
    const auto isMapRouteQuery = isTracked && trackedQuery.submitReturnRva == kGpsMapRouteSubmitReturnRva;
    const auto shouldLog =
        kEnableGpsQueryLifecycleHooks
            ? ((isTracked && (perQueryCall < kMaxTrackedSamplesPerQuery || result)) ||
               (!isTracked && callCount < kMaxUntrackedSamples && aQueryId < 2))
            : (result && isMapRouteQuery);

    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook GPSQueryResultFetch 0x7094b8 call=" << callCount << " queryId=" << aQueryId
             << " tracked=" << static_cast<uint32_t>(isTracked) << " perQueryCall=" << perQueryCall
             << " value=" << static_cast<uint32_t>(result);
        if (isTracked)
        {
            AppendTrackedGpsQuery(line, trackedQuery);
        }
        AppendPointerWithRva(line, "manager", aManager);
        AppendPointerWithRva(line, "outPath", aOutPath);
        AppendReturnRva(line, returnAddress);
        AppendPathBufferSummary(line, "outPath", aOutPath);
        if (result && isMapRouteQuery)
        {
            AppendGpsResultObjectSummary(line, "gpsResult", aOutPath);
        }
        LogRed4ext(line.str());
    }

    return result;
}

int32_t DetourGpsQueryStatus(void* aManager, uint32_t aQueryId)
{
    constexpr uint32_t kMaxUntrackedSamples = 32;
    constexpr uint32_t kMaxTrackedSamplesPerQuery = 24;

    const auto callCount = gGpsQueryStatusLogCount++;
    const auto returnAddress = __builtin_return_address(0);

    int32_t result = 0;
    if (gOriginalGpsQueryStatus)
    {
        result = gOriginalGpsQueryStatus(aManager, aQueryId);
    }

    TrackedGpsQuery trackedQuery{};
    uint32_t perQueryCall = 0;
    const auto isTracked = NoteTrackedGpsQueryStatus(aQueryId, aManager, trackedQuery, perQueryCall);
    const auto shouldLog = (isTracked && (perQueryCall < kMaxTrackedSamplesPerQuery || result != 0)) ||
                           (!isTracked && callCount < kMaxUntrackedSamples && aQueryId < 2);

    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook GPSQueryStatus 0xaa5704 call=" << callCount << " queryId=" << aQueryId
             << " tracked=" << static_cast<uint32_t>(isTracked) << " perQueryCall=" << perQueryCall
             << " value=" << result;
        if (isTracked)
        {
            AppendTrackedGpsQuery(line, trackedQuery);
        }
        AppendPointerWithRva(line, "manager", aManager);
        AppendReturnRva(line, returnAddress);
        LogRed4ext(line.str());
    }

    return result;
}

void DetourGpsDispatchAdvance(void* aRouteSystem, void* aRuntimeQuery, void* aOutTicket, void* aTargetPosition,
                              void* aSourcePosition)
{
    constexpr uint32_t kMaxCalls = 128;

    const auto callCount = NextGpsAsyncLogCall(gGpsDispatchAdvanceLogCount);
    const auto shouldLog = callCount < kMaxCalls;
    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook GPSDispatchAdvance 0x8d17d8 call=" << callCount;
        AppendRouteSystemSummary(line, "routeSystem", aRouteSystem);
        AppendPointerWithRva(line, "runtimeQuery", aRuntimeQuery);
        AppendPointerWithRva(line, "outTicket", aOutTicket);
        AppendUint32Field(line, "runtimeQuery_state138", aRuntimeQuery, 0x138);
        AppendUint32Field(line, "runtimeQuery_bank13c", aRuntimeQuery, 0x13C);
        AppendVector4Pointer(line, "target", aTargetPosition);
        AppendVector4Pointer(line, "source", aSourcePosition);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
    }

    if (gOriginalGpsDispatchAdvance)
    {
        gOriginalGpsDispatchAdvance(aRouteSystem, aRuntimeQuery, aOutTicket, aTargetPosition, aSourcePosition);
    }

    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook GPSDispatchAdvance result call=" << callCount;
        AppendUint32Field(line, "outTicket_value", aOutTicket, 0x00);
        AppendRouteSystemSummary(line, "routeSystem", aRouteSystem);
        LogRed4ext(line.str());
    }
}

void* DetourGpsTrafficResultTake(void* aTrafficService, void* aOutResult, uint32_t aTicket, void* aOutMeta)
{
    constexpr uint32_t kMaxCalls = 128;

    const auto callCount = NextGpsAsyncLogCall(gGpsTrafficResultTakeLogCount);
    const auto shouldLog = callCount < kMaxCalls;
    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook GPSTrafficResultTake 0x709d5c call=" << callCount << " ticket=" << aTicket;
        AppendPointerWithRva(line, "trafficService", aTrafficService);
        AppendTrafficResultHolderSummary(line, "outResultBefore", aOutResult);
        AppendPointerWithRva(line, "outMeta", aOutMeta);
        AppendBytesPointer(line, "outMeta_raw10_before", aOutMeta, 0x10);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
    }

    void* result = nullptr;
    if (gOriginalGpsTrafficResultTake)
    {
        result = gOriginalGpsTrafficResultTake(aTrafficService, aOutResult, aTicket, aOutMeta);
    }

    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook GPSTrafficResultTake result call=" << callCount;
        AppendPointerWithRva(line, "value", result);
        AppendTrafficResultHolderSummary(line, "outResultAfter", aOutResult);
        AppendPointerWithRva(line, "outMeta", aOutMeta);
        AppendBytesPointer(line, "outMeta_raw10_after", aOutMeta, 0x10);
        LogRed4ext(line.str());
    }

    return result;
}

void DetourGpsRoutePostprocess(void* aOutPath, void* aTrafficResult, void* aRouteRecords, float aScale)
{
    constexpr uint32_t kMaxCalls = 128;

    const auto callCount = NextGpsAsyncLogCall(gGpsRoutePostprocessLogCount);
    const auto shouldLog = callCount < kMaxCalls;
    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook GPSRoutePostprocess 0x44830c call=" << callCount << " scale=" << aScale;
        AppendPointerWithRva(line, "outPath", aOutPath);
        AppendTrafficResultSummary(line, "trafficResult", aTrafficResult);
        AppendPointerWithRva(line, "routeRecords", aRouteRecords);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
    }

    if (gOriginalGpsRoutePostprocess)
    {
        gOriginalGpsRoutePostprocess(aOutPath, aTrafficResult, aRouteRecords, aScale);
    }

    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook GPSRoutePostprocess result call=" << callCount;
        AppendGpsResultObjectSummary(line, "outPath", aOutPath);
        LogRed4ext(line.str());
    }
}

const char* TrafficLaneCategory(uint16_t aFlags)
{
    if ((aFlags & kTrafficLaneFlagHighway) != 0)
    {
        return "highway";
    }
    if ((aFlags & kTrafficLaneFlagRoad) != 0)
    {
        return "road";
    }
    if ((aFlags & kTrafficLaneFlagPavement) != 0)
    {
        return "pavement";
    }
    if ((aFlags & kTrafficLaneFlagGpsOnly) != 0)
    {
        return "gpsonly";
    }

    return "other";
}

float GpsSolverNodeWeightMultiplier(uint16_t aFlags)
{
    if ((aFlags & kTrafficLaneFlagHighway) != 0)
    {
        return gGpsSolverHighwayMultiplier.load(std::memory_order_relaxed);
    }
    if ((aFlags & kTrafficLaneFlagGpsOnly) != 0)
    {
        return kGpsSolverGpsOnlyMultiplier;
    }
    if ((aFlags & kTrafficLaneFlagPavement) != 0)
    {
        return kGpsSolverPavementMultiplier;
    }

    return 1.0f;
}

float ReadGpsSolverNonRoadMultiplier(uint32_t aRetry)
{
    if (aRetry < kGpsSolverNonRoadMultipliers.size())
    {
        return kGpsSolverNonRoadMultipliers[aRetry];
    }

    return 1.0f;
}

float TrafficEdgeScoreMultiplier(uint16_t aFlags, bool aDriving)
{
    if (!aDriving)
    {
        return 1.0f;
    }

    if ((aFlags & kTrafficLaneFlagHighway) != 0)
    {
        return 0.35f;
    }
    if ((aFlags & kTrafficLaneFlagRoad) != 0)
    {
        return 0.70f;
    }
    if ((aFlags & kTrafficLaneFlagGpsOnly) != 0)
    {
        return 1.35f;
    }
    if ((aFlags & kTrafficLaneFlagPavement) != 0)
    {
        return 2.75f;
    }

    return 1.50f;
}

float DetourGpsTrafficEdgeScore(void* aSearchContext, void* aCandidate, void* aReferencePoint)
{
    float baseScore = 0.0f;
    if (gOriginalGpsTrafficEdgeScore)
    {
        baseScore = gOriginalGpsTrafficEdgeScore(aSearchContext, aCandidate, aReferencePoint);
    }

    auto* query = ReadPointerField(aSearchContext, 0x10);
    auto* lane = ReadPointerField(aCandidate, 0x00);
    uint8_t queryMode = 0xFF;
    uint16_t laneFlags = 0;
    if (query)
    {
        TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(query) + 0x41), queryMode);
    }
    if (lane)
    {
        TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(lane) + 0x88), laneFlags);
    }

    const auto driving = queryMode != 0 && queryMode != 0xFF;
    const auto multiplier = TrafficEdgeScoreMultiplier(laneFlags, driving);
    const auto patchedScore = baseScore * multiplier;

    constexpr LONG kMaxLoggedCalls = 64;
    LONG callCount = -1;
    if (InterlockedCompareExchange(&gGpsTrafficEdgeScoreLogCount, 0, 0) < kMaxLoggedCalls)
    {
        callCount = InterlockedIncrement(&gGpsTrafficEdgeScoreLogCount) - 1;
    }

    if (callCount >= 0 && callCount < kMaxLoggedCalls)
    {
        std::ostringstream line;
        const auto flags = line.flags();
        const auto precision = line.precision();
        line << "hook GPSTrafficEdgeScore 0x8d46cc call=" << callCount
             << " category=" << TrafficLaneCategory(laneFlags)
             << " driving=" << static_cast<uint32_t>(driving)
             << " query41=" << static_cast<uint32_t>(queryMode)
             << " laneFlags88=0x" << std::hex << laneFlags << std::dec
             << " base=" << std::fixed << std::setprecision(3) << baseScore
             << " multiplier=" << multiplier
             << " result=" << patchedScore;
        line.flags(flags);
        line.precision(precision);
        AppendPointerWithRva(line, "ctx", aSearchContext);
        AppendPointerWithRva(line, "query", query);
        AppendPointerWithRva(line, "candidate", aCandidate);
        AppendPointerWithRva(line, "lane", lane);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
    }

    return patchedScore;
}

void DetourGpsAsyncWorkerTick(void* aRouteSystem)
{
    constexpr uint32_t kMaxCalls = 192;

    const auto callCount = NextGpsAsyncLogCall(gGpsAsyncWorkerTickLogCount);
    const auto shouldLogBefore = callCount < 16 || (callCount < kMaxCalls && HasPendingRouteWork(aRouteSystem));
    if (shouldLogBefore)
    {
        std::ostringstream line;
        line << "hook GPSAsyncWorkerTick 0x126b28 call=" << callCount;
        AppendRouteSystemSummary(line, "routeSystem", aRouteSystem);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
    }

    if (gOriginalGpsAsyncWorkerTick)
    {
        gOriginalGpsAsyncWorkerTick(aRouteSystem);
    }

    const auto shouldLogAfter = shouldLogBefore || (callCount < kMaxCalls && HasPendingRouteWork(aRouteSystem));
    if (shouldLogAfter)
    {
        std::ostringstream line;
        line << "hook GPSAsyncWorkerTick result call=" << callCount;
        AppendRouteSystemSummary(line, "routeSystem", aRouteSystem);
        LogRed4ext(line.str());
    }
}

bool DetourGpsRouteJobStart(void* aRouteSystem, void* aActiveEntry, float aDelta, void* aOutLocal)
{
    constexpr uint32_t kMaxCalls = 128;

    const auto callCount = NextGpsAsyncLogCall(gGpsRouteJobStartLogCount);
    const auto shouldLog = kEnableGpsRouteJobStartTrace && callCount < kMaxCalls;
    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook GPSRouteJobStart 0x818928 call=" << callCount << " dt=" << aDelta;
        AppendRouteSystemSummary(line, "routeSystem", aRouteSystem);
        AppendRouteQueueEntrySummary(line, "activeEntry", aActiveEntry);
        AppendRouteOutputPointerSummary(line, "outLocal", aOutLocal);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
    }

    bool result = false;
    if (gOriginalGpsRouteJobStart)
    {
        if (kEnableGpsCostTablePatch && IsDrivingRouteJob(aRouteSystem, aActiveEntry))
        {
            std::lock_guard patchLock(gGpsCostTablePatchMutex);
            ScopedGpsCostTablePatch patch(true, callCount);
            result = gOriginalGpsRouteJobStart(aRouteSystem, aActiveEntry, aDelta, aOutLocal);
        }
        else
        {
            result = gOriginalGpsRouteJobStart(aRouteSystem, aActiveEntry, aDelta, aOutLocal);
        }
    }

    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook GPSRouteJobStart result call=" << callCount << " value=" << static_cast<uint32_t>(result);
        AppendRouteSystemSummary(line, "routeSystem", aRouteSystem);
        AppendRouteQueueEntrySummary(line, "activeEntry", aActiveEntry);
        AppendRouteOutputPointerSummary(line, "outLocal", aOutLocal);
        LogRed4ext(line.str());
    }

    return result;
}

bool DetourGpsRouteJobPoll(void* aRouteSystem, void* aActiveEntry)
{
    constexpr uint32_t kMaxCalls = 128;

    const auto callCount = NextGpsAsyncLogCall(gGpsRouteJobPollLogCount);
    const auto shouldLog = callCount < kMaxCalls;
    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook GPSRouteJobPoll 0x883cd8 call=" << callCount;
        AppendRouteSystemSummary(line, "routeSystem", aRouteSystem);
        AppendRouteQueueEntrySummary(line, "activeEntry", aActiveEntry);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
    }

    bool result = false;
    if (gOriginalGpsRouteJobPoll)
    {
        result = gOriginalGpsRouteJobPoll(aRouteSystem, aActiveEntry);
    }

    if (shouldLog || result)
    {
        std::ostringstream line;
        line << "hook GPSRouteJobPoll result call=" << callCount << " value=" << static_cast<uint32_t>(result);
        AppendRouteSystemSummary(line, "routeSystem", aRouteSystem);
        AppendRouteQueueEntrySummary(line, "activeEntry", aActiveEntry);
        LogRed4ext(line.str());
    }

    return result;
}

void* DetourGpsRouteFinish(void* aResultQueue, void* aOutEntry, void* aActiveEntry, void* aLocalResult)
{
    constexpr uint32_t kMaxCalls = 128;

    const auto callCount = NextGpsAsyncLogCall(gGpsRouteFinishLogCount);
    const auto shouldLog = callCount < kMaxCalls;
    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook GPSRouteFinish 0x11f5f60 call=" << callCount;
        AppendPointerWithRva(line, "resultQueue", aResultQueue);
        AppendRouteQueueEntrySummary(line, "activeEntry", aActiveEntry);
        AppendRouteOutputPointerSummary(line, "localResult", aLocalResult);
        AppendPointerWithRva(line, "outEntry", aOutEntry);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
    }

    void* result = nullptr;
    if (gOriginalGpsRouteFinish)
    {
        result = gOriginalGpsRouteFinish(aResultQueue, aOutEntry, aActiveEntry, aLocalResult);
    }

    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook GPSRouteFinish result call=" << callCount;
        AppendPointerWithRva(line, "value", result);
        AppendRouteOutputPointerSummary(line, "localResult", aLocalResult);
        AppendPointerWithRva(line, "outEntry", aOutEntry);
        LogRed4ext(line.str());
    }

    return result;
}

void* DetourGpsRouteJobBuild(void* aJob, void* aOut, void* aRequest)
{
    constexpr uint32_t kMaxCalls = 128;

    const auto callCount = NextGpsAsyncLogCall(gGpsRouteJobBuildLogCount);
    const auto shouldLog = callCount < kMaxCalls;
    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook GPSRouteJobBuild 0x818ba8 call=" << callCount;
        AppendRouteJobSummary(line, "job", aJob);
        AppendRouteOutputPointerSummary(line, "out", aOut);
        AppendRouteRequestFields(line, "request", aRequest, 0x00);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
    }

    void* result = nullptr;
    if (gOriginalGpsRouteJobBuild)
    {
        result = gOriginalGpsRouteJobBuild(aJob, aOut, aRequest);
    }

    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook GPSRouteJobBuild result call=" << callCount;
        AppendPointerWithRva(line, "value", result);
        AppendRouteOutputPointerSummary(line, "out", aOut);
        AppendRouteJobSummary(line, "job", aJob);
        LogRed4ext(line.str());
    }

    return result;
}

int32_t DetourGpsRouteProducer(void* aGraph, void* aQuery, void* aOutResult)
{
    constexpr uint32_t kMaxCalls = 512;

    uint32_t callCount = 0;
    bool shouldLog = false;
    {
        std::lock_guard lock(gGpsCostLogMutex);
        callCount = gGpsRouteProducerLogCount++;
        shouldLog = kEnableGpsLocalSearchTraceHooks ||
            (kEnableGpsSpatialEdgeWeightPatchStats && callCount < kMaxCalls && ShouldLogGpsRouteProducerStats(aQuery));
    }

    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook GPSRouteProducer 0x44cc7c call=" << callCount;
        AppendPointerWithRva(line, "graph", aGraph);
        AppendPointerWithRva(line, "query", aQuery);
        AppendPointerWithRva(line, "outResult", aOutResult);
        AppendGpsRouteProducerQuerySummary(line, aQuery);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
    }

    MaybeReloadGpsSpatialWeights();

    GpsEdgeCostStats stats{};
    auto* previousStats = gActiveGpsEdgeCostStats;
    const auto previousRouteProducerCall = gActiveGpsRouteProducerCall;
    gActiveGpsEdgeCostStats = &stats;
    gActiveGpsRouteProducerCall = callCount;

    int32_t result = 0x80000008;
    if (gOriginalGpsRouteProducer)
    {
        result = gOriginalGpsRouteProducer(aGraph, aQuery, aOutResult);
    }

    gActiveGpsEdgeCostStats = previousStats;
    gActiveGpsRouteProducerCall = previousRouteProducerCall;

    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook GPSRouteProducer result call=" << callCount << " value=0x" << std::hex
             << static_cast<uint32_t>(result) << std::dec;
        AppendUint32Field(line, "out_u0c", aOutResult, 0x0C);
        AppendUint32Field(line, "out_u1c", aOutResult, 0x1C);
        AppendUint32Field(line, "out_u20", aOutResult, 0x20);
        AppendUint32Field(line, "out_u24", aOutResult, 0x24);
        AppendUint32Field(line, "out_u2c", aOutResult, 0x2C);
        AppendUint32Field(line, "out_u30lo", aOutResult, 0x30);
        AppendUint32Field(line, "out_u34hi", aOutResult, 0x34);
        AppendUint16Field(line, "out_flags40", aOutResult, 0x40);
        AppendByteField(line, "out_state42", aOutResult, 0x42);
        AppendGpsEdgeCostStats(line, stats);
        LogRed4ext(line.str());
    }

    return result;
}

int32_t DetourGpsSearch(void* aGraph, uint64_t aStartHandle, uint64_t aTargetHandle, void* aStartPoint,
                        void* aTargetPoint, void* aCostProvider, void* aOutPath, uint32_t* aOutCount)
{
    constexpr uint32_t kMaxCalls = 384;

    uint32_t callCount = 0;
    bool shouldLog = false;
    {
        std::lock_guard lock(gGpsCostLogMutex);
        callCount = gGpsSearchLogCount++;
        shouldLog = kEnableGpsTraceHooks && callCount < kMaxCalls;
    }

    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook GPSSearch 0x44f054 call=" << callCount << " startHandle=0x" << std::hex << aStartHandle
             << " targetHandle=0x" << aTargetHandle << std::dec;
        AppendPointerWithRva(line, "graph", aGraph);
        AppendVector3Pointer(line, "startPoint", aStartPoint);
        AppendVector3Pointer(line, "targetPoint", aTargetPoint);
        AppendPointerWithRva(line, "outPath", aOutPath);
        AppendPointerWithRva(line, "outCount", aOutCount);
        AppendGpsCostProviderCompact(line, "provider", aCostProvider);
        if (gActiveGpsRouteProducerCall != UINT32_MAX)
        {
            line << " routeProducerCall=" << gActiveGpsRouteProducerCall;
        }
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
    }

    GpsProviderFilterStats filterStats{};
    auto* previousFilterStats = gActiveGpsProviderFilterStats;
    gActiveGpsProviderFilterStats = &filterStats;

    int32_t result = 0x80000008;
    if (gOriginalGpsSearch)
    {
        ScopedGpsProviderClassPatch providerPatch(aCostProvider, callCount);
        result = gOriginalGpsSearch(aGraph, aStartHandle, aTargetHandle, aStartPoint, aTargetPoint, aCostProvider,
                                    aOutPath, aOutCount);
    }

    gActiveGpsProviderFilterStats = previousFilterStats;

    if (shouldLog)
    {
        uint32_t outCount = 0;
        const auto hasOutCount = aOutCount && TryReadMemory(aOutCount, outCount);

        std::ostringstream line;
        line << "hook GPSSearch result call=" << callCount << " value=0x" << std::hex
             << static_cast<uint32_t>(result) << std::dec << " hasOutCount=" << static_cast<uint32_t>(hasOutCount);
        if (hasOutCount)
        {
            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;
}

int32_t DetourGpsResolveHandle(void* aGraph, uint64_t aHandle, void* aOutSegment, void* aOutPoint)
{
    const auto returnAddress = __builtin_return_address(0);
    uintptr_t returnRva = 0;
    TryGetImageRva(returnAddress, returnRva);

    int32_t result = 0x80000008;
    if (gOriginalGpsResolveHandle)
    {
        result = gOriginalGpsResolveHandle(aGraph, aHandle, aOutSegment, aOutPoint);
    }

    void* segment = nullptr;
    void* point = nullptr;
    const auto hasSegment = aOutSegment && TryReadMemory(aOutSegment, segment);
    const auto hasPoint = aOutPoint && TryReadMemory(aOutPoint, point);

    const auto pointIndex = aHandle & 0xFFFFF;
    uint32_t callCount = 0;
    uint32_t returnCallCount = 0;
    uint32_t traceWindowSerial = 0;
    bool inTraceWindow = false;
    bool shouldLog = false;
    {
        std::lock_guard lock(gGpsCostLogMutex);
        callCount = gGpsResolveHandleLogCount++;
        const auto returnBucket = GetGpsResolveReturnBucket(returnRva);
        inTraceWindow = GetElapsedMs() <= gGpsResolveTraceWindowUntilMs;
        traceWindowSerial = gGpsResolveTraceWindowSerial;
        if (inTraceWindow && returnBucket < gGpsResolveHandleRetLogCounts.size())
        {
            returnCallCount = gGpsResolveHandleRetLogCounts[returnBucket]++;
        }
        shouldLog = result >= 0 && hasSegment && hasPoint &&
                    ((IsSelectedGpsResolveReturnRva(returnRva) && inTraceWindow &&
                      returnCallCount < kGpsResolveSelectedRetLogLimit) ||
                     callCount < kGpsResolveStartupLogLimit);
    }

    if (shouldLog)
    {
        auto* segmentMeta = ReadPointerField(segment, 0x08);
        auto* pointArray = ReadPointerField(segment, 0x10);
        auto* coordArray = ReadPointerField(segment, 0x18);
        auto* aux20 = ReadPointerField(segment, 0x20);
        auto* aux28 = ReadPointerField(segment, 0x28);
        auto* aux30 = ReadPointerField(segment, 0x30);
        auto* aux38 = ReadPointerField(segment, 0x38);

        const auto auxPointOffset = pointIndex * 12;
        auto* aux28Point = aux28 ? reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(aux28) + auxPointOffset) : nullptr;

        std::ostringstream line;
        line << "hook GPSResolveHandle 0x44e1a8 call=" << callCount << " retCall=" << returnCallCount
             << " traceWindow=" << traceWindowSerial << " inTraceWindow=" << (inTraceWindow ? 1 : 0)
             << " value=0x" << std::hex << static_cast<uint32_t>(result) << " handle=0x" << aHandle << std::dec;
        AppendGpsPackedHandleSummary(line, "handle", aHandle);
        AppendPointerWithRva(line, "graph", aGraph);
        AppendPointerWithRva(line, "outSegment", aOutSegment);
        AppendPointerWithRva(line, "outPoint", aOutPoint);
        AppendPointerWithRva(line, "segment", segment);
        AppendPointerWithRva(line, "point", point);
        AppendPointerWithRva(line, "segment_p08", segmentMeta);
        AppendPointerWithRva(line, "segment_p10", pointArray);
        AppendPointerWithRva(line, "segment_p18", coordArray);
        AppendPointerWithRva(line, "segment_p20", aux20);
        AppendPointerWithRva(line, "segment_p28", aux28);
        AppendPointerWithRva(line, "segment_p30", aux30);
        AppendPointerWithRva(line, "segment_p38", aux38);
        AppendGpsPointSummary(line, "pointSummary", point);
        const auto shouldDumpBytes =
            callCount < kGpsResolveVerboseDumpLimit ||
            (inTraceWindow && returnCallCount < kGpsResolveVerboseDumpLimit);
        line << " dump=" << (shouldDumpBytes ? 1 : 0);
        if (shouldDumpBytes)
        {
            AppendBytesPointer(line, "segment_raw68", segment, 0x68);
            AppendBytesPointer(line, "point_raw14", point, 0x14);
            AppendBytesPointer(line, "segment_p08_raw30", segmentMeta, 0x30);
            AppendBytesPointer(line, "segment_p20_raw40", aux20, 0x40);
            AppendBytesPointer(line, "segment_p28_point_raw18", aux28Point, 0x18);
            AppendBytesPointer(line, "segment_p30_raw40", aux30, 0x40);
            AppendBytesPointer(line, "segment_p38_raw40", aux38, 0x40);
        }
        if (gActiveGpsRouteProducerCall != UINT32_MAX)
        {
            line << " routeProducerCall=" << gActiveGpsRouteProducerCall;
        }
        AppendReturnRva(line, returnAddress);
        LogRed4ext(line.str());
    }

    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)
{
    const auto returnAddress = __builtin_return_address(0);
    uintptr_t returnRva = 0;
    TryGetImageRva(returnAddress, returnRva);

    float result = 0.0f;
    if (gOriginalGpsEdgeCost)
    {
        result = gOriginalGpsEdgeCost(aCostProvider, aCurrentState, aNeighborState, aPreviousHandle, aPreviousSegment,
                                      aPreviousPoint, aCurrentHandle, aCurrentSegment, aCurrentPoint, aNeighborHandle,
                                      aNeighborSegment, aNeighborPoint);
    }

    const auto sourceClass = ReadGpsPointClass(aCurrentPoint);
    const auto neighborClass = ReadGpsPointClass(aNeighborPoint);
    const auto originalResult = result;

    char spatialClass = '.';
    float spatialMultiplier = 1.0f;
    bool spatialAdjusted = false;
    bool spatialLookupOk = false;
    const auto spatialPatchAllowed =
        kEnableGpsSpatialEdgeWeightPatch &&
        (!kGpsSpatialPatchRequiresRouteProducer || gActiveGpsRouteProducerCall != UINT32_MAX);
    if (spatialPatchAllowed && result > 0.0f && std::isfinite(result) &&
        TryLookupGpsSpatialEdgeClass(aCurrentState, aNeighborState, spatialClass))
    {
        spatialLookupOk = true;
        spatialMultiplier = GpsSpatialRoadClassMultiplier(spatialClass);
        result *= spatialMultiplier;
        spatialAdjusted = spatialMultiplier != 1.0f;
    }

    if (gActiveGpsEdgeCostStats)
    {
        NoteGpsEdgeCostStats(*gActiveGpsEdgeCostStats, sourceClass, neighborClass, result, originalResult, returnRva,
                             aCurrentHandle, aNeighborHandle, spatialClass, spatialMultiplier, spatialPatchAllowed,
                             spatialLookupOk, spatialAdjusted);
    }

    float multiplier = 0.0f;
    const auto hasMultiplier =
        neighborClass < 64 &&
        TryReadFloatField(aCostProvider, 0x08 + static_cast<uintptr_t>(neighborClass) * sizeof(float), multiplier);

    uint32_t callCount = 0;
    uint32_t classCallCount = 0;
    bool shouldLog = false;
    {
        std::lock_guard lock(gGpsCostLogMutex);
        callCount = gGpsEdgeCostLogCount++;
        if (neighborClass < gGpsEdgeCostClassLogCounts.size())
        {
            classCallCount = gGpsEdgeCostClassLogCounts[neighborClass]++;
        }
        shouldLog =
            (kEnableGpsLocalSearchTraceHooks && (callCount < 32 || (neighborClass < 64 && classCallCount < 1))) ||
            (kEnableGpsSpatialEdgeWeightPatchTrace && spatialAdjusted && callCount < 128);
    }

    if (shouldLog)
    {
        const auto baseCost = hasMultiplier && multiplier != 0.0f ? originalResult / multiplier : 0.0f;

        std::ostringstream line;
        line << "hook GPSEdgeCost 0x44f838 call=" << callCount << " classCall=" << classCallCount
             << " sourceClass=" << static_cast<uint32_t>(sourceClass)
             << " neighborClass=" << static_cast<uint32_t>(neighborClass) << " value=" << result;
        if (spatialPatchAllowed)
        {
            line << " originalValue=" << originalResult << " spatialClass=" << GpsSpatialRoadClassName(spatialClass)
                 << " spatialMultiplier=" << spatialMultiplier
                 << " spatialAdjusted=" << static_cast<uint32_t>(spatialAdjusted);
        }
        if (hasMultiplier)
        {
            line << " multiplier=" << multiplier << " baseCost=" << baseCost;
        }
        else
        {
            line << " multiplier=<unreadable>";
        }
        line << " previousHandle=0x" << std::hex << aPreviousHandle << " currentHandle=0x" << aCurrentHandle
             << " neighborHandle=0x" << aNeighborHandle << std::dec;
        AppendPointerWithRva(line, "provider", aCostProvider);
        AppendVector3Pointer(line, "currentState", aCurrentState);
        AppendVector3Pointer(line, "neighborState", aNeighborState);
        AppendPointerWithRva(line, "previousSegment", aPreviousSegment);
        AppendGpsPointSummary(line, "previousPoint", aPreviousPoint);
        AppendPointerWithRva(line, "currentSegment", aCurrentSegment);
        AppendGpsPointSummary(line, "currentPoint", aCurrentPoint);
        AppendPointerWithRva(line, "neighborSegment", aNeighborSegment);
        AppendGpsPointSummary(line, "neighborPoint", aNeighborPoint);
        AppendReturnRva(line, returnAddress);
        LogRed4ext(line.str());
    }

    return result;
}

void LogGpsMultiplierSignature(const char* aName, uintptr_t aRva, bool aAuxiliary, void* aJob, void* aNode,
                               float aValue, const void* aReturnAddress)
{
    GpsMultiplierSignature signature{};
    if (!ReadGpsMultiplierSignature(aAuxiliary, aJob, aNode, aValue, signature))
    {
        return;
    }

    uint32_t callCount = 0;
    uint32_t signatureCount = 0;
    const auto isNew = NoteGpsMultiplierSignature(signature, callCount, signatureCount);
    const auto shouldLog = isNew || signatureCount == 8 || signatureCount == 64 || signatureCount == 512;
    if (!shouldLog)
    {
        return;
    }

    std::ostringstream line;
    line << "hook " << aName << " 0x" << std::hex << aRva << std::dec << " call=" << callCount
         << " signatureCount=" << signatureCount << " new=" << static_cast<uint32_t>(isNew);
    AppendPointerWithRva(line, "job", aJob);
    AppendPointerWithRva(line, "node", aNode);
    AppendGpsMultiplierSignature(line, signature);
    AppendReturnRva(line, aReturnAddress);
    LogRed4ext(line.str());
}

float DetourGpsAuxMultiplier(void* aJob, void* aNode)
{
    float result = 1.0f;
    if (gOriginalGpsAuxMultiplier)
    {
        result = gOriginalGpsAuxMultiplier(aJob, aNode);
    }

    if (kEnableGpsMultiplierSignatureTrace)
    {
        LogGpsMultiplierSignature("GPSAuxMultiplier", kGpsAuxMultiplierRva, true, aJob, aNode, result,
                                  __builtin_return_address(0));
    }
    return result;
}

float DetourGpsNodeMultiplier(void* aJob, void* aNode)
{
    float result = 1.0f;
    uint32_t mode = 0;
    const auto jobAddress = reinterpret_cast<uintptr_t>(aJob);
    if (!aJob || !TryReadMemory(reinterpret_cast<const void*>(jobAddress + 0xC4), mode))
    {
        return result;
    }

    uint16_t nodeFlags = 0;
    const auto nodeAddress = reinterpret_cast<uintptr_t>(aNode);

    if (mode == 1)
    {
        if (!aNode || !TryReadMemory(reinterpret_cast<const void*>(nodeAddress + 0x88), nodeFlags))
        {
            return result;
        }

        return (nodeFlags & kTrafficLaneFlagPavement) != 0 ? 1.0f : 1.5f;
    }

    if (mode != 0 && mode != 2 && mode != 4)
    {
        return result;
    }

    if (!aNode || !TryReadMemory(reinterpret_cast<const void*>(nodeAddress + 0x88), nodeFlags))
    {
        return result;
    }

    if ((nodeFlags & kTrafficLaneFlagRoad) != 0)
    {
        result = (nodeFlags & 0x0001) != 0 ? 1.0f : 1.2f;

        uint8_t node93 = 0;
        if (TryReadMemory(reinterpret_cast<const void*>(nodeAddress + 0x93), node93) && node93 == 1)
        {
            result *= 1.1f;
        }
    }
    else
    {
        uint32_t retry = 0;
        TryReadMemory(reinterpret_cast<const void*>(jobAddress + 0x54), retry);
        result = ReadGpsSolverNonRoadMultiplier(retry);
    }

    if (IsGpsRoadStyleMode(mode) && std::isfinite(result))
    {
        result *= GpsSolverNodeWeightMultiplier(nodeFlags);
    }

    if (kEnableGpsMultiplierSignatureTrace)
    {
        LogGpsMultiplierSignature("GPSNodeMultiplier", kGpsNodeMultiplierRva, false, aJob, aNode, result,
                                  __builtin_return_address(0));
    }
    return result;
}

void AttachProbeHook(const char* aName, uintptr_t aRva, void* aDetour, void** aOriginal)
{
    if (!gSdk || !gSdk->hooking || !gSdk->hooking->Attach)
    {
        LogRed4ext("hook attach skipped: RED4ext hooking API unavailable");
        return;
    }

    auto* target = reinterpret_cast<void*>(GetImageBase() + aRva);
    const auto attached = gSdk->hooking->Attach(gHandle, target, aDetour, aOriginal);

    std::ostringstream line;
    line << "hook attach " << aName << " target=" << target << " rva=0x" << std::hex << aRva << std::dec
         << " result=" << (attached ? "ok" : "failed") << " original=" << (aOriginal ? *aOriginal : nullptr);
    LogRed4ext(line.str());
}

void AttachAbsoluteHook(const char* aName, void* aTarget, void* aDetour, void** aOriginal)
{
    if (!gSdk || !gSdk->hooking || !gSdk->hooking->Attach)
    {
        LogRed4ext("absolute hook attach skipped: RED4ext hooking API unavailable");
        return;
    }

    if (!aTarget)
    {
        std::ostringstream line;
        line << "absolute hook attach skipped " << aName << ": target=null";
        LogRed4ext(line.str());
        return;
    }

    const auto attached = gSdk->hooking->Attach(gHandle, aTarget, aDetour, aOriginal);

    std::ostringstream line;
    line << "absolute hook attach " << aName;
    AppendPointerWithRva(line, "target", aTarget);
    line << " result=" << (attached ? "ok" : "failed");
    AppendPointerWithRva(line, "original", aOriginal ? *aOriginal : nullptr);
    LogRed4ext(line.str());
}

void DetachProbeHook(const char* aName, uintptr_t aRva)
{
    if (!gSdk || !gSdk->hooking || !gSdk->hooking->Detach)
    {
        return;
    }

    auto* target = reinterpret_cast<void*>(GetImageBase() + aRva);
    const auto detached = gSdk->hooking->Detach(gHandle, target);

    std::ostringstream line;
    line << "hook detach " << aName << " target=" << target << " result=" << (detached ? "ok" : "failed");
    LogRed4ext(line.str());
}

void DetachAbsoluteHook(const char* aName, void* aTarget)
{
    if (!gSdk || !gSdk->hooking || !gSdk->hooking->Detach || !aTarget)
    {
        return;
    }

    const auto detached = gSdk->hooking->Detach(gHandle, aTarget);

    std::ostringstream line;
    line << "absolute hook detach " << aName;
    AppendPointerWithRva(line, "target", aTarget);
    line << " result=" << (detached ? "ok" : "failed");
    LogRed4ext(line.str());
}

void DetourObserver1ServiceRouteLookup(void* aService, void* aOut, uint32_t aRouteId)
{
    const auto shouldLog = gObserver1ServiceRouteLookupLogCount < 96;
    const auto callCount = gObserver1ServiceRouteLookupLogCount;

    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook Observer1Service::RouteLookup slot220 call=" << callCount << " routeId=" << aRouteId
             << "/0x" << std::hex << aRouteId << std::dec;
        AppendPointerWithRva(line, "service", aService);
        AppendPointerWithRva(line, "out", aOut);
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
        ++gObserver1ServiceRouteLookupLogCount;
    }

    if (gOriginalObserver1ServiceRouteLookup)
    {
        gOriginalObserver1ServiceRouteLookup(aService, aOut, aRouteId);
    }

    if (shouldLog)
    {
        std::ostringstream line;
        line << "hook Observer1Service::RouteLookup slot220 result call=" << callCount;
        AppendPointerField(line, "out_f00", aOut, 0x00);
        AppendPointerField(line, "out_f08", aOut, 0x08);
        AppendPointerField(line, "out_f10", aOut, 0x10);

        auto* routeData = ReadPointerField(aOut, 0x00);
        if (routeData)
        {
            AppendPointerWithRva(line, "routeData", routeData);
            AppendPointerField(line, "routeData_f40", routeData, 0x40);
            AppendPointerField(line, "routeData_f48", routeData, 0x48);
            AppendPointerField(line, "routeData_f50", routeData, 0x50);
            AppendPointerField(line, "routeData_f58", routeData, 0x58);
        }

        LogRed4ext(line.str());
    }
}

void MaybeAttachObserver1ServiceRouteLookup(void* aService)
{
    if (gObserver1ServiceRouteLookupHookAttachAttempted || !aService)
    {
        return;
    }

    auto* vtable = ReadPointerField(aService, 0x00);
    if (!IsInImage(vtable))
    {
        return;
    }

    gObserver1ServiceRouteLookupTarget =
        ReadPointerField(vtable, kObserver1ServiceRouteLookupSlotOffset);
    if (!gObserver1ServiceRouteLookupTarget)
    {
        return;
    }

    gObserver1ServiceRouteLookupHookAttachAttempted = true;
    AttachAbsoluteHook("Observer1Service::RouteLookup slot220", gObserver1ServiceRouteLookupTarget,
                       reinterpret_cast<void*>(&DetourObserver1ServiceRouteLookup),
                       reinterpret_cast<void**>(&gOriginalObserver1ServiceRouteLookup));
}

void* DetourObserver1ServiceLookup(void* aServiceOwner, void* aSystemType)
{
    void* result = nullptr;
    if (gOriginalObserver1ServiceLookup)
    {
        result = gOriginalObserver1ServiceLookup(aServiceOwner, aSystemType);
    }

    auto* observer1SystemType = GetObserver1SystemType();
    if (!observer1SystemType || aSystemType != observer1SystemType)
    {
        return result;
    }

    if (gObserver1ServiceLookupLogCount < 96)
    {
        std::ostringstream line;
        line << "hook Observer1ServiceLookup 0x287c44 call=" << gObserver1ServiceLookupLogCount;
        AppendPointerWithRva(line, "serviceOwner", aServiceOwner);
        AppendPointerWithRva(line, "systemType", aSystemType);
        AppendPointerWithRva(line, "result", result);
        AppendObjectVtableSlots(line, result, {0x218, 0x220, 0x228, 0x230, 0x238, 0x240});
        AppendReturnRva(line, __builtin_return_address(0));
        LogRed4ext(line.str());
        ++gObserver1ServiceLookupLogCount;
    }

    MaybeAttachObserver1ServiceRouteLookup(result);

    return result;
}

void InspectAndHookMappinSystem(void* aThis)
{
    auto* resolver = reinterpret_cast<MappinSystemResolverFunc>(GetImageBase() + kMappinSystemResolverRva);
    void* system = resolver ? resolver(aThis) : nullptr;
    auto* vtable = system ? *reinterpret_cast<void***>(system) : nullptr;

    if (gMappinSystemSlotLogCount < 16)
    {
        LogMappinSystemSlots(gMappinSystemSlotLogCount, system, vtable);
        ++gMappinSystemSlotLogCount;
    }

    if (gMappinSlotHookAttachAttempted || !vtable)
    {
        return;
    }

    gMappinSlotHookAttachAttempted = true;
    gMappinUpdateCustomPositionTarget =
        *reinterpret_cast<void**>(reinterpret_cast<uintptr_t>(vtable) + kMappinUpdateCustomPositionSlotOffset);
    gMappinSetTrackedTarget =
        *reinterpret_cast<void**>(reinterpret_cast<uintptr_t>(vtable) + kMappinSetTrackedSlotOffset);
    gMappinClearTrackedTarget =
        *reinterpret_cast<void**>(reinterpret_cast<uintptr_t>(vtable) + kMappinClearTrackedSlotOffset);
    gMappinFramePathTarget =
        *reinterpret_cast<void**>(reinterpret_cast<uintptr_t>(vtable) + kMappinFramePathSlotOffset);
    gMappinPositionQueryTarget =
        *reinterpret_cast<void**>(reinterpret_cast<uintptr_t>(vtable) + kMappinPositionQuerySlotOffset);
    gMappinCreateCustomPositionTarget =
        *reinterpret_cast<void**>(reinterpret_cast<uintptr_t>(vtable) + kMappinCreateCustomPositionSlotOffset);
    gMappinReleaseQueryTarget =
        *reinterpret_cast<void**>(reinterpret_cast<uintptr_t>(vtable) + kMappinReleaseQuerySlotOffset);
    gMappinActiveRouteTarget =
        *reinterpret_cast<void**>(reinterpret_cast<uintptr_t>(vtable) + kMappinActiveRouteSlotOffset);

    AttachAbsoluteHook("MappinSystem::UpdateCustomPosition slot1f0", gMappinUpdateCustomPositionTarget,
                       reinterpret_cast<void*>(&DetourMappinUpdateCustomPosition),
                       reinterpret_cast<void**>(&gOriginalMappinUpdateCustomPosition));
    AttachAbsoluteHook("MappinSystem::SetTracked slot220", gMappinSetTrackedTarget,
                       reinterpret_cast<void*>(&DetourMappinSetTracked),
                       reinterpret_cast<void**>(&gOriginalMappinSetTracked));
    AttachAbsoluteHook("MappinSystem::ClearTracked slot228", gMappinClearTrackedTarget,
                       reinterpret_cast<void*>(&DetourMappinClearTracked),
                       reinterpret_cast<void**>(&gOriginalMappinClearTracked));
    AttachAbsoluteHook("MappinSystem::FramePath slot280", gMappinFramePathTarget,
                       reinterpret_cast<void*>(&DetourMappinFramePath),
                       reinterpret_cast<void**>(&gOriginalMappinFramePath));
    AttachAbsoluteHook("MappinSystem::PositionQuery slot2e0", gMappinPositionQueryTarget,
                       reinterpret_cast<void*>(&DetourMappinPositionQuery),
                       reinterpret_cast<void**>(&gOriginalMappinPositionQuery));
    AttachAbsoluteHook("MappinSystem::CreateCustomPosition slot2f0", gMappinCreateCustomPositionTarget,
                       reinterpret_cast<void*>(&DetourMappinCreateCustomPosition),
                       reinterpret_cast<void**>(&gOriginalMappinCreateCustomPosition));
    AttachAbsoluteHook("MappinSystem::ReleaseQuery slot368", gMappinReleaseQueryTarget,
                       reinterpret_cast<void*>(&DetourMappinReleaseQuery),
                       reinterpret_cast<void**>(&gOriginalMappinReleaseQuery));
    AttachAbsoluteHook("MappinSystem::ActiveRoute slot3a8", gMappinActiveRouteTarget,
                       reinterpret_cast<void*>(&DetourMappinActiveRoute),
                       reinterpret_cast<void**>(&gOriginalMappinActiveRoute));
}

bool OnRunningEnter(void* aApp)
{
    std::ostringstream line;
    line << "GameState Running OnEnter app=" << aApp;
    LogRed4ext(line.str());
    return true;
}

bool OnRunningUpdate(void* aApp)
{
    if (gUpdateLogCount < 5)
    {
        std::ostringstream line;
        line << "GameState Running OnUpdate app=" << aApp << " count=" << gUpdateLogCount;
        LogRed4ext(line.str());
        ++gUpdateLogCount;
    }

    MaybeReloadGpsSolverWeights();

    return false;
}

bool OnRunningExit(void* aApp)
{
    std::ostringstream line;
    line << "GameState Running OnExit app=" << aApp;
    LogRed4ext(line.str());
    return true;
}

RED4ext::v1::GameState gRunningState{
    .OnEnter = OnRunningEnter,
    .OnUpdate = OnRunningUpdate,
    .OnExit = OnRunningExit,
};
}

BOOL APIENTRY DllMain(HMODULE aModule, DWORD aReason, LPVOID)
{
    if (aReason == DLL_PROCESS_ATTACH)
    {
        gModule = aModule;
        LARGE_INTEGER frequency{};
        LARGE_INTEGER counter{};
        QueryPerformanceFrequency(&frequency);
        QueryPerformanceCounter(&counter);
        gLogFrequency = static_cast<uint64_t>(frequency.QuadPart);
        gLogStartTick = static_cast<uint64_t>(counter.QuadPart);
    }
    return TRUE;
}

RED4EXT_C_EXPORT bool RED4EXT_CALL Main(RED4ext::v1::PluginHandle aHandle, RED4ext::v1::EMainReason aReason,
                                        const RED4ext::v1::Sdk* aSdk)
{
    gHandle = aHandle;
    gSdk = aSdk;

    std::ostringstream line;
    line << "EdgeWeightGPS Main reason=" << ReasonToString(aReason) << " handle=" << aHandle << " sdk=" << aSdk;
    if (aSdk && aSdk->runtime)
    {
        line << " runtime=" << static_cast<uint32_t>(aSdk->runtime->major) << "." << aSdk->runtime->minor << "."
             << aSdk->runtime->patch;
    }
    LogRed4ext(line.str());

    if (aReason == RED4ext::v1::EMainReason::Load && aSdk && aSdk->gameStates && aSdk->gameStates->Add)
    {
        const auto added = aSdk->gameStates->Add(aHandle, kGameStateRunning, &gRunningState);
        LogRed4ext(added ? "registered Running game-state callbacks" : "failed to register Running game-state callbacks");

        if (kEnableGpsSpatialEdgeWeightPatch)
        {
            std::array<float, 5> values = {
                gGpsSpatialMultipliers[0].load(std::memory_order_relaxed),
                gGpsSpatialMultipliers[1].load(std::memory_order_relaxed),
                gGpsSpatialMultipliers[2].load(std::memory_order_relaxed),
                gGpsSpatialMultipliers[3].load(std::memory_order_relaxed),
                gGpsSpatialMultipliers[4].load(std::memory_order_relaxed),
            };
            std::ostringstream line;
            line << "spatial weights path=" << GetSpatialWeightsPath().string();
            LogRed4ext(line.str());
            LogGpsSpatialMultipliers("spatial weights defaults", values);
        }

        if (kEnableGpsNodeMultiplierInlinePatch)
        {
            std::ostringstream line;
            line << "solver weights path=" << GetSolverWeightsPath().string();
            LogRed4ext(line.str());
            MaybeReloadGpsSolverWeights(true);
            LogGpsSolverHighwayMultiplier("solver weights active",
                                          gGpsSolverHighwayMultiplier.load(std::memory_order_relaxed));
        }

        InstallGpsNodeMultiplierInlinePatch();

        if (kEnableGpsQueryLifecycleHooks)
        {
            AttachProbeHook("RunGPSQuery body 0x29bd128", kRunGpsQueryBodyRva,
                            reinterpret_cast<void*>(&DetourRunGpsQueryBody),
                            reinterpret_cast<void**>(&gOriginalRunGpsQueryBody));
            AttachProbeHook("UpdateGPSQuery body 0x29bd254", kUpdateGpsQueryBodyRva,
                            reinterpret_cast<void*>(&DetourUpdateGpsQueryBody),
                            reinterpret_cast<void**>(&gOriginalUpdateGpsQueryBody));
        }
        if (kEnableGpsQueryLifecycleHooks || kEnableGpsQueryResultTraceHooks)
        {
            AttachProbeHook("GPSQuerySubmit 0x70a42c", kGpsQuerySubmitRva,
                            reinterpret_cast<void*>(&DetourGpsQuerySubmit),
                            reinterpret_cast<void**>(&gOriginalGpsQuerySubmit));
            AttachProbeHook("GPSQueryResultFetch 0x7094b8", kGpsQueryResultFetchRva,
                            reinterpret_cast<void*>(&DetourGpsQueryResultFetch),
                            reinterpret_cast<void**>(&gOriginalGpsQueryResultFetch));
        }
        if (kEnableGpsQueryLifecycleHooks)
        {
            AttachProbeHook("GPSQueryDispatch 0x70a570", kGpsQueryDispatchRva,
                            reinterpret_cast<void*>(&DetourGpsQueryDispatch),
                            reinterpret_cast<void**>(&gOriginalGpsQueryDispatch));
            AttachProbeHook("GPSQueryStatus 0xaa5704", kGpsQueryStatusRva,
                            reinterpret_cast<void*>(&DetourGpsQueryStatus),
                            reinterpret_cast<void**>(&gOriginalGpsQueryStatus));
        }
        if (kEnableGpsAsyncTrafficProbe)
        {
            AttachProbeHook("GPSDispatchAdvance 0x8d17d8", kGpsDispatchAdvanceRva,
                            reinterpret_cast<void*>(&DetourGpsDispatchAdvance),
                            reinterpret_cast<void**>(&gOriginalGpsDispatchAdvance));
            AttachProbeHook("GPSTrafficResultTake 0x709d5c", kGpsTrafficResultTakeRva,
                            reinterpret_cast<void*>(&DetourGpsTrafficResultTake),
                            reinterpret_cast<void**>(&gOriginalGpsTrafficResultTake));
            AttachProbeHook("GPSRoutePostprocess 0x44830c", kGpsRoutePostprocessRva,
                            reinterpret_cast<void*>(&DetourGpsRoutePostprocess),
                            reinterpret_cast<void**>(&gOriginalGpsRoutePostprocess));
        }
        if (kEnableGpsTrafficEdgeWeightPatch)
        {
            AttachProbeHook("GPSTrafficEdgeScore 0x8d46cc", kGpsTrafficEdgeScoreRva,
                            reinterpret_cast<void*>(&DetourGpsTrafficEdgeScore),
                            reinterpret_cast<void**>(&gOriginalGpsTrafficEdgeScore));
        }
        if (kEnableGpsRouteJobLifecycleHooks)
        {
            AttachProbeHook("GPSDispatchAdvance 0x8d17d8", kGpsDispatchAdvanceRva,
                            reinterpret_cast<void*>(&DetourGpsDispatchAdvance),
                            reinterpret_cast<void**>(&gOriginalGpsDispatchAdvance));
            AttachProbeHook("GPSAsyncWorkerTick 0x126b28", kGpsAsyncWorkerTickRva,
                            reinterpret_cast<void*>(&DetourGpsAsyncWorkerTick),
                            reinterpret_cast<void**>(&gOriginalGpsAsyncWorkerTick));
            AttachProbeHook("GPSRouteJobPoll 0x883cd8", kGpsRouteJobPollRva,
                            reinterpret_cast<void*>(&DetourGpsRouteJobPoll),
                            reinterpret_cast<void**>(&gOriginalGpsRouteJobPoll));
            AttachProbeHook("GPSRouteFinish 0x11f5f60", kGpsRouteFinishRva,
                            reinterpret_cast<void*>(&DetourGpsRouteFinish),
                            reinterpret_cast<void**>(&gOriginalGpsRouteFinish));
            AttachProbeHook("GPSRouteJobBuild 0x818ba8", kGpsRouteJobBuildRva,
                            reinterpret_cast<void*>(&DetourGpsRouteJobBuild),
                            reinterpret_cast<void**>(&gOriginalGpsRouteJobBuild));
        }
        if (kEnableGpsCostTablePatch || kEnableGpsRouteJobStartTrace)
        {
            AttachProbeHook("GPSRouteJobStart 0x818928", kGpsRouteJobStartRva,
                            reinterpret_cast<void*>(&DetourGpsRouteJobStart),
                            reinterpret_cast<void**>(&gOriginalGpsRouteJobStart));
        }
        if (kEnableGpsProviderClassPatch || kEnableGpsLocalSearchTraceHooks)
        {
            AttachProbeHook("GPSSearch 0x44f054", kGpsSearchRva, reinterpret_cast<void*>(&DetourGpsSearch),
                            reinterpret_cast<void**>(&gOriginalGpsSearch));
        }
        if (kEnableGpsRouteProducerHook)
        {
            AttachProbeHook("GPSRouteProducer 0x44cc7c", kGpsRouteProducerRva,
                            reinterpret_cast<void*>(&DetourGpsRouteProducer),
                            reinterpret_cast<void**>(&gOriginalGpsRouteProducer));
        }
        if (kEnableGpsLocalSearchTraceHooks)
        {
            AttachProbeHook("GPSResolveHandle 0x44e1a8", kGpsResolveHandleRva,
                            reinterpret_cast<void*>(&DetourGpsResolveHandle),
                            reinterpret_cast<void**>(&gOriginalGpsResolveHandle));
        }
        if (kEnableGpsEdgeCostHook)
        {
            AttachProbeHook("GPSEdgeCost 0x44f838", kGpsEdgeCostRva, reinterpret_cast<void*>(&DetourGpsEdgeCost),
                            reinterpret_cast<void**>(&gOriginalGpsEdgeCost));
        }
        if (kEnableGpsLocalSearchTraceHooks)
        {
            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 (kEnableGpsAuxMultiplierHook)
            {
                AttachProbeHook("GPSAuxMultiplier 0x40bb00", kGpsAuxMultiplierRva,
                                reinterpret_cast<void*>(&DetourGpsAuxMultiplier),
                                reinterpret_cast<void**>(&gOriginalGpsAuxMultiplier));
            }
            AttachProbeHook("GPSNodeMultiplier 0x40bb40", kGpsNodeMultiplierRva,
                            reinterpret_cast<void*>(&DetourGpsNodeMultiplier),
                            reinterpret_cast<void**>(&gOriginalGpsNodeMultiplier));
        }
        if (kEnableRouteUiTraceHooks)
        {
            AttachProbeHook("GPSSystem/Tick", kGpsSystemTickRva, reinterpret_cast<void*>(&DetourGpsSystemTick),
                            reinterpret_cast<void**>(&gOriginalGpsSystemTick));
            AttachProbeHook("JournalManager::TrackEntry impl", kJournalTrackEntryImplRva,
                            reinterpret_cast<void*>(&DetourJournalTrackEntry),
                            reinterpret_cast<void**>(&gOriginalJournalTrackEntry));
            if (kEnableVerboseJournalListenerHooks)
            {
                AttachProbeHook("JournalListener::Objective vt28", kJournalListenerObjectiveVt28Rva,
                                reinterpret_cast<void*>(&DetourJournalListenerObjectiveVt28),
                                reinterpret_cast<void**>(&gOriginalJournalListenerObjectiveVt28));
                AttachProbeHook("JournalListener::Objective vt30", kJournalListenerObjectiveVt30Rva,
                                reinterpret_cast<void*>(&DetourJournalListenerObjectiveVt30),
                                reinterpret_cast<void**>(&gOriginalJournalListenerObjectiveVt30));
                AttachProbeHook("JournalListener::Route vt28", kJournalListenerRouteVt28Rva,
                                reinterpret_cast<void*>(&DetourJournalListenerRouteVt28),
                                reinterpret_cast<void**>(&gOriginalJournalListenerRouteVt28));
                AttachProbeHook("JournalListener::Route vt30", kJournalListenerRouteVt30Rva,
                                reinterpret_cast<void*>(&DetourJournalListenerRouteVt30),
                                reinterpret_cast<void**>(&gOriginalJournalListenerRouteVt30));
                AttachProbeHook("JournalListener::RouteAlt vt28", kJournalListenerRouteAltVt28Rva,
                                reinterpret_cast<void*>(&DetourJournalListenerRouteAltVt28),
                                reinterpret_cast<void**>(&gOriginalJournalListenerRouteAltVt28));
                AttachProbeHook("JournalListener::RouteAlt vt30", kJournalListenerRouteAltVt30Rva,
                                reinterpret_cast<void*>(&DetourJournalListenerRouteAltVt30),
                                reinterpret_cast<void**>(&gOriginalJournalListenerRouteAltVt30));
                AttachProbeHook("JournalListener::UI vt28", kJournalListenerUiVt28Rva,
                                reinterpret_cast<void*>(&DetourJournalListenerUiVt28),
                                reinterpret_cast<void**>(&gOriginalJournalListenerUiVt28));
                AttachProbeHook("JournalListener::UI vt30", kJournalListenerUiVt30Rva,
                                reinterpret_cast<void*>(&DetourJournalListenerUiVt30),
                                reinterpret_cast<void**>(&gOriginalJournalListenerUiVt30));
                AttachProbeHook("JournalListener::Tracker vt28", kJournalListenerTrackerVt28Rva,
                                reinterpret_cast<void*>(&DetourJournalListenerTrackerVt28),
                                reinterpret_cast<void**>(&gOriginalJournalListenerTrackerVt28));
            }
            AttachProbeHook("JournalRouteBridge::Update candidate", kJournalRouteBridgeRva,
                            reinterpret_cast<void*>(&DetourJournalRouteBridge),
                            reinterpret_cast<void**>(&gOriginalJournalRouteBridge));
            AttachProbeHook("MappinSystem::RouteEventEnqueue slot240", kMappinRouteEventEnqueueRva,
                            reinterpret_cast<void*>(&DetourMappinRouteEventEnqueue),
                            reinterpret_cast<void**>(&gOriginalMappinRouteEventEnqueue));
            AttachProbeHook("MappinRouteEvent::Handle", kMappinRouteEventHandlerRva,
                            reinterpret_cast<void*>(&DetourMappinRouteEventHandler),
                            reinterpret_cast<void**>(&gOriginalMappinRouteEventHandler));
            AttachProbeHook("MappinSystem::RouteActivate", kMappinRouteActivateRva,
                            reinterpret_cast<void*>(&DetourMappinRouteActivate),
                            reinterpret_cast<void**>(&gOriginalMappinRouteActivate));
            AttachProbeHook("MappinSystem::RouteDeactivate", kMappinRouteDeactivateRva,
                            reinterpret_cast<void*>(&DetourMappinRouteDeactivate),
                            reinterpret_cast<void**>(&gOriginalMappinRouteDeactivate));
            AttachProbeHook("RouteBuildCandidate 0x5625a4", kRouteBuildCandidateRva,
                            reinterpret_cast<void*>(&DetourRouteBuildCandidate),
                            reinterpret_cast<void**>(&gOriginalRouteBuildCandidate));
            if (kEnableVerboseRouteObserverHooks)
            {
                AttachProbeHook("RouteObserver0::Activate slot48", kRouteObserver0ActivateRva,
                                reinterpret_cast<void*>(&DetourRouteObserver0Activate),
                                reinterpret_cast<void**>(&gOriginalRouteObserver0Activate));
                AttachProbeHook("RouteObserver1::Activate slot48", kRouteObserver1ActivateRva,
                                reinterpret_cast<void*>(&DetourRouteObserver1Activate),
                                reinterpret_cast<void**>(&gOriginalRouteObserver1Activate));
                AttachProbeHook("RouteObserver23::Activate slot48", kRouteObserver23ActivateRva,
                                reinterpret_cast<void*>(&DetourRouteObserver23Activate),
                                reinterpret_cast<void**>(&gOriginalRouteObserver23Activate));
                AttachProbeHook("RouteObserver0::Deactivate slot50", kRouteObserver0DeactivateRva,
                                reinterpret_cast<void*>(&DetourRouteObserver0Deactivate),
                                reinterpret_cast<void**>(&gOriginalRouteObserver0Deactivate));
                AttachProbeHook("RouteObserver1::Deactivate slot50", kRouteObserver1DeactivateRva,
                                reinterpret_cast<void*>(&DetourRouteObserver1Deactivate),
                                reinterpret_cast<void**>(&gOriginalRouteObserver1Deactivate));
                AttachProbeHook("RouteObserver23::Deactivate slot50", kRouteObserver23DeactivateRva,
                                reinterpret_cast<void*>(&DetourRouteObserver23Deactivate),
                                reinterpret_cast<void**>(&gOriginalRouteObserver23Deactivate));
            }
            AttachProbeHook("FrameMappinPathWrapper", kFrameMappinPathWrapperRva,
                            reinterpret_cast<void*>(&DetourFrameMappinPathWrapper),
                            reinterpret_cast<void**>(&gOriginalFrameMappinPathWrapper));
            AttachProbeHook("FrameMappinPathCore", kFrameMappinPathCoreRva,
                            reinterpret_cast<void*>(&DetourFrameMappinPathCore),
                            reinterpret_cast<void**>(&gOriginalFrameMappinPathCore));
            AttachProbeHook("SetSelectedMappinWrapper", kSetSelectedMappinWrapperRva,
                            reinterpret_cast<void*>(&DetourSetSelectedMappinWrapper),
                            reinterpret_cast<void**>(&gOriginalSetSelectedMappinWrapper));
            AttachProbeHook("SetSelectedMappinByIdWrapper", kSetSelectedMappinByIdWrapperRva,
                            reinterpret_cast<void*>(&DetourSetSelectedMappinByIdWrapper),
                            reinterpret_cast<void**>(&gOriginalSetSelectedMappinByIdWrapper));
            AttachProbeHook("SetSelectedMappinByPositionWrapper", kSetSelectedMappinByPositionWrapperRva,
                            reinterpret_cast<void*>(&DetourSetSelectedMappinByPositionWrapper),
                            reinterpret_cast<void**>(&gOriginalSetSelectedMappinByPositionWrapper));
            AttachProbeHook("SetSelectedMappinCore", kSetSelectedMappinCoreRva,
                            reinterpret_cast<void*>(&DetourSetSelectedMappinCore),
                            reinterpret_cast<void**>(&gOriginalSetSelectedMappinCore));
            AttachProbeHook("TrackCustomPositionMappinWrapper", kTrackCustomPositionMappinWrapperRva,
                            reinterpret_cast<void*>(&DetourTrackCustomPositionMappinWrapper),
                            reinterpret_cast<void**>(&gOriginalTrackCustomPositionMappinWrapper));
            AttachProbeHook("TrackCustomPositionMappinCore", kTrackCustomPositionMappinCoreRva,
                            reinterpret_cast<void*>(&DetourTrackCustomPositionMappinCore),
                            reinterpret_cast<void**>(&gOriginalTrackCustomPositionMappinCore));
            AttachProbeHook("UntrackCustomPositionMappinWrapper", kUntrackCustomPositionMappinWrapperRva,
                            reinterpret_cast<void*>(&DetourUntrackCustomPositionMappinWrapper),
                            reinterpret_cast<void**>(&gOriginalUntrackCustomPositionMappinWrapper));
            AttachProbeHook("TrackMappinWrapper", kTrackMappinWrapperRva,
                            reinterpret_cast<void*>(&DetourTrackMappinWrapper),
                            reinterpret_cast<void**>(&gOriginalTrackMappinWrapper));
            AttachProbeHook("TrackMappinCore", kTrackMappinCoreRva, reinterpret_cast<void*>(&DetourTrackMappinCore),
                            reinterpret_cast<void**>(&gOriginalTrackMappinCore));
            AttachProbeHook("SetMappinTrackingAlternativeWrapper", kSetMappinTrackingAlternativeWrapperRva,
                            reinterpret_cast<void*>(&DetourSetMappinTrackingAlternativeWrapper),
                            reinterpret_cast<void**>(&gOriginalSetMappinTrackingAlternativeWrapper));
        }
    }

    if (aReason == RED4ext::v1::EMainReason::Unload)
    {
        RemoveGpsNodeMultiplierInlinePatch();

        if (kEnableGpsQueryLifecycleHooks)
        {
            DetachProbeHook("RunGPSQuery body 0x29bd128", kRunGpsQueryBodyRva);
            DetachProbeHook("UpdateGPSQuery body 0x29bd254", kUpdateGpsQueryBodyRva);
        }
        if (kEnableGpsQueryLifecycleHooks || kEnableGpsQueryResultTraceHooks)
        {
            DetachProbeHook("GPSQuerySubmit 0x70a42c", kGpsQuerySubmitRva);
            DetachProbeHook("GPSQueryResultFetch 0x7094b8", kGpsQueryResultFetchRva);
        }
        if (kEnableGpsQueryLifecycleHooks)
        {
            DetachProbeHook("GPSQueryDispatch 0x70a570", kGpsQueryDispatchRva);
            DetachProbeHook("GPSQueryStatus 0xaa5704", kGpsQueryStatusRva);
        }
        if (kEnableGpsAsyncTrafficProbe)
        {
            DetachProbeHook("GPSDispatchAdvance 0x8d17d8", kGpsDispatchAdvanceRva);
            DetachProbeHook("GPSTrafficResultTake 0x709d5c", kGpsTrafficResultTakeRva);
            DetachProbeHook("GPSRoutePostprocess 0x44830c", kGpsRoutePostprocessRva);
        }
        if (kEnableGpsTrafficEdgeWeightPatch)
        {
            DetachProbeHook("GPSTrafficEdgeScore 0x8d46cc", kGpsTrafficEdgeScoreRva);
        }
        if (kEnableGpsRouteJobLifecycleHooks)
        {
            DetachProbeHook("GPSDispatchAdvance 0x8d17d8", kGpsDispatchAdvanceRva);
            DetachProbeHook("GPSAsyncWorkerTick 0x126b28", kGpsAsyncWorkerTickRva);
            DetachProbeHook("GPSRouteJobPoll 0x883cd8", kGpsRouteJobPollRva);
            DetachProbeHook("GPSRouteFinish 0x11f5f60", kGpsRouteFinishRva);
            DetachProbeHook("GPSRouteJobBuild 0x818ba8", kGpsRouteJobBuildRva);
        }
        if (kEnableGpsCostTablePatch || kEnableGpsRouteJobStartTrace)
        {
            DetachProbeHook("GPSRouteJobStart 0x818928", kGpsRouteJobStartRva);
        }
        if (kEnableGpsProviderClassPatch || kEnableGpsLocalSearchTraceHooks)
        {
            DetachProbeHook("GPSSearch 0x44f054", kGpsSearchRva);
        }
        if (kEnableGpsRouteProducerHook)
        {
            DetachProbeHook("GPSRouteProducer 0x44cc7c", kGpsRouteProducerRva);
        }
        if (kEnableGpsLocalSearchTraceHooks)
        {
            DetachProbeHook("GPSResolveHandle 0x44e1a8", kGpsResolveHandleRva);
        }
        if (kEnableGpsEdgeCostHook)
        {
            DetachProbeHook("GPSEdgeCost 0x44f838", kGpsEdgeCostRva);
        }
        if (kEnableGpsLocalSearchTraceHooks)
        {
            DetachProbeHook("GPSFilteredProviderFilter 0x44ff68", kGpsFilteredProviderFilterRva);
            DetachProbeHook("GPSBaseProviderFilter 0x450b08", kGpsBaseProviderFilterRva);
        }
        if (kEnableGpsMultiplierHooks)
        {
            if (kEnableGpsAuxMultiplierHook)
            {
                DetachProbeHook("GPSAuxMultiplier 0x40bb00", kGpsAuxMultiplierRva);
            }
            DetachProbeHook("GPSNodeMultiplier 0x40bb40", kGpsNodeMultiplierRva);
        }
        if (kEnableRouteUiTraceHooks)
        {
            DetachProbeHook("GPSSystem/Tick", kGpsSystemTickRva);
            DetachProbeHook("JournalManager::TrackEntry impl", kJournalTrackEntryImplRva);
            if (kEnableVerboseJournalListenerHooks)
            {
                DetachProbeHook("JournalListener::Objective vt28", kJournalListenerObjectiveVt28Rva);
                DetachProbeHook("JournalListener::Objective vt30", kJournalListenerObjectiveVt30Rva);
                DetachProbeHook("JournalListener::Route vt28", kJournalListenerRouteVt28Rva);
                DetachProbeHook("JournalListener::Route vt30", kJournalListenerRouteVt30Rva);
                DetachProbeHook("JournalListener::RouteAlt vt28", kJournalListenerRouteAltVt28Rva);
                DetachProbeHook("JournalListener::RouteAlt vt30", kJournalListenerRouteAltVt30Rva);
                DetachProbeHook("JournalListener::UI vt28", kJournalListenerUiVt28Rva);
                DetachProbeHook("JournalListener::UI vt30", kJournalListenerUiVt30Rva);
                DetachProbeHook("JournalListener::Tracker vt28", kJournalListenerTrackerVt28Rva);
            }
            DetachProbeHook("JournalRouteBridge::Update candidate", kJournalRouteBridgeRva);
            DetachProbeHook("MappinSystem::RouteEventEnqueue slot240", kMappinRouteEventEnqueueRva);
            DetachProbeHook("MappinRouteEvent::Handle", kMappinRouteEventHandlerRva);
            DetachProbeHook("MappinSystem::RouteActivate", kMappinRouteActivateRva);
            DetachProbeHook("MappinSystem::RouteDeactivate", kMappinRouteDeactivateRva);
            DetachProbeHook("RouteBuildCandidate 0x5625a4", kRouteBuildCandidateRva);
            if (kEnableVerboseRouteObserverHooks)
            {
                DetachProbeHook("RouteObserver0::Activate slot48", kRouteObserver0ActivateRva);
                DetachProbeHook("RouteObserver1::Activate slot48", kRouteObserver1ActivateRva);
                DetachProbeHook("RouteObserver23::Activate slot48", kRouteObserver23ActivateRva);
                DetachProbeHook("RouteObserver0::Deactivate slot50", kRouteObserver0DeactivateRva);
                DetachProbeHook("RouteObserver1::Deactivate slot50", kRouteObserver1DeactivateRva);
                DetachProbeHook("RouteObserver23::Deactivate slot50", kRouteObserver23DeactivateRva);
            }
            DetachProbeHook("FrameMappinPathWrapper", kFrameMappinPathWrapperRva);
            DetachProbeHook("FrameMappinPathCore", kFrameMappinPathCoreRva);
            DetachProbeHook("SetSelectedMappinWrapper", kSetSelectedMappinWrapperRva);
            DetachProbeHook("SetSelectedMappinByIdWrapper", kSetSelectedMappinByIdWrapperRva);
            DetachProbeHook("SetSelectedMappinByPositionWrapper", kSetSelectedMappinByPositionWrapperRva);
            DetachProbeHook("SetSelectedMappinCore", kSetSelectedMappinCoreRva);
            DetachProbeHook("TrackCustomPositionMappinWrapper", kTrackCustomPositionMappinWrapperRva);
            DetachProbeHook("TrackCustomPositionMappinCore", kTrackCustomPositionMappinCoreRva);
            DetachProbeHook("UntrackCustomPositionMappinWrapper", kUntrackCustomPositionMappinWrapperRva);
            DetachProbeHook("TrackMappinWrapper", kTrackMappinWrapperRva);
            DetachProbeHook("TrackMappinCore", kTrackMappinCoreRva);
            DetachProbeHook("SetMappinTrackingAlternativeWrapper", kSetMappinTrackingAlternativeWrapperRva);
            DetachAbsoluteHook("MappinSystem::UpdateCustomPosition slot1f0", gMappinUpdateCustomPositionTarget);
            DetachAbsoluteHook("MappinSystem::SetTracked slot220", gMappinSetTrackedTarget);
            DetachAbsoluteHook("MappinSystem::ClearTracked slot228", gMappinClearTrackedTarget);
            DetachAbsoluteHook("MappinSystem::FramePath slot280", gMappinFramePathTarget);
            DetachAbsoluteHook("MappinSystem::PositionQuery slot2e0", gMappinPositionQueryTarget);
            DetachAbsoluteHook("MappinSystem::CreateCustomPosition slot2f0", gMappinCreateCustomPositionTarget);
            DetachAbsoluteHook("MappinSystem::ReleaseQuery slot368", gMappinReleaseQueryTarget);
            DetachAbsoluteHook("MappinSystem::ActiveRoute slot3a8", gMappinActiveRouteTarget);
            DetachAbsoluteHook("Observer1Service::RouteLookup slot220", gObserver1ServiceRouteLookupTarget);
        }
    }

    return true;
}

RED4EXT_C_EXPORT void RED4EXT_CALL Query(RED4ext::v1::PluginInfo* aInfo)
{
    aInfo->name = L"EdgeWeightGPS";
    aInfo->author = L"salt+Codex";
    aInfo->version = RED4ext::v1::SemVer{0, 1, 0, {0, 0}};
    aInfo->runtime = RED4ext::v1::FileVer{3, 0, 80, 51928};
    aInfo->sdk = RED4ext::v1::SemVer{0, 5, 0, {0, 0}};
}

RED4EXT_C_EXPORT uint32_t RED4EXT_CALL Supports()
{
    return RED4EXT_API_VERSION_1_COMPAT_0;
}
