//! Watch for a (re)launched FIFA17.exe and auto-apply the ProtoSSL cert patches //! the moment its unpacked code is mapped, plus the CardsDLL FUT store patches. //! Idempotent; keeps watching across relaunches. //! //! Port of `fifa17-recon/tools/autopatch.py`, tick for tick: cert gates once per //! pid, store patches re-enforced every second (the game rewrites those sites), //! then the resolver-guard capability reported once per pid. The passes //! themselves live in `openfut_autopatch::patch`; this is the loop and the CLI. use std::collections::HashSet; use std::process::ExitCode; use std::thread::sleep; use std::time::Duration; use openfut_autopatch::patch::{cert_pass, enforce_store_patches}; use openfut_autopatch::procmem::{self, ProcMem}; use openfut_autopatch::{parse_launcher_pid, Logger}; /// One tick per second, as in the Python. const TICK: Duration = Duration::from_secs(1); /// Per-pid bookkeeping so each of these lines is logged exactly once per client /// process (the Python's three module-level sets). #[derive(Default)] struct Seen { patched: HashSet, store_patched: HashSet, guard_reported: HashSet, } fn main() -> ExitCode { let launcher_pid = match parse_launcher_pid(std::env::args().skip(1)) { Ok(pid) => pid, Err(()) => { eprintln!("invalid --launcher-pid"); return ExitCode::FAILURE; } }; let logger = match Logger::from_env() { Ok(logger) => logger, Err(e) => { eprintln!("cannot resolve the autopatch log path: {e}"); return ExitCode::FAILURE; } }; logger.log("=== AUTOPATCH watching for FIFA17.exe ==="); let mut seen = Seen::default(); loop { // `if launcher_pid and not os.path.exists(...)`: pid 0 is falsy in the // Python, so `--launcher-pid 0` parses but is never watched. if let Some(pid) = launcher_pid { if pid != 0 && !procmem::pid_alive(pid) { logger.log(&format!("launcher pid {pid} exited; stopping autopatch")); break; } } for pid in procmem::find_pids() { let mem = ProcMem::new(pid); if !seen.patched.contains(&pid) && !cert_pass(&mem, &mut |line| logger.log(line), &mut seen.patched) { // Code not mapped yet: nothing else to do for this pid this tick. continue; } // Store patches are enforced on EVERY tick, not once: the game // rewrites these sites, so a single pass at startup does not hold. let Some(cbase) = procmem::cardsdll_base(pid) else { continue; }; match enforce_store_patches( &mem, cbase, &mut |line| logger.log(line), &mut seen.guard_reported, ) { Ok(()) => { if seen.store_patched.insert(pid) { logger.log(&format!( "pid {pid}: PATCHED store gates in CardsDLL @ {cbase:#x}" )); } } Err(e) => logger.log(&format!("pid {pid}: store patch write failed: {e}")), } } sleep(TICK); } ExitCode::SUCCESS }