Decode GPS route result records

This commit is contained in:
2026-06-20 13:37:21 -05:00
parent aaf319e1b4
commit 56a451a7cb
2 changed files with 114 additions and 3 deletions
+37 -3
View File
@@ -168,9 +168,43 @@ Latest GPS-query finding:
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, including first/last route point candidates from `outPath + 0x28`.
This is intended to identify whether the returned route is a raw solver path,
a smoothed draw path, or both.
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:
+77
View File
@@ -735,6 +735,27 @@ void AppendBytesField(std::ostringstream& aLine, const char* aLabel, void* aObje
aLine.fill(fill);
}
void AppendBytesPointer(std::ostringstream& aLine, const char* aLabel, const void* aPointer, size_t aSize)
{
aLine << " " << aLabel << "=";
if (!aPointer || reinterpret_cast<uintptr_t>(aPointer) < 0x10000 || !IsReadableMemory(aPointer, aSize))
{
aLine << "<unreadable>";
return;
}
const auto flags = aLine.flags();
const auto fill = aLine.fill();
aLine << std::hex << std::setfill('0');
for (size_t index = 0; index < aSize; ++index)
{
const auto byte = *(reinterpret_cast<const uint8_t*>(aPointer) + index);
aLine << std::setw(2) << static_cast<uint32_t>(byte);
}
aLine.flags(flags);
aLine.fill(fill);
}
struct Vector4Value
{
float x;
@@ -870,6 +891,61 @@ void AppendPathBufferSummary(std::ostringstream& aLine, const char* aPrefix, voi
}
}
void AppendGpsResultElementArraySummary(std::ostringstream& aLine, const char* aPrefix, void* aResultObject)
{
uint32_t capacity = 0;
uint32_t count = 0;
auto* data = ReadPointerField(aResultObject, 0x28);
TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aResultObject) + 0x30), capacity);
TryReadMemory(reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(aResultObject) + 0x34), count);
const auto prefix = std::string(aPrefix);
aLine << " " << prefix << "_ptr28_capacity30=" << capacity;
aLine << " " << prefix << "_ptr28_count34=" << count;
if (!data || reinterpret_cast<uintptr_t>(data) < 0x10000 || count == 0 || count > 256)
{
return;
}
const auto rawSize = std::min<size_t>(0x80, static_cast<size_t>(count) * sizeof(void*));
AppendBytesPointer(aLine, (prefix + "_ptr28_ptrsRaw").c_str(), data, rawSize);
const auto appendElement = [&](uint32_t aIndex, const char* aLabel) {
auto* element = ReadPointerField(data, static_cast<uintptr_t>(aIndex) * sizeof(void*));
const auto elementPrefix = prefix + "_ptr28_" + aLabel;
AppendPointerWithRva(aLine, elementPrefix.c_str(), element);
if (!element || reinterpret_cast<uintptr_t>(element) < 0x10000)
{
return;
}
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<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);
AppendBytesPointer(aLine, (elementPrefix + "_f68raw").c_str(), samples, sampleRawSize);
}
};
const auto firstLimit = std::min<uint32_t>(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");
}
}
void AppendGpsResultObjectSummary(std::ostringstream& aLine, const char* aPrefix, void* aResultObject)
{
if (!aResultObject)
@@ -890,6 +966,7 @@ void AppendGpsResultObjectSummary(std::ostringstream& aLine, const char* aPrefix
AppendUint64Field(aLine, (std::string(aPrefix) + "_u30").c_str(), aResultObject, 0x30);
AppendUint32Field(aLine, (std::string(aPrefix) + "_u30lo").c_str(), aResultObject, 0x30);
AppendUint32Field(aLine, (std::string(aPrefix) + "_u34hi").c_str(), aResultObject, 0x34);
AppendGpsResultElementArraySummary(aLine, aPrefix, aResultObject);
AppendVectorListCandidate(aLine, (std::string(aPrefix) + "_vec38").c_str(), aResultObject, 0x38, 0x3C, 12);
AppendUint16Field(aLine, (std::string(aPrefix) + "_flags40").c_str(), aResultObject, 0x40);
AppendByteField(aLine, (std::string(aPrefix) + "_state42").c_str(), aResultObject, 0x42);