fifa17-recon: the FUT-hub Transfer List tile counts, and the hub parser is NOT reflection
The Transfer List hub tile read "0 items / Selling 0" while a card was actively
listed. Enumerating the /hub parser FUN_180139610 straight from the on-disk
CardsDLL (objdump) refutes the old ENDPOINT_MAP claim that it uses C++ reflection
with "no atom ladder, nothing to enumerate": it has an ordinary running-sum atom
ladder reading 18 atoms. The tile is fed by hub.tradePile (0x333), a nested object
(sub-deser 0x18013ead0) reading count/selling/sold as scalar ints -- the same
scheme as GetAuctionCount, so serving it in the hub body is freeze-safe. The tile
never re-polls the standalone /tradePile/counts, which is why fixing that endpoint
alone did not move the tile.
Also: the hub tile polls LOWERCASE tradepile/counts while the Transfer List screen
uses camelCase tradePile; our case-sensitive routes matched only the screen, so the
tile's counts call fell through to /trade and got a shape the counts deser skips.
Made the tradePile routes case-insensitive.
And bake the proven transfer-market flags (FUT_TRADING/PILESIZES/TRADEABLE/
DISCARD_TABLE/DISCARD_SEND) into openfut-fut.sh so a plain `start` brings up the
working state instead of regressing trading to greyed-out.
- tools/utas_server.py: hub_data() serves tradePile:{count,selling,sold};
tradePile routes now re.I
- tools/openfut-fut.sh: utas launched with the working flag set
- docs/ENDPOINT_MAP.md: full 18-atom hub map + tile map, correction of the
reflection claim
- tools/ghidra_queries/objdump_atom_ladder.py: the objdump-based atom-ladder
decoder used to derive the above
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lrx9to3pihN6Sm9sXgc8np
This commit is contained in:
@@ -1329,14 +1329,44 @@ this absence is asserted over the whole function, not a slice.
|
||||
- **Handled:** `utas_server.SETTINGS`, `FUT_SETTINGS` (default `gates`).
|
||||
`off` restores the historical `{"configs": []}`.
|
||||
|
||||
### FutGetHubDataServerResponse — CONFIDENCE: LOW (full schema) / HIGH (served {} works) — GAP
|
||||
- **Wrapper:** `0x1801736ad` → inner `0x180173a50` / `0x180173b10` / `0x180173c00`.
|
||||
### FutGetHubDataServerResponse — CONFIDENCE: HIGH (schema fully enumerated) — ✅ HANDLED (tiles populated)
|
||||
- **Deser:** `FUN_180139610` (root object parser). Wrapper `0x1801736ad`.
|
||||
- **HTTP:** `GET ut/%s/hub`
|
||||
- **Note:** uses **C++ reflection / vtable dispatch** (`call [rax+0x10]`,
|
||||
`call [rdx+0x1f8]`), NOT an inline atom ladder — no static field ladder to
|
||||
read. It aggregates sub-objects (userInfo, settings, messages, etc.), each with
|
||||
its own deser. Empty `{}` is tolerated (fields default).
|
||||
- **Handled:** `utas_server` serves `{}` (validated hub-reaching). Deep populate = GAP.
|
||||
- **CORRECTION (2026-08-06):** the earlier note here — "uses C++ reflection /
|
||||
vtable dispatch, NOT an inline atom ladder, no static field ladder to read,
|
||||
GAP" — was **WRONG**. `FUN_180139610` has an ordinary inline atom ladder: a
|
||||
running-sum `sub ecx,d / … / cmp ecx,d` dispatch plus a few direct `cmp esi,imm`.
|
||||
It reads **18 atoms**, all enumerated below straight from the on-disk CardsDLL
|
||||
via objdump (`fifa17-recon` scratchpad `hub_ladder.py`). The vtable calls are the
|
||||
per-sub-object dispatch one indirection deeper, not the field read itself.
|
||||
- **The 18 root atoms** (name ← `fut_atoms.tsv`):
|
||||
`allObjectivesForCurrentGameSpaceId`(0x15), `auctionCount`(0x33),
|
||||
`championEvent`(0x7a), `clubPlayers`(0x90), `draftSummary`(0xe4),
|
||||
`friendlySeason`(0x131), `leaderboard`(0x186), `liveMessagesAvailable`(0x190),
|
||||
`objectivesForCurrentUser`(0x1e3), `offlineSeason`(0x1ec), `ONLINE`(0x1f1),
|
||||
`onlineSeason`(0x1f6), `SINGLE_PLAYER`(0x29d), `squad`(0x2cd),
|
||||
`tournament`(0x328), `tournamentProgress`(0x32c), `tradePile`(0x333),
|
||||
`watchlist`(0x381).
|
||||
- **TILE MAP (which atom drives which hub tile):**
|
||||
- `clubPlayers`(0x90) int → MY CLUB tile "N players" (TILE_ID 0x210)
|
||||
- `auctionCount`(0x33) int → TRANSFER MARKET tile "N LIVE TRANSFERS" (TILE_ID 0x1b0)
|
||||
- `tradePile`(0x333) **nested object**, sub-deser `0x18013ead0` → TRANSFER LIST
|
||||
tile "N ITEMS / Selling / Sold". Sub-atoms: `count`(0xbc), `notification`(0x1da),
|
||||
`selling`(0x2b8), `sold`(0x2c9) — all scalar int via `0x1801c79d0` (5 int reads,
|
||||
one SKIP, object field loop; no array/nested object → no type-desync surface).
|
||||
Same atom scheme as `FutGetAuctionCount`. **All active listings are `selling`;
|
||||
`count == selling == len(listings)`, `sold == 0`.**
|
||||
- `watchlist`(0x381) nested object, sub-deser `0x18013f3b0` → WATCH LIST tile (not
|
||||
yet populated; empty watch list defaults to 0, which is correct today).
|
||||
- **LIVE SYMPTOM this fixed (2026-08-06):** a card was actively listed
|
||||
(`auctionCount` 1, Listed Items screen showed it) yet the TRANSFER LIST tile read
|
||||
"0 items / Selling 0". The tile reads `hub.tradePile`, which we were omitting; it
|
||||
does **not** re-poll `/tradePile/counts` (the standalone GetAuctionCount endpoint)
|
||||
once at the hub. Serving `hub.tradePile:{count,selling,sold}` corrected the tile.
|
||||
- **Handled:** `utas_server.hub_data()` serves `clubPlayers`, `auctionCount`, and
|
||||
`tradePile:{count,selling,sold}` (`FUT_HUBDATA=1`, default on). Remaining atoms
|
||||
(seasons/draft/tournament/objectives/leaderboard summaries) default to 0/absent,
|
||||
which is correct while those modes are unpopulated.
|
||||
|
||||
### FutUserDataServerResponse — CONFIDENCE: MEDIUM
|
||||
- **Deser:** `0x18016dd50` (lea r8 @ `0x18016d98d`)
|
||||
|
||||
Reference in New Issue
Block a user