From b34f2e2dee996892cdba25237f50e902171d2855 Mon Sep 17 00:00:00 2001 From: Jacob Babor Date: Sat, 20 Jun 2026 13:43:55 -0500 Subject: [PATCH] Decode GPS route element records --- docs/red4ext-logging-shim.md | 35 ++++++++-------- red4ext/EdgeWeightGPS/src/Main.cpp | 67 +++++++++++++++++------------- 2 files changed, 55 insertions(+), 47 deletions(-) diff --git a/docs/red4ext-logging-shim.md b/docs/red4ext-logging-shim.md index 5be03ec..1209fb1 100644 --- a/docs/red4ext-logging-shim.md +++ b/docs/red4ext-logging-shim.md @@ -166,13 +166,11 @@ Latest GPS-query finding: 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. +- The current installed probe adds a read-only dump of result-object fields + around offsets `0x20` through `0x5f` on successful active-route fetches. + The field at `outPath + 0x28` is the data pointer for an internal route + element vector, but live data does not decode as plain `Vector3`/`Vector4` + coordinates. Latest route-result findings: @@ -185,16 +183,19 @@ Latest route-result findings: 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`). + drains one route result record at a time from an internal vector, then copies + that record into `outPath`. +- The copied route-result subobject contains a vector at `outPath + 0x28`. + Its live count is the dword at `outPath + 0x34`; in the latest run this + cleanly matched the visible routes: side job `12`, Sinnerman `23`, Claire's + Garage `18`, and custom pin `17`. +- The vector elements are 0x28-byte records, not pointers. `0x41be2c` calls + `0x41be94` with `(data, data + count * 40)`, so the next probe logs each + route element as a compact 40-byte record tuple: + `h00,u08,u0c,u10,u14,h18,h20`. +- A nearby helper at `0x41c508` does iterate an array of 8-byte pointers at + offset `+0x28`, but that is a different route-adjacent object type, not the + `0x7094b8` output record captured in `outPath`. Autodrive finding: diff --git a/red4ext/EdgeWeightGPS/src/Main.cpp b/red4ext/EdgeWeightGPS/src/Main.cpp index 9b4a20f..af80ebd 100644 --- a/red4ext/EdgeWeightGPS/src/Main.cpp +++ b/red4ext/EdgeWeightGPS/src/Main.cpp @@ -908,42 +908,51 @@ void AppendGpsResultElementArraySummary(std::ostringstream& aLine, const char* a return; } - const auto rawSize = std::min(0x80, static_cast(count) * sizeof(void*)); - AppendBytesPointer(aLine, (prefix + "_ptr28_ptrsRaw").c_str(), data, rawSize); + constexpr uintptr_t kRouteRecordStride = 0x28; + const auto rawSize = std::min(0xA0, static_cast(count) * kRouteRecordStride); + AppendBytesPointer(aLine, (prefix + "_ptr28_rec40raw").c_str(), data, rawSize); - const auto appendElement = [&](uint32_t aIndex, const char* aLabel) { - auto* element = ReadPointerField(data, static_cast(aIndex) * sizeof(void*)); - const auto elementPrefix = prefix + "_ptr28_" + aLabel; - AppendPointerWithRva(aLine, elementPrefix.c_str(), element); - if (!element || reinterpret_cast(element) < 0x10000) + const auto flags = aLine.flags(); + const auto fill = aLine.fill(); + aLine << " " << prefix << "_ptr28_rec40=["; + for (uint32_t index = 0; index < count; ++index) + { + const auto recordAddress = reinterpret_cast(data) + static_cast(index) * kRouteRecordStride; + const auto* record = reinterpret_cast(recordAddress); + + uint64_t h00 = 0; + uint32_t u08 = 0; + uint32_t u0c = 0; + uint32_t u10 = 0; + uint32_t u14 = 0; + uint64_t h18 = 0; + uint64_t h20 = 0; + const auto hasRecord = TryReadMemory(record, h00) && + TryReadMemory(reinterpret_cast(recordAddress + 0x08), u08) && + TryReadMemory(reinterpret_cast(recordAddress + 0x0C), u0c) && + TryReadMemory(reinterpret_cast(recordAddress + 0x10), u10) && + TryReadMemory(reinterpret_cast(recordAddress + 0x14), u14) && + TryReadMemory(reinterpret_cast(recordAddress + 0x18), h18) && + TryReadMemory(reinterpret_cast(recordAddress + 0x20), h20); + + if (index > 0) { - return; + aLine << ";"; } - 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(reinterpret_cast(element) + 0x74), sampleCount); - if (samples && reinterpret_cast(samples) >= 0x10000 && sampleCount > 0 && sampleCount <= 4096) + aLine << index << ":"; + if (!hasRecord) { - const auto sampleRawSize = std::min(0x20, static_cast(sampleCount) * 0x08); - AppendBytesPointer(aLine, (elementPrefix + "_f68raw").c_str(), samples, sampleRawSize); + aLine << ""; + continue; } - }; - const auto firstLimit = std::min(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"); + aLine << std::hex << h00 << "," << std::dec << u08 << "," << u0c << "," << u10 << "," << u14 << "," + << std::hex << h18 << "," << h20 << std::dec; } + aLine << "]"; + aLine.flags(flags); + aLine.fill(fill); } void AppendGpsResultObjectSummary(std::ostringstream& aLine, const char* aPrefix, void* aResultObject) @@ -958,8 +967,6 @@ void AppendGpsResultObjectSummary(std::ostringstream& aLine, const char* aPrefix 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);