LSX gate: FIFA reaches "connecting to EA Servers" via bridge

Implement the LSX (EA App bootstrap, TCP :3216→:3217) server in the bridge
and clear the loading-screen crash gate. FIFA 23 now completes the full LSX
handshake + session against OpenFUT and advances to the online/Blaze phase
("<persona> is connecting to the EA Servers…") — verified live, not a cached
session (the displayed persona "fun" comes from our GetProfile).

Root cause of the invariant loading-screen crash: get_config() omitted the
PROGRESSIVE_INSTALLATION and PROGRESSIVE_INSTALLATION_EVENT services (Name="PI").
FIFA builds its service registry from GetConfig and resolves that handle during
online-init; the missing service left a null handle it dereferenced at
FIFA23.exe+0x133dfc5 (mov rax,[r13+0x18], r13=NULL) ~2s after LSX — identical
across every prior run. Found via FIFA's own CrashReport_*.json (stable RIP) +
diffing anadius64.dll's service-registration table against ours.

Also in this change:
- process_frame(): handle FIFA's pipelined (batched) LSX messages per read,
  eliminating the ~15s handshake hang.
- Real anadius values (RE'd from anadius64.dll + anadius.cfg): PersonaId/UserId/
  persona, locale list, GetGameInfo fixed `GameInfo="<value>"` attribute,
  GetAllGameInfo 14-attr schema, GetSetting `Setting=` attribute.
- IsProgressiveInstallationAvailable served from sender="PI".
- docs/connection-gate-findings.md: full RE trail; relocate stale openfut-hook
  copy under docs/ (canonical hook lives in openfut-launcher).

Next gate: Blaze/online (M2), ridden over ProtoSSL in-process.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-07-02 15:37:33 -07:00
parent c58e7326a1
commit 0138a39f3e
14 changed files with 1243 additions and 21 deletions
+47 -10
View File
@@ -1,19 +1,56 @@
use std::sync::Arc;
use std::{path::Path, sync::Arc};
use tokio_rustls::TlsAcceptor;
/// Generate a self-signed certificate covering localhost and EA FUT hostnames.
/// Returns (cert_pem, key_pem) as byte vectors.
/// Load a previously persisted cert/key pair, or generate and save a new one.
///
/// For FIFA 23 to connect, the cert must be installed as a trusted CA in the OS
/// trust store, OR certificate validation must be disabled in the game binary.
/// Reusing the same cert across restarts means clients only need to trust it once.
pub fn load_or_generate_cert(cert_path: &Path, key_path: &Path) -> anyhow::Result<(Vec<u8>, Vec<u8>)> {
if cert_path.exists() && key_path.exists() {
let cert_pem = std::fs::read(cert_path)?;
let key_pem = std::fs::read(key_path)?;
tracing::info!("Loaded existing TLS cert from {:?}", cert_path);
return Ok((cert_pem, key_pem));
}
let (cert_pem, key_pem) = generate_self_signed_cert()?;
if let Some(parent) = cert_path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(cert_path, &cert_pem)?;
std::fs::write(key_path, &key_pem)?;
tracing::info!("Generated new TLS cert, saved to {:?}", cert_path);
Ok((cert_pem, key_pem))
}
/// Generate a self-signed certificate covering EA FUT hostnames.
/// CN is set to `fut.ea.com` so ProtoSSL's CN hostname check passes.
/// SANs include wildcard entries for all `*.ea.com` and `*.easports.com` subdomains
/// so connections to any EA subdomain are covered.
pub fn generate_self_signed_cert() -> anyhow::Result<(Vec<u8>, Vec<u8>)> {
let subject_alt_names = vec![
"localhost".to_string(),
"127.0.0.1".to_string(),
use rcgen::{Certificate, CertificateParams, DistinguishedName, DnType, SanType};
use std::net::{IpAddr, Ipv4Addr};
let mut params = CertificateParams::new(vec![
"fut.ea.com".to_string(),
"utas.mob.v4.fut.ea.com".to_string(),
];
let cert = rcgen::generate_simple_self_signed(subject_alt_names)?;
"accounts.ea.com".to_string(),
"pin.data.ea.com".to_string(),
"gateway.ea.com".to_string(),
"localhost".to_string(),
]);
// Wildcard SANs cover every EA subdomain the game might contact
params.subject_alt_names.push(SanType::DnsName("*.ea.com".to_string()));
params.subject_alt_names.push(SanType::DnsName("*.easports.com".to_string()));
params.subject_alt_names.push(SanType::DnsName("*.ugc.footapi.com".to_string()));
params.subject_alt_names.push(SanType::IpAddress(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))));
// CN must match the primary hostname so ProtoSSL's CN check passes
let mut dn = DistinguishedName::new();
dn.push(DnType::CommonName, "fut.ea.com");
params.distinguished_name = dn;
let cert = Certificate::from_params(params)?;
let cert_pem = cert.serialize_pem()?.into_bytes();
let key_pem = cert.serialize_private_key_pem().into_bytes();
Ok((cert_pem, key_pem))