Document native GPS patch trampoline
This commit is contained in:
@@ -15,6 +15,9 @@
|
||||
#include <sstream>
|
||||
#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
|
||||
{
|
||||
struct Logger
|
||||
@@ -82,9 +85,15 @@ 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.
|
||||
constexpr uintptr_t kGpsRelaxUpdateRva = 0x040BA58;
|
||||
constexpr uintptr_t kGpsExpandListARelaxPatchRva = 0x040B49A;
|
||||
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 kPatchCaveSize = 80;
|
||||
constexpr float kFixedEdgePenalty = 80.0f;
|
||||
@@ -104,6 +113,9 @@ RED4ext::v1::PluginHandle gHandle = nullptr;
|
||||
const RED4ext::v1::Sdk* gSdk = nullptr;
|
||||
|
||||
std::array<PatchSite, 2> 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.
|
||||
{
|
||||
"expand-list-a",
|
||||
kGpsExpandListARelaxPatchRva,
|
||||
@@ -238,6 +250,13 @@ bool InstallPatchSite(PatchSite& aSite)
|
||||
cave[offset++] = byte;
|
||||
}
|
||||
|
||||
// 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].
|
||||
// - 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.
|
||||
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
|
||||
@@ -246,6 +265,8 @@ bool InstallPatchSite(PatchSite& aSite)
|
||||
EmitAbsoluteRaxCall(cave, offset, GameImageBase() + kGpsRelaxUpdateRva);
|
||||
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)
|
||||
{
|
||||
cave[offset++] = 0x90;
|
||||
@@ -340,6 +361,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.
|
||||
RemovePatchSites();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user