Probe observer1 route service lookup

This commit is contained in:
2026-06-20 01:30:56 -05:00
parent ad4907040a
commit 8c8cc784e0
3 changed files with 149 additions and 2 deletions
+6
View File
@@ -40,6 +40,10 @@ Current behavior:
active byte at offset `0x84`, the service lookup key at offset `0x8c`, and
the runtime observer1 service-owner path used before its vtable `0x220`
lookup.
- Hooks observer1's generic service lookup at `0x287c44`, filters it to the
runtime type pointer at `.data` RVA `0x342f6a8`, logs the returned service
object and slot `0x220`, then dynamically hooks that service `0x220` route
lookup to log its route-id output handle.
- Resolves the native mappin system when one of those paths fires, logs relevant
vtable slot addresses, and temporarily hooks the route-adjacent slots.
@@ -58,6 +62,8 @@ Current route-probe focus:
- mappin route activate/deactivate: `0xaa6330`, `0x27abd7c`
- mappin route observer callbacks: `0xaa6610`, `0xaa6628`, `0xaa63e0`,
`0x27b10c0`, `0x295d4a0`, `0x286a85c`
- observer1 service lookup and returned service route lookup:
`0x287c44`, dynamic service vtable slot `0x220`
- route-build candidate called by the bridge: `0x5625a4`
Quest/objective pins did not fire the mappin tracking hooks in live tests. The
+20
View File
@@ -365,6 +365,26 @@ handles/strings under its inner object at offsets around `+0x180`, `+0x1a0`,
`routeObject + 0x8c` so the next runtime test can tell whether observer1 is
crossing into GPS/path computation or just updating presentation state.
The `07:22:14` controlled run confirmed observer1 is in the deliberate route
click window, not just load-time setup. The three quest route clicks produced
the expected sequence:
```text
JournalManager.TrackEntry
MappinSystem.RouteEventEnqueue old active=0 / new active=1
MappinSystem.RouteDeactivate old route key
MappinSystem.RouteActivate new route key
RouteObserver1 deactivate/activate with the matching routeObject + 0x8c route id
```
The observer1 service-owner object was stable across those calls
(`serviceOwner` vtable `0x142b74ea8`, slot `0x08` target `0x140287c44`).
Static disassembly of `0x140287c44` shows a generic type-to-service lookup, so
the next probe hooks that function, filters on the runtime type pointer at
`0x342f6a8`, logs the returned service object, and dynamically hooks the
returned service's vtable slot `0x220`. That slot is the immediate next native
call after the lookup and receives the route id from `routeObject + 0x8c`.
The most route-building-looking static candidate currently in this chain is
`0x5625a4`, called once from the bridge at `0x5984ec`. The bridge resolves
journal route ids through virtual journal-manager calls, collects several route
+123 -2
View File
@@ -138,6 +138,8 @@ 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;
HMODULE gModule = nullptr;
std::mutex gLogMutex;
@@ -190,6 +192,8 @@ uint32_t gRouteObserver23ActivateLogCount = 0;
uint32_t gRouteObserver0DeactivateLogCount = 0;
uint32_t gRouteObserver1DeactivateLogCount = 0;
uint32_t gRouteObserver23DeactivateLogCount = 0;
uint32_t gObserver1ServiceLookupLogCount = 0;
uint32_t gObserver1ServiceRouteLookupLogCount = 0;
uint64_t gLogStartTick = 0;
uint64_t gLogFrequency = 0;
void* gMappinSetTrackedTarget = nullptr;
@@ -200,7 +204,9 @@ void* gMappinPositionQueryTarget = nullptr;
void* gMappinCreateCustomPositionTarget = nullptr;
void* gMappinReleaseQueryTarget = nullptr;
void* gMappinActiveRouteTarget = nullptr;
void* gObserver1ServiceRouteLookupTarget = nullptr;
bool gMappinSlotHookAttachAttempted = false;
bool gObserver1ServiceRouteLookupHookAttachAttempted = false;
using ProbeFunc = void (*)(void* aThis, void* a2, void* a3, void* a4);
using JournalTrackEntryFunc = void (*)(void* aThis, void* aEntryHandle);
@@ -229,6 +235,8 @@ using RouteBuildCandidateFunc = void (*)(void* aThis, uint32_t aRouteId, uint32_
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);
ProbeFunc gOriginalGpsSystemTick = nullptr;
JournalTrackEntryFunc gOriginalJournalTrackEntry = nullptr;
ScriptNativeVoidFunc gOriginalFrameMappinPathWrapper = nullptr;
@@ -272,6 +280,8 @@ RouteObserverCallbackFunc gOriginalRouteObserver23Activate = nullptr;
RouteObserverCallbackFunc gOriginalRouteObserver0Deactivate = nullptr;
RouteObserverCallbackFunc gOriginalRouteObserver1Deactivate = nullptr;
RouteObserverCallbackFunc gOriginalRouteObserver23Deactivate = nullptr;
Observer1ServiceLookupFunc gOriginalObserver1ServiceLookup = nullptr;
Observer1ServiceRouteLookupFunc gOriginalObserver1ServiceRouteLookup = nullptr;
std::filesystem::path GetLogPath()
{
@@ -556,6 +566,11 @@ void* ReadPointerField(void* aObject, uintptr_t aOffset)
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);
@@ -679,8 +694,7 @@ void LogRouteObserverContext(const char* aName, uint32_t aCount, void* aObserver
{
auto* observerInner = ReadPointerField(aObserver, 0x08);
auto* observerInnerServiceOwner = ReadPointerField(observerInner, 0x18);
auto* observer1SystemType =
ReadPointerField(reinterpret_cast<void*>(GetImageBase() + kObserver1SystemTypeGlobalRva), 0x00);
auto* observer1SystemType = GetObserver1SystemType();
auto* routeObject = ReadPointerField(aRouteEntry, 0x00);
std::ostringstream line;
@@ -1801,6 +1815,108 @@ void DetachAbsoluteHook(const char* aName, void* aTarget)
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);
@@ -2001,6 +2117,9 @@ RED4EXT_C_EXPORT bool RED4EXT_CALL Main(RED4ext::v1::PluginHandle aHandle, RED4e
AttachProbeHook("RouteObserver23::Deactivate slot50", kRouteObserver23DeactivateRva,
reinterpret_cast<void*>(&DetourRouteObserver23Deactivate),
reinterpret_cast<void**>(&gOriginalRouteObserver23Deactivate));
AttachProbeHook("Observer1ServiceLookup 0x287c44", kObserver1ServiceLookupRva,
reinterpret_cast<void*>(&DetourObserver1ServiceLookup),
reinterpret_cast<void**>(&gOriginalObserver1ServiceLookup));
AttachProbeHook("FrameMappinPathWrapper", kFrameMappinPathWrapperRva,
reinterpret_cast<void*>(&DetourFrameMappinPathWrapper),
reinterpret_cast<void**>(&gOriginalFrameMappinPathWrapper));
@@ -2063,6 +2182,7 @@ RED4EXT_C_EXPORT bool RED4EXT_CALL Main(RED4ext::v1::PluginHandle aHandle, RED4e
DetachProbeHook("RouteObserver0::Deactivate slot50", kRouteObserver0DeactivateRva);
DetachProbeHook("RouteObserver1::Deactivate slot50", kRouteObserver1DeactivateRva);
DetachProbeHook("RouteObserver23::Deactivate slot50", kRouteObserver23DeactivateRva);
DetachProbeHook("Observer1ServiceLookup 0x287c44", kObserver1ServiceLookupRva);
DetachProbeHook("FrameMappinPathWrapper", kFrameMappinPathWrapperRva);
DetachProbeHook("FrameMappinPathCore", kFrameMappinPathCoreRva);
DetachProbeHook("SetSelectedMappinWrapper", kSetSelectedMappinWrapperRva);
@@ -2083,6 +2203,7 @@ RED4EXT_C_EXPORT bool RED4EXT_CALL Main(RED4ext::v1::PluginHandle aHandle, RED4e
DetachAbsoluteHook("MappinSystem::CreateCustomPosition slot2f0", gMappinCreateCustomPositionTarget);
DetachAbsoluteHook("MappinSystem::ReleaseQuery slot368", gMappinReleaseQueryTarget);
DetachAbsoluteHook("MappinSystem::ActiveRoute slot3a8", gMappinActiveRouteTarget);
DetachAbsoluteHook("Observer1Service::RouteLookup slot220", gObserver1ServiceRouteLookupTarget);
}
return true;