Files
OpenFUT/fifa17-recon/docs/OPENCODE_ENDPOINT_PROMPT.md
T
funman300 4e89cce37d fifa17-recon: store fix (v2/store gate + flags) + full FUT endpoint map
Store "not available" root cause reversed from CardsDLL:
- ut/v2/game/fifa17/store is an ELIGIBILITY gate (FutStorePackQuantities
  deser 0x1801758c0), not a quantity list. It reads one key "result"
  (atom 0x288); the store screen refuses to open unless SUCCESS. Was
  unhandled -> catch-all {} -> "not available". Now returns {"result":"SUCCESS"}.
- Store-screen entitlement checks (0x18001749d/0x1800175a2) read IS_*/
  *_PURCHASE_ENABLED Blaze flags, separate from storeEnabled. Added the full
  confirmed set (14 flags) to FUT_RS4_CONFIG.
- Catalog: assetId (0x23) is the real pack identity; extPrice inner keys are
  amount/currency (not mtx). (Also gated client-side by GetSystemMetrics>1024x768.)

Full FUT API reversed (clean-room, CardsDLL only) into docs/ENDPOINT_MAP.md:
~100 FutXServerResponse types across 7 feature groups (market, SBC, draft,
seasons/match, club, store, user/hub), each with deserializer VA, atom-mapped
field schema + types, freeze-risk flags, and minimal known-good JSON.
Tooling kept: tools/atomdump.py (dumps the 907-atom key table at 0x1802d2760)
-> docs/fut_atoms.tsv. Research prompt: docs/OPENCODE_ENDPOINT_PROMPT.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PN5bmpDVQR1aXgefyWAt7o
2026-08-02 17:13:11 -07:00

11 KiB
Raw Blame History

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/hostseasw.easports.com127.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:Fut<X>ServerResponse"; 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 Fut<X>ServerResponse 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]

{"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:

{"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:

{"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:<clubItemId>} 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.