Files
2077-gps-mod/red4ext/EdgeWeightGPS/src/Main.cpp
T
2026-06-20 13:37:21 -05:00

2917 lines
113 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 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;
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;
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;
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);
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;
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;
}
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));
}
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;
}
const auto rawSize = std::min<size_t>(0x80, static_cast<size_t>(count) * sizeof(void*));
AppendBytesPointer(aLine, (prefix + "_ptr28_ptrsRaw").c_str(), data, rawSize);
const auto appendElement = [&](uint32_t aIndex, const char* aLabel) {
auto* element = ReadPointerField(data, static_cast<uintptr_t>(aIndex) * sizeof(void*));
const auto elementPrefix = prefix + "_ptr28_" + aLabel;
AppendPointerWithRva(aLine, elementPrefix.c_str(), element);
if (!element || reinterpret_cast<uintptr_t>(element) < 0x10000)
{
return;
}
AppendPointerField(aLine, (elementPrefix + "_f68").c_str(), element, 0x68);
AppendUint32Field(aLine, (elementPrefix + "_u74").c_str(), element, 0x74);
AppendPointerField(aLine, (elementPrefix + "_f90").c_str(), element, 0x90);
AppendUint32Field(aLine, (elementPrefix + "_u98").c_str(), element, 0x98);
uint32_t sampleCount = 0;
auto* samples = ReadPointerField(element, 0x68);
TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(element) + 0x74), sampleCount);
if (samples && reinterpret_cast<uintptr_t>(samples) >= 0x10000 && sampleCount > 0 && sampleCount <= 4096)
{
const auto sampleRawSize = std::min<size_t>(0x20, static_cast<size_t>(sampleCount) * 0x08);
AppendBytesPointer(aLine, (elementPrefix + "_f68raw").c_str(), samples, sampleRawSize);
}
};
const auto firstLimit = std::min<uint32_t>(count, 4);
for (uint32_t index = 0; index < firstLimit; ++index)
{
appendElement(index, ("elem" + std::to_string(index)).c_str());
}
if (count > firstLimit)
{
appendElement(count - 1, "last");
}
}
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);
AppendVector3ListCandidate(aLine, (std::string(aPrefix) + "_vec28s12").c_str(), aResultObject, 0x28, 0x30, 12);
AppendVectorListCandidate(aLine, (std::string(aPrefix) + "_vec28s16").c_str(), aResultObject, 0x28, 0x30, 16);
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);
}
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 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");
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("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)
{
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("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;
}