diff --git a/red4ext/EdgeWeightGPS/src/Main.cpp b/red4ext/EdgeWeightGPS/src/Main.cpp index 67e646c..1cc29a7 100644 --- a/red4ext/EdgeWeightGPS/src/Main.cpp +++ b/red4ext/EdgeWeightGPS/src/Main.cpp @@ -86,8 +86,9 @@ struct Sdk namespace { // Executable RVAs in the tested Cyberpunk 2077 build. The two expansion sites -// are ordinary road-neighbor relax callsites; both feed the native relax/update -// helper that commits a candidate route node when the new score wins. +// are the known ordinary road-neighbor relax callsites; both feed the native +// relax/update helper that commits a candidate route node when the new score +// wins. Solver paths that do not flow through these callsites are not changed. constexpr uintptr_t kGpsRelaxUpdateRva = 0x040BA58; constexpr uintptr_t kGpsExpandListARelaxPatchRva = 0x040B49A; constexpr uintptr_t kGpsExpandListBRelaxPatchRva = 0x040B6C3; @@ -96,6 +97,10 @@ constexpr uintptr_t kGpsExpandListBRelaxPatchRva = 0x040B6C3; // of native code. constexpr size_t kRelaxPatchSize = 23; constexpr size_t kPatchCaveSize = 80; +// Added to each candidate relaxation handled by the patched expansion sites. +// This is not a rewrite of stored graph edge weights; routes with more ordinary +// segment relaxations accumulate more penalty than routes with fewer/smoother +// segments, which is what makes the constant affect route choice. constexpr float kFixedEdgePenalty = 80.0f; struct PatchSite @@ -114,8 +119,9 @@ const RED4ext::v1::Sdk* gSdk = nullptr; std::array gPatchSites{{ // The two sites share the same relax/update tail but arrive with the - // current node cost in different XMM registers. Each expected instruction - // is replayed first so the cave preserves vanilla's edge accumulation. + // neighbor candidate cost in different XMM registers. Each expected + // instruction is replayed first so the cave preserves vanilla's edge + // accumulation. { "expand-list-a", kGpsExpandListARelaxPatchRva, @@ -228,7 +234,7 @@ bool InstallPatchSite(PatchSite& aSite) if (std::memcmp(target, aSite.expectedFirstInstruction.data(), aSite.expectedFirstInstruction.size()) != 0) { std::ostringstream line; - line << "GPS fixed-edge penalty skipped site=" << aSite.name + line << "GPS candidate-score penalty skipped site=" << aSite.name << " reason=unexpected-bytes rva=0x" << std::hex << aSite.targetRva; LogError(line.str()); return false; @@ -239,7 +245,7 @@ bool InstallPatchSite(PatchSite& aSite) if (!cave) { std::ostringstream line; - line << "GPS fixed-edge penalty skipped site=" << aSite.name << " reason=alloc-failed"; + line << "GPS candidate-score penalty skipped site=" << aSite.name << " reason=alloc-failed"; LogError(line.str()); return false; } @@ -251,12 +257,15 @@ bool InstallPatchSite(PatchSite& aSite) } // Register contract for the native relax/update call: - // - xmm6 holds this edge's accumulated g-score. - // - xmm0 is the candidate f-score passed through [rsp+0x20]. + // - replaying the site-specific add makes xmm6 the neighbor candidate's + // path-cost contribution for this relaxation. + // - xmm0 carries the remaining score term; vanilla adds xmm6 into it + // before passing the final candidate score through [rsp+0x20]. // - xmm3 mirrors xmm6 for the helper call. - // The cave replays vanilla's first add into xmm6, adds our fixed segment - // penalty, rebuilds the helper arguments, then jumps back after the - // overwritten instruction window. + // The cave adds a fixed per-relaxation penalty into xmm6 before rebuilding + // the helper arguments, so this biases against routes made from many small + // ordinary road-neighbor relaxations rather than uniformly lifting the + // whole graph at rest. const auto addPenaltyOffset = offset; EmitBytes(cave, offset, {0xF3, 0x0F, 0x58, 0x35, 0x00, 0x00, 0x00, 0x00}); // addss xmm6, [rip+disp32] EmitBytes(cave, offset, {0xF3, 0x0F, 0x58, 0xC6}); // addss xmm0, xmm6 @@ -290,7 +299,7 @@ bool InstallPatchSite(PatchSite& aSite) { VirtualFree(cave, 0, MEM_RELEASE); std::ostringstream line; - line << "GPS fixed-edge penalty skipped site=" << aSite.name << " reason=protect-failed"; + line << "GPS candidate-score penalty skipped site=" << aSite.name << " reason=protect-failed"; LogError(line.str()); return false; } @@ -305,7 +314,7 @@ bool InstallPatchSite(PatchSite& aSite) aSite.applied = true; std::ostringstream line; - line << "GPS fixed-edge penalty applied site=" << aSite.name << " rva=0x" << std::hex << aSite.targetRva + line << "GPS candidate-score penalty applied site=" << aSite.name << " rva=0x" << std::hex << aSite.targetRva << " cave=" << static_cast(cave) << std::dec << " penalty=" << kFixedEdgePenalty; LogInfo(line.str()); return true; @@ -331,13 +340,13 @@ void RemovePatchSites() VirtualProtect(target, site.original.size(), oldProtect, &ignoredProtect); std::ostringstream line; - line << "GPS fixed-edge penalty restored site=" << site.name; + line << "GPS candidate-score penalty restored site=" << site.name; LogInfo(line.str()); } else { std::ostringstream line; - line << "GPS fixed-edge penalty restore failed site=" << site.name << " reason=protect-failed"; + line << "GPS candidate-score penalty restore failed site=" << site.name << " reason=protect-failed"; LogError(line.str()); } @@ -361,8 +370,8 @@ bool InstallPatchSites() if (!ok) { - // Route scoring should be all-or-nothing: one patched expansion site - // and one vanilla site would produce inconsistent route costs. + // Both known expansion sites must be patched together; otherwise these + // two relaxation paths would score candidate routes inconsistently. RemovePatchSites(); }