diff --git a/fifa17-recon/docs/ENDPOINT_MAP.md b/fifa17-recon/docs/ENDPOINT_MAP.md index 5232512..fd2294f 100644 --- a/fifa17-recon/docs/ENDPOINT_MAP.md +++ b/fifa17-recon/docs/ENDPOINT_MAP.md @@ -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)