Launcher side of the verified patched-client capability handshake. When
autopatch proves the CardsDLL empty-My-Packs resolver guard is active for the
CURRENT FIFA process, the launcher advertises that to the backend so the backend
may drop the synthetic 65534 sentinel for that session only. Additive and
fail-closed: any parse/registration failure leaves the backend on its default
sentinel path.
- New src/fifa17_capability.rs:
* Fifa17ClientCapabilities { empty_mypacks_resolver: Option<u32> } — per-FIFA-
process state, UNKNOWN at each launch, discarded when that process ends
(never persisted, so a prior launch's capability cannot leak).
* parse_capability_line() / parse_fifa_pid() — pure parsers for autopatch's
stdout token `[store-guard] verified capability fifa17.empty_mypacks_resolver=<v>
fifa_pid=<pid>`; the non-advertising `guard status=...` line yields None.
* register() — tiny stdlib-HTTP POST /openfut/fifa17/capability, modeled on
account_sync::sync (Connection: close, 3s timeouts, 2xx check).
- local_services::spawn: autopatch stdout reader parses each raw line; on the
first verified line it sets the shared capability sink, logs, and fires exactly
one backend register() for this FIFA process. Capability wiring is bundled in a
CapabilityWiring struct (Some for autopatch, None for LSX). LSX unchanged.
- app.rs: LauncherApp holds the shared Fifa17ClientCapabilities; it is reset to
UNKNOWN at the start of launch_game (and when autopatch is stopped) so a new
FIFA process never inherits a previous launch's capability.
- Tests: parse (verified/non-advertising/unrelated/version-2) + a register()
round-trip against an in-process listener.
Design + contract: docs/plans/FIFA17_PATCHED_CLIENT_CAPABILITY.md (superproject).
Pre-existing openfut-hook/* working-tree changes are intentionally left uncommitted.
Add a GUI "Arm client" button that reproduces client_arm.sh in a single
pkexec batch: kernel.yama.ptrace_scope=0, DNAT of EA's hardcoded redirector
IP to the OpenFUT server (+ MASQUERADE reply path), and /etc/hosts rewrites
for every dead EA hostname (removing foreign shadow lines first, so glibc's
first-match resolution can't land on a stale loopback entry). All steps are
idempotent (delete-then-add) and injection-safe: config values are charset-
validated and rejected on a surprising character, never shell-escaped. arm()
returns the concrete change list, which the button logs line-by-line and
echoes as an inline pass/fail status on the pre-launch tab (no tab jump, no
reuse of the local-services toast).
This necessarily lands the surrounding launcher modularization the arm
feature is built on, extracted from the former monolithic app.rs/process.rs:
- preflight: advisory pre-launch checks (ptrace, redirector DNAT, hostnames,
backend reachability) that colour rows but never block Launch
- local_services: launcher-owned LSX/autopatch child processes
- game_launch, account_sync, health, netcheck helpers
- openfut-common: dependency-free shared server-destination/port mapping,
used by both the launcher and (separately) openfut_hook.dll
openfut-hook RE changes are intentionally left uncommitted (separate concern).
fmt + clippy -D warnings clean; 46 tests pass.
GUI desktop app (egui/eframe) that manages the openfut-core and
openfut-bridge services with a single window.
- Dashboard: start/stop each service independently or together; live
status indicators; quick-status for cert and hosts
- Logs: real-time stdout/stderr from both processes, colour-coded by
level, follow mode and manual clear
- Setup: add/remove EA hostname redirects in /etc/hosts (pkexec/sudo);
install the bridge TLS cert into the system CA store
- Config: all binary paths and env vars editable in-app; persisted to
~/.config/openfut-launcher/config.json
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>