diff --git a/docs/red4ext-logging-shim.md b/docs/red4ext-logging-shim.md index 72089a9..c8ab92c 100644 --- a/docs/red4ext-logging-shim.md +++ b/docs/red4ext-logging-shim.md @@ -236,6 +236,31 @@ Current static producer lead: `0x41be69`; patching there would alter copied/displayed records after route choice. The cost patch needs to happen upstream of the `0x44cc7c` result construction path. +- `0x44f054` is the strongest route-planning lead so far. It validates the + packed start/target lane handles, initializes a search-state table and open + list, pushes the start state, repeatedly pops the best state, checks for the + destination handle, traverses adjacency lists, and writes predecessor, + accumulated-cost, and priority fields for improved neighbor states. This is + the native GPS graph search loop. +- The search-state record is 0x20 bytes. Offsets `+0x00/+0x04/+0x08` are a + world position, `+0x0c` is accumulated path cost, `+0x10` is priority + (`cost + heuristic`), `+0x14` stores open/closed flags and the predecessor + index, and `+0x18` stores the packed lane handle. +- `0x44f7bc` is the heuristic helper. It returns world-space distance to the + target scaled by the constant at `0x1431ef3a0` (`0.9990000129`), unless a + route-shape helper supplies a shorter adjusted distance. +- The search loop calls a strategy object's vtable slot `+0x08` as an edge + admissibility predicate and slot `+0x10` as the edge-cost callback. For the + base/class-filter strategies, slot `+0x10` resolves to `0x44f838`. +- `0x44f838` computes the geometric edge distance and multiplies it by + `provider[0x08 + classId * 4]`, where `classId` is + `(*(routePoint + 0x13) & 0x3f)`. The GPS planner is therefore weighted, but + by a compact baked road-class table, not by traffic `maxSpeed` or lane + connection probabilities. +- The active probe now hooks `0x44f054` and `0x44f838` read-only. It logs the + live provider vtable, the 64 class multipliers, start/target handles, and a + capped sample of edge-cost calls with point class IDs, masks, handles, and + returned costs. Autodrive finding: diff --git a/red4ext/EdgeWeightGPS/src/Main.cpp b/red4ext/EdgeWeightGPS/src/Main.cpp index 9465fd6..b8acd44 100644 --- a/red4ext/EdgeWeightGPS/src/Main.cpp +++ b/red4ext/EdgeWeightGPS/src/Main.cpp @@ -150,6 +150,8 @@ constexpr uintptr_t kGpsQuerySubmitRva = 0x070A42C; constexpr uintptr_t kGpsQueryDispatchRva = 0x070A570; constexpr uintptr_t kGpsQueryResultFetchRva = 0x07094B8; constexpr uintptr_t kGpsQueryStatusRva = 0x0AA5704; +constexpr uintptr_t kGpsSearchRva = 0x044F054; +constexpr uintptr_t kGpsEdgeCostRva = 0x044F838; HMODULE gModule = nullptr; std::mutex gLogMutex; @@ -210,6 +212,10 @@ uint32_t gGpsQuerySubmitLogCount = 0; uint32_t gGpsQueryDispatchLogCount = 0; uint32_t gGpsQueryResultFetchLogCount = 0; uint32_t gGpsQueryStatusLogCount = 0; +uint32_t gGpsSearchLogCount = 0; +uint32_t gGpsEdgeCostLogCount = 0; +std::array gGpsEdgeCostClassLogCounts{}; +std::mutex gGpsCostLogMutex; uint64_t gLogStartTick = 0; uint64_t gLogFrequency = 0; void* gMappinSetTrackedTarget = nullptr; @@ -274,6 +280,12 @@ using GpsQuerySubmitFunc = int32_t (*)(void* aManager, void* aQuery); using GpsQueryDispatchFunc = void (*)(void* aRuntimeQuery, void* aTargetPosition, void* aArg3, void* aArg4); using GpsQueryResultFetchFunc = bool (*)(void* aManager, uint32_t aQueryId, void* aOutPath); using GpsQueryStatusFunc = int32_t (*)(void* aManager, uint32_t aQueryId); +using GpsSearchFunc = int32_t (*)(void* aGraph, uint64_t aStartHandle, uint64_t aTargetHandle, void* aStartPoint, + void* aTargetPoint, void* aCostProvider, void* aOutPath, uint32_t* aOutCount); +using GpsEdgeCostFunc = float (*)(void* aCostProvider, void* aCurrentState, void* aNeighborState, + uint64_t aPreviousHandle, void* aPreviousSegment, void* aPreviousPoint, + uint64_t aCurrentHandle, void* aCurrentSegment, void* aCurrentPoint, + uint64_t aNeighborHandle, void* aNeighborSegment, void* aNeighborPoint); ProbeFunc gOriginalGpsSystemTick = nullptr; JournalTrackEntryFunc gOriginalJournalTrackEntry = nullptr; ScriptNativeVoidFunc gOriginalFrameMappinPathWrapper = nullptr; @@ -325,6 +337,8 @@ GpsQuerySubmitFunc gOriginalGpsQuerySubmit = nullptr; GpsQueryDispatchFunc gOriginalGpsQueryDispatch = nullptr; GpsQueryResultFetchFunc gOriginalGpsQueryResultFetch = nullptr; GpsQueryStatusFunc gOriginalGpsQueryStatus = nullptr; +GpsSearchFunc gOriginalGpsSearch = nullptr; +GpsEdgeCostFunc gOriginalGpsEdgeCost = nullptr; std::filesystem::path GetLogPath() { @@ -817,6 +831,107 @@ void AppendVector4Field(std::ostringstream& aLine, const char* aLabel, void* aOb AppendVector4Pointer(aLine, aLabel, reinterpret_cast(reinterpret_cast(aObject) + aOffset)); } +bool TryReadFloatField(void* aObject, uintptr_t aOffset, float& aValue) +{ + return aObject && TryReadMemory(reinterpret_cast(reinterpret_cast(aObject) + aOffset), + aValue); +} + +uint8_t ReadGpsPointClass(void* aPoint) +{ + uint8_t value = 0xFF; + if (!aPoint || !TryReadMemory(reinterpret_cast(reinterpret_cast(aPoint) + 0x13), value)) + { + return 0xFF; + } + + return value & 0x3F; +} + +uint16_t ReadGpsPointMask(void* aPoint) +{ + uint16_t value = 0; + if (!aPoint) + { + return 0; + } + + TryReadMemory(reinterpret_cast(reinterpret_cast(aPoint) + 0x10), value); + return value; +} + +void AppendGpsPointSummary(std::ostringstream& aLine, const char* aPrefix, void* aPoint) +{ + const auto prefix = std::string(aPrefix); + AppendPointerWithRva(aLine, prefix.c_str(), aPoint); + if (!aPoint) + { + return; + } + + uint32_t adjacency = 0; + uint16_t mask = 0; + uint8_t byte12 = 0; + uint8_t byte13 = 0; + TryReadMemory(aPoint, adjacency); + TryReadMemory(reinterpret_cast(reinterpret_cast(aPoint) + 0x10), mask); + TryReadMemory(reinterpret_cast(reinterpret_cast(aPoint) + 0x12), byte12); + TryReadMemory(reinterpret_cast(reinterpret_cast(aPoint) + 0x13), byte13); + + aLine << " " << prefix << "_adj=" << adjacency; + aLine << " " << prefix << "_mask10=0x" << std::hex << mask << std::dec; + aLine << " " << prefix << "_b12=0x" << std::hex << static_cast(byte12); + aLine << " " << prefix << "_class13=" << std::dec << static_cast(byte13 & 0x3F); + aLine << " " << prefix << "_raw13=0x" << std::hex << static_cast(byte13) << std::dec; +} + +void AppendGpsCostProviderSummary(std::ostringstream& aLine, const char* aPrefix, void* aProvider) +{ + const auto prefix = std::string(aPrefix); + AppendPointerWithRva(aLine, prefix.c_str(), aProvider); + if (!aProvider) + { + return; + } + + auto* vtable = ReadPointerField(aProvider, 0x00); + AppendPointerWithRva(aLine, (prefix + "_vtable").c_str(), vtable); + if (IsInImage(vtable)) + { + AppendPointerWithRva(aLine, (prefix + "_slot08").c_str(), ReadPointerField(vtable, 0x08)); + AppendPointerWithRva(aLine, (prefix + "_slot10").c_str(), ReadPointerField(vtable, 0x10)); + } + + AppendUint16Field(aLine, (prefix + "_mask108").c_str(), aProvider, 0x108); + AppendUint16Field(aLine, (prefix + "_mask10a").c_str(), aProvider, 0x10A); + + const auto flags = aLine.flags(); + const auto precision = aLine.precision(); + aLine << " " << prefix << "_mul=["; + for (uint32_t index = 0; index < 64; ++index) + { + float value = 0.0f; + const auto hasValue = TryReadFloatField(aProvider, 0x08 + static_cast(index) * sizeof(float), value); + if (index > 0) + { + aLine << ","; + } + + aLine << index << ":"; + if (hasValue) + { + aLine << std::fixed << std::setprecision(3) << value; + } + else + { + aLine << "?"; + } + } + aLine << "]"; + aLine.flags(flags); + aLine.precision(precision); +} + void AppendVector3ListCandidate(std::ostringstream& aLine, const char* aPrefix, void* aObject, uintptr_t aDataOffset, uintptr_t aCountOffset, uintptr_t aStride) { @@ -2419,6 +2534,126 @@ int32_t DetourGpsQueryStatus(void* aManager, uint32_t aQueryId) return result; } +int32_t DetourGpsSearch(void* aGraph, uint64_t aStartHandle, uint64_t aTargetHandle, void* aStartPoint, + void* aTargetPoint, void* aCostProvider, void* aOutPath, uint32_t* aOutCount) +{ + constexpr uint32_t kMaxCalls = 96; + + uint32_t callCount = 0; + bool shouldLog = false; + { + std::lock_guard lock(gGpsCostLogMutex); + callCount = gGpsSearchLogCount++; + shouldLog = callCount < kMaxCalls; + } + + if (shouldLog) + { + std::ostringstream line; + line << "hook GPSSearch 0x44f054 call=" << callCount << " startHandle=0x" << std::hex << aStartHandle + << " targetHandle=0x" << aTargetHandle << std::dec; + AppendPointerWithRva(line, "graph", aGraph); + AppendVector3Pointer(line, "startPoint", aStartPoint); + AppendVector3Pointer(line, "targetPoint", aTargetPoint); + AppendPointerWithRva(line, "outPath", aOutPath); + AppendPointerWithRva(line, "outCount", aOutCount); + AppendGpsCostProviderSummary(line, "provider", aCostProvider); + AppendReturnRva(line, __builtin_return_address(0)); + LogRed4ext(line.str()); + } + + int32_t result = 0x80000008; + if (gOriginalGpsSearch) + { + result = gOriginalGpsSearch(aGraph, aStartHandle, aTargetHandle, aStartPoint, aTargetPoint, aCostProvider, + aOutPath, aOutCount); + } + + if (shouldLog) + { + uint32_t outCount = 0; + const auto hasOutCount = aOutCount && TryReadMemory(aOutCount, outCount); + + std::ostringstream line; + line << "hook GPSSearch result call=" << callCount << " value=0x" << std::hex + << static_cast(result) << std::dec << " hasOutCount=" << static_cast(hasOutCount); + if (hasOutCount) + { + line << " outCount=" << outCount; + } + LogRed4ext(line.str()); + } + + return result; +} + +float DetourGpsEdgeCost(void* aCostProvider, void* aCurrentState, void* aNeighborState, uint64_t aPreviousHandle, + void* aPreviousSegment, void* aPreviousPoint, uint64_t aCurrentHandle, void* aCurrentSegment, + void* aCurrentPoint, uint64_t aNeighborHandle, void* aNeighborSegment, void* aNeighborPoint) +{ + float result = 0.0f; + if (gOriginalGpsEdgeCost) + { + result = gOriginalGpsEdgeCost(aCostProvider, aCurrentState, aNeighborState, aPreviousHandle, aPreviousSegment, + aPreviousPoint, aCurrentHandle, aCurrentSegment, aCurrentPoint, aNeighborHandle, + aNeighborSegment, aNeighborPoint); + } + + const auto sourceClass = ReadGpsPointClass(aCurrentPoint); + const auto neighborClass = ReadGpsPointClass(aNeighborPoint); + + float multiplier = 0.0f; + const auto hasMultiplier = sourceClass < 64 && + TryReadFloatField(aCostProvider, 0x08 + static_cast(sourceClass) * sizeof(float), + multiplier); + + uint32_t callCount = 0; + uint32_t classCallCount = 0; + bool shouldLog = false; + { + std::lock_guard lock(gGpsCostLogMutex); + callCount = gGpsEdgeCostLogCount++; + if (sourceClass < gGpsEdgeCostClassLogCounts.size()) + { + classCallCount = gGpsEdgeCostClassLogCounts[sourceClass]++; + } + shouldLog = callCount < 256 || (sourceClass < 64 && classCallCount < 4); + } + + if (shouldLog) + { + const auto baseCost = hasMultiplier && multiplier != 0.0f ? result / multiplier : 0.0f; + + std::ostringstream line; + line << "hook GPSEdgeCost 0x44f838 call=" << callCount << " classCall=" << classCallCount + << " sourceClass=" << static_cast(sourceClass) + << " neighborClass=" << static_cast(neighborClass) << " value=" << result; + if (hasMultiplier) + { + line << " multiplier=" << multiplier << " baseCost=" << baseCost; + } + else + { + line << " multiplier="; + } + line << " previousHandle=0x" << std::hex << aPreviousHandle << " currentHandle=0x" << aCurrentHandle + << " neighborHandle=0x" << aNeighborHandle << std::dec; + AppendPointerWithRva(line, "provider", aCostProvider); + AppendVector3Pointer(line, "currentState", aCurrentState); + AppendVector3Pointer(line, "neighborState", aNeighborState); + AppendPointerWithRva(line, "previousSegment", aPreviousSegment); + AppendGpsPointSummary(line, "previousPoint", aPreviousPoint); + AppendPointerWithRva(line, "currentSegment", aCurrentSegment); + AppendGpsPointSummary(line, "currentPoint", aCurrentPoint); + AppendPointerWithRva(line, "neighborSegment", aNeighborSegment); + AppendGpsPointSummary(line, "neighborPoint", aNeighborPoint); + AppendReturnRva(line, __builtin_return_address(0)); + LogRed4ext(line.str()); + } + + return result; +} + void AttachProbeHook(const char* aName, uintptr_t aRva, void* aDetour, void** aOriginal) { if (!gSdk || !gSdk->hooking || !gSdk->hooking->Attach) @@ -2745,6 +2980,10 @@ RED4EXT_C_EXPORT bool RED4EXT_CALL Main(RED4ext::v1::PluginHandle aHandle, RED4e AttachProbeHook("GPSQueryStatus 0xaa5704", kGpsQueryStatusRva, reinterpret_cast(&DetourGpsQueryStatus), reinterpret_cast(&gOriginalGpsQueryStatus)); + AttachProbeHook("GPSSearch 0x44f054", kGpsSearchRva, reinterpret_cast(&DetourGpsSearch), + reinterpret_cast(&gOriginalGpsSearch)); + AttachProbeHook("GPSEdgeCost 0x44f838", kGpsEdgeCostRva, reinterpret_cast(&DetourGpsEdgeCost), + reinterpret_cast(&gOriginalGpsEdgeCost)); AttachProbeHook("GPSSystem/Tick", kGpsSystemTickRva, reinterpret_cast(&DetourGpsSystemTick), reinterpret_cast(&gOriginalGpsSystemTick)); AttachProbeHook("JournalManager::TrackEntry impl", kJournalTrackEntryImplRva, @@ -2864,6 +3103,8 @@ RED4EXT_C_EXPORT bool RED4EXT_CALL Main(RED4ext::v1::PluginHandle aHandle, RED4e DetachProbeHook("GPSQueryDispatch 0x70a570", kGpsQueryDispatchRva); DetachProbeHook("GPSQueryResultFetch 0x7094b8", kGpsQueryResultFetchRva); DetachProbeHook("GPSQueryStatus 0xaa5704", kGpsQueryStatusRva); + DetachProbeHook("GPSSearch 0x44f054", kGpsSearchRva); + DetachProbeHook("GPSEdgeCost 0x44f838", kGpsEdgeCostRva); DetachProbeHook("GPSSystem/Tick", kGpsSystemTickRva); DetachProbeHook("JournalManager::TrackEntry impl", kJournalTrackEntryImplRva); if (kEnableVerboseJournalListenerHooks)