Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d3790a83a |
+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