diff --git a/src/game_launch.rs b/src/game_launch.rs index 7a6d752..bb53eb7 100644 --- a/src/game_launch.rs +++ b/src/game_launch.rs @@ -24,6 +24,7 @@ //! falls back to it, so an existing working setup cannot be broken by upgrading. use parking_lot::Mutex; +use std::collections::BTreeMap; use std::io::{BufRead, BufReader}; use std::path::{Path, PathBuf}; use std::process::{Child, Command, Stdio}; @@ -67,6 +68,7 @@ pub fn launch( for (k, v) in &profile.env { cmd.env(k, v); } + cmd.env("WINEDLLOVERRIDES", hook_dll_overrides(&profile.env)); if !profile.wine_prefix.trim().is_empty() { cmd.env("WINEPREFIX", &profile.wine_prefix); } @@ -93,6 +95,30 @@ pub fn launch( Ok(()) } +/// The `WINEDLLOVERRIDES` value the game must be started with. +/// +/// The hook ships as a `version.dll` proxy inside the game directory, and Proton +/// prefers a local DLL over its own builtin ONLY when `WINEDLLOVERRIDES` names it +/// (see `setup::STEAM_LAUNCH_OPTIONS`). Steam users get that from their launch +/// options; when the launcher spawns the runner itself, nothing else supplies it. +/// +/// Without it the failure is silent and badly misleading: the hook never loads, so +/// the `openfut.cfg` the launcher just wrote is inert, the game ignores the +/// configured Blaze ports, and `/etc/hosts` quietly routes it to whatever answers +/// on EA's real ports. It looks like a working launch against the configured +/// server while actually talking to a different one. +/// +/// A profile that already pins `version=` wins: an operator overriding the hijack +/// deliberately must not be silently overruled. +fn hook_dll_overrides(env: &BTreeMap) -> String { + const HOOK: &str = "version=n,b"; + match env.get("WINEDLLOVERRIDES").map(|v| v.trim()) { + Some(existing) if existing.contains("version=") => existing.to_string(), + Some(existing) if !existing.is_empty() => format!("{existing};{HOOK}"), + _ => HOOK.to_string(), + } +} + /// Create the Wine prefix's `dosdevices` entries the profile asks for. /// /// Equivalent to `mkdir -p $WINEPREFIX/dosdevices && ln -sfn `: @@ -463,4 +489,32 @@ mod tests { let err = launch(&profile, &log(), || {}).unwrap_err().to_string(); assert!(err.contains("game_dir does not exist"), "{err}"); } + + #[test] + fn a_profile_without_overrides_still_gets_the_hook_hijack() { + // The regression this guards: FIFA launched from the launcher ignored the + // configured Blaze ports entirely, because Proton loaded its own builtin + // version.dll and the hook proxy never ran. The launch looked healthy. + assert_eq!(hook_dll_overrides(&BTreeMap::new()), "version=n,b"); + } + + #[test] + fn unrelated_overrides_are_preserved_and_appended_to() { + let env = BTreeMap::from([("WINEDLLOVERRIDES".to_string(), "d3d11=n".to_string())]); + assert_eq!(hook_dll_overrides(&env), "d3d11=n;version=n,b"); + } + + #[test] + fn an_explicit_version_override_is_never_overruled() { + // An operator disabling the hijack on purpose must win, otherwise the + // setting is a lie. + let env = BTreeMap::from([("WINEDLLOVERRIDES".to_string(), "version=b".to_string())]); + assert_eq!(hook_dll_overrides(&env), "version=b"); + } + + #[test] + fn a_blank_override_is_treated_as_absent_rather_than_appended_to() { + let env = BTreeMap::from([("WINEDLLOVERRIDES".to_string(), " ".to_string())]); + assert_eq!(hook_dll_overrides(&env), "version=n,b"); + } }