From 9c6919e722672faf2a98f72f3419d173c52353a0 Mon Sep 17 00:00:00 2001 From: Jacob Babor Date: Sat, 20 Jun 2026 13:13:08 -0500 Subject: [PATCH] Dump GPS result object fields --- docs/red4ext-logging-shim.md | 29 +++++--- red4ext/EdgeWeightGPS/src/Main.cpp | 106 +++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 9 deletions(-) diff --git a/docs/red4ext-logging-shim.md b/docs/red4ext-logging-shim.md index 1640584..e87f024 100644 --- a/docs/red4ext-logging-shim.md +++ b/docs/red4ext-logging-shim.md @@ -144,18 +144,29 @@ Latest GPS-query finding: - During a controlled route test, the normal world-map GPS path did not call the script-native `RunGPSQuery`/`UpdateGPSQuery` bodies. - Each deliberate quest/custom route click called the shared submitter - `0x70a42c` from return RVA `0x8d20d4`, then returned query IDs `3`, `4`, `5`, - and `6` for the three quest routes plus the custom pin. + `0x70a42c` from return RVA `0x8d20d4`. In the 2026-06-20 19:03 run, the + deliberately plotted routes returned query IDs `17`, `18`, `19`, and `20` + for the side job, Sinnerman, Claire's Garage, and a custom pin. - Each submitted route produced three `0x70a570` dispatches: current/player position, a nearby snapped/lane position, then the destination position. -- The dense log stream immediately after Space-to-Continue was repeated - `0x7094b8` result polling for startup query IDs `0`/`1`, matching the HUD / - minimap route initialization delay. The next probe tracks submitted IDs so - route-click completion can be captured without being drowned by startup - polling. +- The dense log stream immediately after Space-to-Continue is repeated + `0x7094b8` result polling for startup/minimap route queries, matching the HUD + route initialization delay. +- `GPSQueryResultFetch 0x7094b8` returning `1` is the route-solve completion + signal. The caller at return RVA `0x52069c` starts handling a completed + result, then the caller at return RVA `0x520783` drains result records. + The visible route record currently appears as a packed point/count pair in + the object passed as `outPath`: the deliberate routes produced `11`, `16`, + `15`, and `14` point-like counts. - Static disassembly confirms `0xaa5704` takes `(manager, queryId)` and returns - a small status code; one caller checks for value `1` as the completed-query - state. + a small status code; one caller checks for value `1`. 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 suspected result-object + fields around offsets `0x20` through `0x5f` on successful active-route + fetches. This is intended to identify whether the returned route is a raw + solver path, a smoothed draw path, or both. Build and install from the Fedora toolbox: diff --git a/red4ext/EdgeWeightGPS/src/Main.cpp b/red4ext/EdgeWeightGPS/src/Main.cpp index e919d74..ed27409 100644 --- a/red4ext/EdgeWeightGPS/src/Main.cpp +++ b/red4ext/EdgeWeightGPS/src/Main.cpp @@ -665,6 +665,38 @@ void AppendUint32Field(std::ostringstream& aLine, const char* aLabel, void* aObj } } +void AppendUint16Field(std::ostringstream& aLine, const char* aLabel, void* aObject, uintptr_t aOffset) +{ + uint16_t value = 0; + const auto hasValue = aObject && TryReadMemory(reinterpret_cast(reinterpret_cast(aObject) + aOffset), + value); + aLine << " " << aLabel << "="; + if (hasValue) + { + aLine << value << "/0x" << std::hex << value << std::dec; + } + else + { + aLine << ""; + } +} + +void AppendUint64Field(std::ostringstream& aLine, const char* aLabel, void* aObject, uintptr_t aOffset) +{ + uint64_t value = 0; + const auto hasValue = aObject && TryReadMemory(reinterpret_cast(reinterpret_cast(aObject) + aOffset), + value); + aLine << " " << aLabel << "="; + if (hasValue) + { + aLine << value << "/0x" << std::hex << value << std::dec; + } + else + { + aLine << ""; + } +} + void AppendByteField(std::ostringstream& aLine, const char* aLabel, void* aObject, uintptr_t aOffset) { uint8_t value = 0; @@ -681,6 +713,28 @@ void AppendByteField(std::ostringstream& aLine, const char* aLabel, void* aObjec } } +void AppendBytesField(std::ostringstream& aLine, const char* aLabel, void* aObject, uintptr_t aOffset, size_t aSize) +{ + const auto* address = aObject ? reinterpret_cast(reinterpret_cast(aObject) + aOffset) : nullptr; + aLine << " " << aLabel << "="; + if (!address || !IsReadableMemory(address, 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(address) + index); + aLine << std::setw(2) << static_cast(byte); + } + aLine.flags(flags); + aLine.fill(fill); +} + struct Vector4Value { float x; @@ -714,6 +768,32 @@ void AppendVector4Field(std::ostringstream& aLine, const char* aLabel, void* aOb AppendVector4Pointer(aLine, aLabel, reinterpret_cast(reinterpret_cast(aObject) + aOffset)); } +void AppendVectorListCandidate(std::ostringstream& aLine, const char* aPrefix, void* aObject, uintptr_t aDataOffset, + uintptr_t aCountOffset, uintptr_t aStride) +{ + uint32_t count = 0; + auto* data = ReadPointerField(aObject, aDataOffset); + TryReadMemory(reinterpret_cast(reinterpret_cast(aObject) + aCountOffset), count); + + const auto dataLabel = std::string(aPrefix) + "_data"; + const auto countLabel = std::string(aPrefix) + "_count"; + AppendPointerWithRva(aLine, dataLabel.c_str(), data); + aLine << " " << countLabel << "=" << count; + + if (!data || reinterpret_cast(data) < 0x10000 || count == 0 || count > 10000) + { + return; + } + + AppendVector4Pointer(aLine, (std::string(aPrefix) + "_first").c_str(), data); + if (count > 1) + { + AppendVector4Pointer(aLine, (std::string(aPrefix) + "_last").c_str(), + reinterpret_cast(reinterpret_cast(data) + + static_cast(count - 1) * aStride)); + } +} + void AppendPathBufferSummary(std::ostringstream& aLine, const char* aPrefix, void* aPathBuffer) { AppendPointerWithRva(aLine, aPrefix, aPathBuffer); @@ -743,6 +823,28 @@ void AppendPathBufferSummary(std::ostringstream& aLine, const char* aPrefix, voi } } +void AppendGpsResultObjectSummary(std::ostringstream& aLine, const char* aPrefix, void* aResultObject) +{ + if (!aResultObject) + { + aLine << " " << aPrefix << "_result="; + return; + } + + AppendBytesField(aLine, (std::string(aPrefix) + "_bytes20").c_str(), aResultObject, 0x20, 0x40); + 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); + 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); + 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); + AppendUint64Field(aLine, (std::string(aPrefix) + "_u48").c_str(), aResultObject, 0x48); + AppendUint64Field(aLine, (std::string(aPrefix) + "_u50").c_str(), aResultObject, 0x50); +} + void TrackGpsQuery(uint32_t aQueryId, void* aManager, uintptr_t aSubmitReturnRva, uint32_t aSubmitCall) { if (aQueryId == UINT32_MAX) @@ -2122,6 +2224,10 @@ bool DetourGpsQueryResultFetch(void* aManager, uint32_t aQueryId, void* aOutPath AppendPointerWithRva(line, "outPath", aOutPath); AppendReturnRva(line, returnAddress); AppendPathBufferSummary(line, "outPath", aOutPath); + if (result && isTracked && trackedQuery.submitReturnRva == 0x8D20D4) + { + AppendGpsResultObjectSummary(line, "gpsResult", aOutPath); + } LogRed4ext(line.str()); }