launcher: persist the hook DLL override in the prefix, not in launch options
Wine ignores the game-directory version.dll proxy unless an override names it. WINEDLLOVERRIDES covers only a process the launcher spawns itself, so the documented fallback was to have the user paste Steam launch options by hand - a step a normal player cannot be expected to perform, and the reason the game had to be started through a specific wrapper at all. The launcher now persists version=native,builtin into the prefix registry via Wine own reg tool before launching (ensure_dll_override). It is /f-idempotent, so it runs on every launch and repairs a prefix the player has reset or replaced, and it applies to EVERY launch path including Steam Play. This mirrors what BepInEx documents for Proton (configure the proxy in winecfg rather than the environment) and what Proton already does in this prefix for other titles. Best-effort: a failure is reported in plain language and the launch still carries WINEDLLOVERRIDES. STEAM_LAUNCH_OPTIONS is demoted to a fallback for prefixes we have never prepared. Also fixes a pre-existing clippy manual_is_multiple_of in app.rs that was failing the strict lint gate. fmt clean, clippy -D warnings clean, 75 tests pass.
This commit is contained in:
+1
-1
@@ -2177,7 +2177,7 @@ fn group_thousands(digits: &str) -> String {
|
||||
let len = bytes.len();
|
||||
let mut out = String::with_capacity(len + len / 3);
|
||||
for (i, b) in bytes.iter().enumerate() {
|
||||
if i > 0 && (len - i) % 3 == 0 {
|
||||
if i > 0 && (len - i).is_multiple_of(3) {
|
||||
out.push(',');
|
||||
}
|
||||
out.push(*b as char);
|
||||
|
||||
@@ -58,6 +58,7 @@ pub fn launch(
|
||||
}
|
||||
|
||||
prepare_prefix(profile, log)?;
|
||||
ensure_dll_override(profile, log);
|
||||
ensure_license(profile, log)?;
|
||||
|
||||
let mut cmd = Command::new(&profile.runner);
|
||||
@@ -95,6 +96,99 @@ pub fn launch(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The registry key Wine reads DLL overrides from, and the one value the hook needs.
|
||||
///
|
||||
/// Wine loads its own builtin `version.dll` unless an override says otherwise, so the
|
||||
/// game-directory proxy is ignored by default. `WINEDLLOVERRIDES` fixes that only for
|
||||
/// a process we spawn ourselves — it cannot help a player who presses Play in Steam,
|
||||
/// which is why the old advice was to paste launch options by hand (see
|
||||
/// `setup::STEAM_LAUNCH_OPTIONS`). Asking a player to edit launch options is exactly
|
||||
/// the kind of step that makes this unusable for anyone who does not already know what
|
||||
/// a DLL override is.
|
||||
///
|
||||
/// Persisting the override in the prefix registry removes the manual step entirely: it
|
||||
/// survives restarts and applies to every launch path, including Steam. This mirrors
|
||||
/// what BepInEx documents for Proton (configure the proxy in winecfg rather than the
|
||||
/// environment) and what Proton itself already does in this prefix for other titles.
|
||||
const DLL_OVERRIDE_KEY: &str = r"HKCU\Software\Wine\DllOverrides";
|
||||
const HOOK_DLL_VALUE: &str = "version";
|
||||
const HOOK_DLL_OVERRIDE: &str = "native,builtin";
|
||||
|
||||
/// `reg add` argv that persists the hook's DLL override, native-first with a builtin
|
||||
/// fallback. `/f` makes it idempotent, so this is safe to run on every launch and
|
||||
/// repairs a prefix a player has reset or replaced.
|
||||
fn dll_override_args() -> [&'static str; 10] {
|
||||
[
|
||||
"reg",
|
||||
"add",
|
||||
DLL_OVERRIDE_KEY,
|
||||
"/v",
|
||||
HOOK_DLL_VALUE,
|
||||
"/t",
|
||||
"REG_SZ",
|
||||
"/d",
|
||||
HOOK_DLL_OVERRIDE,
|
||||
"/f",
|
||||
]
|
||||
}
|
||||
|
||||
/// Persist the hook's DLL override into the prefix, so the game loads the proxy no
|
||||
/// matter how it is started.
|
||||
///
|
||||
/// Best-effort by design: a failure here is not fatal, because a launch we spawn also
|
||||
/// carries `WINEDLLOVERRIDES`. It is reported in plain language rather than as a Wine
|
||||
/// error, since the player cannot act on the latter.
|
||||
fn ensure_dll_override(profile: &GameProfile, log: &Log) {
|
||||
if profile.wine_prefix.trim().is_empty() {
|
||||
return;
|
||||
}
|
||||
let mut cmd = Command::new(&profile.runner);
|
||||
cmd.args(dll_override_args())
|
||||
.current_dir(&profile.game_dir)
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null());
|
||||
for (k, v) in &profile.env {
|
||||
cmd.env(k, v);
|
||||
}
|
||||
cmd.env("WINEPREFIX", &profile.wine_prefix);
|
||||
match cmd.status() {
|
||||
Ok(status) if status.success() => {
|
||||
say(log, "[launcher] game files ready (mod support enabled)");
|
||||
}
|
||||
Ok(_) | Err(_) => say(
|
||||
log,
|
||||
"[launcher] could not pre-enable mod support in the game prefix; \
|
||||
launching anyway (this launch still enables it directly)",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod override_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn dll_override_is_persisted_native_first_and_idempotently() {
|
||||
let args = dll_override_args();
|
||||
assert_eq!(args[0], "reg");
|
||||
assert_eq!(args[1], "add");
|
||||
assert_eq!(
|
||||
args[2], r"HKCU\Software\Wine\DllOverrides",
|
||||
"Wine reads overrides from this key; a typo silently leaves the hook unloaded"
|
||||
);
|
||||
assert_eq!(args[4], "version", "the hook ships as a version.dll proxy");
|
||||
assert_eq!(
|
||||
args[8], "native,builtin",
|
||||
"native first so the proxy wins, builtin as fallback so a missing proxy \
|
||||
cannot make the game unlaunchable"
|
||||
);
|
||||
assert_eq!(
|
||||
args[9], "/f",
|
||||
"idempotent, so running it on every launch repairs a reset prefix"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The `WINEDLLOVERRIDES` value the game must be started with.
|
||||
///
|
||||
/// The hook ships as a `version.dll` proxy inside the game directory, and Proton
|
||||
|
||||
+8
-2
@@ -126,8 +126,14 @@ pub fn hook_dll_deployed(game_dir: &Path) -> bool {
|
||||
game_dir.join("version.dll").exists()
|
||||
}
|
||||
|
||||
/// The Steam launch options the user needs to paste in to enable the override.
|
||||
/// Proton loads local DLLs named in WINEDLLOVERRIDES ahead of system ones.
|
||||
/// Steam launch options that enable the hook's DLL override.
|
||||
///
|
||||
/// Kept only as a fallback to show a user who runs the game outside this launcher on
|
||||
/// a prefix we have never prepared. It is NOT the normal path any more: the launcher
|
||||
/// persists the override in the prefix registry itself
|
||||
/// (`game_launch::ensure_dll_override`), which applies to every launch including
|
||||
/// Steam's own Play button. Telling a player to paste launch options is exactly the
|
||||
/// kind of manual step this launcher exists to remove.
|
||||
pub const STEAM_LAUNCH_OPTIONS: &str = "WINEDLLOVERRIDES=\"version=n,b\" %command%";
|
||||
|
||||
// ── Game launch ───────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user