diff --git a/docs/red4ext-logging-shim.md b/docs/red4ext-logging-shim.md index 8796f85..25e33ae 100644 --- a/docs/red4ext-logging-shim.md +++ b/docs/red4ext-logging-shim.md @@ -37,7 +37,9 @@ Current behavior: `0x286a85c`. - Logs the route entry pointer and route object pointer passed to those observers, including route object vtable slots and common fields such as the - active byte at offset `0x84`. + 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. - Resolves the native mappin system when one of those paths fires, logs relevant vtable slot addresses, and temporarily hooks the route-adjacent slots. diff --git a/docs/traffic-system-debrief.md b/docs/traffic-system-debrief.md index c09e5bf..2d87565 100644 --- a/docs/traffic-system-debrief.md +++ b/docs/traffic-system-debrief.md @@ -337,6 +337,34 @@ The installed probe now hooks those callbacks directly and logs the route entry, route object, route object vtable slots, and the route object's active byte at offset `0x84`. +The first observer-object run separated three route-object vtable families: + +```text +vtable 0x142b31138: quest/objective route objects, key at +0x40 +vtable 0x142af1510: quest/objective route objects, key at +0x40 +vtable 0x142b30e20: custom/player pin route objects, key at +0x40 +``` + +`RouteActivate` and `RouteDeactivate` really do flip the route object's active +byte at `+0x84`. The observer callbacks receive the same route entry after that +flip. + +Static disassembly of observer0 (`0xaa6610 -> 0xaa6678`) shows it consuming +route-object virtuals at `+0x1a8`, `+0x188`, `+0x140`, and `+0x198`. +`+0x198` returns a route enum from a nested descriptor; `+0x188` returns a +descriptor payload pointer; and `+0x140` is a route-family-specific boolean. +That observer stores derived route state into buckets under the observer object, +choosing one bucket only when the enum is `0x8e`. + +Static disassembly of observer1 (`0xaa6628`) is now the highest-value +downstream branch. It resolves a route object, then calls a generic system +getter with a runtime type/global at `.data` RVA `0x342f6a8`, calls the returned +service's vtable slot `0x220` with `routeObject + 0x8c`, and stores generated +handles/strings under its inner object at offsets around `+0x180`, `+0x1a0`, +`+0x1c0`, and `+0x1e0`. The current probe logs that service-owner path and +`routeObject + 0x8c` so the next runtime test can tell whether observer1 is +crossing into GPS/path computation or just updating presentation state. + 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 diff --git a/red4ext/EdgeWeightGPS/src/Main.cpp b/red4ext/EdgeWeightGPS/src/Main.cpp index e68bba6..02e5d52 100644 --- a/red4ext/EdgeWeightGPS/src/Main.cpp +++ b/red4ext/EdgeWeightGPS/src/Main.cpp @@ -137,6 +137,7 @@ 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; HMODULE gModule = nullptr; std::mutex gLogMutex; @@ -677,12 +678,17 @@ void LogMappinRouteCollections(const char* aName, uint32_t aCount, void* aSystem void LogRouteObserverContext(const char* aName, uint32_t aCount, void* aObserver, void* aRouteEntry) { auto* observerInner = ReadPointerField(aObserver, 0x08); + auto* observerInnerServiceOwner = ReadPointerField(observerInner, 0x18); + auto* observer1SystemType = + ReadPointerField(reinterpret_cast(GetImageBase() + kObserver1SystemTypeGlobalRva), 0x00); 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); @@ -696,6 +702,31 @@ void LogRouteObserverContext(const char* aName, uint32_t aCount, void* 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; @@ -710,9 +741,13 @@ void LogRouteObserverContext(const char* aName, uint32_t aCount, void* aObserver 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()); }