feat(fifa17): opt-in commerce settings, the server half of the transfer-list fix

"Place on Transfer List" is greyed for two reasons. This crate already fixes one
(owned copies emit `untradeable: false`). The other is `tradingEnabled`: the
client's struct defaults it to 0 — it is not a flag we have been overwriting, it
is a flag nobody has ever sent — and it gates the service half of the
TO_TRADE_PILE predicate (vtable slot +0x270, gate byte 0x1fd2e, measured 0 live).
`GET /settings` has always answered `{"configs": []}`.

The schema is high-confidence: FutGetSettingsServerResponse (deser 0x18013c6d0,
read end to end) has a single `configs` key holding `{type, value}` rows, and the
key ladder holds nothing else. `type` is the setting NAME. The row set is ported
from the shape the Python oracle would emit rather than invented.

Default OFF (`OPENFUT_FIFA17_COMMERCE_SETTINGS=1` opts in), because the flags are
RECOVERED BUT UNTESTED and the empty list is the live-proven body — the house
rule is that a flag defaults to the live-proven value. This also moves the
capability out of the oracle we are retiring and into Rust, where it can actually
be reached once Python is gone.

Verified against a real host on both settings: OFF returns {"configs":[]}
byte-identical to today, ON returns the 8-row body with tradingEnabled. It
explains why the menu entry is greyed; it does not promise the market works.
This commit is contained in:
funman300
2026-08-21 21:47:13 +00:00
parent 52df78d24a
commit d74aee33f7
2 changed files with 94 additions and 6 deletions
+17 -2
View File
@@ -3926,8 +3926,9 @@ impl Server {
json_status(200, &non_economy::accountinfo_body())
}
Route::Settings => {
eprintln!("utas-host owner=RUST route=settings status=200");
json_status(200, &non_economy::settings_body())
let commerce = commerce_settings_enabled();
eprintln!("utas-host owner=RUST route=settings status=200 commerce={commerce}");
json_status(200, &non_economy::settings_body(commerce))
}
Route::LeaderboardOptions => {
eprintln!("utas-host owner=RUST route=leaderboards-options status=200");
@@ -4771,6 +4772,20 @@ fn fifa17_sidlog(sid: &str) -> String {
}
}
/// Whether `GET …/settings` should turn the client's commerce flags on.
///
/// OFF unless `OPENFUT_FIFA17_COMMERCE_SETTINGS=1`, because the flags are
/// recovered but UNTESTED and the empty config list is the live-proven body. The
/// house rule is that a flag defaults to the live-proven value.
///
/// Turning it on is the SERVER half of the transfer-list fix — the client's
/// `tradingEnabled` gate defaults to 0 and nothing has ever sent it. It needs a
/// launch to confirm, and it does not promise the market behind the menu works.
fn commerce_settings_enabled() -> bool {
static ENABLED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ENABLED.get_or_init(|| std::env::var("OPENFUT_FIFA17_COMMERCE_SETTINGS").as_deref() == Ok("1"))
}
/// A JSON response with an explicit status.
fn json_status(status: u16, v: &Value) -> WireResponse {
let body = serde_json::to_vec(v).unwrap_or_default();