15 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.TrackEntryat RVA0x5944fc, which is the native handoff used by quest/objective world-map route plotting. - Dumps the
JournalManagerlistener array at offsets0x210/0x21cand the listener vtable slots0x28,0x30, and0x50, matching the indirect calls made by the nativeTrackEntryimplementation. - Hooks the non-default journal listener callbacks seen during route plotting:
0xea89a8,0xea8958,0xe63f80,0xe63e6c,0xe63f00,0x8d136c,0x431a34,0x55a4e4, and0x14de238. - 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, and0x5625a4. - Logs mappin route-event fields, active/deactive route keys, the mappin
active-route map at
system + 0x1a0, and the route observer list atsystem + 0x280. - Hooks the route observer callbacks reached by route activation/deactivation:
0xaa6610,0xaa6628,0xaa63e0,0x27b10c0,0x295d4a0, and0x286a85c. - 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 offset0x8c, and the runtime observer1 service-owner path used before its vtable0x220lookup. - Hooks observer1's generic service lookup at
0x287c44, filters it to the runtime type pointer at.dataRVA0x342f6a8, logs the returned service object and slot0x220, then dynamically hooks that service0x220route lookup to log its route-id output handle. - The observer1 service branch was later identified as a false positive:
0x287c44returns theJournalManagerobject (vtable 0x1430f0890) and the dynamic slot0x220resolves route IDs to readable journal/UI metadata strings such asinternet,home,clubs, andarasaka. It is now left in the source but no longer attached in the active probe. - Hooks the GPS query lifecycle found from the
RunGPSQuery/UpdateGPSQuerynative registration cluster:0x29bd128(RunGPSQuerybody),0x29bd254(UpdateGPSQuerybody),0x70a42c(shared query submitter),0x70a570(low-level query dispatch),0x7094b8(query result/path fetch), and0xaa5704(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
0x70a42cand always logs result/status polling for those tracked IDs. Startup polling of query IDs0/1is 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:
FrameMappinPathwrapper/core:0x27c4314,0x27bc1ecSetSelectedMappinwrappers/core:0x27c4a38,0x27c4944,0x27c49c4,0x27c1684TrackCustomPositionMappinwrapper/core:0x27c4aac,0x27c2318- mappin-system slots
0x1f0,0x280, and0x2f0 JournalManager.TrackEntryimplementation:0x5944fc- non-default
TrackEntrylistener 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 slot0x220(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: automaticTrackEntryduring load, no route input.06:32:27: map opened.06:32:44,06:32:50,06:32:56: deliberate quest/objective route plots producedJournalManager::TrackEntrycalls from return RVA0x26ac34e.- 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 sawGPSSystem/Tick. - User clock
01:27: Space to continue. The log saw automaticJournalManager.TrackEntry, a denseJournalRouteBridgeburst, and thirtyRouteBuildCandidate 0x5625a4calls. About 5.5 seconds later the mappin route activation/deactivation functions fired. - User clock
01:52: map open. Hovering icons produced the expectedSetSelectedMappinbursts. - 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
Observer1ServiceLookuphook fired, but it proved observer1 is not route solving. It returned the sameJournalManagervtable seen in earlier journal traces, and its slot0x220resolved numeric route IDs into journal/UI category strings. That branch is closed as marker metadata.
Current static lead:
RunGPSQueryandUpdateGPSQueryare registered native thunks at0x29bd5acand0x29bd6c8. Their deeper bodies are0x29bd128and0x29bd254.RunGPSQuerysubmits a query through a subsystem reached via an object field at+0x130, returning a query ID or-1.UpdateGPSQueryfetches a completed query result through0x7094b8, copies a path point buffer, and writes the resulting point array for script/UI consumption.- The shared submitter
0x70a42cis 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/UpdateGPSQuerybodies. - Each deliberate quest/custom route click called the shared submitter
0x70a42cfrom return RVA0x8d20d4. In the 2026-06-20 19:03 run, the deliberately plotted routes returned query IDs17,18,19, and20for the side job, Sinnerman, Claire's Garage, and a custom pin. - Each submitted route produced three
0x70a570dispatches: current/player position, a nearby snapped/lane position, then the destination position. - The dense log stream immediately after Space-to-Continue is repeated
0x7094b8result polling for startup/minimap route queries, matching the HUD route initialization delay. GPSQueryResultFetch 0x7094b8returning1is the route-solve completion signal. The caller at return RVA0x52069cstarts handling a completed result, then the caller at return RVA0x520783drains result records. The visible route record appears in the object passed asoutPath: offset0x30is a packed point count (N/Non the first drain record, thenN), and offset0x28looks like the route point array pointer. The deliberate routes produced the familiar11,16,15, and14point-like counts in the 2026-06-20 19:03 run, and11,16,15,11in the 19:16 map routine where the custom pin was different.- Static disassembly confirms
0xaa5704takes(manager, queryId)and returns a small status code; one caller checks for value1. 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 result-object fields
around offsets
0x20through0x5fon successful active-route fetches. The field atoutPath + 0x28is the data pointer for an internal route element vector, but live data does not decode as plainVector3/Vector4coordinates.
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, Sinnerman23, Claire's Garage18, and the custom pin17. - Static disassembly of the
0x520783result-drain caller shows0x7094b8drains one route result record at a time from an internal vector, then copies that record intooutPath. - The copied route-result subobject contains a vector at
outPath + 0x28. Its live count is the dword atoutPath + 0x34; in the latest run this cleanly matched the visible routes: side job12, Sinnerman23, Claire's Garage18, and custom pin17. - The vector elements are 0x28-byte records, not pointers.
0x41be2ccalls0x41be94with(data, data + count * 40). The active probe logs each route element as a compact 40-byte record tuple:h00,u08hex,u0c,u10,u14,h18,h18lowFloat,h18highFloat,h20. - A nearby helper at
0x41c508does iterate an array of 8-byte pointers at offset+0x28, but that is a different route-adjacent object type, not the0x7094b8output record captured inoutPath.
Latest route-element decode:
- User clock
45:37: Continue on the main menu. - User clock
46:05: Space to continue. - User clock
46:15: map opened and the usual route-click routine was run. - The startup/minimap route and the later explicit Claire's Garage map route
produced identical 18-record
ptr28sequences, so the same solved route object feeds both HUD startup and deliberate map plotting. - The four deliberate map routes completed as route result IDs
2through5: side job12records / final span end68, Sinnerman23records / final span end188, Claire's Garage18records / final span end86, custom pin14records / final span end78. - Within every route,
u10andu14are monotonically increasing start/end indexes into a flattened point/span buffer. The finalu14equalsgpsResult_u20for every route. h00is a stable per-segment handle: common route prefixes share identicalh00sequences, and the custom pin and Claire's Garage routes share their first ten solved segments before diverging.u08is packed metadata. Values appear byte-structured, for example0x00020202,0x01020203,0x02020103, and0x03020003, rather than a simple scalar weight.h18splits into two 32-bit floats. The high half is consistently a sane road-scale float, often in the20to230range, and is likely segment length/cost-like output. The low half is usually zero or a tiny value, with non-zero values appearing around the start segment.h20has been zero in all captured full-map GPS result records.
Current static producer lead:
0x70a908packages the route endpoints and query settings, then calls0x44cc7c.0x44cc7cis now the main producer corridor, not merely a generic unknown helper. It allocates large scratch state, projects the query endpoints, and calls deeper pathfinding routines that should decide route cost before the0x7094b8fetch/drain path copies final result records.- Direct xrefs confirm
0x41be94has only the result-copy caller0x41be69; patching there would alter copied/displayed records after route choice. The cost patch needs to happen upstream of the0x44cc7cresult construction path. 0x44f054is the strongest route-planning lead so far. It validates the packed start/target lane handles, initializes a search-state table and open list, pushes the start state, repeatedly pops the best state, checks for the destination handle, traverses adjacency lists, and writes predecessor, accumulated-cost, and priority fields for improved neighbor states. This is the native GPS graph search loop.- The search-state record is 0x20 bytes. Offsets
+0x00/+0x04/+0x08are a world position,+0x0cis accumulated path cost,+0x10is priority (cost + heuristic),+0x14stores open/closed flags and the predecessor index, and+0x18stores the packed lane handle. 0x44f7bcis the heuristic helper. It returns world-space distance to the target scaled by the constant at0x1431ef3a0(0.9990000129), unless a route-shape helper supplies a shorter adjusted distance.- The search loop calls a strategy object's vtable slot
+0x08as an edge admissibility predicate and slot+0x10as the edge-cost callback. For the base/class-filter strategies, slot+0x10resolves to0x44f838. 0x44f838computes the geometric edge distance and multiplies it byprovider[0x08 + classId * 4], whereclassIdis(*(routePoint + 0x13) & 0x3f). The GPS planner is therefore weighted, but by a compact baked road-class table, not by trafficmaxSpeedor lane connection probabilities.- The active probe now hooks
0x44f054and0x44f838read-only. It logs the live provider vtable, the 64 class multipliers, start/target handles, and a capped sample of edge-cost calls with point class IDs, masks, handles, and returned costs.
Autodrive finding:
- User clock
28:28and28:48: enabling autodrive did not use the full world-map GPS caller. It submitted repeated0x70a42cqueries from return RVA0x8ed760, withquery_fcc=4, near-current vehicle positions, and result fetches from return RVA0x8ed7df. - 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
0x8d20d4withquery_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.