# opencode task — map the remaining FIFA 17 FUT endpoints for the offline rebuild ## Context OpenFUT runs FIFA 17 Ultimate Team fully offline (clean-room; EA servers are dead). A working Python backend already exists at `~/Documents/OpenFUT/fifa17-recon/tools/` and FIFA talks to it (`/etc/hosts` → `easw.easports.com` → `127.0.0.1:8099`). Many endpoints are already reversed and served. Your job is **RESEARCH ONLY**: produce a complete map of the FUT endpoints that are LEFT to build, using the tools/logs/disassembly that already exist. Do NOT rewrite the backend — output `~/Documents/OpenFUT/fifa17-recon/docs/ENDPOINT_MAP.md`. ## CLEAN-ROOM RULE (HARD) Use ONLY files we own + our own client's traffic. NEVER use leaked EA source of any kind. ## Use the ALREADY-BUILT tools — do not rebuild these 1. **`tools/utas_server.py`** — the live backend. Its `ROUTES` table is the AUTHORITATIVE list of endpoints already handled: `auth`, `delete/auth`, `phishing/{trusteddevice, validate,question}`, `user/credits`, `user/list`, `user/accountinfo`, `user`, `squad` (+`/list`), `hub`, `userMassInfo`, `season`, `club`, `item(/resource)/defid`, `store/purchasegroup`, `store/transaction`, `purchased`. Read it to see what's DONE and the exact response shapes used. Its `_handle()` logs `!! UNMAPPED PATH -> catch-all 200 {}` for any endpoint FIFA hits that ISN'T handled yet — those are your gaps. 2. **`/tmp/utas_server.log`** — GROUND TRUTH of every request FIFA makes (method, path, headers, body) and our response. Start here: ``` grep 'UNMAPPED PATH' -B1 /tmp/utas_server.log # endpoints we stub {} grep -oE '(GET|POST|PUT) /ut/(v2/)?game/fifa17/[^ ?]*' /tmp/utas_server.log | sort -u ``` This is the real, ordered list of what the client requests per FUT screen. (Caveat: the log is temp — cleared on reboot — and only reflects FUT screens actually visited. For richer traffic, run the harness and navigate more FUT menus first.) 3. **`tools/memtool.py`** — live FIFA17 `/proc/mem` reader/patcher (base `0x140000000`), for inspecting parsed structs live if a format is ambiguous. 4. **`tools/fut_store.py` / `tools/fut_seed.py`** — the data models (club items, squad, packs) the backend already uses; extend these conceptually, don't reinvent. 5. **`tools/openfut-fut.sh`** — starts the whole harness (lsx/blaze/roster/utas) if you need it running to capture more traffic. `tools/root_arm.sh` arms host state (needs sudo). 6. **`docs/CARD_SYSTEM.md`** — the reversed card/parser system + method (READ FIRST). It already documents the SAX-parser internals so you don't re-derive them. ## Disassembly (regenerate once; same method `docs/CARD_SYSTEM.md` uses) ``` mkdir -p /tmp/fut && cp "/mnt/games/FIFA 17/CardsDLL_Win64_retail.dll" /tmp/fut/cardsdll.dll objdump -d -M intel /tmp/fut/cardsdll.dll > /tmp/fut/cardsdll.asm strings -t x /tmp/fut/cardsdll.dll > /tmp/fut/cardsdll.strings ``` Shared-parser CHEATSHEET (reuse, don't re-derive): endpoint paths are strings `"ut/%s/..."` (`%s` = `"game/fifa17"`); response structs are `"RS4:FutServerResponse"`; each has a SAX deserializer that hashes each key via FNV-1a fn `0x180180d00` (seed `0x811c9dc5`) to an ATOM int, looks the name up in the table at `0x1802d2760` (`table[atom]=char*`), and dispatches via a jump-table; unknown keys hit the value-SKIP handler `0x180135ff0` (container-aware, safe). Field TYPE must match its handler (scalar getters `0x1801c79d0`/`0x1801c7620`/ `0x1801c7aa0` vs nested object/array handlers) — a scalar handler fed an object/array **DESYNCS the parser and hard-freezes the game**, so type fidelity is mandatory. Endpoint resolve table: `RS4::ServerSettings::resolve 0x180124270`. ## Method (per endpoint) For each gap endpoint (from the log's UNMAPPED list + the `"ut/%s/..."` strings not yet in `ROUTES`): grep the strings for its path → find its `FutServerResponse` struct → find its deserializer → list the atoms it dispatches, map atom→key via `0x1802d2760`, note each field's JSON type + required-vs-skip → write the minimal known-good response JSON. ## WHERE TO START 1. Read `docs/CARD_SYSTEM.md` and skim `tools/utas_server.py` `ROUTES`. 2. Run the two log greps above → the definitive list of endpoints FIFA calls but we only stub `{}`. Rank them by FUT feature. 3. List ALL `"ut/%s/..."` path strings and subtract the ones already in `ROUTES` → the endpoints not yet even discovered in traffic. 4. Reverse the response format for each gap, prioritizing the core loop first: **transfermarket** (search/bid/buy/list/watchlist), **tradepile**, **SBC** (challenges/submit), **objectives**, **draft**, **seasons/single-player**, **match** (squad-battles/kickoff result), then club stats / concept squads / loans. ## Deliverable `docs/ENDPOINT_MAP.md`, one section per feature: for each endpoint — method, path, whether already handled or a gap, request body shape, response shape (exact keys+types, required vs optional), deserializer VA, and a minimal known-good example JSON. This is the spec for finishing the FUT backend (target: port into the Rust `openfut-core` behind a FIFA-17 bridge). --- ## APPENDIX — known atoms + verified response templates (reuse these; don't re-derive) ### Parser internals (already reversed) - key → atom: FNV-1a fn `0x180180d00` (seed `0x811c9dc5`) → `table[atom]=char*` at `0x1802d2760` - dispatch: range jump-tables; unknown key → value-SKIP `0x180135ff0` (container-aware, safe) - scalar getters (leaf, no descend): int/num `0x1801c79d0`, bool `0x1801c7620`, string `0x1801c7aa0` - endpoint resolve: `RS4::ServerSettings::resolve 0x180124270`; path `"ut/%s/.."`, `%s="game/fifa17"` - shared ITEM/card deserializer: `0x18013fe00` (used by club, squad slots, pack itemList, purchased) ### Common shared keys (atom in hex | JSON type) ``` itemData 0x16b (obj/array-elem) | id 0x15c (int) | resourceId 0x287 (int) | assetId 0x23 (int) index 0x163 (int) | kitNumber 0x17a (int) | rating 0x274 (int) | preferredPosition 0x24a (str) cardsubtypeid 0x6c (int) | attributeList 0x31 (array) | currencies 0xc5 (array) currencies element: name 0x1d0 (str) | funds 0x134 (int) | finalFunds 0x124 (int) currency-name literals are CASE-SENSITIVE strings: "coins", "points", "DRAFT_TOKEN" configs 0xa2 (array) [settings response, key = "configs"] ``` ### FutUserCreditsServerResponse — GET user/credits (deser 0x180122c50) [VERIFIED WORKS] ```json {"currencies":[{"name":"coins","funds":15000,"finalFunds":15000}, {"name":"points","funds":0,"finalFunds":0}], "unopenedPacks":{"preOrderPacks":0,"recoveredPacks":0}} ``` Coins read from `currencies[name=="coins"].funds`. A bare `{"credits":n}` is SKIPPED → 0. ### FutStoreGetPackTypesServerResponse — GET store/purchasegroup/all (deser 0x1801234e0) Root key MUST be `"purchase"` `0x260` (array); optional `"timestamp"` `0x31b`. Per-pack (parser `0x18013af30`): `id 0x15c`(int16, the pack identity) | `packType 0x20f`(str) | `description 0xd1`(str) | `currencies 0xc5`(array {name,funds,finalFunds} = coins price) | `extPrice 0x119`(obj {`finalPrice 0x125`, `originalPrice 0x205`}, each a currency→amount map incl `"mtx"`=FIFA-Points) | `packContentInfo 0x20c`(obj: `bronzeQuantity 0x63`, `silverQuantity 0x2c6`, `goldQuantity 0x149`, `rareQuantity 0x273`, `itemQuantity 0x170`) | `quantity 0x26b`(int, 0=unlimited) | `isPremium 0x176` | `saleType 0x298` | `state 0x2eb` | `visible 0x37d`(sets flag, DON'T send the value—desyncs). **⚠ The parser is NOT the store gate (CONFIRMED by disassembly).** Per-pack parser epilogue `0x18013badc` pushes every parsed pack unconditionally — no valid/drop predicate exists in the parse path. The `"not available"` error (`FUT_CatalogNotAvailable`, msg-id `0x7550`) comes from TWO downstream gates, neither JSON-schema-related: 1. **Resolution gate `0x18001756e`**: calls `GetSystemMetrics` — if the display is **≤ 1024×768**, the store is declared unavailable regardless of any JSON. Run FIFA at **> 1024×768** (1280×720 passes). *Cheapest cause to eliminate — check this first.* 2. **Store-data-model load status** (`0x180013cf0`): `model+0x30` must become `1` and the screen's cached status `[rsi+0x250]` must not stay `-1`; else post `0x7550` at `0x180013d6c`. Fed by two entitlement checks — `0x18001749d` (`vtable+0x138`) and `0x1800175a2` (`vtable+0x280`) — that read the **Blaze client-config purchase flags**: `IS_STORE_ENABLED`, `IS_COIN_PURCHASABLE`, `IS_FIFAPOINT_AVAILABLE`, `COINS_PURCHASE_ENABLED`, `POINTS_PURCHASE_ENABLED`, `MONEY_PURCHASE_ENABLED`. These are SEPARATE from `storeEnabled`/`cardPackStoreEnabled` and must ALSO be set in the Blaze config. Recognized per-pack enum tokens (send these exact strings to avoid enum-reject): `saleType` → `"promo"`/`"deal"`; a limit-type field → `NONE`/`QUANTITY`/`TIME`/`TIME_QUANTITY`; pack `state` → `"active"`. Currency token is lowercase `"coins"`/`"mtx"` (NOT uppercase). ### FutCreatePackServerResponse — PUT store/transaction (pack reveal, deser 0x180162880) Wrapper key `"createPackResponse"` `0xbe`: ```json {"createPackResponse":{"itemList":[/*cards*/],"numberItems":7, "purchasedPackId":102,"duplicateItemIdList":[]}} ``` (atoms: itemList `0x16e`, numberItems `0x1dd`, purchasedPackId `0x264`, duplicateItemIdList `0xec`.) BUY signal = transaction body has `"packId"` `0x20b` AND `state 0x2eb != "TRANSACTIONCANCEL"`. state enum strings: `TRANSACTIONCREATED`(carries packId=the buy), `PURCHASECOMPLETE`, `TRANSACTIONCOMPLETE`, `TRANSACTIONCANCEL`. `FutGetPurchasedItemsServerResponse` — GET purchased: `{"itemData":[/*cards*/]}` (itemData `0x16b`). ### GetUserMassInfo — GET userMassInfo (deser 0x180174630) [FREEZE-SENSITIVE] Top-level: `userInfo 0x370`, `squad 0x2cd`, `settings`, `userData`. MUST serve `{}` unless every field type-matches, else the parser hard-freezes. `userInfo` deser `0x18013ec10`; SAFE minimal that carries coins: ```json {"userInfo":{"currencies":[{"name":"coins","funds":15000}],"sessionCoinsBankBalance":15000}} ``` (`sessionCoinsBankBalance 0x2bb`.) DANGER: `squadList` must be an OBJECT `{"squad":[...]}` NOT an array (array → desync → freeze). Container fields `feature`/`reliability`/ `unopenedPacks`/`bidTokens`/`actives` must match exact shape or be omitted. ### LoadActiveSquad — GET squad/0 (deser 0x18013d1f0) Keys: `id 0x15c` | `personaId 0x21b` | `squadName 0x2d3` | `formation 0x12b`(str) | `squadType 0x2d6` | `chemistry 0x81` | `starRating 0x2e2` | `captain 0x69` | `manager 0x1a8`(array) | `actives 0xb`(array) | `custom 0xc6`(STRING of 33 ints) | `players 0x238`(array of {index,itemData,kitNumber}) | `kicktakers 0x178`(array). Squad PUT stores slots as `itemData={id:}` references (re-embed full items on GET, see `reconstruct_squad`). > Atoms are index-into-`0x1802d2760`; a few above came from mixed-confidence passes — when a > value doesn't take, verify the atom by locating the key string in `cardsdll.strings` and > re-hashing. Field TYPE fidelity is mandatory (scalar-vs-container mismatch = freeze). > High-confidence/verified: `user/credits`, `createPackResponse`, the store gate analysis, > and the `userMassInfo` safe-shape.