Files
2077-gps-mod/red4ext/EdgeWeightGPS/src/Main.cpp
T

4484 lines
169 KiB
C++

#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 <chrono>
#include <cstring>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <initializer_list>
#include <mutex>
#include <sstream>
#include <string>
#include <string_view>
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 kEnableGpsCostTablePatch = false;
constexpr bool kEnableGpsProviderClassPatch = true;
constexpr bool kEnableGpsProviderClassPatchTrace = true;
constexpr bool kEnableGpsTraceHooks = false;
constexpr bool kEnableGpsRouteJobStartTrace = false;
constexpr bool kEnableGpsLocalSearchHooks = false;
constexpr bool kEnableRouteUiTraceHooks = false;
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 kGpsDispatchAdvanceRva = 0x08D17D8;
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 kGpsEdgeCostRva = 0x044F838;
constexpr uintptr_t kGpsBaseCostProviderVtableRva = 0x2AE6120;
constexpr uintptr_t kGpsFilteredCostProviderVtableRva = 0x2AE60F8;
constexpr uintptr_t kGpsAuxMultiplierRva = 0x040BB00;
constexpr uintptr_t kGpsNodeMultiplierRva = 0x040BB40;
constexpr uintptr_t kGpsCostMultiplierTableRva = 0x3154D28;
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;
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 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 gGpsEdgeCostLogCount = 0;
uint32_t gGpsAuxMultiplierLogCount = 0;
uint32_t gGpsNodeMultiplierLogCount = 0;
uint32_t gGpsCostTablePatchLogCount = 0;
uint32_t gGpsProviderClassPatchLogCount = 0;
std::array<uint32_t, 64> gGpsEdgeCostClassLogCounts{};
std::mutex gGpsCostLogMutex;
std::mutex gGpsAsyncLogMutex;
std::mutex gGpsCostTablePatchMutex;
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;
uint8_t sourceClass = 0xFF;
uint8_t neighborClass = 0xFF;
};
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{};
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 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 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 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 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 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;
GpsAsyncWorkerTickFunc gOriginalGpsAsyncWorkerTick = nullptr;
GpsRouteJobStartFunc gOriginalGpsRouteJobStart = nullptr;
GpsRouteJobPollFunc gOriginalGpsRouteJobPoll = nullptr;
GpsRouteFinishFunc gOriginalGpsRouteFinish = nullptr;
GpsRouteJobBuildFunc gOriginalGpsRouteJobBuild = nullptr;
GpsRouteProducerFunc gOriginalGpsRouteProducer = nullptr;
GpsSearchFunc gOriginalGpsSearch = nullptr;
GpsEdgeCostFunc gOriginalGpsEdgeCost = nullptr;
GpsMultiplierFunc gOriginalGpsAuxMultiplier = nullptr;
GpsMultiplierFunc gOriginalGpsNodeMultiplier = nullptr;
std::filesystem::path GetLogPath()
{
wchar_t modulePath[MAX_PATH]{};
if (gModule && GetModuleFileNameW(gModule, modulePath, MAX_PATH) > 0)
{
auto path = std::filesystem::path(modulePath);
return path.parent_path() / L"EdgeWeightGPS.log";
}
return std::filesystem::path(L"red4ext") / L"plugins" / L"EdgeWeightGPS" / L"EdgeWeightGPS.log";
}
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;
}
uint64_t elapsedMs = 0;
if (gLogStartTick && gLogFrequency)
{
LARGE_INTEGER counter{};
QueryPerformanceCounter(&counter);
elapsedMs = static_cast<uint64_t>((counter.QuadPart - gLogStartTick) * 1000 / gLogFrequency);
}
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());
}
}
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 (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;
};
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 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 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 << " 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 << " 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 NoteGpsEdgeCostStats(GpsEdgeCostStats& aStats, uint8_t aSourceClass, uint8_t aNeighborClass, float aCost,
uintptr_t aReturnRva, uint64_t aCurrentHandle, uint64_t aNeighborHandle)
{
++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 (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.sourceClass = aSourceClass;
sample.neighborClass = aNeighborClass;
}
}
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);
}
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 IsDrivingRouteJob(void* aRouteSystem, void* aActiveEntry)
{
uint32_t mode = 0;
if (TryReadUint32FieldValue(aActiveEntry, 0x80, mode) && mode == 2)
{
return true;
}
auto* job = ReadPointerField(aRouteSystem, 0x90D0);
return TryReadUint32FieldValue(job, 0xC4, mode) && mode == 2;
}
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 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;
const auto shouldLog = callCount < kMaxCalls;
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);
AppendReturnRva(line, returnAddress);
LogRed4ext(line.str());
++gGpsQuerySubmitLogCount;
}
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 shouldLog = (isTracked && (perQueryCall < kMaxTrackedSamplesPerQuery || result)) ||
(!isTracked && callCount < kMaxUntrackedSamples && aQueryId < 2);
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 && isTracked && trackedQuery.submitReturnRva == 0x8D20D4)
{
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 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 = callCount < kMaxCalls;
}
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());
}
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());
}
int32_t result = 0x80000008;
if (gOriginalGpsSearch)
{
ScopedGpsProviderClassPatch providerPatch(aCostProvider, callCount);
result = gOriginalGpsSearch(aGraph, aStartHandle, aTargetHandle, aStartPoint, aTargetPoint, aCostProvider,
aOutPath, aOutCount);
}
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;
}
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);
if (gActiveGpsEdgeCostStats)
{
NoteGpsEdgeCostStats(*gActiveGpsEdgeCostStats, sourceClass, neighborClass, result, returnRva, aCurrentHandle,
aNeighborHandle);
}
float multiplier = 0.0f;
const auto hasMultiplier = sourceClass < 64 &&
TryReadFloatField(aCostProvider, 0x08 + static_cast<uintptr_t>(sourceClass) * sizeof(float),
multiplier);
uint32_t callCount = 0;
uint32_t classCallCount = 0;
bool shouldLog = false;
{
std::lock_guard lock(gGpsCostLogMutex);
callCount = gGpsEdgeCostLogCount++;
if (sourceClass < gGpsEdgeCostClassLogCounts.size())
{
classCallCount = gGpsEdgeCostClassLogCounts[sourceClass]++;
}
shouldLog = callCount < 32 || (sourceClass < 64 && classCallCount < 1);
}
if (shouldLog)
{
const auto baseCost = hasMultiplier && multiplier != 0.0f ? result / 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 (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);
}
LogGpsMultiplierSignature("GPSAuxMultiplier", kGpsAuxMultiplierRva, true, aJob, aNode, result,
__builtin_return_address(0));
return result;
}
float DetourGpsNodeMultiplier(void* aJob, void* aNode)
{
float result = 1.0f;
if (gOriginalGpsNodeMultiplier)
{
result = gOriginalGpsNodeMultiplier(aJob, aNode);
}
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;
}
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 (kEnableGpsTraceHooks)
{
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));
AttachProbeHook("GPSQuerySubmit 0x70a42c", kGpsQuerySubmitRva,
reinterpret_cast<void*>(&DetourGpsQuerySubmit),
reinterpret_cast<void**>(&gOriginalGpsQuerySubmit));
AttachProbeHook("GPSQueryDispatch 0x70a570", kGpsQueryDispatchRva,
reinterpret_cast<void*>(&DetourGpsQueryDispatch),
reinterpret_cast<void**>(&gOriginalGpsQueryDispatch));
AttachProbeHook("GPSQueryResultFetch 0x7094b8", kGpsQueryResultFetchRva,
reinterpret_cast<void*>(&DetourGpsQueryResultFetch),
reinterpret_cast<void**>(&gOriginalGpsQueryResultFetch));
AttachProbeHook("GPSQueryStatus 0xaa5704", kGpsQueryStatusRva,
reinterpret_cast<void*>(&DetourGpsQueryStatus),
reinterpret_cast<void**>(&gOriginalGpsQueryStatus));
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 || (kEnableGpsTraceHooks && kEnableGpsLocalSearchHooks))
{
AttachProbeHook("GPSSearch 0x44f054", kGpsSearchRva, reinterpret_cast<void*>(&DetourGpsSearch),
reinterpret_cast<void**>(&gOriginalGpsSearch));
}
if (kEnableGpsTraceHooks && kEnableGpsLocalSearchHooks)
{
AttachProbeHook("GPSRouteProducer 0x44cc7c", kGpsRouteProducerRva,
reinterpret_cast<void*>(&DetourGpsRouteProducer),
reinterpret_cast<void**>(&gOriginalGpsRouteProducer));
AttachProbeHook("GPSEdgeCost 0x44f838", kGpsEdgeCostRva, reinterpret_cast<void*>(&DetourGpsEdgeCost),
reinterpret_cast<void**>(&gOriginalGpsEdgeCost));
}
if (kEnableGpsMultiplierHooks)
{
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)
{
if (kEnableGpsTraceHooks)
{
DetachProbeHook("RunGPSQuery body 0x29bd128", kRunGpsQueryBodyRva);
DetachProbeHook("UpdateGPSQuery body 0x29bd254", kUpdateGpsQueryBodyRva);
DetachProbeHook("GPSQuerySubmit 0x70a42c", kGpsQuerySubmitRva);
DetachProbeHook("GPSQueryDispatch 0x70a570", kGpsQueryDispatchRva);
DetachProbeHook("GPSQueryResultFetch 0x7094b8", kGpsQueryResultFetchRva);
DetachProbeHook("GPSQueryStatus 0xaa5704", kGpsQueryStatusRva);
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 || (kEnableGpsTraceHooks && kEnableGpsLocalSearchHooks))
{
DetachProbeHook("GPSSearch 0x44f054", kGpsSearchRva);
}
if (kEnableGpsTraceHooks && kEnableGpsLocalSearchHooks)
{
DetachProbeHook("GPSRouteProducer 0x44cc7c", kGpsRouteProducerRva);
DetachProbeHook("GPSEdgeCost 0x44f838", kGpsEdgeCostRva);
}
if (kEnableGpsMultiplierHooks)
{
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;
}