fix(seasons): supply the FUT web-file base so Seasons stops failing

Entering single-player Seasons showed "There was a problem communicating with
the FIFA Ultimate Team Servers". The deployed trace caught the whole chain:

  SEASON_CALL: LoadOfflineSeasons_asyncimpl(0x57560)
  SEASONS_WEBFILE_URL: url="packs/loc/storepackdescriptions.en_us.xml"
  SEASONS_STAGE1: status(+0x1c)=999 -> CACHE_PACKNAMES_FAILED
  SEASONS_LOAD_CALLBACK: final kind=ERROR result="CACHE_PACKNAMES_FAILED"

Not a server fault: no /season/* request is ever made. The client's
RS4::ServerSettings CDN base is EMPTY in the emulator, so the pack-names web
file is requested as a BARE relative path and 999s, and Seasons aborts on that
prerequisite.

Ports the base-supply rewriter from wip/seasons/base-supply-veh onto the
deployed lineage (that branch forked before the TLS work and cannot be rebased),
taking only the URL supply: absolute urls pass through untouched, and the
success-forcing CACHE_PACKNAMES bypass is deliberately NOT taken — masking the
failure would hide whether the supply actually worked.

Corrects the port while porting: the branch hardcoded 8110, where nothing
listens. The content server is POW (`pow_server.py`, kind "content") on 8085 —
the port Blaze already advertises to the client as its content host. Verified
live: GET http://10.10.0.120:8085/fut/packs/loc/storepackdescriptions.en_us.xml
returns 200 with a 180-byte XLIFF document. `default_ports::FUT_CONTENT` is now
8085 and the base is still derived from openfut.cfg, so no address is compiled
in (confirmed absent from the artifact).

Artifact keeps the roster TLS gate patch (11 fifa17_tls markers) and the kit
trace alongside the new base supply.
This commit is contained in:
funman300
2026-08-21 16:12:41 +00:00
parent 7edf682291
commit 5294f589ad
4 changed files with 155 additions and 10 deletions
+61 -2
View File
@@ -53,6 +53,17 @@ pub mod default_ports {
pub const BLAZE_REDIRECTOR: u16 = 42127;
/// OpenFUT FIFA 17 Blaze main listener.
pub const BLAZE_MAIN: u16 = 42130;
/// OpenFUT FUT web-file (CDN) content server. Unlike the others this is not
/// an EA redirect target: the client never dials it directly, because its
/// `RS4::ServerSettings` CDN base arrives EMPTY in the emulator. The hook
/// supplies the missing `<base>/fut/` prefix, and the base is built from the
/// configured server host plus this port.
///
/// 8085 is the POW content server (`pow_server.py`, kind "content"), which is
/// the port Blaze already advertises to the client as its content host and
/// which serves `/fut/packs/loc/storepackdescriptions.en_us.xml`. Verified
/// live: that path returns 200 with a 180-byte XLIFF document.
pub const FUT_CONTENT: u16 = 8085;
}
/// OpenFUT destination ports. Each field is where an intercepted EA source port
@@ -66,6 +77,9 @@ pub struct OpenFutPorts {
pub blaze_redirector: u16,
/// Destination for EA :42127 traffic (Blaze main).
pub blaze_main: u16,
/// FUT web-file content server. Not a redirect destination — see
/// [`default_ports::FUT_CONTENT`].
pub fut_content: u16,
}
impl Default for OpenFutPorts {
@@ -74,6 +88,7 @@ impl Default for OpenFutPorts {
https: default_ports::HTTPS,
blaze_redirector: default_ports::BLAZE_REDIRECTOR,
blaze_main: default_ports::BLAZE_MAIN,
fut_content: default_ports::FUT_CONTENT,
}
}
}
@@ -244,6 +259,7 @@ impl ServerConfig {
"https_port" => ports.https = parse_port(value)?,
"blaze_redirector_port" => ports.blaze_redirector = parse_port(value)?,
"blaze_main_port" => ports.blaze_main = parse_port(value)?,
"fut_content_port" => ports.fut_content = parse_port(value)?,
other => {
return Err(ConfigError::MalformedConfig(format!(
"line {}: unknown key '{other}'",
@@ -260,8 +276,27 @@ impl ServerConfig {
/// Serialize to the structured `openfut.cfg` format.
pub fn to_cfg_string(&self) -> String {
format!(
"host={}\nhttps_port={}\nblaze_redirector_port={}\nblaze_main_port={}\n",
self.host, self.ports.https, self.ports.blaze_redirector, self.ports.blaze_main
"host={}\nhttps_port={}\nblaze_redirector_port={}\nblaze_main_port={}\nfut_content_port={}\n",
self.host,
self.ports.https,
self.ports.blaze_redirector,
self.ports.blaze_main,
self.ports.fut_content
)
}
/// Base URL the FUT web-file (CDN) prefix is built from, e.g.
/// `http://10.10.0.120:8085/fut/`.
///
/// The client's `RS4::ServerSettings` CDN base arrives EMPTY in the emulator,
/// so FUT web-file urls reach the download entry point as bare relative paths
/// and fail. The hook supplies this prefix. Built from the SAME configured
/// host as every other redirect, so a lab address is never compiled in.
pub fn fut_content_base(&self) -> String {
format!(
"http://{}:{}/fut/",
self.host.trim(),
self.ports.fut_content
)
}
@@ -449,12 +484,36 @@ mod tests {
https: 8443,
blaze_redirector: 10041,
blaze_main: 42127,
fut_content: 8085,
},
};
let s = c.to_cfg_string();
assert_eq!(ServerConfig::parse(&s).unwrap(), c);
}
/// The FUT web-file prefix follows the CONFIGURED server, so no lab address
/// is ever compiled into the hook.
#[test]
fn fut_content_base_follows_the_configured_host() {
let c = ServerConfig::parse("host=192.168.1.50\n").unwrap();
assert_eq!(c.fut_content_base(), "http://192.168.1.50:8085/fut/");
let c = ServerConfig::parse("host=fut.mylan.home\nfut_content_port=9110\n").unwrap();
assert_eq!(c.fut_content_base(), "http://fut.mylan.home:9110/fut/");
}
/// A cfg written before `fut_content_port` existed must still parse, taking
/// the default rather than failing the whole config (which would disarm the
/// network redirect too).
#[test]
fn cfg_without_content_port_takes_the_default() {
let c = ServerConfig::parse(
"host=10.0.0.5\nhttps_port=8443\nblaze_redirector_port=42127\nblaze_main_port=42130\n",
)
.unwrap();
assert_eq!(c.ports.fut_content, default_ports::FUT_CONTENT);
}
#[test]
fn configured_ipv4_becomes_correct_sockaddr() {
// Resolve an IPv4 literal and confirm the sin_addr value.