Files
2077-gps-mod/docs/red4ext-logging-shim.md
T
2026-06-20 13:37:21 -05:00

11 KiB

RED4ext logging shim

EdgeWeightGPS is a RED4ext plugin used to probe native GPS/mappin code paths without changing gameplay data.

It writes EdgeWeightGPS.log beside the installed DLL:

Cyberpunk 2077/red4ext/plugins/EdgeWeightGPS/EdgeWeightGPS.log

Current behavior:

  • Logs plugin load/unload.
  • Logs the RED4ext plugin handle and SDK pointer value passed to Main.
  • Registers Running game-state callbacks so route tests can be timestamped.
  • Hooks selected native wrappers and cores around GPSSystem, mappin tracking, world-map mappin selection, and map path framing.
  • Hooks JournalManager.TrackEntry at RVA 0x5944fc, which is the native handoff used by quest/objective world-map route plotting.
  • Dumps the JournalManager listener array at offsets 0x210/0x21c and the listener vtable slots 0x28, 0x30, and 0x50, matching the indirect calls made by the native TrackEntry implementation.
  • Hooks the non-default journal listener callbacks seen during route plotting: 0xea89a8, 0xea8958, 0xe63f80, 0xe63e6c, 0xe63f00, 0x8d136c, 0x431a34, 0x55a4e4, and 0x14de238.
  • For those listener callbacks, logs the listener object, event pointer, common object fields, and vtable slots on candidate nested owner/service objects.
  • Hooks the route handoff found from the journal listener bridge: 0x598250, 0x13763d8, 0xaa62d0, 0xaa6330, 0x27abd7c, and 0x5625a4.
  • Logs mappin route-event fields, active/deactive route keys, the mappin active-route map at system + 0x1a0, and the route observer list at system + 0x280.
  • Hooks the route observer callbacks reached by route activation/deactivation: 0xaa6610, 0xaa6628, 0xaa63e0, 0x27b10c0, 0x295d4a0, and 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, 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.
  • The observer1 service branch was later identified as a false positive: 0x287c44 returns the JournalManager object (vtable 0x1430f0890) and the dynamic slot 0x220 resolves route IDs to readable journal/UI metadata strings such as internet, home, clubs, and arasaka. It is now left in the source but no longer attached in the active probe.
  • Hooks the GPS query lifecycle found from the RunGPSQuery/UpdateGPSQuery native registration cluster: 0x29bd128 (RunGPSQuery body), 0x29bd254 (UpdateGPSQuery body), 0x70a42c (shared query submitter), 0x70a570 (low-level query dispatch), 0x7094b8 (query result/path fetch), and 0xaa5704 (query status check).
  • Logs query endpoints, returned query IDs, shared submitter return RVAs, query state fields, status values, result-fetch success, and summarized path buffers/point counts.
  • The active probe now records query IDs returned from 0x70a42c and always logs result/status polling for those tracked IDs. Startup polling of query IDs 0/1 is sampled only briefly so it cannot exhaust the route-click log budget.
  • Resolves the native mappin system when one of those paths fires, logs relevant vtable slot addresses, and temporarily hooks the route-adjacent slots.

Current route-probe focus:

  • FrameMappinPath wrapper/core: 0x27c4314, 0x27bc1ec
  • SetSelectedMappin wrappers/core: 0x27c4a38, 0x27c4944, 0x27c49c4, 0x27c1684
  • TrackCustomPositionMappin wrapper/core: 0x27c4aac, 0x27c2318
  • mappin-system slots 0x1f0, 0x280, and 0x2f0
  • JournalManager.TrackEntry implementation: 0x5944fc
  • non-default TrackEntry listener callbacks listed above
  • journal/mappin route bridge candidate: 0x598250
  • mappin route-event enqueue: 0x13763d8
  • mappin route-event handler: 0xaa62d0
  • 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 (disabled after being classified as journal metadata)
  • route-build candidate called by the bridge: 0x5625a4
  • GPS query lifecycle: 0x29bd128, 0x29bd254, 0x70a42c, 0x70a570, 0x7094b8, 0xaa5704

Quest/objective pins did not fire the mappin tracking hooks in live tests. The REDscript decompile shows that those pins call JournalManager.TrackEntry instead, so the current useful runtime question is which native listener reacts to tracked-entry changes.

Most recent controlled test:

  • 06:31:43: user hit Continue / GPS tick logged.
  • 06:32:02: automatic TrackEntry during load, no route input.
  • 06:32:27: map opened.
  • 06:32:44, 06:32:50, 06:32:56: deliberate quest/objective route plots produced JournalManager::TrackEntry calls from return RVA 0x26ac34e.
  • After map open, the journal listener count rose to 254. Most listener entries dispatch to no-op 0x14a700; the non-default callbacks above are the current drill-down targets.

