Add live-tunable GPS solver highway weight
This commit is contained in:
+46
-11
@@ -2,6 +2,41 @@
|
|||||||
|
|
||||||
Current date/time context: 2026-06-26, local timezone America/Chicago.
|
Current date/time context: 2026-06-26, local timezone America/Chicago.
|
||||||
|
|
||||||
|
## 2026-06-26 Confirmed Solver-Cost Patch
|
||||||
|
|
||||||
|
- We have unequivocally found the player GPS route-cost lever.
|
||||||
|
- The crashing RED4ext detour on `0x40bb40` has been replaced by an inline
|
||||||
|
patch at the road-tail block `0x40bb98`. The patch preserves vanilla
|
||||||
|
`node+0x93` behavior, checks `node+0x88 & 0x4000` for `Highway`, and then
|
||||||
|
multiplies highway lane cost by a tunable float.
|
||||||
|
- This inline patch avoids the register-clobber crash from the C++ detour.
|
||||||
|
Static disassembly showed the optimized caller reusing volatile state after
|
||||||
|
calling the tiny vanilla multiplier helper.
|
||||||
|
- The proof build used highway multiplier `0.35` and visibly changed player
|
||||||
|
map routes. It also caused over-aggressive behavior, including a bad custom
|
||||||
|
pin hairpin around the city-center roundabout, proving the hook is real but
|
||||||
|
that `0.35` is too low.
|
||||||
|
- The vanilla A/B build disabled only the inline multiplier patch and restored
|
||||||
|
obviously different route choices. User confirmed the custom pin returned to
|
||||||
|
the straight city-center roundabout route, while the `0.35` build took the
|
||||||
|
wrong hairpin.
|
||||||
|
- Current installed build enables the inline patch with default highway
|
||||||
|
multiplier `0.80` and polls
|
||||||
|
`red4ext/plugins/EdgeWeightGPS/solver_weights.bin` every 500 ms. The file is
|
||||||
|
exactly one little-endian float32 highway multiplier.
|
||||||
|
- The inline patch is deliberately broad across the vanilla road-tail branch:
|
||||||
|
reaching `0x40bb98` already means vanilla selected road-style modes `0/2/4`,
|
||||||
|
so we do not hard-gate on `solver+0xc4 == 2`. This avoids creating a manual
|
||||||
|
drive/autodrive disparity. Pedestrian mode `1` uses a different vanilla
|
||||||
|
branch and is not changed by this road-tail patch.
|
||||||
|
- Preset writer:
|
||||||
|
`tools/write_solver_weights.py`. Preset binaries live in
|
||||||
|
`presets/solver_weights/`: `vanilla`, `mild`, `default`, `strong`,
|
||||||
|
`proof-highway-cheap`, and `highway-expensive`.
|
||||||
|
- Evidence logs:
|
||||||
|
`logs/EdgeWeightGPS_inline_highway035_changed_routes_1449.log` and
|
||||||
|
`logs/EdgeWeightGPS_vanilla_ab_2120.log`.
|
||||||
|
|
||||||
## 2026-06-26 Static Update
|
## 2026-06-26 Static Update
|
||||||
|
|
||||||
- Installed RED4ext state is safe: the game plugin directory contains
|
- Installed RED4ext state is safe: the game plugin directory contains
|
||||||
@@ -110,19 +145,19 @@ Important caveat:
|
|||||||
- Older DLLs are preserved disabled as:
|
- Older DLLs are preserved disabled as:
|
||||||
`Cyberpunk 2077/red4ext/plugins/EdgeWeightGPS/EdgeWeightGPS.dll.disabled`
|
`Cyberpunk 2077/red4ext/plugins/EdgeWeightGPS/EdgeWeightGPS.dll.disabled`
|
||||||
and `EdgeWeightGPS.dd1423b.dll.disabled`.
|
and `EdgeWeightGPS.dd1423b.dll.disabled`.
|
||||||
- Source defaults now enable the full async solver multiplier hook:
|
- Source defaults enable the inline road-tail patch:
|
||||||
`kEnableGpsMultiplierHooks = true`.
|
`kEnableGpsNodeMultiplierInlinePatch = true`.
|
||||||
- The current behavior patch is deliberately narrow and diagnostic:
|
- Source defaults do not enable the old full-function detour:
|
||||||
`DetourGpsNodeMultiplier` hooks native `0x40bb40`, applies only when
|
`kEnableGpsMultiplierHooks = false`.
|
||||||
`job+0xc4 == 2` (driving route mode), reads lane/node flags from
|
- Current behavior patch is the inline `0x40bb98` code-cave patch. It preserves
|
||||||
`node+0x88`, and multiplies vanilla node edge cost by highway `0.35`,
|
vanilla road cost except for highway lanes, whose cost is multiplied by the
|
||||||
GPSOnly `1.10`, pavement `1.25`, and all other flags `1.0`.
|
current `solver_weights.bin` float. Installed default is `0.80`.
|
||||||
- `0x40bb00` auxiliary/reverse multiplier is hooked only for signature logging;
|
- `solver_weights.bin` lives beside the DLL and is hot-polled every 500 ms.
|
||||||
it is not patched yet.
|
|
||||||
- The plugin log was truncated immediately before installing this build.
|
- The plugin log was truncated immediately before installing this build.
|
||||||
- Latest archived test log:
|
- Latest archived test log:
|
||||||
`logs/EdgeWeightGPS_spatial_inversion_no_route_change_1845.log`.
|
`logs/EdgeWeightGPS_vanilla_ab_2120.log`.
|
||||||
- Game is cold as of the latest user message.
|
- A cold restart is required after installing a new DLL; changing
|
||||||
|
`solver_weights.bin` after that is hot-reloaded.
|
||||||
|
|
||||||
## 2026-06-26 Full Async Solver Findings
|
## 2026-06-26 Full Async Solver Findings
|
||||||
|
|
||||||
|
|||||||
@@ -66,12 +66,22 @@ Current behavior:
|
|||||||
|
|
||||||
Active build note:
|
Active build note:
|
||||||
|
|
||||||
- The currently installed build is disabled on disk:
|
- The currently installed build is active as
|
||||||
`EdgeWeightGPS.dll.disabled`. There is no active `EdgeWeightGPS.dll` in the
|
`red4ext/plugins/EdgeWeightGPS/EdgeWeightGPS.dll`.
|
||||||
game plugin directory.
|
- Source defaults enable the stable inline solver-cost patch at `0x40bb98`
|
||||||
- Source defaults are still read-only for route behavior. They disable provider
|
(`kEnableGpsNodeMultiplierInlinePatch = true`) and keep the old full-function
|
||||||
class multiplier overrides and the spatial edge-cost patch, while keeping GPS
|
`0x40bb40` C++ detour disabled (`kEnableGpsMultiplierHooks = false`).
|
||||||
query lifecycle tracing available for probe builds.
|
- The inline patch preserves vanilla road cost except for highway lanes in the
|
||||||
|
road-tail branch. Highway lanes are multiplied by the live value in
|
||||||
|
`solver_weights.bin`, defaulting to `0.80`.
|
||||||
|
- The patch is intentionally not hard-gated on `solver+0xc4 == 2`; the vanilla
|
||||||
|
branch already limits it to road-style modes `0/2/4`, which keeps manual
|
||||||
|
drive and autodrive-like callers from diverging while leaving pedestrian mode
|
||||||
|
`1` alone.
|
||||||
|
- `solver_weights.bin` lives beside the DLL and is exactly one little-endian
|
||||||
|
float32. The plugin polls it every 500 ms from the Running game-state update.
|
||||||
|
`tools/write_solver_weights.py` writes the installed file or generates preset
|
||||||
|
binaries under `presets/solver_weights/`.
|
||||||
- The async route-job hooks are split behind `kEnableGpsRouteJobLifecycleHooks`
|
- The async route-job hooks are split behind `kEnableGpsRouteJobLifecycleHooks`
|
||||||
and are disabled in the active probe; this keeps the log focused on
|
and are disabled in the active probe; this keeps the log focused on
|
||||||
`0x70a42c` submissions, `0x7094b8` result records, and local
|
`0x70a42c` submissions, `0x7094b8` result records, and local
|
||||||
|
|||||||
@@ -980,32 +980,62 @@ the original symptom: the solver is weighted enough to avoid obviously invalid
|
|||||||
surfaces, but it has no strong reason to stay on highway corridors for long
|
surfaces, but it has no strong reason to stay on highway corridors for long
|
||||||
vehicle trips.
|
vehicle trips.
|
||||||
|
|
||||||
The current RED4ext prototype therefore hooks `0x40bb40` and only changes
|
The first RED4ext prototype tried to hook `0x40bb40` as a normal C++ detour.
|
||||||
driving-mode calls (`solver/job + 0xc4 == 2`). It reads flags from
|
That found the correct function but crashed during load-time GPS warmup because
|
||||||
`node+0x88`, leaves vanilla results intact for ordinary roads and unknown
|
the optimized caller expects the tiny vanilla helper not to disturb volatile
|
||||||
surfaces, and applies this diagnostic multiplier set:
|
register state used immediately afterward.
|
||||||
|
|
||||||
|
The stable prototype now patches inline at the road-tail block `0x40bb98`
|
||||||
|
instead. Reaching that block already means the vanilla function selected one of
|
||||||
|
the road-style modes `0/2/4`; pedestrian mode `1` uses a different branch. The
|
||||||
|
patch preserves vanilla `node+0x93` behavior, checks `node+0x88 & 0x4000`, and
|
||||||
|
applies an extra multiplier only for highway lanes.
|
||||||
|
|
||||||
|
The proof multiplier was:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Highway 0.35
|
Highway 0.35
|
||||||
GPSOnly 1.10
|
|
||||||
Pavement 1.25
|
|
||||||
Other 1.00
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`0x40bb00` is currently hooked only for signature logging. If the node
|
Live A/B testing confirmed this is the decisive world-map route-cost lever.
|
||||||
multiplier test moves routes but produces weird reverse/turnaround behavior,
|
With `0.35`, routes visibly changed and a custom pin around the city-center
|
||||||
the next step is to tune or gate the auxiliary multiplier as well.
|
roundabout took an obviously bad highway/ramp hairpin. With the inline patch
|
||||||
|
disabled, the same route returned to the straight vanilla path through the
|
||||||
|
roundabout.
|
||||||
|
|
||||||
|
The current installed build uses a default highway multiplier of `0.80` and
|
||||||
|
hot-reloads `solver_weights.bin` from the plugin directory. The file is exactly
|
||||||
|
one little-endian float32. `tools/write_solver_weights.py` writes the installed
|
||||||
|
file and can generate named presets:
|
||||||
|
|
||||||
|
```text
|
||||||
|
vanilla 1.00
|
||||||
|
mild 0.90
|
||||||
|
default 0.80
|
||||||
|
strong 0.70
|
||||||
|
proof-highway-cheap 0.35
|
||||||
|
highway-expensive 3.00
|
||||||
|
```
|
||||||
|
|
||||||
|
The patch intentionally does not hard-gate on `solver+0xc4 == 2`, because
|
||||||
|
autodrive-like local vehicle navigation may use another road-style mode. Since
|
||||||
|
the patch lives in the vanilla road-tail branch, it applies to road-style modes
|
||||||
|
`0/2/4` and still leaves pedestrian mode `1` alone.
|
||||||
|
|
||||||
|
`0x40bb00` is not patched yet. If tuned highway weights still produce
|
||||||
|
reverse/turnaround artifacts, inspect or patch that auxiliary multiplier so
|
||||||
|
reverse/special progress branches receive a compatible policy.
|
||||||
|
|
||||||
## Future Improvements
|
## Future Improvements
|
||||||
|
|
||||||
Better versions of this mod could:
|
Better versions of this mod could:
|
||||||
|
|
||||||
- tune the `0x40bb40` solver multiplier after live A/B testing confirms visible
|
- tune the `0x40bb98` inline highway multiplier from the current `0.80` default
|
||||||
route changes
|
using live `solver_weights.bin` presets
|
||||||
- inspect and, if needed, patch `0x40bb00` so reverse/special progress branches
|
- inspect and, if needed, patch `0x40bb00` so reverse/special progress branches
|
||||||
receive the same road-class policy as forward expansion
|
receive the same road-class policy as forward expansion
|
||||||
- add a tiny live-tuning file for the solver multipliers, similar to the earlier
|
- extend `solver_weights.bin` beyond one highway float only if GPSOnly/pavement
|
||||||
`spatial_weights.bin`, once the hook is confirmed stable
|
tuning becomes necessary
|
||||||
- replace the crashing `0x8d46cc` detour with a narrower inline patch or a
|
- replace the crashing `0x8d46cc` detour with a narrower inline patch or a
|
||||||
safer caller-side hook
|
safer caller-side hook
|
||||||
- decode enough of the `VAND` graph to join navigation points to traffic lane
|
- decode enough of the `VAND` graph to join navigation points to traffic lane
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
ヘフL?
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
fff?
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
33�>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
333?
|
||||||
Binary file not shown.
@@ -102,7 +102,7 @@ constexpr bool kEnableVerboseMappinRouteState = false;
|
|||||||
constexpr bool kEnableGpsMultiplierHooks = false;
|
constexpr bool kEnableGpsMultiplierHooks = false;
|
||||||
constexpr bool kEnableGpsAuxMultiplierHook = false;
|
constexpr bool kEnableGpsAuxMultiplierHook = false;
|
||||||
constexpr bool kEnableGpsMultiplierSignatureTrace = false;
|
constexpr bool kEnableGpsMultiplierSignatureTrace = false;
|
||||||
constexpr bool kEnableGpsNodeMultiplierInlinePatch = false;
|
constexpr bool kEnableGpsNodeMultiplierInlinePatch = true;
|
||||||
constexpr bool kEnableGpsCostTablePatch = false;
|
constexpr bool kEnableGpsCostTablePatch = false;
|
||||||
constexpr bool kEnableGpsProviderClassPatch = false;
|
constexpr bool kEnableGpsProviderClassPatch = false;
|
||||||
constexpr bool kEnableGpsProviderClassPatchTrace = false;
|
constexpr bool kEnableGpsProviderClassPatchTrace = false;
|
||||||
@@ -213,7 +213,7 @@ constexpr uint16_t kTrafficLaneFlagNoAiDriving = 0x2000;
|
|||||||
constexpr uint16_t kTrafficLaneFlagHighway = 0x4000;
|
constexpr uint16_t kTrafficLaneFlagHighway = 0x4000;
|
||||||
constexpr uint16_t kTrafficLaneFlagNoAutodrive = 0x8000;
|
constexpr uint16_t kTrafficLaneFlagNoAutodrive = 0x8000;
|
||||||
constexpr uintptr_t kMinimumReadableAddress = 0x10000;
|
constexpr uintptr_t kMinimumReadableAddress = 0x10000;
|
||||||
constexpr float kGpsSolverHighwayMultiplier = 0.35f;
|
constexpr float kGpsSolverHighwayMultiplierDefault = 0.80f;
|
||||||
constexpr float kGpsSolverGpsOnlyMultiplier = 1.10f;
|
constexpr float kGpsSolverGpsOnlyMultiplier = 1.10f;
|
||||||
constexpr float kGpsSolverPavementMultiplier = 1.25f;
|
constexpr float kGpsSolverPavementMultiplier = 1.25f;
|
||||||
constexpr size_t kGpsNodeMultiplierRoadTailPatchSize = 14;
|
constexpr size_t kGpsNodeMultiplierRoadTailPatchSize = 14;
|
||||||
@@ -226,6 +226,7 @@ constexpr float kGpsSpatialRoadMultiplier = 1.0f;
|
|||||||
constexpr float kGpsSpatialGpsOnlyMultiplier = 1.20f;
|
constexpr float kGpsSpatialGpsOnlyMultiplier = 1.20f;
|
||||||
constexpr float kGpsSpatialPavementMultiplier = 1.35f;
|
constexpr float kGpsSpatialPavementMultiplier = 1.35f;
|
||||||
constexpr float kGpsSpatialUnknownMultiplier = 1.05f;
|
constexpr float kGpsSpatialUnknownMultiplier = 1.05f;
|
||||||
|
constexpr uint64_t kGpsSolverWeightsReloadIntervalMs = 500;
|
||||||
constexpr uint64_t kGpsSpatialWeightsReloadIntervalMs = 500;
|
constexpr uint64_t kGpsSpatialWeightsReloadIntervalMs = 500;
|
||||||
|
|
||||||
struct GpsProviderClassOverride
|
struct GpsProviderClassOverride
|
||||||
@@ -327,10 +328,12 @@ uint32_t gGpsResolveTraceWindowSerial = 0;
|
|||||||
std::array<uint32_t, 64> gGpsEdgeCostClassLogCounts{};
|
std::array<uint32_t, 64> gGpsEdgeCostClassLogCounts{};
|
||||||
std::array<uint8_t, kGpsNodeMultiplierRoadTailPatchSize> gGpsNodeMultiplierRoadTailOriginal{};
|
std::array<uint8_t, kGpsNodeMultiplierRoadTailPatchSize> gGpsNodeMultiplierRoadTailOriginal{};
|
||||||
void* gGpsNodeMultiplierRoadTailCave = nullptr;
|
void* gGpsNodeMultiplierRoadTailCave = nullptr;
|
||||||
|
uint8_t* gGpsNodeMultiplierRoadTailHighwayConstant = nullptr;
|
||||||
bool gGpsNodeMultiplierRoadTailPatchApplied = false;
|
bool gGpsNodeMultiplierRoadTailPatchApplied = false;
|
||||||
std::mutex gGpsCostLogMutex;
|
std::mutex gGpsCostLogMutex;
|
||||||
std::mutex gGpsAsyncLogMutex;
|
std::mutex gGpsAsyncLogMutex;
|
||||||
std::mutex gGpsCostTablePatchMutex;
|
std::mutex gGpsCostTablePatchMutex;
|
||||||
|
std::mutex gGpsSolverWeightsMutex;
|
||||||
uint64_t gLogStartTick = 0;
|
uint64_t gLogStartTick = 0;
|
||||||
uint64_t gLogFrequency = 0;
|
uint64_t gLogFrequency = 0;
|
||||||
void* gMappinSetTrackedTarget = nullptr;
|
void* gMappinSetTrackedTarget = nullptr;
|
||||||
@@ -569,9 +572,13 @@ std::array<std::atomic<float>, 5> gGpsSpatialMultipliers = {
|
|||||||
kGpsSpatialPavementMultiplier,
|
kGpsSpatialPavementMultiplier,
|
||||||
kGpsSpatialUnknownMultiplier,
|
kGpsSpatialUnknownMultiplier,
|
||||||
};
|
};
|
||||||
|
std::atomic<float> gGpsSolverHighwayMultiplier{kGpsSolverHighwayMultiplierDefault};
|
||||||
std::mutex gGpsSpatialWeightsMutex;
|
std::mutex gGpsSpatialWeightsMutex;
|
||||||
|
std::atomic<uint64_t> gGpsSolverWeightsNextReloadCheckMs = 0;
|
||||||
std::atomic<uint64_t> gGpsSpatialWeightsNextReloadCheckMs = 0;
|
std::atomic<uint64_t> gGpsSpatialWeightsNextReloadCheckMs = 0;
|
||||||
|
std::filesystem::file_time_type gGpsSolverWeightsLastWriteTime{};
|
||||||
std::filesystem::file_time_type gGpsSpatialWeightsLastWriteTime{};
|
std::filesystem::file_time_type gGpsSpatialWeightsLastWriteTime{};
|
||||||
|
bool gGpsSolverWeightsFileKnown = false;
|
||||||
bool gGpsSpatialWeightsFileKnown = false;
|
bool gGpsSpatialWeightsFileKnown = false;
|
||||||
|
|
||||||
uintptr_t GetImageBase();
|
uintptr_t GetImageBase();
|
||||||
@@ -598,6 +605,11 @@ std::filesystem::path GetSpatialWeightsPath()
|
|||||||
return GetPluginDirectory() / L"spatial_weights.bin";
|
return GetPluginDirectory() / L"spatial_weights.bin";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::filesystem::path GetSolverWeightsPath()
|
||||||
|
{
|
||||||
|
return GetPluginDirectory() / L"solver_weights.bin";
|
||||||
|
}
|
||||||
|
|
||||||
uint64_t GetElapsedMs()
|
uint64_t GetElapsedMs()
|
||||||
{
|
{
|
||||||
if (!gLogStartTick || !gLogFrequency)
|
if (!gLogStartTick || !gLogFrequency)
|
||||||
@@ -652,6 +664,103 @@ void WriteFloat(uint8_t* aTarget, float aValue)
|
|||||||
std::memcpy(aTarget, &aValue, sizeof(aValue));
|
std::memcpy(aTarget, &aValue, sizeof(aValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsUsableSolverMultiplier(float aValue)
|
||||||
|
{
|
||||||
|
return std::isfinite(aValue) && aValue > 0.0f && aValue < 20.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StoreGpsSolverHighwayMultiplier(float aValue)
|
||||||
|
{
|
||||||
|
gGpsSolverHighwayMultiplier.store(aValue, std::memory_order_relaxed);
|
||||||
|
|
||||||
|
if (gGpsNodeMultiplierRoadTailHighwayConstant)
|
||||||
|
{
|
||||||
|
WriteFloat(gGpsNodeMultiplierRoadTailHighwayConstant, aValue);
|
||||||
|
FlushInstructionCache(GetCurrentProcess(), gGpsNodeMultiplierRoadTailHighwayConstant, sizeof(aValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogGpsSolverHighwayMultiplier(std::string_view aPrefix, float aValue)
|
||||||
|
{
|
||||||
|
std::ostringstream line;
|
||||||
|
line << aPrefix << " highway=" << aValue;
|
||||||
|
LogRed4ext(line.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaybeReloadGpsSolverWeights(bool aForce = false)
|
||||||
|
{
|
||||||
|
if (!kEnableGpsNodeMultiplierInlinePatch)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto nowMs = GetElapsedMs();
|
||||||
|
if (!aForce && nowMs < gGpsSolverWeightsNextReloadCheckMs.load(std::memory_order_relaxed))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::lock_guard lock(gGpsSolverWeightsMutex);
|
||||||
|
if (!aForce && nowMs < gGpsSolverWeightsNextReloadCheckMs.load(std::memory_order_relaxed))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gGpsSolverWeightsNextReloadCheckMs.store(nowMs + kGpsSolverWeightsReloadIntervalMs, std::memory_order_relaxed);
|
||||||
|
|
||||||
|
const auto path = GetSolverWeightsPath();
|
||||||
|
std::error_code error;
|
||||||
|
const auto writeTime = std::filesystem::last_write_time(path, error);
|
||||||
|
if (error)
|
||||||
|
{
|
||||||
|
if (aForce || gGpsSolverWeightsFileKnown)
|
||||||
|
{
|
||||||
|
gGpsSolverWeightsFileKnown = false;
|
||||||
|
std::ostringstream line;
|
||||||
|
line << "solver weights file unavailable path=" << path.string()
|
||||||
|
<< " using highway=" << gGpsSolverHighwayMultiplier.load(std::memory_order_relaxed);
|
||||||
|
LogRed4ext(line.str());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!aForce && gGpsSolverWeightsFileKnown && writeTime == gGpsSolverWeightsLastWriteTime)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float highwayMultiplier = 0.0f;
|
||||||
|
std::ifstream input(path, std::ios::binary);
|
||||||
|
input.read(reinterpret_cast<char*>(&highwayMultiplier), static_cast<std::streamsize>(sizeof(highwayMultiplier)));
|
||||||
|
if (input.gcount() != static_cast<std::streamsize>(sizeof(highwayMultiplier)))
|
||||||
|
{
|
||||||
|
std::ostringstream line;
|
||||||
|
line << "solver weights reload skipped path=" << path.string() << " bytes=" << input.gcount()
|
||||||
|
<< " expected=" << sizeof(highwayMultiplier);
|
||||||
|
LogRed4ext(line.str());
|
||||||
|
gGpsSolverWeightsFileKnown = true;
|
||||||
|
gGpsSolverWeightsLastWriteTime = writeTime;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsUsableSolverMultiplier(highwayMultiplier))
|
||||||
|
{
|
||||||
|
std::ostringstream line;
|
||||||
|
line << "solver weights reload skipped path=" << path.string() << " invalidHighway=" << highwayMultiplier;
|
||||||
|
LogRed4ext(line.str());
|
||||||
|
gGpsSolverWeightsFileKnown = true;
|
||||||
|
gGpsSolverWeightsLastWriteTime = writeTime;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StoreGpsSolverHighwayMultiplier(highwayMultiplier);
|
||||||
|
gGpsSolverWeightsFileKnown = true;
|
||||||
|
gGpsSolverWeightsLastWriteTime = writeTime;
|
||||||
|
|
||||||
|
std::ostringstream line;
|
||||||
|
line << "solver weights reloaded path=" << path.string() << " highway=" << highwayMultiplier;
|
||||||
|
LogRed4ext(line.str());
|
||||||
|
}
|
||||||
|
|
||||||
bool InstallGpsNodeMultiplierInlinePatch()
|
bool InstallGpsNodeMultiplierInlinePatch()
|
||||||
{
|
{
|
||||||
if (!kEnableGpsNodeMultiplierInlinePatch || gGpsNodeMultiplierRoadTailPatchApplied)
|
if (!kEnableGpsNodeMultiplierInlinePatch || gGpsNodeMultiplierRoadTailPatchApplied)
|
||||||
@@ -659,7 +768,7 @@ bool InstallGpsNodeMultiplierInlinePatch()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr size_t kCaveSize = 64;
|
constexpr size_t kCaveSize = 80;
|
||||||
auto* cave = static_cast<uint8_t*>(
|
auto* cave = static_cast<uint8_t*>(
|
||||||
VirtualAlloc(nullptr, kCaveSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE));
|
VirtualAlloc(nullptr, kCaveSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE));
|
||||||
if (!cave)
|
if (!cave)
|
||||||
@@ -668,8 +777,9 @@ bool InstallGpsNodeMultiplierInlinePatch()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replaces the road-tail at 0x40bb98. This preserves the vanilla node93
|
// Replaces the road-tail at 0x40bb98. Reaching this block already means
|
||||||
// multiplier, then applies the highway multiplier before returning.
|
// vanilla selected one of the road-style solver modes (0/2/4), so keep
|
||||||
|
// the change broad enough for manual GPS and autodrive-like callers.
|
||||||
size_t offset = 0;
|
size_t offset = 0;
|
||||||
const auto emit = [&](std::initializer_list<uint8_t> aBytes) {
|
const auto emit = [&](std::initializer_list<uint8_t> aBytes) {
|
||||||
for (auto byte : aBytes)
|
for (auto byte : aBytes)
|
||||||
@@ -691,7 +801,7 @@ bool InstallGpsNodeMultiplierInlinePatch()
|
|||||||
emit({0xF3, 0x0F, 0x59, 0x05, 0x00, 0x00, 0x00, 0x00}); // mulss xmm0, [rip+disp32]
|
emit({0xF3, 0x0F, 0x59, 0x05, 0x00, 0x00, 0x00, 0x00}); // mulss xmm0, [rip+disp32]
|
||||||
emit({0xC3}); // ret
|
emit({0xC3}); // ret
|
||||||
|
|
||||||
while (offset < 40)
|
while ((offset % alignof(float)) != 0)
|
||||||
{
|
{
|
||||||
cave[offset++] = 0x90;
|
cave[offset++] = 0x90;
|
||||||
}
|
}
|
||||||
@@ -700,7 +810,8 @@ bool InstallGpsNodeMultiplierInlinePatch()
|
|||||||
WriteFloat(cave + offset, 1.1f);
|
WriteFloat(cave + offset, 1.1f);
|
||||||
offset += sizeof(float);
|
offset += sizeof(float);
|
||||||
const auto highwayConstantOffset = offset;
|
const auto highwayConstantOffset = offset;
|
||||||
WriteFloat(cave + offset, kGpsSolverHighwayMultiplier);
|
const auto highwayMultiplier = gGpsSolverHighwayMultiplier.load(std::memory_order_relaxed);
|
||||||
|
WriteFloat(cave + offset, highwayMultiplier);
|
||||||
offset += sizeof(float);
|
offset += sizeof(float);
|
||||||
|
|
||||||
const auto caveAddress = reinterpret_cast<uintptr_t>(cave);
|
const auto caveAddress = reinterpret_cast<uintptr_t>(cave);
|
||||||
@@ -743,12 +854,13 @@ bool InstallGpsNodeMultiplierInlinePatch()
|
|||||||
VirtualProtect(target, sizeof(patch), oldProtect, &ignoredProtect);
|
VirtualProtect(target, sizeof(patch), oldProtect, &ignoredProtect);
|
||||||
|
|
||||||
gGpsNodeMultiplierRoadTailCave = cave;
|
gGpsNodeMultiplierRoadTailCave = cave;
|
||||||
|
gGpsNodeMultiplierRoadTailHighwayConstant = cave + highwayConstantOffset;
|
||||||
gGpsNodeMultiplierRoadTailPatchApplied = true;
|
gGpsNodeMultiplierRoadTailPatchApplied = true;
|
||||||
|
|
||||||
std::ostringstream line;
|
std::ostringstream line;
|
||||||
line << "gps-node-multiplier-inline-patch applied target_rva=0x" << std::hex
|
line << "gps-node-multiplier-inline-patch applied target_rva=0x" << std::hex
|
||||||
<< kGpsNodeMultiplierRoadTailRva << " cave=" << static_cast<void*>(cave)
|
<< kGpsNodeMultiplierRoadTailRva << " cave=" << static_cast<void*>(cave)
|
||||||
<< " highwayMultiplier=" << std::dec << kGpsSolverHighwayMultiplier;
|
<< " highwayMultiplier=" << std::dec << highwayMultiplier << " modes=road-tail";
|
||||||
LogRed4ext(line.str());
|
LogRed4ext(line.str());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -782,6 +894,7 @@ void RemoveGpsNodeMultiplierInlinePatch()
|
|||||||
gGpsNodeMultiplierRoadTailCave = nullptr;
|
gGpsNodeMultiplierRoadTailCave = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gGpsNodeMultiplierRoadTailHighwayConstant = nullptr;
|
||||||
gGpsNodeMultiplierRoadTailPatchApplied = false;
|
gGpsNodeMultiplierRoadTailPatchApplied = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2338,16 +2451,21 @@ bool TryReadUint32FieldValue(void* aObject, uintptr_t aOffset, uint32_t& aValue)
|
|||||||
aValue);
|
aValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsGpsRoadStyleMode(uint32_t aMode)
|
||||||
|
{
|
||||||
|
return aMode == 0 || aMode == 2 || aMode == 4;
|
||||||
|
}
|
||||||
|
|
||||||
bool IsDrivingRouteJob(void* aRouteSystem, void* aActiveEntry)
|
bool IsDrivingRouteJob(void* aRouteSystem, void* aActiveEntry)
|
||||||
{
|
{
|
||||||
uint32_t mode = 0;
|
uint32_t mode = 0;
|
||||||
if (TryReadUint32FieldValue(aActiveEntry, 0x80, mode) && mode == 2)
|
if (TryReadUint32FieldValue(aActiveEntry, 0x80, mode) && IsGpsRoadStyleMode(mode))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* job = ReadPointerField(aRouteSystem, 0x90D0);
|
auto* job = ReadPointerField(aRouteSystem, 0x90D0);
|
||||||
return TryReadUint32FieldValue(job, 0xC4, mode) && mode == 2;
|
return TryReadUint32FieldValue(job, 0xC4, mode) && IsGpsRoadStyleMode(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppendFloatArray(std::ostringstream& aLine, const char* aLabel, const float* aValues, size_t aCount)
|
void AppendFloatArray(std::ostringstream& aLine, const char* aLabel, const float* aValues, size_t aCount)
|
||||||
@@ -4339,7 +4457,7 @@ float GpsSolverNodeWeightMultiplier(uint16_t aFlags)
|
|||||||
{
|
{
|
||||||
if ((aFlags & kTrafficLaneFlagHighway) != 0)
|
if ((aFlags & kTrafficLaneFlagHighway) != 0)
|
||||||
{
|
{
|
||||||
return kGpsSolverHighwayMultiplier;
|
return gGpsSolverHighwayMultiplier.load(std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
if ((aFlags & kTrafficLaneFlagGpsOnly) != 0)
|
if ((aFlags & kTrafficLaneFlagGpsOnly) != 0)
|
||||||
{
|
{
|
||||||
@@ -5084,7 +5202,7 @@ float DetourGpsNodeMultiplier(void* aJob, void* aNode)
|
|||||||
result = ReadGpsSolverNonRoadMultiplier(retry);
|
result = ReadGpsSolverNonRoadMultiplier(retry);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode == 2 && std::isfinite(result))
|
if (IsGpsRoadStyleMode(mode) && std::isfinite(result))
|
||||||
{
|
{
|
||||||
result *= GpsSolverNodeWeightMultiplier(nodeFlags);
|
result *= GpsSolverNodeWeightMultiplier(nodeFlags);
|
||||||
}
|
}
|
||||||
@@ -5352,6 +5470,8 @@ bool OnRunningUpdate(void* aApp)
|
|||||||
++gUpdateLogCount;
|
++gUpdateLogCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MaybeReloadGpsSolverWeights();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5420,6 +5540,16 @@ RED4EXT_C_EXPORT bool RED4EXT_CALL Main(RED4ext::v1::PluginHandle aHandle, RED4e
|
|||||||
LogGpsSpatialMultipliers("spatial weights defaults", values);
|
LogGpsSpatialMultipliers("spatial weights defaults", values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (kEnableGpsNodeMultiplierInlinePatch)
|
||||||
|
{
|
||||||
|
std::ostringstream line;
|
||||||
|
line << "solver weights path=" << GetSolverWeightsPath().string();
|
||||||
|
LogRed4ext(line.str());
|
||||||
|
MaybeReloadGpsSolverWeights(true);
|
||||||
|
LogGpsSolverHighwayMultiplier("solver weights active",
|
||||||
|
gGpsSolverHighwayMultiplier.load(std::memory_order_relaxed));
|
||||||
|
}
|
||||||
|
|
||||||
InstallGpsNodeMultiplierInlinePatch();
|
InstallGpsNodeMultiplierInlinePatch();
|
||||||
|
|
||||||
if (kEnableGpsQueryLifecycleHooks)
|
if (kEnableGpsQueryLifecycleHooks)
|
||||||
|
|||||||
Executable
+60
@@ -0,0 +1,60 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Write EdgeWeightGPS solver highway presets as one raw float32 value."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import struct
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_INSTALL_PATH = Path(
|
||||||
|
"/var/home/salt/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/common/"
|
||||||
|
"Cyberpunk 2077/red4ext/plugins/EdgeWeightGPS/solver_weights.bin"
|
||||||
|
)
|
||||||
|
|
||||||
|
PRESETS: dict[str, float] = {
|
||||||
|
"vanilla": 1.00,
|
||||||
|
"default": 0.80,
|
||||||
|
"mild": 0.90,
|
||||||
|
"strong": 0.70,
|
||||||
|
"proof-highway-cheap": 0.35,
|
||||||
|
"highway-expensive": 3.00,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_multiplier(text: str) -> float:
|
||||||
|
value = float(text)
|
||||||
|
if not 0.0 < value < 20.0:
|
||||||
|
raise argparse.ArgumentTypeError("multiplier must be greater than 0 and less than 20")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def write_weight(path: Path, value: float) -> None:
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
path.write_bytes(struct.pack("<f", value))
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("preset", nargs="?", choices=sorted(PRESETS), default="default")
|
||||||
|
parser.add_argument("--value", type=parse_multiplier, help="explicit highway multiplier")
|
||||||
|
parser.add_argument("--output", type=Path, default=DEFAULT_INSTALL_PATH)
|
||||||
|
parser.add_argument("--preset-dir", type=Path, help="write every named preset into this directory")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.preset_dir:
|
||||||
|
for name, value in PRESETS.items():
|
||||||
|
path = args.preset_dir / f"{name}.bin"
|
||||||
|
write_weight(path, value)
|
||||||
|
print(f"{path}: {value}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
value = args.value if args.value is not None else PRESETS[args.preset]
|
||||||
|
write_weight(args.output, value)
|
||||||
|
print(f"{args.output}: {value}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Reference in New Issue
Block a user