diff --git a/docs/red4ext-logging-shim.md b/docs/red4ext-logging-shim.md index ab9862e..5be03ec 100644 --- a/docs/red4ext-logging-shim.md +++ b/docs/red4ext-logging-shim.md @@ -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: diff --git a/red4ext/EdgeWeightGPS/src/Main.cpp b/red4ext/EdgeWeightGPS/src/Main.cpp index 8e744c0..9b4a20f 100644 --- a/red4ext/EdgeWeightGPS/src/Main.cpp +++ b/red4ext/EdgeWeightGPS/src/Main.cpp @@ -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(aPointer) < 0x10000 || !IsReadableMemory(aPointer, aSize)) + { + aLine << ""; + 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(aPointer) + index); + aLine << std::setw(2) << static_cast(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(reinterpret_cast(aResultObject) + 0x30), capacity); + TryReadMemory(reinterpret_cast(reinterpret_cast(aResultObject) + 0x34), count); + + const auto prefix = std::string(aPrefix); + aLine << " " << prefix << "_ptr28_capacity30=" << capacity; + aLine << " " << prefix << "_ptr28_count34=" << count; + + if (!data || reinterpret_cast(data) < 0x10000 || count == 0 || count > 256) + { + return; + } + + const auto rawSize = std::min(0x80, static_cast(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(aIndex) * sizeof(void*)); + const auto elementPrefix = prefix + "_ptr28_" + aLabel; + AppendPointerWithRva(aLine, elementPrefix.c_str(), element); + if (!element || reinterpret_cast(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(reinterpret_cast(element) + 0x74), sampleCount); + if (samples && reinterpret_cast(samples) >= 0x10000 && sampleCount > 0 && sampleCount <= 4096) + { + const auto sampleRawSize = std::min(0x20, static_cast(sampleCount) * 0x08); + AppendBytesPointer(aLine, (elementPrefix + "_f68raw").c_str(), samples, sampleRawSize); + } + }; + + 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"); + } +} + 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);