diff --git a/red4ext/EdgeWeightGPS/src/Main.cpp b/red4ext/EdgeWeightGPS/src/Main.cpp index b8acd44..21a5d12 100644 --- a/red4ext/EdgeWeightGPS/src/Main.cpp +++ b/red4ext/EdgeWeightGPS/src/Main.cpp @@ -150,6 +150,7 @@ constexpr uintptr_t kGpsQuerySubmitRva = 0x070A42C; constexpr uintptr_t kGpsQueryDispatchRva = 0x070A570; constexpr uintptr_t kGpsQueryResultFetchRva = 0x07094B8; constexpr uintptr_t kGpsQueryStatusRva = 0x0AA5704; +constexpr uintptr_t kGpsRouteProducerRva = 0x044CC7C; constexpr uintptr_t kGpsSearchRva = 0x044F054; constexpr uintptr_t kGpsEdgeCostRva = 0x044F838; @@ -212,6 +213,7 @@ uint32_t gGpsQuerySubmitLogCount = 0; uint32_t gGpsQueryDispatchLogCount = 0; uint32_t gGpsQueryResultFetchLogCount = 0; uint32_t gGpsQueryStatusLogCount = 0; +uint32_t gGpsRouteProducerLogCount = 0; uint32_t gGpsSearchLogCount = 0; uint32_t gGpsEdgeCostLogCount = 0; std::array gGpsEdgeCostClassLogCounts{}; @@ -245,6 +247,36 @@ std::mutex gTrackedGpsQueryMutex; std::array gTrackedGpsQueries{}; uint32_t gTrackedGpsQueryNext = 0; +struct GpsEdgeCostSample +{ + uint64_t currentHandle = 0; + uint64_t neighborHandle = 0; + uintptr_t returnRva = 0; + float cost = 0.0f; + uint8_t sourceClass = 0xFF; + uint8_t neighborClass = 0xFF; +}; + +struct GpsEdgeCostStats +{ + uint32_t total = 0; + uint32_t invalidSourceClass = 0; + uint32_t invalidNeighborClass = 0; + float costSum = 0.0f; + float minCost = 0.0f; + float maxCost = 0.0f; + std::array sourceClasses{}; + std::array neighborClasses{}; + std::array returnRvas{}; + std::array returnRvaCounts{}; + uint32_t otherReturnRvas = 0; + std::array samples{}; + uint32_t sampleCount = 0; +}; + +thread_local GpsEdgeCostStats* gActiveGpsEdgeCostStats = nullptr; +thread_local uint32_t gActiveGpsRouteProducerCall = UINT32_MAX; + using ProbeFunc = void (*)(void* aThis, void* a2, void* a3, void* a4); using JournalTrackEntryFunc = void (*)(void* aThis, void* aEntryHandle); using ScriptNativeVoidFunc = void (*)(void* aThis, void* aStack, void* aResult); @@ -280,6 +312,7 @@ 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 GpsRouteProducerFunc = int32_t (*)(void* aGraph, void* aQuery, void* aOutResult); 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, @@ -337,6 +370,7 @@ GpsQuerySubmitFunc gOriginalGpsQuerySubmit = nullptr; GpsQueryDispatchFunc gOriginalGpsQueryDispatch = nullptr; GpsQueryResultFetchFunc gOriginalGpsQueryResultFetch = nullptr; GpsQueryStatusFunc gOriginalGpsQueryStatus = nullptr; +GpsRouteProducerFunc gOriginalGpsRouteProducer = nullptr; GpsSearchFunc gOriginalGpsSearch = nullptr; GpsEdgeCostFunc gOriginalGpsEdgeCost = nullptr; @@ -837,6 +871,25 @@ bool TryReadFloatField(void* aObject, uintptr_t aOffset, float& aValue) aValue); } +void AppendFloatField(std::ostringstream& aLine, const char* aLabel, void* aObject, uintptr_t aOffset) +{ + float value = 0.0f; + const auto hasValue = TryReadFloatField(aObject, aOffset, value); + aLine << " " << aLabel << "="; + if (hasValue) + { + const auto flags = aLine.flags(); + const auto precision = aLine.precision(); + aLine << std::fixed << std::setprecision(3) << value; + aLine.flags(flags); + aLine.precision(precision); + } + else + { + aLine << ""; + } +} + uint8_t ReadGpsPointClass(void* aPoint) { uint8_t value = 0xFF; @@ -932,6 +985,260 @@ void AppendGpsCostProviderSummary(std::ostringstream& aLine, const char* aPrefix aLine.precision(precision); } +void AppendGpsCostProviderCompact(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 << "_nonunitMul=["; + bool first = true; + for (uint32_t index = 0; index < 64; ++index) + { + float value = 0.0f; + if (!TryReadFloatField(aProvider, 0x08 + static_cast(index) * sizeof(float), value)) + { + continue; + } + + if (value > 0.999f && value < 1.001f) + { + continue; + } + + if (!first) + { + aLine << ","; + } + first = false; + aLine << index << ":" << std::fixed << std::setprecision(3) << value; + } + if (first) + { + aLine << "none"; + } + aLine << "]"; + aLine.flags(flags); + aLine.precision(precision); +} + +void AppendClassCounts(std::ostringstream& aLine, const char* aLabel, const std::array& aCounts, + uint32_t aInvalidCount) +{ + aLine << " " << aLabel << "=["; + bool first = true; + for (uint32_t index = 0; index < aCounts.size(); ++index) + { + if (aCounts[index] == 0) + { + continue; + } + + if (!first) + { + aLine << ","; + } + first = false; + aLine << index << ":" << aCounts[index]; + } + + if (aInvalidCount > 0) + { + if (!first) + { + aLine << ","; + } + first = false; + aLine << "invalid:" << aInvalidCount; + } + + if (first) + { + aLine << "none"; + } + + aLine << "]"; +} + +void AppendGpsEdgeCostStats(std::ostringstream& aLine, const GpsEdgeCostStats& aStats) +{ + aLine << " edgeCostCalls=" << aStats.total; + if (aStats.total > 0) + { + const auto flags = aLine.flags(); + const auto precision = aLine.precision(); + aLine << " edgeCostMin=" << std::fixed << std::setprecision(3) << aStats.minCost; + aLine << " edgeCostMax=" << std::fixed << std::setprecision(3) << aStats.maxCost; + aLine << " edgeCostAvg=" << std::fixed << std::setprecision(3) + << (aStats.costSum / static_cast(aStats.total)); + aLine.flags(flags); + aLine.precision(precision); + } + + AppendClassCounts(aLine, "edgeSrcClasses", aStats.sourceClasses, aStats.invalidSourceClass); + AppendClassCounts(aLine, "edgeDstClasses", aStats.neighborClasses, aStats.invalidNeighborClass); + + aLine << " edgeRetRvas=["; + bool first = true; + for (uint32_t index = 0; index < aStats.returnRvas.size(); ++index) + { + if (aStats.returnRvas[index] == 0) + { + continue; + } + + if (!first) + { + aLine << ","; + } + first = false; + aLine << "0x" << std::hex << aStats.returnRvas[index] << std::dec << ":" << aStats.returnRvaCounts[index]; + } + if (aStats.otherReturnRvas > 0) + { + if (!first) + { + aLine << ","; + } + first = false; + aLine << "other:" << aStats.otherReturnRvas; + } + if (first) + { + aLine << "none"; + } + aLine << "]"; + + aLine << " edgeSamples=["; + const auto sampleCount = std::min(aStats.sampleCount, static_cast(aStats.samples.size())); + for (uint32_t index = 0; index < sampleCount; ++index) + { + const auto& sample = aStats.samples[index]; + if (index > 0) + { + aLine << ";"; + } + + const auto flags = aLine.flags(); + const auto precision = aLine.precision(); + aLine << index << ":" << static_cast(sample.sourceClass) << ">" + << static_cast(sample.neighborClass) << " cost=" << std::fixed << std::setprecision(3) + << sample.cost << " ret=0x" << std::hex << sample.returnRva << " cur=0x" << sample.currentHandle + << " dst=0x" << sample.neighborHandle << std::dec; + aLine.flags(flags); + aLine.precision(precision); + } + if (sampleCount == 0) + { + aLine << "none"; + } + aLine << "]"; +} + +void NoteGpsEdgeCostStats(GpsEdgeCostStats& aStats, uint8_t aSourceClass, uint8_t aNeighborClass, float aCost, + uintptr_t aReturnRva, uint64_t aCurrentHandle, uint64_t aNeighborHandle) +{ + ++aStats.total; + aStats.costSum += aCost; + if (aStats.total == 1) + { + aStats.minCost = aCost; + aStats.maxCost = aCost; + } + else + { + aStats.minCost = std::min(aStats.minCost, aCost); + aStats.maxCost = std::max(aStats.maxCost, aCost); + } + + if (aSourceClass < aStats.sourceClasses.size()) + { + ++aStats.sourceClasses[aSourceClass]; + } + else + { + ++aStats.invalidSourceClass; + } + + if (aNeighborClass < aStats.neighborClasses.size()) + { + ++aStats.neighborClasses[aNeighborClass]; + } + else + { + ++aStats.invalidNeighborClass; + } + + if (aReturnRva != 0) + { + bool recorded = false; + for (uint32_t index = 0; index < aStats.returnRvas.size(); ++index) + { + if (aStats.returnRvas[index] == aReturnRva) + { + ++aStats.returnRvaCounts[index]; + recorded = true; + break; + } + + if (aStats.returnRvas[index] == 0) + { + aStats.returnRvas[index] = aReturnRva; + aStats.returnRvaCounts[index] = 1; + recorded = true; + break; + } + } + + if (!recorded) + { + ++aStats.otherReturnRvas; + } + } + + if (aStats.sampleCount < aStats.samples.size()) + { + auto& sample = aStats.samples[aStats.sampleCount++]; + sample.currentHandle = aCurrentHandle; + sample.neighborHandle = aNeighborHandle; + sample.returnRva = aReturnRva; + sample.cost = aCost; + sample.sourceClass = aSourceClass; + sample.neighborClass = aNeighborClass; + } +} + +void AppendGpsRouteProducerQuerySummary(std::ostringstream& aLine, void* aQuery) +{ + AppendVector4Field(aLine, "query_start00", aQuery, 0x00); + AppendVector4Field(aLine, "query_end10", aQuery, 0x10); + AppendFloatField(aLine, "query_f68", aQuery, 0x68); + AppendFloatField(aLine, "query_f78", aQuery, 0x78); + AppendFloatField(aLine, "query_f88", aQuery, 0x88); + AppendUint32Field(aLine, "query_f8c", aQuery, 0x8C); + AppendUint32Field(aLine, "query_f90", aQuery, 0x90); + AppendUint32Field(aLine, "query_f94", aQuery, 0x94); + AppendPointerField(aLine, "query_ptr98", aQuery, 0x98); + AppendPointerField(aLine, "query_ptra0", aQuery, 0xA0); + AppendUint32Field(aLine, "query_flagsa8", aQuery, 0xA8); +} + void AppendVector3ListCandidate(std::ostringstream& aLine, const char* aPrefix, void* aObject, uintptr_t aDataOffset, uintptr_t aCountOffset, uintptr_t aStride) { @@ -2534,10 +2841,70 @@ int32_t DetourGpsQueryStatus(void* aManager, uint32_t aQueryId) return result; } +int32_t DetourGpsRouteProducer(void* aGraph, void* aQuery, void* aOutResult) +{ + constexpr uint32_t kMaxCalls = 512; + + uint32_t callCount = 0; + bool shouldLog = false; + { + std::lock_guard lock(gGpsCostLogMutex); + callCount = gGpsRouteProducerLogCount++; + shouldLog = callCount < kMaxCalls; + } + + if (shouldLog) + { + std::ostringstream line; + line << "hook GPSRouteProducer 0x44cc7c call=" << callCount; + AppendPointerWithRva(line, "graph", aGraph); + AppendPointerWithRva(line, "query", aQuery); + AppendPointerWithRva(line, "outResult", aOutResult); + AppendGpsRouteProducerQuerySummary(line, aQuery); + AppendReturnRva(line, __builtin_return_address(0)); + LogRed4ext(line.str()); + } + + GpsEdgeCostStats stats{}; + auto* previousStats = gActiveGpsEdgeCostStats; + const auto previousRouteProducerCall = gActiveGpsRouteProducerCall; + gActiveGpsEdgeCostStats = &stats; + gActiveGpsRouteProducerCall = callCount; + + int32_t result = 0x80000008; + if (gOriginalGpsRouteProducer) + { + result = gOriginalGpsRouteProducer(aGraph, aQuery, aOutResult); + } + + gActiveGpsEdgeCostStats = previousStats; + gActiveGpsRouteProducerCall = previousRouteProducerCall; + + if (shouldLog) + { + std::ostringstream line; + line << "hook GPSRouteProducer result call=" << callCount << " value=0x" << std::hex + << static_cast(result) << std::dec; + AppendUint32Field(line, "out_u0c", aOutResult, 0x0C); + AppendUint32Field(line, "out_u1c", aOutResult, 0x1C); + AppendUint32Field(line, "out_u20", aOutResult, 0x20); + AppendUint32Field(line, "out_u24", aOutResult, 0x24); + AppendUint32Field(line, "out_u2c", aOutResult, 0x2C); + AppendUint32Field(line, "out_u30lo", aOutResult, 0x30); + AppendUint32Field(line, "out_u34hi", aOutResult, 0x34); + AppendUint16Field(line, "out_flags40", aOutResult, 0x40); + AppendByteField(line, "out_state42", aOutResult, 0x42); + AppendGpsEdgeCostStats(line, stats); + LogRed4ext(line.str()); + } + + 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; + constexpr uint32_t kMaxCalls = 384; uint32_t callCount = 0; bool shouldLog = false; @@ -2557,7 +2924,11 @@ int32_t DetourGpsSearch(void* aGraph, uint64_t aStartHandle, uint64_t aTargetHan AppendVector3Pointer(line, "targetPoint", aTargetPoint); AppendPointerWithRva(line, "outPath", aOutPath); AppendPointerWithRva(line, "outCount", aOutCount); - AppendGpsCostProviderSummary(line, "provider", aCostProvider); + AppendGpsCostProviderCompact(line, "provider", aCostProvider); + if (gActiveGpsRouteProducerCall != UINT32_MAX) + { + line << " routeProducerCall=" << gActiveGpsRouteProducerCall; + } AppendReturnRva(line, __builtin_return_address(0)); LogRed4ext(line.str()); } @@ -2591,6 +2962,10 @@ float DetourGpsEdgeCost(void* aCostProvider, void* aCurrentState, void* aNeighbo void* aPreviousSegment, void* aPreviousPoint, uint64_t aCurrentHandle, void* aCurrentSegment, void* aCurrentPoint, uint64_t aNeighborHandle, void* aNeighborSegment, void* aNeighborPoint) { + const auto returnAddress = __builtin_return_address(0); + uintptr_t returnRva = 0; + TryGetImageRva(returnAddress, returnRva); + float result = 0.0f; if (gOriginalGpsEdgeCost) { @@ -2602,6 +2977,12 @@ float DetourGpsEdgeCost(void* aCostProvider, void* aCurrentState, void* aNeighbo const auto sourceClass = ReadGpsPointClass(aCurrentPoint); const auto neighborClass = ReadGpsPointClass(aNeighborPoint); + if (gActiveGpsEdgeCostStats) + { + NoteGpsEdgeCostStats(*gActiveGpsEdgeCostStats, sourceClass, neighborClass, result, returnRva, aCurrentHandle, + aNeighborHandle); + } + float multiplier = 0.0f; const auto hasMultiplier = sourceClass < 64 && TryReadFloatField(aCostProvider, 0x08 + static_cast(sourceClass) * sizeof(float), @@ -2617,7 +2998,7 @@ float DetourGpsEdgeCost(void* aCostProvider, void* aCurrentState, void* aNeighbo { classCallCount = gGpsEdgeCostClassLogCounts[sourceClass]++; } - shouldLog = callCount < 256 || (sourceClass < 64 && classCallCount < 4); + shouldLog = callCount < 32 || (sourceClass < 64 && classCallCount < 1); } if (shouldLog) @@ -2647,7 +3028,7 @@ float DetourGpsEdgeCost(void* aCostProvider, void* aCurrentState, void* aNeighbo AppendGpsPointSummary(line, "currentPoint", aCurrentPoint); AppendPointerWithRva(line, "neighborSegment", aNeighborSegment); AppendGpsPointSummary(line, "neighborPoint", aNeighborPoint); - AppendReturnRva(line, __builtin_return_address(0)); + AppendReturnRva(line, returnAddress); LogRed4ext(line.str()); } @@ -2980,6 +3361,9 @@ RED4EXT_C_EXPORT bool RED4EXT_CALL Main(RED4ext::v1::PluginHandle aHandle, RED4e AttachProbeHook("GPSQueryStatus 0xaa5704", kGpsQueryStatusRva, reinterpret_cast(&DetourGpsQueryStatus), reinterpret_cast(&gOriginalGpsQueryStatus)); + AttachProbeHook("GPSRouteProducer 0x44cc7c", kGpsRouteProducerRva, + reinterpret_cast(&DetourGpsRouteProducer), + reinterpret_cast(&gOriginalGpsRouteProducer)); AttachProbeHook("GPSSearch 0x44f054", kGpsSearchRva, reinterpret_cast(&DetourGpsSearch), reinterpret_cast(&gOriginalGpsSearch)); AttachProbeHook("GPSEdgeCost 0x44f838", kGpsEdgeCostRva, reinterpret_cast(&DetourGpsEdgeCost), @@ -3103,6 +3487,7 @@ 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("GPSRouteProducer 0x44cc7c", kGpsRouteProducerRva); DetachProbeHook("GPSSearch 0x44f054", kGpsSearchRva); DetachProbeHook("GPSEdgeCost 0x44f838", kGpsEdgeCostRva); DetachProbeHook("GPSSystem/Tick", kGpsSystemTickRva);