Yesterday's settings-gate plan asserted that IS_FRIENDLY_SEASON_ENABLED and
IS_DRAFT_MODE_ENABLED "have never been set to true by anything, on any run", and
proposed spending a launch on that premise. Measured against the running client,
both read 1, and so does packOpeningAnimationEnabled, while /settings has only ever
been answered {"configs": []}.
disp 0x1fd3a (friendlySeasonsEnabled) value = 1
disp 0x1fd3d (enableDraftMode) value = 1
disp 0x1fd45 (packOpeningAnimationEnabled) value = 1
Reproduced on two separate launches and two different pids.
Where the reasoning went wrong: the finding that FUN_18011dc50 is the only writer of
those bytes, and that the FutDataManagerImpl constructor never touches them, was
correct. The inference was not. The applier runs whether or not the configs array has
content, and the struct it is handed defaults these fields to 1, so the bytes were
being written all along. "Nothing populates the array" was treated as "nothing writes
the byte". Only the first of those was ever established.
Seasons therefore does not refuse because its gate byte is false. Its gate byte is
true. That diagnosis restarts, and the live test in section 3 should not be run as
written. The doc keeps the wrong turn on the record rather than quietly deleting it.
tools/gate_byte_probe.py makes this repeatable instead of a one-off. It is read-only
(O_RDONLY + pread), resolves the pid by comm, re-derives the CardsDLL slide from
/proc/<pid>/maps rather than caching it across launches, proves the slide against the
FNV prologue at 0x180180d00 read from the on-disk PE before trusting any address, and
decodes each gate displacement out of its accessor stub (0f b6 81 <disp32>) rather
than reading it from a table. Needs the client at the FUT hub, since CardsDLL loads
only then.
Also carries the two /settings changes that were pending from before: the mode
defaults to `off` (the live-proven baseline, since nothing here has faced the game)
and the transfer-pile probe is 77 rather than 100, because 100 is a stock-looking
number that would prove nothing if it showed up in game.
Live: 439 contract checks pass. check_settings_flags.py passes in all four modes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
7.7 KiB
The /settings feature gate - live-test script
CORRECTION, 2026-08-05 evening. Section 1 of this document is FALSE and the test in section 3 should not be run as written.
Section 1 claims
IS_FRIENDLY_SEASON_ENABLEDandIS_DRAFT_MODE_ENABLED"have never been set to true by anything, on any run". They are measured as 1, on two separate launches, while/settingswas answering{"configs": []}:disp 0x1fd3a (friendlySeasonsEnabled) value = 1 disp 0x1fd3d (enableDraftMode) value = 1 disp 0x1fd45 (packOpeningAnimationEnabled) value = 1Reproduce with
tools/gate_byte_probe.py(needs the client at the FUT hub, since CardsDLL loads only then): it resolves the pid by comm, re-derives the CardsDLL slide from/proc/<pid>/maps, proves it against the FNV prologue at0x180180d00read from disk, walks the model singleton atDAT_1802e6398, and decodes each displacement out of its accessor stub (0f b6 81 <disp32>) rather than assuming it.Where the reasoning went wrong. The finding that
FUN_18011dc50is the only writer and that theFutDataManagerImplconstructor never touches those bytes was correct. The inference drawn from it was not. The applier runs whether or not the configs array has content, and the settings struct it is handed defaults these fields to 1, so the bytes were being written all along. "Nothing populates the array" was treated as "nothing writes the byte". Those are different claims and only the first one was established.Seasons therefore does not refuse because its gate byte is false. Its gate byte is true. The mechanism is still unknown and needs a fresh diagnosis. Everything below the correction is kept as the record of a wrong turn, not as a plan.
Written 2026-08-05, after reversing FutGetSettingsServerResponse end to end.
Nothing here has been in front of the game yet. The code default is off, which
serves the exact historical {"configs": []}, so the tree is currently at the
proven baseline and this test is opt-in.
Full schema, atom ids, gate bytes and accessor addresses are in ENDPOINT_MAP.md
under FutGetSettingsServerResponse. This file is only the experiment.
1. The claim being tested
GET /settings is requested 11 times a session and has always been answered with
an empty array. The array is not decoration:
- Each element is
{"type": "<name>", "value": <scalar>}. The client hashes the string value oftypethrough the atom hasher and switches on it, 42 arms wide, so a flag is a row rather than a key. FUN_18011dc50is the only writer of theIS_*UI gate bytes insideFutDataManagerImpl, and every line of it isbyte = (field == 1).- The
FutDataManagerImplconstructor never touches those bytes. The whole 16620-char decompile was scanned for the block; it is absent.
So IS_FRIENDLY_SEASON_ENABLED and IS_DRAFT_MODE_ENABLED have never been set
to true by anything, on any run, in the whole history of this project.
That is a mechanism for the standing bug in which Seasons refuses while making zero requests to any of the four servers. No response shape could ever explain that. A UI key evaluated from a byte nobody wrote does.
The store is the control that makes this readable. IS_STORE_ENABLED is the
same kind of byte read the same way, and the store screen works. It works because
storeEnabled and its siblings already reach the client through the Blaze
client-config store (FUT_RS4_CONFIG). That list contains no seasons flag, no
draft flag, no tournament flag. Same mechanism, one populated, one blank.
This is a hypothesis with a mechanism, not a demonstrated cause.
2. Pre-flight, from the terminal, costs nothing
cd fifa17-recon/tools
FUT_SETTINGS=gates python3 check_settings_flags.py # expect: 14 rows, PASS
python3 check_settings_flags.py # expect: mode=off, PASS
The checker asserts every shipped flag name against both the atom table and
the recovered switch arms. Both are needed: enableSquadBuildingSetsFeature is a
genuine atom with no arm in this switch, so the atom table alone would wave
through a flag that does nothing. A misnamed flag is silently inert and looks
exactly like a failed fix, which is the failure mode this guards.
3. The run
Budget: one launch.
cd fifa17-recon/tools
FUT_SETTINGS=gates ./openfut-fut.sh start
~/Desktop/launch-fifa17.sh
Then, in order, and write down what each one does:
- Store. Open it. This is the control and it goes first, because if populating the array broke the store then the applier demonstrably ran and everything after this reads differently.
- Transfer list capacity. Transfers → Transfer List. Read the capacity
number. We send
maximumTradePileSize = 77, a number FUT would never choose on its own. - Seasons. Single-player Seasons, the exact path that has been refusing.
- FUT Draft. Both the offline and online entries.
- Tournaments, for
tournamentQuitEnabled.
4. Reading the result
The control in step 2 is what makes a negative result informative, so read it before concluding anything about steps 3 to 5.
| Store (1) | Capacity (2) | Seasons (3) | Reading |
|---|---|---|---|
| works | 77 | opens | Confirmed. The gate was the empty array. Make gates the default and move to the /match shape, which has been blocked behind this. |
| works | 77 | still refuses | The array reached the consumer and the flag was applied, so the gate is upstream of the UI key. The settings line is then dead for Seasons and the next move is a live probe of the refusal path, not more response work. This is a real result, not a null one. |
| works | not 77 | still refuses | The array never reached the consumer at all. Everything above is untested rather than refuted. Suspect the massinfo settings member (the deser's other caller) is what the client actually reads, and check which of the two paths fires in /tmp/utas.log. |
| breaks | any | any | The applier ran and re-asserting the store flags did not hold them. Fall back to FUT_SETTINGS=keep, which sends only the already-working flags plus the control. If keep also breaks the store, populating the array is harmful in itself and the whole approach is wrong. |
keep exists precisely so that "populating the array at all" and "the new gates"
can be separated without guessing, and it costs one restart to use.
5. What would make this whole plan wrong
The gate might not be a UI key at all. Seasons could be refusing on an
entitlement, a persona attribute, or a Blaze session property evaluated inside
the Denuvo-packed executable, in which case no /settings body reaches it. The
step-2 control is what tells these apart: it distinguishes "the flag did not
help" from "the array was never consumed", and no boolean flag can do that alone.
The store control could be weaker than it looks. The argument assumes
IS_STORE_ENABLED currently comes from the Blaze store rather than from a
default. If it turns out the store screen does not read that key at all, then it
is not a control for anything and the reasoning in §1 loses its anchor.
Draft has a second known suspect. GET ut/%s/squad/mode/draft/state is still
answered by the generic /squad handler with a full active-squad object, which
is a textbook type-desync candidate. If Draft still fails while Seasons opens,
that route is the next thing to look at, not the flag.
A negative result here is worth having. The settings array has been the standing suspect for the greyed-out entry points for two rounds without anyone sending a single flag. Ruling it out costs one launch and removes it from the backlog permanently.