feat(fifa17): report verified client patch capability
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.
This commit is contained in:
+31
-3
@@ -50,6 +50,11 @@ pub struct LauncherApp {
|
||||
/// Result of the last "Arm client" click, shown inline beneath the button so
|
||||
/// the outcome appears where the user acted — not on another tab.
|
||||
arm_status: Option<(bool, String)>,
|
||||
|
||||
/// Capabilities verified for the *current* FIFA process (shared with the
|
||||
/// autopatch stdout reader). Reset to unknown at each launch so a new FIFA
|
||||
/// process never inherits a previous launch's capability.
|
||||
fifa17_caps: Arc<Mutex<crate::fifa17_capability::Fifa17ClientCapabilities>>,
|
||||
}
|
||||
|
||||
impl LauncherApp {
|
||||
@@ -86,6 +91,7 @@ impl LauncherApp {
|
||||
local_services_message: None,
|
||||
preflight: None,
|
||||
arm_status: None,
|
||||
fifa17_caps: Arc::new(Mutex::new(Default::default())),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,6 +320,9 @@ impl LauncherApp {
|
||||
} else if ap_running {
|
||||
if ui.button("Stop").clicked() {
|
||||
self.autopatch.stop(&self.game_logs, Service::Autopatch);
|
||||
// The verified capability belongs to the FIFA process
|
||||
// autopatch was serving; drop it when autopatch stops.
|
||||
*self.fifa17_caps.lock().unwrap() = Default::default();
|
||||
}
|
||||
} else if ui.button("Start").clicked() {
|
||||
let _ = self.start_local_service(Service::Autopatch);
|
||||
@@ -448,6 +457,9 @@ impl LauncherApp {
|
||||
}
|
||||
|
||||
fn launch_game(&mut self) {
|
||||
// A new FIFA process starts UNKNOWN: never inherit a prior launch's
|
||||
// verified capability. The autopatch stdout reader re-populates this.
|
||||
*self.fifa17_caps.lock().unwrap() = Default::default();
|
||||
if let Err(message) = self.config.validate_launch_config() {
|
||||
self.game_logs
|
||||
.lock()
|
||||
@@ -538,6 +550,21 @@ impl LauncherApp {
|
||||
use crate::local_services::spawn;
|
||||
let py = self.config.fifa17_python.clone();
|
||||
let dir = self.config.fifa17_tools_dir.clone();
|
||||
let persona_id = self.config.fut_persona_id;
|
||||
let persona_name = self.config.fut_persona_name.clone();
|
||||
let logs = Arc::clone(&self.game_logs);
|
||||
// Only autopatch advertises the verified resolver guard, so only it
|
||||
// receives the shared capability sink; LSX passes None.
|
||||
let capability = match which {
|
||||
crate::local_services::Service::Autopatch => {
|
||||
Some(crate::local_services::CapabilityWiring {
|
||||
server_host: self.config.openfut_server_host.clone(),
|
||||
account_sync_port: self.config.openfut_account_sync_port,
|
||||
sink: Arc::clone(&self.fifa17_caps),
|
||||
})
|
||||
}
|
||||
crate::local_services::Service::Lsx => None,
|
||||
};
|
||||
let slot = match which {
|
||||
crate::local_services::Service::Lsx => &mut self.lsx,
|
||||
crate::local_services::Service::Autopatch => &mut self.autopatch,
|
||||
@@ -546,9 +573,10 @@ impl LauncherApp {
|
||||
which,
|
||||
&py,
|
||||
&dir,
|
||||
self.config.fut_persona_id,
|
||||
&self.config.fut_persona_name,
|
||||
Arc::clone(&self.game_logs),
|
||||
persona_id,
|
||||
&persona_name,
|
||||
capability,
|
||||
logs,
|
||||
) {
|
||||
Ok(child) => {
|
||||
*slot = crate::local_services::ManagedService::from_child(child);
|
||||
|
||||
Reference in New Issue
Block a user