From 3bb48147609c802495eda712c4d4a275b9592b5e Mon Sep 17 00:00:00 2001 From: funman300 Date: Wed, 19 Aug 2026 20:12:06 +0000 Subject: [PATCH] fix(fifa17): open Seasons/Draft via /settings config gate RE-confirmed root cause: the FUT client's settings applier FUN_18011dc50 is the sole writer of the IS_* UI gate bytes (byte = field==1). The Rust host served GET /ut/game/fifa17/settings as {"configs":[]}, so the applier never ran and Single-Player Seasons/Draft refused to open while issuing ZERO server requests (friendlySeasonsEnabled -> settings slot 0x16 -> model byte 0x1fd3a = IS_FRIENDLY_SEASON_ENABLED). settings_body() is now env-gated: default (OPENFUT_FUT_SETTINGS unset/off) keeps the empty baseline; OPENFUT_FUT_SETTINGS=gates returns the enable list. The applier writes ALL gate bytes, so the working store flags (storeEnabled, coinEnabled, cardPackStoreEnabled, pointsPackStoreEnabled, tradingEnabled, + _JP variants) are re-asserted alongside the Seasons/Draft enables to avoid clearing the live Store. Instantly revertible by restarting without the env. --- openfut-adapter-fifa17/src/fut/non_economy.rs | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/openfut-adapter-fifa17/src/fut/non_economy.rs b/openfut-adapter-fifa17/src/fut/non_economy.rs index 5c75324..b484500 100644 --- a/openfut-adapter-fifa17/src/fut/non_economy.rs +++ b/openfut-adapter-fifa17/src/fut/non_economy.rs @@ -24,9 +24,48 @@ pub fn accountinfo_body() -> Value { json!({}) } -/// `GET …/settings` — production oracle returns an empty config list. +/// `GET …/settings` — the FUT client applies this `configs` array via the applier +/// `FUN_18011dc50`, the ONLY writer of the `IS_*` UI gate bytes (each +/// `byte = (field == 1)`). A flag never sent is a gate never opened — which is why +/// Single-Player Seasons/Draft refuse to open while issuing ZERO server requests +/// (`friendlySeasonsEnabled` → slot `[0x16]` → model byte `0x1fd3a` +/// `IS_FRIENDLY_SEASON_ENABLED`; RE-confirmed via pyghidra on CardsDLL). +/// +/// Default (`OPENFUT_FUT_SETTINGS` unset/`off`) is the historical empty baseline. +/// `OPENFUT_FUT_SETTINGS=gates` populates the array. SAFETY: populating it is what +/// makes the applier RUN, and it then writes EVERY gate byte, so the already-live +/// store flags MUST be re-asserted here or enabling Seasons would clear the working +/// Store (those reach the client today via the Blaze FUT_RS4_CONFIG store, not +/// here). Mirrors the audited list in `utas_server.py` `_SETTINGS_KEEP`/`_GATES`. pub fn settings_body() -> Value { - json!({ "configs": [] }) + if std::env::var("OPENFUT_FUT_SETTINGS").as_deref() != Ok("gates") { + return json!({ "configs": [] }); + } + // Already-live store flags re-asserted (pinned to their working state). + const KEEP: &[&str] = &[ + "storeEnabled", + "storeEnabled_JP", + "coinEnabled", + "coinEnabled_JP", + "cardPackStoreEnabled", + "cardPackStoreEnabled_JP", + "pointsPackStoreEnabled", + "tradingEnabled", + ]; + // Mode gates that nothing has ever populated (the point of the change). + const GATES: &[&str] = &[ + "friendlySeasonsEnabled", + "enableDraftMode", + "enableSinglePlayerDraftMode", + "enableOfflineDraftMode", + "tournamentQuitEnabled", + ]; + let configs: Vec = KEEP + .iter() + .chain(GATES.iter()) + .map(|k| json!({ "type": k, "value": 1 })) + .collect(); + json!({ "configs": configs }) } /// `GET …/leaderboards/options` — production oracle (FUT_MODES off) returns an