diff --git a/docs/FIFA17_TRANSFER_MARKET_WIRE.md b/docs/FIFA17_TRANSFER_MARKET_WIRE.md index 595d961..75af4bf 100644 --- a/docs/FIFA17_TRANSFER_MARKET_WIRE.md +++ b/docs/FIFA17_TRANSFER_MARKET_WIRE.md @@ -396,3 +396,73 @@ state, and it is reachable in two ways, both now closed: Tests: `returning_an_expired_listing_to_the_club_ends_its_auction`, `a_pile_move_never_disturbs_a_sale_in_flight`, `club_excludes_listed_items_and_paginates_the_visible_set`. + +--- + +## Q2 representation — narrowed to ONE candidate by elimination (2026-08-17) + +Re-entry discriminator result: **CONFIRMED BUG.** After fully leaving and re-entering +FUT, the active auction reconstructs correctly under LISTED ITEMS, but the unlisted +TRANSFER LIST section is **empty** — item `100000059` does not survive. The +immediate post-move visibility was client-local only, so our representation cannot +durably reconstruct trade-pile membership. + +The representation is now pinned by elimination over PROVEN facts, not chosen: + +1. **Only one route can own a trade-pile list.** The complete CardsDLL route table + (`.rdata 0x18021df80`, 45 routes + 3 empty admin slots) is dumped verbatim at + `docs/evidence/market-lifecycle-2026-08-17/cardsdll-route-table.txt` via + `fifa17-recon/tools/route_table_dump.py` (static, read-only, VA→offset resolved + through the real PE section table). Row 30 `ut/%s/tradePile` is the ONLY + trade-pile route. There is no trade-pile *items* route. +2. **That route carries only auction records.** `FutGetTradePile` (`0x180170810`) + deserializes the shared IS-list body (`0x18013e7f0`), whose `auctionInfo` + elements go through `0x18013e410` — the twelve-atom auction record. +3. **`pile` cannot be set from the wire.** `pile` (atom 0x226) has NO arm in the item + deserializer `FUN_18013fe00` (checked in all four dispatch forms); `item+0x60` is + assigned by the OWNING LIST, reading 1 for every `/club` item and 6 for every + `/purchased` item. So membership is conferred by *arriving in the list*, never by + a field we can add. +4. **Of the twelve atoms, only `tradeState` can express lifecycle.** +5. **`tradeState`'s vocabulary is closed and has exactly one unused value.** The table + walk at `0x180229e40` decodes `active=1 inactive=2 expired=3 closed=4`, anything + else `-1`. `active`, `expired` and `closed` are all already spoken for by the + observed lifecycle. + +**Therefore an unlisted trade-pile item can only be an `auctionInfo` record with +`tradeState: "inactive"`.** That is the sole encoding the client's own parser can +accept for "in the pile, no auction" — reached by elimination, not invention. + +Status: **INFERRED-BY-ELIMINATION**, not yet CONFIRMED. One binary question remains, +and it is about client *rendering*, not encoding: does the Flash Transfer List place +an `inactive` record in the unlisted section? The corpus's warning that "`inactive` +decodes but no client path treats it specially" was established for the market +PRESENTATION function (`flagA`/`flagB`), where `inactive` is indeed unremarkable — +that is consistent with, and does not contradict, using it for list membership. + +Ruled out on evidence, do not retry: adding `itemData.pile` (inert, no deserializer +arm), a second route (none exists), and `tradeId: 0` / invented `itemState` values +(unnecessary — `tradeState` alone carries the distinction). + +### Acceptance test for whatever lands + +```text +move item Club -> Transfer List +leave FUT entirely +re-enter FUT +item appears in the unlisted TRANSFER LIST section +item can be returned to Club +no auction exists unless explicitly listed +``` + +Plus the active lifecycle must remain unchanged: active listing under LISTED ITEMS, +`expires` counting down, active non-selectable, expiry making it actionable, relist +working. + +### Revised domain invariant + +Core pile membership is NOT equivalent to `/tradePile` visibility. Core ownership/pile +state and auction lifecycle are separate authoritative facts; the wire may expose them +through different FIFA 17 resources. **A transition is complete only when a fresh FIFA +session can reconstruct the same user-visible state** — re-entry is the acceptance +test, not the immediate post-mutation response. diff --git a/docs/evidence/market-lifecycle-2026-08-17/cardsdll-route-table.txt b/docs/evidence/market-lifecycle-2026-08-17/cardsdll-route-table.txt new file mode 100644 index 0000000..95f033a --- /dev/null +++ b/docs/evidence/market-lifecycle-2026-08-17/cardsdll-route-table.txt @@ -0,0 +1,61 @@ +image_base=0x180000000 sections=7 + .text rva=0x00001000 vsize=0x1e3f62 rawptr=0x00000400 rawsize=0x1e4000 + .rdata rva=0x001e5000 vsize=0xa4094 rawptr=0x001e4400 rawsize=0xa4200 + .data rva=0x0028a000 vsize=0x65c08 rawptr=0x00288600 rawsize=0x54000 + .pdata rva=0x002f0000 vsize=0x132cc rawptr=0x002dc600 rawsize=0x13400 + .tls rva=0x00304000 vsize=0x9 rawptr=0x002efa00 rawsize=0x200 + .rsrc rva=0x00305000 vsize=0x4e8 rawptr=0x002efc00 rawsize=0x600 + .reloc rva=0x00306000 vsize=0x16bc4 rawptr=0x002f0200 rawsize=0x16c00 + +route table VA 0x18021df80 -> file offset 0x21d380 + +# field A field B +0 'ut/%s/auctionhouse' 'AUCTIONHOUSE' +1 'ut/%s/clubUser' 'CLUB_USER' +2 'ut/%s/user/list' 'CLUB_INFO' +3 'ut/%s/club' 'CLUB' +4 'ut/%s/defid' 'DREAM' +5 'ut/%s/squad' 'SQUAD' +6 'ut/delete/%s/squad' 'DELETE_SQUAD' +7 'ut/%s/leaderboards/options' 'LBOPTIONS' +8 'ut/%s/leaderboards' 'LBDEFAULT' +9 'ut/%s/activeMessage' 'PAFPRACTICE' +10 'ut/%s' 'UT' +11 'ut/%s/user' 'USER' +12 'ut/delete/%s/user' 'DELETEUSER' +13 'ut/%s/item' 'ITEMS' +14 'ut/%s/item/resource' 'ITEMS_BY_RES' +15 'ut/delete/%s/item' 'DELETEITEMS' +16 'ut/%s/match' 'MATCH' +17 'ut/%s/sbs' 'SBC' +18 'ut/%s/tournament' 'TOURNAMENT' +19 'ut/%s/tournament/user' 'TOURNAMENTUSER' +20 'ut/delete/%s/tournament/user' 'TOURNAMENTQUIT' +21 'ut/%s/season' 'SEASON' +22 'ut/%s/season/user' 'SEASONUSER' +23 'ut/%s/season/%%s/user' 'SEASONUSER_ALTER' +24 'ut/%s/season/%%s/reset' 'SEASONRESET' +25 'ut/%s/season/friendly' 'FRIENDLYSEASON' +26 'ut/%s/purchased' 'PURCHASED' +27 'ut/%s/store' 'STORE' +28 'ut/%s/watchList' 'WATCHLIST' +29 'ut/delete/%s/watchList' 'DELETEWATCHLIST' +30 'ut/%s/tradePile' 'TRADEPILE' +31 'ut/%s/trade' 'TRADE' +32 'ut/delete/%s/trade' 'DELETETRADE' +33 'ut/%s/marketdata' 'MARKETDATA' +34 'ut/%s/clientdata' 'CLIENTDATA' +35 'ut/auth' 'AUTH' +36 'ut/delete/auth' 'DELETE_AUTH' +37 'ut/%s/phishing' 'PHISHING' +38 'ut/%s/captcha' 'CAPTCHA' +39 'ut/%s/tfa' 'TFA' +40 'ut/%s/squad/mode' 'SQUADMODE' +41 'ut/%s/draft/mode' 'DRAFT' +42 'ut/%s/champion' 'CHAMPIONS' +43 'ut/v2/%s/store' 'V2STORE' +44 'ut/%s/livemessage' 'LIVEMESSAGE' +45 '' 'ADMIN' +46 '' 'DEBUG' +47 '' 'MAINTENANCE' +-- table ends after 48 rows -- diff --git a/fifa17-recon/tools/route_table_dump.py b/fifa17-recon/tools/route_table_dump.py new file mode 100644 index 0000000..61f949b --- /dev/null +++ b/fifa17-recon/tools/route_table_dump.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +"""Dump CardsDLL's 45-row route table from the ON-DISK PE. READ-ONLY, static. + +The transfer-market analysis locates the table at .rdata 0x18021df80 as +{char*, char*} rows. This resolves VA->file offset properly through the PE section +table rather than assuming a single .text mapping, then prints every row so we can +see whether any route other than `tradePile` could own a trade-pile ITEM list. +""" +import struct, sys + +DLL = "/mnt/games/FIFA 17/CardsDLL_Win64_retail.dll" +TABLE_VA = 0x18021DF80 +MAX_ROWS = 64 + +pe = open(DLL, "rb").read() +e_lfanew = struct.unpack_from(" file offset %s" % (TABLE_VA, hex(base) if base else None)) +assert base, "table VA did not resolve" + +print("\n%-4s %-34s %s" % ("#", "field A", "field B")) +rows = 0 +for i in range(MAX_ROWS): + a_va, b_va = struct.unpack_from("