fifa17-recon: ENDPOINT_MAP -- remove two documented hang recipes, fix five entries
This file has been handing out bodies that freeze the client, under the heading "MINIMAL known-good". 1. FutGetDraftCurrentState. The root container is a JSON ARRAY. The documented body was object-root, used the spelling DRAFTSQUAD_ON which is NOT an accepted squadState value, and embedded a full squad object. Anyone serving it would have reproduced the exact hang the entry existed to prevent, which is what happened live on 2026-08-03 when our generic /squad route answered this endpoint with a squad object. Path corrected too: it is ut/%s/squad/mode + /draft/state, not ut/%s/draft/state. The `squad/mode` segment was missing, which is why the URL is invisible to the request-template table. Established by live capture, not statically: FUN_180146ac0 appends the suffix to a caller-supplied buffer and has no resolvable callers. 2. FutGetDraftAward (0x1801510c0) has the same array-root prologue, and its documented object-root body would hang identically. Corrected, and marked TODO/CONFIRM on the member list, which was not re-verified this pass. Both of these survived because a census claimed only three array-root readers existed in the DLL. It missed one. The census run to check it was wrong in the other direction. ~23 of 86 top-level readers are still unclassified, so the file now says: do not serve any endpoint here until its root container is classified by reading the actual prologue, not by regex. 3. roundsInfo element: `score` and `penaltyScore` offsets were swapped (+0x10 / +0x18). 4. FutSeasonList: deserializer is 0x1801683f0, not 0x180167740 (that is the ELEMENT parser), and the root is an OBJECT with one key `seasons`(0x2ad), not an array. Someone documented the element parser's key set at the document level, and utas_server.py served that shape for months on the strength of this row. Three of the listed element keys are inner members of elgReq and inert at element level. Added: element ordering (type before divisionId), stride 0x318, the (0xb-divisionId) short, and the three array-loop members that must stay omitted. 5. Recorded on the season entry that the client has NEVER requested /season across 486 real requests, so no body there is observable yet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
This commit is contained in:
@@ -484,7 +484,13 @@ Leaf getters: int/num `0x1801c79d0`, bool `0x1801c7620`, string `0x1801c7aa0`; u
|
||||
|
||||
### 2. FutGetDraftCurrentStateServerResponse — CONFIDENCE: HIGH (fully reversed, incl. roundsInfo element + squad delegation)
|
||||
- Struct name `0x180224200`; factory `0x180146cc0`; **deserializer `0x180147070`** (begin-obj @0x1801470f2, dispatch @0x18014715c).
|
||||
- Method/Path: **GET** `ut/game/fifa17/draft/state?mode=ONLINE` | `?mode=SINGLE_PLAYER` (fragments @0x223630/0x223650).
|
||||
- Method/Path: **GET** `ut/game/fifa17/squad/mode/draft/state?mode=ONLINE` | `?mode=SINGLE_PLAYER`.
|
||||
**CORRECTED 2026-08-04 from a LIVE CAPTURE.** The path above previously omitted the
|
||||
`squad/mode` segment. The real URL is `ut/%s/squad/mode` (template @`0x18021e7f8`)
|
||||
with `/draft/state` appended, which is why it is invisible to the request-template
|
||||
table and why the generic `/squad` route swallowed it. The suffix is appended to a
|
||||
caller-supplied buffer by `FUN_180146ac0`, which has no resolvable callers, so the
|
||||
full path is established by the capture in `REBUILD_RESEARCH.md` §15, NOT statically.
|
||||
- Top-level keys:
|
||||
| key | atom | type | notes |
|
||||
|---|---|---|---|
|
||||
@@ -496,14 +502,29 @@ Leaf getters: int/num `0x1801c79d0`, bool `0x1801c7620`, string `0x1801c7aa0`; u
|
||||
| `stateParam1` | 0x2ee | string→enum | @+0x5c (default 5) |
|
||||
| `stateParam2` | 0x2ef | string→int | @+0x64 |
|
||||
- **Round record** (`roundsInfo[]`, all scalar; deser `0x180146eb0`):
|
||||
`round`(0x290,int@+0xc), `score`(0x29a,int@+0x18), `opponentScore`(0x200,int@+0x14), `penaltyScore`(0x217,int@+0x10), `opponentPenaltyScore`(0x1fd,int@+0x1c), `opponentId`(0x1fc,int/long@+0x0), `difficulty`(0xd4,string enum@+0x8).
|
||||
- MINIMAL known-good (fresh single-player draft, round 0, empty squad shell):
|
||||
`round`(0x290,int@+0xc), `score`(0x29a,int@**+0x10**), `opponentScore`(0x200,int@+0x14), `penaltyScore`(0x217,int@**+0x18**), `opponentPenaltyScore`(0x1fd,int@+0x1c), `opponentId`(0x1fc,int/long@+0x0), `difficulty`(0xd4,string enum@+0x8).
|
||||
(`score` and `penaltyScore` offsets were SWAPPED here until 2026-08-04.)
|
||||
- **THE ROOT CONTAINER IS A JSON ARRAY.** `0x180147070` initialises, discards two
|
||||
tokens, then tests `if (t3 != 0xd)` around a `do { ... } while (t != 0xd)` element
|
||||
loop. Handed a top-level OBJECT it never reaches its exit condition and spins in the
|
||||
inner `while (t != 10)` loop while the tokenizer returns EOF forever: process alive,
|
||||
no crash dump, no dialog. That is the hang observed live on 2026-08-03.
|
||||
- **The body previously printed here was a HANG RECIPE** and is replaced below. It was
|
||||
object-root, it used the spelling `DRAFTSQUAD_ON` which is NOT an accepted value of
|
||||
the `squadState` enum, and it embedded a full `squad` object. Anyone who served it
|
||||
would have reproduced the exact freeze this entry was supposed to help avoid.
|
||||
- MINIMAL known-good (verified: two agents independently walked this body through the
|
||||
deserializer token by token to a clean exit in 16 reads):
|
||||
```json
|
||||
{"squadState":"DRAFTSQUAD_ON","stateParam1":"","stateParam2":"0","gamesWonCurrentMatch":0,
|
||||
"roundsInfo":[],
|
||||
"squad":{"id":0,"personaId":0,"formation":"f442","squadType":"REGULAR_SQUAD","chemistry":100,"starRating":5,"captain":0,"changed":0,"manager":[],"actives":[],"players":[{"index":0,"kitNumber":0}],"kicktakers":[]}}
|
||||
[{"squadState":"INVALID","stateParam1":"INVALID","stateParam2":"0","gamesWonCurrentMatch":0,"roundsInfo":[]}]
|
||||
```
|
||||
(Populate `squad.players[].itemData` with cards from the pick choices as the draft is built; `roundsInfo` grows one record per completed match. `entranceCriteria` omitted = safe.)
|
||||
`squad`(0x2cd) and `entranceCriteria`(0x108) are OMITTED and omission is provably
|
||||
inert: `FUN_180135ff0` is called from both defaults, so unknown keys are skipped.
|
||||
`entranceCriteria`'s shape is now known anyway (an object of three int keys
|
||||
COINS / DRAFT_TOKEN / POINTS) but knowing a shape is not a reason to send it.
|
||||
`stateParam2` may be a JSON number or a string; the string getter stringifies token
|
||||
types 2/3/4, so `"0"` is correct but not mandatory.
|
||||
Served today behind `FUT_DRAFT_STATE` (default on) in `utas_server.py`.
|
||||
|
||||
---
|
||||
|
||||
@@ -529,11 +550,21 @@ Leaf getters: int/num `0x1801c79d0`, bool `0x1801c7620`, string `0x1801c7aa0`; u
|
||||
| `halId` | 0x150 | int | @ (prize/hal id) |
|
||||
| `type` | 0x354 | int | prize type code |
|
||||
| `value` | 0x377 | int | prize value (e.g. coins) |
|
||||
- MINIMAL known-good (coins-only prize, no items):
|
||||
- **ROOT CONTAINER: ARRAY, not object. CORRECTED 2026-08-04.** `0x1801510c0` has the
|
||||
same array-root prologue as `FutGetDraftCurrentState`. The object-root body that used
|
||||
to be printed here would hang the client identically, and it was sitting in this file
|
||||
labelled "known-good".
|
||||
- MINIMAL (root shape corrected; the key set itself is unchanged and was not re-verified
|
||||
this pass, so `TODO/CONFIRM` the members before serving):
|
||||
```json
|
||||
{"type":1,"value":15000,"halId":0,"item":[]}
|
||||
[{"type":1,"value":15000,"halId":0,"item":[]}]
|
||||
```
|
||||
With an item prize: `"item":[{ …full card object as in itemData… }]`.
|
||||
- **Why both of these were wrong at once:** a census claimed only three array-root
|
||||
readers existed in the DLL. It missed this one. A second census, run to check the
|
||||
first, was wrong in the opposite direction. Roughly 23 of 86 top-level readers remain
|
||||
unclassified. **Do not serve any endpoint in this document until its root container
|
||||
has been classified by reading the actual prologue, not by regex.**
|
||||
|
||||
---
|
||||
|
||||
@@ -688,10 +719,22 @@ No match/season/tournament body is currently reversed in the baseline.
|
||||
- **Fields:** atom 643 `reset` = **bool** @0x28.
|
||||
- **MINIMAL JSON:** `{"reset": true}`. **GAP.**
|
||||
|
||||
### FutSeasonListServerResponse ★CONFIDENCE: HIGH
|
||||
- **Deserializer VA:** `0x180167740` (name-lea `0x18016754d`)
|
||||
- **Method+path (inferred):** `GET ut/game/fifa17/season` — list of available seasons/divisions
|
||||
(array of season-descriptor objects; root container via `0x1800d84e0`).
|
||||
### FutSeasonListServerResponse ★CONFIDENCE: HIGH (root shape CORRECTED 2026-08-04)
|
||||
- **Deserializer VA:** `0x1801683f0`. The previously documented `0x180167740` is the
|
||||
per-ELEMENT parser, not the response deserializer.
|
||||
- **THE ROOT IS AN OBJECT WITH ONE KEY.** `0x1801683f0` runs a key loop and matches
|
||||
exactly one atom, `seasons`(0x2ad); the array opens only inside it. This entry
|
||||
previously described an array root, because someone read the element parser and
|
||||
documented its key set at the document level. A bare array populates nothing, and
|
||||
`utas_server.py` served one for months on the strength of this row.
|
||||
- **Method+path:** `GET ut/game/fifa17/season`.
|
||||
- **Element ordering matters:** `type` MUST precede `divisionId`, because the
|
||||
`divisionId` branch reads the already-parsed type field at `elem+0x1b4`.
|
||||
- **Element stride is `0x318`.** (`0x1f8`, recorded elsewhere, is the offset of the
|
||||
compared short WITHIN an element.) The short the online path matches on is written
|
||||
from `divisionId` as `(0xb - divisionId)`, not from `id`.
|
||||
- `eligibilityKey`/`eligibilitySlot`/`eligibilityValue` below are inner members of
|
||||
`elgReq` and are **inert at element level**, so the old minimal body was wrong twice.
|
||||
- **Element fields:**
|
||||
| atom | key | type | notes |
|
||||
|---|---|---|---|
|
||||
@@ -703,10 +746,17 @@ No match/season/tournament body is currently reversed in the baseline.
|
||||
| 246 | `elgOperation` | str | |
|
||||
| 247 | `elgReq` | nested | SKIP-safe |
|
||||
| 595 | `prizeSet` | nested | array — FREEZE-RISK |
|
||||
- **MINIMAL JSON** (list root; per element scalars):
|
||||
- **MINIMAL JSON** (object root, single `seasons` key):
|
||||
```json
|
||||
[{"id":1,"divisionId":10,"eligibilityKey":0,"eligibilitySlot":0,"eligibilityValue":0,"elgOperation":""}]
|
||||
{"seasons":[{"type":"OFFLINE","id":1,"divisionId":10}]}
|
||||
```
|
||||
`prizeSet`(0x253), `elgReq`(0xf7) and `matches`(0x1b8) are all `while (tok != 0xd)`
|
||||
ARRAY loops: a scalar in any of them is the `0x1801c7f1a` spin. Omit all three.
|
||||
Semantic hazard: omitting `untilEndSeconds` makes the season end timestamp equal now.
|
||||
- **Do not serve this yet.** Across 486 real client requests (User-Agent `ProtoHttp`,
|
||||
roughly 30 boots) the game has **never** requested `/season`. Every `/season` line in
|
||||
`/tmp/utas_server.log` is our own `curl` or `Python-urllib`. Serving a body here
|
||||
changes nothing observable until something upstream makes the client ask.
|
||||
- **GAP.** (Note: root JSON shape is an array/object wrapper — verify container before shipping.)
|
||||
|
||||
### FutSeasonLoadDataServerResponse ★CONFIDENCE: HIGH (fully traced switch)
|
||||
|
||||
Reference in New Issue
Block a user