diff --git a/scripts/client-hook-override.py b/scripts/client-hook-override.py new file mode 100755 index 0000000..2ed4b8b --- /dev/null +++ b/scripts/client-hook-override.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +"""Ensure the deployed launcher starts FIFA with the hook's WINEDLLOVERRIDES. + +The hook ships as a `version.dll` proxy in the game directory, and Proton prefers a +local DLL over its own builtin ONLY when WINEDLLOVERRIDES names it. Launcher builds +before openfut-launcher c542415 apply `game_profile.env` but never that override, so +the hook silently never loads -- and with no hook there is no port rewrite, the +openfut.cfg the launcher just wrote is inert, and the client reaches EA's real Blaze +ports (production, via /etc/hosts) while looking like a healthy configured launch. + +This writes the override into `game_profile.env`, which the deployed launcher DOES +apply, so it works without rebuilding. It is forward-compatible with the fixed +launcher: hook_dll_overrides() defers to a profile that already pins `version=`, so +the value set here simply wins and nothing conflicts. + + python3 scripts/client-hook-override.py show + python3 scripts/client-hook-override.py apply + python3 scripts/client-hook-override.py revert + +Safety: the prior value (including its absence) is recorded to a sidecar before the +first mutation, so `revert` restores the real previous state rather than guessing; +every operation re-reads and prints the result; and it refuses to edit while the +launcher is running, because the launcher holds its config in memory and would write +the stale value straight back over ours. +""" +import json +import subprocess +import sys + +CLIENT = "alex@10.10.0.105" +CFG = "/home/alex/.config/openfut-launcher/config.json" +SIDECAR = "/home/alex/.config/openfut-launcher/config.json.openfut-prev-dlloverrides" +KEY = "WINEDLLOVERRIDES" +HOOK = "version=n,b" +ABSENT = "" + + +def ssh(script): + r = subprocess.run(["ssh", "-o", "BatchMode=yes", CLIENT, script], + capture_output=True, text=True, timeout=60) + if r.returncode != 0: + raise SystemExit(f"ssh failed ({r.returncode}): {r.stderr.strip()}") + return r.stdout + + +def launcher_running(): + """comm is truncated by the kernel to 15 chars ("openfut-launche").""" + out = ssh("for d in /proc/[0-9]*; do c=$(cat $d/comm 2>/dev/null); " + "case \"$c\" in openfut-launche*) echo ${d#/proc/};; esac; done || true") + return bool(out.strip()) + + +def fifa_running(): + out = ssh("for d in /proc/[0-9]*; do " + "[ \"$(cat $d/comm 2>/dev/null)\" = FIFA17.exe ] && echo ${d#/proc/}; " + "done || true") + return bool(out.strip()) + + +def load(): + return json.loads(ssh(f'cat "{CFG}"')) + + +def env_of(d): + return d.setdefault("game_profile", {}).setdefault("env", {}) + + +def save(d): + body = json.dumps(d, indent=2, sort_keys=True) + ssh(f'cat > "{CFG}" <<\'EOF\'\n{body}\nEOF') + + +def show(): + d = load() + cur = env_of(d).get(KEY, ABSENT) + print(f"--- {CFG}") + print(f" game_profile.env[{KEY}] = {cur}") + print(f" hook active on launch = {'YES' if 'version=' in cur else 'NO'}") + side = ssh(f'cat "{SIDECAR}" 2>/dev/null || true').strip() + print(f" recorded previous value = {side or '(none recorded yet)'}") + print(f" launcher running = {'YES' if launcher_running() else 'no'}") + return cur + + +def guard(): + if fifa_running(): + raise SystemExit("REFUSING: a FIFA client is running; exit it first.") + if launcher_running(): + raise SystemExit( + "REFUSING: openfut-launcher is running. It holds its config in memory and\n" + "would write the old value back over ours. Quit the launcher first.") + + +def apply_override(): + guard() + d = load() + env = env_of(d) + prior = env.get(KEY, ABSENT) + if not ssh(f'cat "{SIDECAR}" 2>/dev/null || true').strip(): + ssh(f'cat > "{SIDECAR}" <<\'EOF\'\n{prior}\nEOF') + print(f"recorded previous value to {SIDECAR}: {prior}") + # Preserve an unrelated override rather than clobbering it, and never double up. + if prior != ABSENT and "version=" in prior: + print(f"already pins version= ({prior}); leaving it alone") + return show() + env[KEY] = HOOK if prior == ABSENT else f"{prior};{HOOK}" + save(d) + after = env_of(load()).get(KEY, ABSENT) + if "version=" not in after: + raise SystemExit(f"VERIFY FAILED: {KEY} is {after!r}") + return show() + + +def revert(): + guard() + side = ssh(f'cat "{SIDECAR}" 2>/dev/null || true').strip() + if not side: + raise SystemExit(f"no recorded previous value at {SIDECAR}; refusing to guess") + d = load() + env = env_of(d) + if side == ABSENT: + env.pop(KEY, None) + else: + env[KEY] = side + save(d) + return show() + + +def main(): + if len(sys.argv) != 2 or sys.argv[1] not in ("show", "apply", "revert"): + print(__doc__) + return 2 + {"show": show, "apply": apply_override, "revert": revert}[sys.argv[1]]() + return 0 + + +if __name__ == "__main__": + sys.exit(main())