Compare commits
2 Commits
8ee791ad0c
...
8135ff96bf
| Author | SHA1 | Date | |
|---|---|---|---|
| 8135ff96bf | |||
| 3ac184a2de |
@@ -15,6 +15,9 @@
|
|||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
// The public SDK headers used by this MinGW build provide the exported plugin
|
||||||
|
// types, but not every callback table layout consumed here. Keep these minimal
|
||||||
|
// ABI mirrors in field order and only use the members this plugin touches.
|
||||||
namespace RED4ext::v1
|
namespace RED4ext::v1
|
||||||
{
|
{
|
||||||
struct Logger
|
struct Logger
|
||||||
@@ -82,11 +85,22 @@ struct Sdk
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
// Executable RVAs in the tested Cyberpunk 2077 build. The two expansion sites
|
||||||
|
// 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 kGpsRelaxUpdateRva = 0x040BA58;
|
||||||
constexpr uintptr_t kGpsExpandListARelaxPatchRva = 0x040B49A;
|
constexpr uintptr_t kGpsExpandListARelaxPatchRva = 0x040B49A;
|
||||||
constexpr uintptr_t kGpsExpandListBRelaxPatchRva = 0x040B6C3;
|
constexpr uintptr_t kGpsExpandListBRelaxPatchRva = 0x040B6C3;
|
||||||
|
// Size of the overwritten instruction window at each expansion site. The
|
||||||
|
// trampoline must return after these whole instructions, never into the middle
|
||||||
|
// of native code.
|
||||||
constexpr size_t kRelaxPatchSize = 23;
|
constexpr size_t kRelaxPatchSize = 23;
|
||||||
constexpr size_t kPatchCaveSize = 80;
|
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;
|
constexpr float kFixedEdgePenalty = 80.0f;
|
||||||
|
|
||||||
struct PatchSite
|
struct PatchSite
|
||||||
@@ -104,6 +118,10 @@ RED4ext::v1::PluginHandle gHandle = nullptr;
|
|||||||
const RED4ext::v1::Sdk* gSdk = nullptr;
|
const RED4ext::v1::Sdk* gSdk = nullptr;
|
||||||
|
|
||||||
std::array<PatchSite, 2> gPatchSites{{
|
std::array<PatchSite, 2> gPatchSites{{
|
||||||
|
// The two sites share the same relax/update tail but arrive with the
|
||||||
|
// neighbor candidate cost in different XMM registers. Each expected
|
||||||
|
// instruction is replayed first so the cave preserves vanilla's edge
|
||||||
|
// accumulation.
|
||||||
{
|
{
|
||||||
"expand-list-a",
|
"expand-list-a",
|
||||||
kGpsExpandListARelaxPatchRva,
|
kGpsExpandListARelaxPatchRva,
|
||||||
@@ -216,7 +234,7 @@ bool InstallPatchSite(PatchSite& aSite)
|
|||||||
if (std::memcmp(target, aSite.expectedFirstInstruction.data(), aSite.expectedFirstInstruction.size()) != 0)
|
if (std::memcmp(target, aSite.expectedFirstInstruction.data(), aSite.expectedFirstInstruction.size()) != 0)
|
||||||
{
|
{
|
||||||
std::ostringstream line;
|
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;
|
<< " reason=unexpected-bytes rva=0x" << std::hex << aSite.targetRva;
|
||||||
LogError(line.str());
|
LogError(line.str());
|
||||||
return false;
|
return false;
|
||||||
@@ -227,7 +245,7 @@ bool InstallPatchSite(PatchSite& aSite)
|
|||||||
if (!cave)
|
if (!cave)
|
||||||
{
|
{
|
||||||
std::ostringstream line;
|
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());
|
LogError(line.str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -238,6 +256,16 @@ bool InstallPatchSite(PatchSite& aSite)
|
|||||||
cave[offset++] = byte;
|
cave[offset++] = byte;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register contract for the native relax/update call:
|
||||||
|
// - 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 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;
|
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, 0x35, 0x00, 0x00, 0x00, 0x00}); // addss xmm6, [rip+disp32]
|
||||||
EmitBytes(cave, offset, {0xF3, 0x0F, 0x58, 0xC6}); // addss xmm0, xmm6
|
EmitBytes(cave, offset, {0xF3, 0x0F, 0x58, 0xC6}); // addss xmm0, xmm6
|
||||||
@@ -246,6 +274,8 @@ bool InstallPatchSite(PatchSite& aSite)
|
|||||||
EmitAbsoluteRaxCall(cave, offset, GameImageBase() + kGpsRelaxUpdateRva);
|
EmitAbsoluteRaxCall(cave, offset, GameImageBase() + kGpsRelaxUpdateRva);
|
||||||
EmitAbsoluteRaxJump(cave, offset, GameImageBase() + aSite.targetRva + kRelaxPatchSize);
|
EmitAbsoluteRaxJump(cave, offset, GameImageBase() + aSite.targetRva + kRelaxPatchSize);
|
||||||
|
|
||||||
|
// Keep the embedded float naturally aligned. The RIP-relative addss above
|
||||||
|
// is patched after the final cave layout is known.
|
||||||
while ((offset % alignof(float)) != 0)
|
while ((offset % alignof(float)) != 0)
|
||||||
{
|
{
|
||||||
cave[offset++] = 0x90;
|
cave[offset++] = 0x90;
|
||||||
@@ -269,7 +299,7 @@ bool InstallPatchSite(PatchSite& aSite)
|
|||||||
{
|
{
|
||||||
VirtualFree(cave, 0, MEM_RELEASE);
|
VirtualFree(cave, 0, MEM_RELEASE);
|
||||||
std::ostringstream line;
|
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());
|
LogError(line.str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -284,7 +314,7 @@ bool InstallPatchSite(PatchSite& aSite)
|
|||||||
aSite.applied = true;
|
aSite.applied = true;
|
||||||
|
|
||||||
std::ostringstream line;
|
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<void*>(cave) << std::dec << " penalty=" << kFixedEdgePenalty;
|
<< " cave=" << static_cast<void*>(cave) << std::dec << " penalty=" << kFixedEdgePenalty;
|
||||||
LogInfo(line.str());
|
LogInfo(line.str());
|
||||||
return true;
|
return true;
|
||||||
@@ -310,13 +340,13 @@ void RemovePatchSites()
|
|||||||
VirtualProtect(target, site.original.size(), oldProtect, &ignoredProtect);
|
VirtualProtect(target, site.original.size(), oldProtect, &ignoredProtect);
|
||||||
|
|
||||||
std::ostringstream line;
|
std::ostringstream line;
|
||||||
line << "GPS fixed-edge penalty restored site=" << site.name;
|
line << "GPS candidate-score penalty restored site=" << site.name;
|
||||||
LogInfo(line.str());
|
LogInfo(line.str());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::ostringstream line;
|
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());
|
LogError(line.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,6 +370,8 @@ bool InstallPatchSites()
|
|||||||
|
|
||||||
if (!ok)
|
if (!ok)
|
||||||
{
|
{
|
||||||
|
// Both known expansion sites must be patched together; otherwise these
|
||||||
|
// two relaxation paths would score candidate routes inconsistently.
|
||||||
RemovePatchSites();
|
RemovePatchSites();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user