Newest controlled test:

  • User clock 01:05: Continue on the main menu. The log saw GPSSystem/Tick.
  • User clock 01:27: Space to continue. The log saw automatic JournalManager.TrackEntry, a dense JournalRouteBridge burst, and thirty RouteBuildCandidate 0x5625a4 calls. About 5.5 seconds later the mappin route activation/deactivation functions fired.
  • User clock 01:52: map open. Hovering icons produced the expected SetSelectedMappin bursts.
  • Deliberate quest route clicks produced JournalManager.TrackEntry, route event enqueue calls for old-route-off/new-route-on, and route event handler calls roughly 11-18 ms later.
  • Custom pin routing used the separate custom-position mappin path, then the shared route activate/deactivate helper.

Latest observer1 probe result:

  • The Observer1ServiceLookup hook fired, but it proved observer1 is not route solving. It returned the same JournalManager vtable seen in earlier journal traces, and its slot 0x220 resolved numeric route IDs into journal/UI category strings. That branch is closed as marker metadata.

Current static lead:

  • RunGPSQuery and UpdateGPSQuery are registered native thunks at 0x29bd5ac and 0x29bd6c8. Their deeper bodies are 0x29bd128 and 0x29bd254.
  • RunGPSQuery submits a query through a subsystem reached via an object field at +0x130, returning a query ID or -1.
  • UpdateGPSQuery fetches a completed query result through 0x7094b8, copies a path point buffer, and writes the resulting point array for script/UI consumption.
  • The shared submitter 0x70a42c is also used by non-GPS navigation callers, so return RVAs and query IDs are important for separating map GPS from traffic or AI path requests.

Latest GPS-query finding:

  • During a controlled route test, the normal world-map GPS path did not call the script-native RunGPSQuery/UpdateGPSQuery bodies.
  • Each deliberate quest/custom route click called the shared submitter 0x70a42c from return RVA 0x8d20d4. In the 2026-06-20 19:03 run, the deliberately plotted routes returned query IDs 17, 18, 19, and 20 for the side job, Sinnerman, Claire's Garage, and a custom pin.
  • Each submitted route produced three 0x70a570 dispatches: current/player position, a nearby snapped/lane position, then the destination position.
  • The dense log stream immediately after Space-to-Continue is repeated 0x7094b8 result polling for startup/minimap route queries, matching the HUD route initialization delay.
  • GPSQueryResultFetch 0x7094b8 returning 1 is the route-solve completion signal. The caller at return RVA 0x52069c starts handling a completed result, then the caller at return RVA 0x520783 drains result records. The visible route record appears in the object passed as outPath: offset 0x30 is a packed point count (N/N on the first drain record, then N), and offset 0x28 looks like the route point array pointer. The deliberate routes produced the familiar 11, 16, 15, and 14 point-like counts in the 2026-06-20 19:03 run, and 11, 16, 15, 11 in the 19:16 map routine where the custom pin was different.
  • Static disassembly confirms 0xaa5704 takes (manager, queryId) and returns a small status code; one caller checks for value 1. Runtime logging shows this is HUD/display lifecycle polling rather than solve completion: it polls the currently displayed query once per second and pauses when the minimap is not rendered.
  • The current installed probe adds a read-only dump of suspected result-object fields around offsets 0x20 through 0x5f on successful active-route fetches. The field at outPath + 0x28 is confirmed to be a pointer-sized route-result member with a count at outPath + 0x30, but live data does not decode as plain Vector3/Vector4 coordinates. The next probe treats it as an internal route element array and dumps raw bytes instead of assuming a float-vector layout.

Latest route-result findings:

  • User clock 26:38: Continue on the main menu.
  • User clock 27:01: Space to continue, with the car in a different position after a previous drive.
  • User clock 27:21: map opened.
  • User clock 28:06: normal route-click routine complete.
  • Same three quest destinations from the shifted start produced different result counts: side job 12, Sinnerman 23, Claire's Garage 18, and the custom pin 17.
  • Static disassembly of the 0x520783 result-drain caller shows 0x7094b8 drains one 0x68-byte route result record at a time from an internal vector.
  • Inside that 0x68-byte record, 0x41c508 iterates the member at offset 0x28 as an array of 8-byte pointers, using the dword at +0x34 as the live count. The dword at +0x30 behaves like capacity or retained allocation size; earlier logs that treated +0x30 as the point count were reading the right magnitude only while capacity and count matched.
  • The pointed objects appear to be route/lane segment objects rather than coordinate vectors. The next probe logs the raw pointer array and selected fields the engine reads from those pointed objects (+0x68, +0x74, +0x90, and +0x98).

Autodrive finding:

  • User clock 28:28 and 28:48: enabling autodrive did not use the full world-map GPS caller. It submitted repeated 0x70a42c queries from return RVA 0x8ed760, with query_fcc=4, near-current vehicle positions, and result fetches from return RVA 0x8ed7df.
  • These autodrive queries appear to be short local vehicle navigation requests using the same query service, separate from the full player GPS map route caller at return RVA 0x8d20d4 with query_fcc=5.

Build and install from the Fedora toolbox:

toolbox run -c 2077 ./tools/install_red4ext_shim.sh

This shim is read-only. It does not enable any of the archived traffic data patches.