From a7e3e43ae9b22578275ba12f4286a142ef7e7055 Mon Sep 17 00:00:00 2001 From: funman300 Date: Thu, 6 Aug 2026 18:59:23 -0700 Subject: [PATCH] fifa17-recon: the refusing modes have no server fix, and the hub-atom lead is cosmetic too Completed the refusing-modes workflow (ground truth + 4 per-mode investigations + adversarial verify each + synthesis). All four mode families -- Seasons, Draft, SBC/Objectives, Tournaments -- are NOT_SERVER_REACHABLE, HIGH confidence, all four adversarial refutations failed. Live re-confirmed on pid 24653 (slide proven via FNV control): every named mode-gating byte reads ENABLED=1 (IS_FRIENDLY_SEASON_ENABLED +0x1fd3a, IS_TOURNAMENT_QUIT_ENABLED +0x1fd3b, IS_DRAFT_MODE_ENABLED +0x1fd3d, plus the unnamed offline-draft-enable +0x1fd3e) yet the tiles stay greyed. The new lead this pass added -- do the six /hub mode sub-objects gate availability? -- is refuted: friendlySeason/offlineSeason/onlineSeason/draftSummary/tournament/ tournamentProgress carry only stats and display strings, no enabled/available/ unlocked atom. They are cosmetic, exactly like hub.tradePile. The one server-writable input that exists (friendlySeasonsEnabled -> +0x1fd3a via applier FUN_18011dc50) has its sole reader in the packed FIFA17.exe front-end via a vtable getter with no CardsDLL caller, and it is already 1. The refusal is decided in the Denuvo-packed Frostbite front-end, which has no server surface. docs/plan-2026-08-06-refusing-modes.md: full evidence chains, gate-byte table, the six sub-deser field maps, per-mode verdicts. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Lrx9to3pihN6Sm9sXgc8np --- .../docs/plan-2026-08-06-refusing-modes.md | 369 ++++++++++++++++++ 1 file changed, 369 insertions(+) create mode 100644 fifa17-recon/docs/plan-2026-08-06-refusing-modes.md diff --git a/fifa17-recon/docs/plan-2026-08-06-refusing-modes.md b/fifa17-recon/docs/plan-2026-08-06-refusing-modes.md new file mode 100644 index 0000000..abfdf9a --- /dev/null +++ b/fifa17-recon/docs/plan-2026-08-06-refusing-modes.md @@ -0,0 +1,369 @@ +# The refusing modes: Seasons, Draft, SBC/Objectives, Tournaments — where the greying is decided + +Written 2026-08-06. One reconnaissance pass over the live gate-byte block and the +six `/hub` mode sub-deserializers, then four parallel per-mode investigations +(Seasons, Draft, SBC+Objectives, Tournaments), each followed by an independent +adversarial verification round. FIFA 17 was running throughout as **pid 24653**, +sitting at the FUT hub, and was read strictly read-only. No server was restarted, +no server code was changed, no memory was poked, and FIFA was never launched or +killed. + +Slide for every live read: `live = static - 0x180000000 + 0x6ffffc140000`, i.e. +slide `0x6ffe7c140000`, re-derived from `/proc/24653/maps` and proved by +`tools/gate_byte_probe.py` reporting **CONTROL FNV MATCH** against the FNV hasher +prologue at `0x180180d00`. CardsDLL is mapped from `/mnt/games/FIFA 17/ +CardsDLL_Win64_retail.dll`; the on-disk copy read with `objdump` is +`/tmp/fut/cardsdll.dll`, image base `0x180000000`. Every address below is +live-verified. + +This document answers one question the brief posed: is the refusal of these four +mode families decided by a **server-reachable input we are failing to send** (a hub +mode sub-object, a massinfo member, a settings/config field, or a dedicated +endpoint), **or** is it decided in the **Denuvo-packed FIFA17.exe / Frostbite +front-end** with no server surface at all? + +--- + +## 1. Headline — final verdicts (after adversarial verify) + +Every mode was independently re-derived by a second agent that attempted to refute +the first. **All four refutations failed. All four verdicts stand.** + +| Mode | Atoms | Final verdict | Confidence | Verify | +|---|---|---|---|---| +| **FUT Seasons** (offline + online + friendly) | `friendlySeason 0x131`, `offlineSeason 0x1ec`, `onlineSeason 0x1f6` | **NOT_SERVER_REACHABLE** | HIGH | agrees (SAME) | +| **FUT Draft** (offline + online) | `draftSummary 0xe4` | **NOT_SERVER_REACHABLE** | HIGH | agrees (SAME), strengthened | +| **SBC + Objectives** | `objectivesForCurrentUser 0x1e3`, `allObjectivesForCurrentGameSpaceId 0x15` | **NOT_SERVER_REACHABLE** | HIGH | agrees (SAME), prior chain corrected | +| **FUT Tournaments** | `tournament 0x328`, `tournamentProgress 0x32c` | **NOT_SERVER_REACHABLE** | HIGH | agrees (SAME) | + +**There is no server fix for any of the four.** Every server-reachable input that +touches these modes is either cosmetic (a hub stat list feeding a caption/count), +an *output* value the client emits and never branches on, or a settings byte that +is **already live=1** while the tile stays greyed. The decision lives in the packed +front-end. This is the same shape as the transfer-market finding of the same day — +except there the switch (`userInfo.feature.trade`) was ours to flip; here **no such +switch exists on the wire.** + +--- + +## 2. Ground truth + +### The named gate-byte block (`FutDataManagerImpl`, live pid 24653) + +Names were resolved by finding the config serializer at `0x18006ccd0`, which pairs +each `IS_*_ENABLED` string key (`.rdata 0x1801fc118..`) with a getter vtable slot, +then decoding each slot's accessor stub (`0f b6 81 c3`) to its model +displacement. All values read live, slide-proven. + +| Name | Displacement / slot | Live value | +|---|---|---| +| (unnamed) | `+0x1fd24` | 0 | +| (unnamed) | `+0x1fd2c` | 1 | +| (unnamed) | `+0x1fd2d` | 1 | +| **IS_TRADING_ENABLED** | `+0x1fd2e` (slot+0x270) | 1 | +| (unnamed) | `+0x1fd30` | 1 | +| (unnamed) | `+0x1fd37` | 1 | +| **IS_FRIENDLY_SEASON_ENABLED** | `+0x1fd3a` (slot+0x2b0) | 1 | +| **IS_TOURNAMENT_QUIT_ENABLED** | `+0x1fd3b` (slot+0x2b8) | 1 | +| **IS_PROCESSING_STATE_ENABLED** | `+0x1fd3c` (slot+0x2c0) | 1 | +| **IS_DRAFT_MODE_ENABLED** | `+0x1fd3d` (slot+0x2c8) | 1 | +| (unnamed) | `+0x1fd3e` (offline-draft-enable) | 1 | +| **IS_STORY_MODE_REWARD_ENABLED** | `+0x1fd3f` (slot+0x2d8) | 1 | +| **IS_RETURNING_USER_REWARDS_SCREEN_ENABLED** | `+0x1fd40` (slot+0x2f0) | 0 | +| (unnamed) | `+0x1fd41` | 0 | +| (unnamed) | `+0x1fd42` (allowGracePeriod, SBC) | 0 | +| (unnamed) | `+0x1fd43` | 0 | +| **objectives-enable** (corrected — see §5.3) | `+0x1fd44` | 1 | +| **packOpeningAnimation** | `+0x1fd45` | 1 | +| (unnamed) | `+0x1fd46` | 1 | +| (unnamed) | `+0x1fd47` | 0 | +| (unnamed) | `+0x1fd48` | 1 | +| **IS_STORE_ENABLED** | computed getter slot+0x280 @`0x18011c600` (not a byte field) | (computed) | + +Every named gate byte that governs a **refusing** mode reads **ENABLED=1** live. +The only `0`-valued `*_ENABLED` byte, `IS_RETURNING_USER_REWARDS_SCREEN_ENABLED`, +does not gate any of the four mode families. This re-confirms the brief's prior +ground truth: the gate-byte layer does **not** explain the refusals. + +### The six `/hub` mode sub-deserializers (`/hub` parser = `FUN_180139610`) + +The hub parser reads 18 atoms via a running-sum sub/dec ladder; six dispatch to the +refusing modes. Each nested sub-deser was read in full. **None carries an +enable/available/unlocked boolean.** + +| Atom | Name | Sub-deser VA | Fields (all cosmetic/data) | +|---|---|---|---| +| `0x131` | friendlySeason | `0x1801392a0` | creationTime, dataVersion, opponentPersonaId, opponentUserPoints, round, seasonId, userPoints, defId (8 ints) | +| `0x1ec` | offlineSeason | `0x18013c3a0` | divisionId, gamesPlayed, points, progressDataVersion, totalGames (strings) | +| `0x1f6` | onlineSeason | `0x18013c3a0` (shared) | divisionId, gamesPlayed, points, progressDataVersion, totalGames (strings) | +| `0xe4` | draftSummary | `0x180138d60` | draftState (str-enum), gamesWon (int) | +| `0x328` | tournament | `0x18013dc00` | id, assetName, imageFormat, silhouetteName, timeUntilEnd, tournamentType, AMATEUR, live_offline, offerState (display) | +| `0x32c` | tournamentProgress | `0x18013df20` | data, tutorialClientData (free-form std::map) | + +The recurring trap: several of these desers write a per-field byte +(`offline/onlineSeason` `[r14+0xa]=1`; `tournament` `[rdi+0x162]=1`) that an early +naive pass could mistake for a JSON enable flag. Every such write is a +**parser-local "field present" marker**, written identically for every field — +**not** a JSON-sourced availability input. This is the same class of mistake that +made `hub.tradePile` look like a gate before it was shown to be a mere count. + +--- + +## 3. FUT Seasons — NOT_SERVER_REACHABLE (HIGH) + +**Atoms:** `friendlySeason 0x131`, `offlineSeason 0x1ec`, `onlineSeason 0x1f6`. +**Gate byte:** `IS_FRIENDLY_SEASON_ENABLED +0x1fd3a`, live=1. + +**Evidence chain.** The decisive site is the gate byte `+0x1fd3a`. A whole-`.text` +grep finds **exactly two** references: + +- **Writer** `0x18011dd2d`: `mov byte[rdi+0x1fd3a],al` inside settings applier + `FUN_18011dc50`, preceded by `cmp dword[rbx+0x58],1 / sete al` — the byte is + `(settings.field+0x58 == 1)`, sourced from config key `friendlySeasonsEnabled`. + This is the **only** writer. +- **Reader** `0x18011c500`: `movzx eax,byte[rcx+0x1fd3a]; ret` — a standalone + vtable getter stub (slot+0x2b0). Its absolute address appears in the file exactly + once, at the vtable, and grep finds **no** call/jmp to `0x18011c500` anywhere in + CardsDLL `.text`. Its only consumer is the packed FIFA17.exe front-end via vtable + dispatch. + +The one server-writable input (`friendlySeasonsEnabled → +0x1fd3a`) is **already 1 +live**, and the tile is still greyed — so the front-end does not gate on this byte +alone; it reads additional non-server state. + +- **Hub sub-objects** carry no enable flag. `offline/onlineSeason` share deser + `0x18013c3a0`, which FNV-hashes string keys and for each stores a division/games/ + points/version stat; `friendlySeason 0x1801392a0` is 8 numeric stats. The + `[r14+0xa]=1` write is the "field present" marker. These feed a caption/count. +- **Settings/massinfo:** `friendlySeasonsEnabled` is the sole season key the applier + consumes → `+0x1fd3a`, already covered. No massinfo member carries a season enable. + There is **no** `onlineSeasonEnabled`/`offlineSeasonEnabled` config key or gate + byte anywhere in the DLL — verify enumerated all 24 gate-region getter stubs and + the only season getter is `+0x1fd3a`. +- **Dedicated endpoint:** `/season` and `/season/user` routes exist in + `utas_server.py` (guarded by `FUT_MODES`) but the client has **never** requested + them — 0 season hits across `captures/`, 486 real ProtoHttp requests over ~30 + boots, none for `/season`. And the tile greys at hub load, *before* any `/season` + request could fire. +- **Front-end:** the only season-enable identifiers in the whole DLL are the config + *input* `friendlySeasonsEnabled` and the *output* getter name + `IS_FRIENDLY_SEASON_ENABLED`. The viewmodel names + (`futonlineseasonsviewmodel`, `futofflineseasonsviewmodel`, + `futfriendlyseasons*viewmodel`) live in the Denuvo-packed FIFA17.exe. + +**Authority boundary.** `friendlySeasonsEnabled` is a **server-writable input**, +but it is already at ENABLED with its only reader **off-DLL (client)**. Offline/ +online seasons have **no server surface at all** — no config key, no gate byte, no +getter. The grey/refuse decision is **client-side**. + +--- + +## 4. FUT Draft — NOT_SERVER_REACHABLE (HIGH, strengthened by verify) + +**Atoms:** `draftSummary 0xe4`. **Gate byte:** `IS_DRAFT_MODE_ENABLED +0x1fd3d`, +live=1. + +**Evidence chain.** Cross-ref of displacement `0x1fd3d` returns exactly two real +sites (a `lea` to `0x1801fd3d8` and an instruction at address `0x18011fd3d` are +coincidental, not xrefs): + +- **Accessor stub** `0x18011c4b0`: `movzx eax,[rcx+0x1fd3d]; ret` (getter vtable + `.rdata 0x18021c568`). +- **Writer** `0x18011dd5a`: `mov [rdi+0x1fd3d],al` in applier `FUN_18011dc50`, + `al = (settings[rbx+0x5c]==1)` = parsed `enableDraftMode`. + +There is **no cmp/test/branch** on this byte anywhere. Its only CardsDLL consumer +is the config serializer `0x18006ccd0`, which walks the `IS_*_ENABLED` key table and +`call [rax+0x2c8]` to **emit** the value outward. So `IS_DRAFT_MODE_ENABLED` is an +**output the client serializes, not an input any logic branches on.** + +**The verifier strengthened this** by finding a consumer the first pass missed: a +flux "DESTINATION" navigation emitter around `0x1800b2700`. At `0x1800b2711` it +loads getter slot `+0x2c8` (draft-enable, `+0x1fd3d`) into `sil` and slot `+0x2d0` +(offline-draft-enable, `+0x1fd3e`) into `[rsp+0x21]`. All six `GOTO_DRAFT_DISABLED` +emit sites (`0x1800b2cb2`, `0x1800b2dc9`, `0x1800b333b/347`, `0x1800b349f/4a7`) are +guarded by `test sil,sil` / `cmp [rsp+0x21],0` and route to `GOTO_DRAFT_DISABLED` +**only when those bytes are 0**, else to `GOTO_DRAFT_OFFLINE/ONLINE`. Both bytes are +**live=1**, so this emitter — the closest thing to a nav decision inside CardsDLL — +already produces the ENABLED destinations, yet the tile is still greyed. + +- **Hub sub-object** `draftSummary 0xe4`, member deser `0x180138d60`: exactly two + atoms — `draftState 0xe3` (STRING → enum decoder `0x180138cc0`, a resume-state + enum: INVALID + 2..8) and `gamesWon 0x13a` (INT). Wrapper `0x18013980c` loops + `ONLINE 0x1f1` / `SINGLE_PLAYER 0x29d`, each → `0x180138d60`. No enable atom; + `draftState` is the continue-state read after entry, not a tile gate. +- **Settings/massinfo:** atoms `enableDraftMode 0xf9` / `enableOfflineDraftMode + 0xfa` / `enableSinglePlayerDraftMode 0xff` land on sibling emit-only bytes + `+0x1fd3d`/`+0x1fd3e`/`+0x1fd3c` via the same applier — none branched on. +- **Dedicated endpoints:** `GET /squad/mode/draft/state` (deser `0x180147070`) and + `POST /purchase/mode/N/draft` (deser `0x18014c260`) are already routed in utas — + but these are the **post-click** entry/session flow (render the draft screen, buy + entry *after* the tile is pressed), not a tile-availability query. +- **Front-end:** token strings (`USER_HAVE_DRAFT_TOKENS 0x1802055f8`, + `GOTO_DRAFT_DISABLED 0x180209aa8`, etc.) are bare key-name `lea` emitters with no + greying branch. Decision is in the packed FIFA17.exe. + +**Authority boundary.** The two server-writable inputs (`enableDraftMode`, +`enableOfflineDraftMode`) are **already at their enabled value**, and **every** +CardsDLL consumer of them (config serializer *and* the navigation emitter) already +treats draft as enabled. The persistent greying is decided **client-side** on +non-server state. + +--- + +## 5. SBC + Objectives — NOT_SERVER_REACHABLE (HIGH, prior chain corrected) + +**Atoms:** `objectivesForCurrentUser 0x1e3`, `allObjectivesForCurrentGameSpaceId +0x15`. **No `IS_OBJECTIVES`/`IS_SBC` gate-byte name exists** — the task premise that +these are governed by no named `FutDataManagerImpl` gate byte is confirmed. + +### 5.1 Hub sub-object = cosmetic list + +In `FUN_180139610` both objectives atoms share one arm: `objectivesForCurrentUser +0x1e3` (`0x180139794`) and `allObjectivesForCurrentGameSpaceId 0x15` +(`0x1801397ad`) both jump to `0x1801398fe`, guarded by the parser-local marker +`cmp BYTE [rsp+0x21],0x1`, calling sub-deser `0x18013a7f0`. That deser parses a +nested `objectives 0x1e2` **array** of records (element parser `0x18006c9b0`) with +**no** enabled/available/unlocked atom — it feeds the "MANAGER TASKS N/M" tile +count/caption, the same cosmetic class as `hub.tradePile`. + +### 5.2 No dedicated endpoint at the hub + +The live log across 26+ hub sessions shows the client requests only `/hub` and +`/settings`; it **never** calls `/sbs/*` (grep count 0) or any `/objectives` +endpoint. `utas_server.py` has no `/sbs` route. No `FutGetObjectivesServerResponse` +class exists — objectives are **ManagerQuests**, client-driven. The `sbs/*` structs +that exist serve challenge **content after entry**, never polled at the hub. + +### 5.3 The correction (verify fixed the first pass's chain) + +The first pass mis-traced objectives to settings field `[0x1c]` → model `+0x1fd28` +(default 60). **The verifier re-derived the settings jump table (dispatch +`0x18013ca1e`, byte-idx `0x18013ced4`, jtbl `0x18013ce90`) and found the truth:** + +- `enableObjectives 0xfd` **and** `enableObjectivesAsManagerTasks 0xfe` route to + handler `0x18013cabd` = clear-only-on-zero into settings field `[0x70]`; applier + `0x18011ddc7` (`cmp [rbx+0x70],1; sete al; mov [rdi+0x1fd44],al`) maps it to model + gate byte **`+0x1fd44`** — which is **inside** the named gate block (not outside, + as the first pass claimed), reads **1 (ENABLED) live**, and has exactly one reader + DLL-wide: a getter stub `0x18011c570` returning the byte to the front-end with no + internal gating use. +- The first pass's `+0x1fd28` (default 60) is actually + `squadBuildingSetsGracePeriodMinutes 0x2d0`, a numeric grace-period param — + behavioral, not availability. +- **SBC side:** `enableSquadBuildingSetsFeature 0x100` falls in the dispatch **gap** + (`0x100-0x18=0xe8 > 0xe7 → DEFAULT/no handler`), as do `squadBuildingSetsClientData + 0x2cf` and `squadChallenge 0x2d1`. Only numeric SBC params have handlers + (`allowGracePeriod 0x18 → +0x1fd42`, `allowUntradeable 0x19 → +0x206f8`, + `gracePeriodMinutes 0x2d0 → [0x1c]/+0x1fd28`). **No SBC availability model byte + exists.** + +So the single server-controllable objectives-enable input (`+0x1fd44`) is already at +1 yet the tile refuses, and SBC has **no** server enable surface whatsoever. + +**Authority boundary.** Objectives-enable is a **server-writable byte already ON**, +read only by the **client**. SBC availability has **no server surface** — its enable +key is in the settings dispatch gap and lands on no byte. Decision is **client-side** +(`futmanagerquestsviewmodel`; providers `FUT_MQ_QUESTS_DATA_DP` / +`FUT_SQUAD_QUESTS_DP`) in the packed FIFA17.exe. + +--- + +## 6. FUT Tournaments — NOT_SERVER_REACHABLE (HIGH) + +**Atoms:** `tournament 0x328`, `tournamentProgress 0x32c`. **Gate byte:** +`IS_TOURNAMENT_QUIT_ENABLED +0x1fd3b`, live=1 — but this governs **quitting** a +tournament, not tile availability, and no `tournamentEnabled` atom exists in +`docs/fut_atoms.tsv`. + +**Evidence chain.** + +- **Hub sub-objects, both cosmetic.** `tournament 0x328` deser `0x18013dc00` writes + only display fields: id `[rdi+0x150]`, round `[rdi+0x160]`, timeUntilEnd + `[rdi+0x158]`, silhouette-int `[rdi+0x15c]`, string blobs `[rdi]`/`[rdi+0xa8]` + (assetName/silhouette/type), an `imageFormat=="dds"` render bool `[rdi+0x163]` + (strcmp vs `.rdata 0x180219400`), and a `tournamentType` enum `[rdi+0x154]` + decoded to `live_offline 0x195`/`live_online 0x196`/`offline 0x1e8`/`online 0x1f0` + — a categorization, not availability. The `[rdi+0x162]=1` write is a + record-completeness marker (all core fields present), not a JSON enable. + `tournamentProgress 0x32c` deser `0x18013df20` builds a std::map (ctor + `0x1801e5210`) of string keys `data 0xc9` / `tutorialClientData ~0x353` — free-form + clientData, no enable atom. (The earlier `0x28a = returningUserRewardsScreenEnabled` + label was a running-sum mis-decode; the true sum is `0xc9+0x28a=0x353 + tutorialClientData`.) +- **Massinfo/settings.** `tournamentCoins 809 → +0x30` and `teamOfTournamentWinner + 776 (bool) → +0x34` appear only in the **FutDestroyMatch** reward deser + `0x180121b60` — a match payout reached only *after* you are inside a tournament + match; a reward count/trophy flag, not a tile gate. The settings applier switch + `0x18013c6d0` has 42 arms; the only tournament arm is `tournamentQuitEnabled 0x32D + → +0x1fd3b` (quit, live=1). +- **Gate byte** `+0x1fd3b`: getter stub `0x18011c660` is the vtable **emit** + accessor the config serializer `0x18006ccd0` pairs with the JSON key to write it + out — the client emits it, does not read it as a server input. Writer + `0x18011dd3d`, `al = sete(cmp settings[rbx+off],1)`, defaults to 1. Already 1, + wrong feature. +- **Dedicated endpoint, never called.** `tournament_list` (deser `0x180169ef0`) and + `tournament_user` (deser `0x180147cb0`) exist in `utas_server.py` but grep over + `captures/` and the live `/tmp/utas_server.log` (3224 lines) finds **zero** + ProtoHttp requests for any `/tournament` path across all boots — same as `/season`. + The responses are never consumed. +- **Front-end.** No CardsDLL response deserializer writes any "tournament + available/unlocked" field. `eligibilities 0xf1` / `unlocks 0x35c` / `available 0x3e` + are SBC/store vocab per `docs/ENDPOINT_MAP.md`, not wired to tournaments. Decision + is in the packed FIFA17.exe. + +**Authority boundary.** The only server-touchable tournament byte +(`IS_TOURNAMENT_QUIT_ENABLED`) is an **emitted output** governing a different +feature, already 1. Everything else is cosmetic hub data or post-entry reward data. +Tile availability is decided **client-side**. + +--- + +## 7. What changed vs the prior conclusion + +The prior workflow examined **only the `FutDataManagerImpl` gate bytes** and +concluded "no server fix" for these modes. This workflow re-opened the question by +chasing the **hub-atom lead** — the six mode sub-deserializers we do not currently +populate — plus massinfo members, settings arms, and dedicated endpoints. + +**The hub-atom lead does not change the conclusion for any mode.** Per mode: + +- **Seasons:** the hub `friendlySeason`/`offline`/`onlineSeason` sub-objects are + numeric stat blobs (division/games/points), cosmetic like `hub.tradePile`. The + `[r14+0xa]=1` byte is a "field present" marker, not a JSON enable. No change — + still NOT_SERVER_REACHABLE. +- **Draft:** `draftSummary` carries only `draftState`+`gamesWon`; verify additionally + found the in-DLL navigation emitter already routes to the *enabled* destination on + current live state. No change — verdict **strengthened**. +- **SBC/Objectives:** the objectives hub arm is a cosmetic list feeding "MANAGER + TASKS N/M". Verify *corrected the prior chain* — the real objectives-enable byte is + `+0x1fd44` (inside the gate block, live=1), and SBC's enable key falls in a + dispatch gap with no byte at all. No change to the verdict; the correction only + hardens it. +- **Tournaments:** both hub sub-objects are display/clientData only. No change. + +**Net:** examining the hub atoms was the right next step, and it closed the lead +rather than opening a fix. Every server-reachable surface for these four modes is +now accounted for and none is an availability input. The prior "no server fix" +conclusion holds, now on much broader evidence. + +--- + +## 8. Client-vs-server authority boundaries (explicit) + +| Surface | Who writes it | Who reads it | Is it a mode-availability gate? | +|---|---|---|---| +| Gate bytes `+0x1fd3a/3b/3d/44` etc. | **server** (settings applier `FUN_18011dc50`) | **client** (getter stubs, off-DLL vtable dispatch) + config serializer `0x18006ccd0` (emit) | No — all live=1, never branched on inside CardsDLL | +| Hub mode sub-objects (`0x131/1ec/1f6/e4/328/32c`) | **server** (`/hub` body) | CardsDLL parsers → cosmetic captions/counts | No — no enable atom in any of the six desers | +| `[r14+0xa]=1`, `[rdi+0x162]=1`, `[rsp+0x21]==1` markers | CardsDLL parser (local) | same parser | No — "field present" bookkeeping, never JSON-sourced | +| Settings config keys (`friendlySeasonsEnabled`, `enableDraftMode`, `enableObjectives`, `tournamentQuitEnabled`) | **server** (`/settings`) | applier → gate bytes → **client** | No — inputs already at enabled; readers are off-DLL | +| SBC enable (`enableSquadBuildingSetsFeature 0x100`) | — | — | **No surface** — falls in the settings dispatch gap, lands on no byte | +| Offline/online season enable | — | — | **No surface** — no config key, no gate byte, no getter | +| `/season`, `/tournament`, `/sbs/*` endpoints | server (utas, routed) | never requested at hub | No — client never polls them; tile greys before any request | +| DestroyMatch reward fields (`tournamentCoins`, `teamOfTournamentWinner`) | server (post-match) | reward payout | No — reached only inside a match | +| The greying/refusal decision itself | — | **client** (Denuvo-packed FIFA17.exe / Frostbite viewmodels) | **This is the gate — and it has no server surface** | + +The single load-bearing fact across all four modes: **every server-writable enable +input that exists is already at ENABLED live, its only reader is the client, and the +tile refuses anyway.** No response body we can send flips a state the front-end has +already decided.