Decode GPS route element records

This commit is contained in:
2026-06-20 13:43:55 -05:00
parent 56a451a7cb
commit b34f2e2dee
2 changed files with 55 additions and 47 deletions
+18 -17
View File
@@ -166,13 +166,11 @@ Latest GPS-query finding:
this is HUD/display lifecycle polling rather than solve completion: it polls 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 the currently displayed query once per second and pauses when the minimap is
not rendered. not rendered.
- The current installed probe adds a read-only dump of suspected result-object - The current installed probe adds a read-only dump of result-object fields
fields around offsets `0x20` through `0x5f` on successful active-route around offsets `0x20` through `0x5f` on successful active-route fetches.
fetches. The field at `outPath + 0x28` is confirmed to be a pointer-sized The field at `outPath + 0x28` is the data pointer for an internal route
route-result member with a count at `outPath + 0x30`, but live data does not element vector, but live data does not decode as plain `Vector3`/`Vector4`
decode as plain `Vector3`/`Vector4` coordinates. The next probe treats it as coordinates.
an internal route element array and dumps raw bytes instead of assuming a
float-vector layout.
Latest route-result findings: 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 result counts: side job `12`, Sinnerman `23`, Claire's Garage `18`, and the
custom pin `17`. custom pin `17`.
- Static disassembly of the `0x520783` result-drain caller shows `0x7094b8` - Static disassembly of the `0x520783` result-drain caller shows `0x7094b8`
drains one 0x68-byte route result record at a time from an internal vector. drains one route result record at a time from an internal vector, then copies
- Inside that 0x68-byte record, `0x41c508` iterates the member at offset that record into `outPath`.
`0x28` as an array of 8-byte pointers, using the dword at `+0x34` as the - The copied route-result subobject contains a vector at `outPath + 0x28`.
live count. The dword at `+0x30` behaves like capacity or retained allocation Its live count is the dword at `outPath + 0x34`; in the latest run this
size; earlier logs that treated `+0x30` as the point count were reading the cleanly matched the visible routes: side job `12`, Sinnerman `23`, Claire's
right magnitude only while capacity and count matched. Garage `18`, and custom pin `17`.
- The pointed objects appear to be route/lane segment objects rather than - The vector elements are 0x28-byte records, not pointers. `0x41be2c` calls
coordinate vectors. The next probe logs the raw pointer array and selected `0x41be94` with `(data, data + count * 40)`, so the next probe logs each
fields the engine reads from those pointed objects (`+0x68`, `+0x74`, route element as a compact 40-byte record tuple:
`+0x90`, and `+0x98`). `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: Autodrive finding:
+37 -30
View File
@@ -908,42 +908,51 @@ void AppendGpsResultElementArraySummary(std::ostringstream& aLine, const char* a
return; return;
} }
const auto rawSize = std::min<size_t>(0x80, static_cast<size_t>(count) * sizeof(void*)); constexpr uintptr_t kRouteRecordStride = 0x28;
AppendBytesPointer(aLine, (prefix + "_ptr28_ptrsRaw").c_str(), data, rawSize); const auto rawSize = std::min<size_t>(0xA0, static_cast<size_t>(count) * kRouteRecordStride);
AppendBytesPointer(aLine, (prefix + "_ptr28_rec40raw").c_str(), data, rawSize);
const auto appendElement = [&](uint32_t aIndex, const char* aLabel) { const auto flags = aLine.flags();
auto* element = ReadPointerField(data, static_cast<uintptr_t>(aIndex) * sizeof(void*)); const auto fill = aLine.fill();
const auto elementPrefix = prefix + "_ptr28_" + aLabel; aLine << " " << prefix << "_ptr28_rec40=[";
AppendPointerWithRva(aLine, elementPrefix.c_str(), element); for (uint32_t index = 0; index < count; ++index)
if (!element || reinterpret_cast<uintptr_t>(element) < 0x10000) {
const auto recordAddress = reinterpret_cast<uintptr_t>(data) + static_cast<uintptr_t>(index) * kRouteRecordStride;
const auto* record = reinterpret_cast<const void*>(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<const void*>(recordAddress + 0x08), u08) &&
TryReadMemory(reinterpret_cast<const void*>(recordAddress + 0x0C), u0c) &&
TryReadMemory(reinterpret_cast<const void*>(recordAddress + 0x10), u10) &&
TryReadMemory(reinterpret_cast<const void*>(recordAddress + 0x14), u14) &&
TryReadMemory(reinterpret_cast<const void*>(recordAddress + 0x18), h18) &&
TryReadMemory(reinterpret_cast<const void*>(recordAddress + 0x20), h20);
if (index > 0)
{ {
return; aLine << ";";
} }
AppendPointerField(aLine, (elementPrefix + "_f68").c_str(), element, 0x68); aLine << index << ":";
AppendUint32Field(aLine, (elementPrefix + "_u74").c_str(), element, 0x74); if (!hasRecord)
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<const void*>(reinterpret_cast<uintptr_t>(element) + 0x74), sampleCount);
if (samples && reinterpret_cast<uintptr_t>(samples) >= 0x10000 && sampleCount > 0 && sampleCount <= 4096)
{ {
const auto sampleRawSize = std::min<size_t>(0x20, static_cast<size_t>(sampleCount) * 0x08); aLine << "<unreadable>";
AppendBytesPointer(aLine, (elementPrefix + "_f68raw").c_str(), samples, sampleRawSize); continue;
} }
};
const auto firstLimit = std::min<uint32_t>(count, 4); aLine << std::hex << h00 << "," << std::dec << u08 << "," << u0c << "," << u10 << "," << u14 << ","
for (uint32_t index = 0; index < firstLimit; ++index) << std::hex << h18 << "," << h20 << std::dec;
{
appendElement(index, ("elem" + std::to_string(index)).c_str());
}
if (count > firstLimit)
{
appendElement(count - 1, "last");
} }
aLine << "]";
aLine.flags(flags);
aLine.fill(fill);
} }
void AppendGpsResultObjectSummary(std::ostringstream& aLine, const char* aPrefix, void* aResultObject) 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) + "_u20").c_str(), aResultObject, 0x20);
AppendUint32Field(aLine, (std::string(aPrefix) + "_u24").c_str(), aResultObject, 0x24); AppendUint32Field(aLine, (std::string(aPrefix) + "_u24").c_str(), aResultObject, 0x24);
AppendPointerField(aLine, (std::string(aPrefix) + "_ptr28").c_str(), aResultObject, 0x28); 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) + "_vec20").c_str(), aResultObject, 0x20, 0x2C, 12);
AppendVectorListCandidate(aLine, (std::string(aPrefix) + "_vec20s16").c_str(), aResultObject, 0x20, 0x2C, 16); AppendVectorListCandidate(aLine, (std::string(aPrefix) + "_vec20s16").c_str(), aResultObject, 0x20, 0x2C, 16);
AppendUint32Field(aLine, (std::string(aPrefix) + "_u2c").c_str(), aResultObject, 0x2C); AppendUint32Field(aLine, (std::string(aPrefix) + "_u2c").c_str(), aResultObject, 0x2C);