Files
OpenFUT/fifa17-recon/docs/plan-2026-08-06-refusing-modes.md
T
funman300 a7e3e43ae9 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lrx9to3pihN6Sm9sXgc8np
2026-08-06 18:59:23 -07:00

22 KiB

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 <disp32> 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.