571c5f9261
Task A, static phase. Ghidra 12.1.2 headless via the repo's own pyghidra harness
over CardsDLL_Win64_retail.dll (13,382 functions). Queries and raw decompiler
output committed under docs/evidence/market-sold-re-2026-08-17/.
RECOVERED FROM THE BINARY
1. No sold token, now EXHAUSTIVELY: both vocabularies dumped to their sentinels
rather than sampled. tradeState is exactly 4 rows; itemState is exactly 12
(invalid/free/WAITING_FOR_GAME/inGame/forSale/offered/activeBadge/
activeHomeKit/activeAwayKit/activeBall/activeStadium/active=255). A sold row
MUST therefore be a combination of existing atoms.
2. What closed does, complete, from the auctionInfo deserializer 0x18013e410:
IS_GLOW = (tradeState==closed) ? bidState != none
: bidState in {outbid, buyNow}
INBOX = bidState in {highest, buyNow}
3. The full record -> Flash map from the publisher 0x1801bf030, superseding the
partial list. The prize: record +0xbf is published as COINS_AWARDED, fed by the
coinsProcessed atom 0x2f4. The corpus had recorded that atom's type and noted
its consumer was never found; it is now traced. DURATION also renders the
localised FUT_AUCTION_EXPIRED when expires underflows.
4. highest vs buyNow on a closed row is UNDECIDABLE from CardsDLL, by proof: both
yield IS_GLOW=1/INBOX=1, bit-identical. But bidState is ALSO published verbatim
as YOURBID alongside STATE and COINS_AWARDED, so the movie does receive the raw
values - the discrimination exists and lives entirely in unread ActionScript.
This retires the question as a static target, and it contradicts the
third-party lore that a seller's sold row is closed+buyNow (the corpus's own
lifecycle table says closed+highest and assigns buyNow to the buyer).
5. The clear-sold verb EXISTS. Builder 0x1801647c0 emits "/sold" when the tradeId
field is zero and "/%lld" otherwise, on route base ut/delete/%s/trade, response
class RS4 FutISRemoveTradeServerResponse. Confirmed by the client's own
request-name table entry RemoveAllSoldFromTradePile. A BULK clear-sold verb only
makes sense if sold rows PERSIST in the seller's pile until cleared, which is
incompatible with our Fix A invariant - so the sold path will require revisiting
it under live validation.
6. The seller's SOLD counter is real, proven end to end with no inference: the hub
tradePile sub-deserializer 0x18013ead0 writes atom sold 0x2c9 to +0x1d8, and the
tile publisher 0x1800b1dc0 renders +0x1d8 as Flash TEXT3 under the localised
caption FUT_TF_SOLD. Siblings: selling -> +0x1d2 -> FUT_TF_SELLING,
count -> +0x1d4 -> FUT_UC_ITEMS, plus FUT_TF_WINNING/FUT_TF_OUTBID on the
Transfer Targets tile. We and the Python oracle both hardcode sold:0, so that
bucket can never fill.
7. Reusable method: an atom id is the INDEX into the alphabetical atom-name pointer
table at base 0x1802d2760. Validated 12/12 against the known auctionInfo atoms
and cross-checked against fifa17-recon/docs/fut_atoms.tsv. Documented gotcha:
resolve a name by the pointer slot INSIDE the table, never by the first matching
string in the binary, or you get confident nonsense.
8. An auction-outcome vocabulary exists (auctionSoldBid 0x39, auctionSoldBuyNow
0x3a, auctionWon*/auctionLost*) but NO deserializer consumes it - every
candidate function was checked for the value-SKIP/atom-loop signature and none
qualifies. Server-side or telemetry only; it does not carry sold state here.
TASK B IS UNDECIDABLE FROM THE CLIENT, and this is a proof of absence: no 0.95 or
0.05 constant of either width, no tax/fee/net/proceeds caption, and no fee
arithmetic anywhere. The client never computes or displays a net, so no experiment
against our own server can measure the rounding - whatever we credit is what it
displays, and there is no oracle. Only an original EA-era seller-balance capture
could settle it. The rule stays an explicit CHOICE (floor the fee, so
fee + proceeds == gross exactly) and is now pinned at the requested boundaries
100/101/119/120/149/150/151/199/200 plus 15,000 and i64::MAX.
Settlement NOT promoted. No production process, port or database was touched.
124 lines
6.7 KiB
Plaintext
124 lines
6.7 KiB
Plaintext
==============================================================================
|
|
== route table continued from 0x18021e0f8 until it stops looking like one
|
|
==============================================================================
|
|
0x18021e100 -> 0x18021e600 'ut/%s/season/%%s/reset'
|
|
0x18021e108 -> 0x18021e618 'SEASONRESET'
|
|
0x18021e110 -> 0x18021e628 'ut/%s/season/friendly'
|
|
0x18021e118 -> 0x18021e640 'FRIENDLYSEASON'
|
|
0x18021e120 -> 0x18021e650 'ut/%s/purchased'
|
|
0x18021e128 -> 0x18021e660 'PURCHASED'
|
|
0x18021e130 -> 0x18021e670 'ut/%s/store'
|
|
0x18021e138 -> 0x18021e67c 'STORE'
|
|
0x18021e140 -> 0x18021e688 'ut/%s/watchList'
|
|
0x18021e148 -> 0x18021e698 'WATCHLIST'
|
|
0x18021e150 -> 0x18021e6a8 'ut/delete/%s/watchList'
|
|
0x18021e158 -> 0x18021e6c0 'DELETEWATCHLIST'
|
|
0x18021e160 -> 0x18021e6d0 'ut/%s/tradePile'
|
|
0x18021e168 -> 0x18021e6e0 'TRADEPILE'
|
|
0x18021e170 -> 0x18021e6f0 'ut/%s/trade'
|
|
0x18021e178 -> 0x18021e6fc 'TRADE'
|
|
0x18021e180 -> 0x18021e708 'ut/delete/%s/trade'
|
|
0x18021e188 -> 0x18021e720 'DELETETRADE'
|
|
0x18021e190 -> 0x18021e730 'ut/%s/marketdata'
|
|
0x18021e198 -> 0x18021e748 'MARKETDATA'
|
|
0x18021e1a0 -> 0x18021e758 'ut/%s/clientdata'
|
|
0x18021e1a8 -> 0x18021e770 'CLIENTDATA'
|
|
0x18021e1b0 -> 0x18021e780 'ut/auth'
|
|
0x18021e1b8 -> 0x18021e788 'AUTH'
|
|
0x18021e1c0 -> 0x18021e790 'ut/delete/auth'
|
|
0x18021e1c8 -> 0x18021e7a0 'DELETE_AUTH'
|
|
0x18021e1d0 -> 0x18021e7b0 'ut/%s/phishing'
|
|
0x18021e1d8 -> 0x18021e7c0 'PHISHING'
|
|
0x18021e1e0 -> 0x18021e7d0 'ut/%s/captcha'
|
|
0x18021e1e8 -> 0x18021e7e0 'CAPTCHA'
|
|
0x18021e1f0 -> 0x18021e7e8 'ut/%s/tfa'
|
|
0x18021e1f8 -> 0x18021e7f4 'TFA'
|
|
0x18021e200 -> 0x18021e7f8 'ut/%s/squad/mode'
|
|
0x18021e208 -> 0x18021e810 'SQUADMODE'
|
|
0x18021e210 -> 0x18021e820 'ut/%s/draft/mode'
|
|
0x18021e218 -> 0x18021e834 'DRAFT'
|
|
0x18021e220 -> 0x18021e840 'ut/%s/champion'
|
|
0x18021e228 -> 0x18021e850 'CHAMPIONS'
|
|
0x18021e230 -> 0x18021e860 'ut/v2/%s/store'
|
|
0x18021e238 -> 0x18021e870 'V2STORE'
|
|
0x18021e240 -> 0x18021e878 'ut/%s/livemessage'
|
|
0x18021e248 -> 0x18021e890 'LIVEMESSAGE'
|
|
0x18021e258 -> 0x18021e89c 'ADMIN'
|
|
0x18021e268 -> 0x18021e8a4 'DEBUG'
|
|
0x18021e278 -> 0x18021e8b0 'MAINTENANCE'
|
|
|
|
==============================================================================
|
|
== itemState table 0x180229cc0 (prior work: 12 rows) — dump generously
|
|
==============================================================================
|
|
[ 0] 0x180229cc0 str=0x18022a2a0 'invalid' val=0 (0x0)
|
|
[ 1] 0x180229cd0 str=0x18022a2a8 'free' val=1 (0x1)
|
|
[ 2] 0x180229ce0 str=0x18022a2b0 'WAITING_FOR_GAME' val=2 (0x2)
|
|
[ 3] 0x180229cf0 str=0x18022a2c4 'inGame' val=2 (0x2)
|
|
[ 4] 0x180229d00 str=0x18022a2d0 'forSale' val=5 (0x5)
|
|
[ 5] 0x180229d10 str=0x18022a2d8 'offered' val=6 (0x6)
|
|
[ 6] 0x180229d20 str=0x18022a2e0 'activeBadge' val=100 (0x64)
|
|
[ 7] 0x180229d30 str=0x18022a2f0 'activeHomeKit' val=101 (0x65)
|
|
[ 8] 0x180229d40 str=0x18022a300 'activeAwayKit' val=102 (0x66)
|
|
[ 9] 0x180229d50 str=0x18022a310 'activeBall' val=103 (0x67)
|
|
[10] 0x180229d60 str=0x18022a320 'activeStadium' val=104 (0x68)
|
|
[11] 0x180229d70 str=0x180221c04 'active' val=255 (0xff)
|
|
[12] 0x180229d80 str=0x0 '' val=4294967295 (0xffffffff)
|
|
[13] 0x180229d90 str=0x18022a330 'none' val=8 (0x8)
|
|
[14] 0x180229da0 str=0x18022a338 'head' val=0 (0x0)
|
|
[15] 0x180229db0 str=0x18022a340 'shoulder' val=1 (0x1)
|
|
[16] 0x180229dc0 str=0x18022a34c 'arm' val=2 (0x2)
|
|
[17] 0x180229dd0 str=0x1802009d0 'back' val=3 (0x3)
|
|
[18] 0x180229de0 str=0x18022a350 'hip' val=4 (0x4)
|
|
[19] 0x180229df0 str=0x18022a354 'leg' val=5 (0x5)
|
|
|
|
==============================================================================
|
|
== tradeState table 0x180229e40 — dump generously
|
|
==============================================================================
|
|
[ 0] 0x180229e40 str=0x180221c04 'active' val=1 (0x1)
|
|
[ 1] 0x180229e50 str=0x18022a360 'inactive' val=2 (0x2)
|
|
[ 2] 0x180229e60 str=0x18022a370 'expired' val=3 (0x3)
|
|
[ 3] 0x180229e70 str=0x18022a378 'closed' val=4 (0x4)
|
|
[ 4] 0x180229e80 str=0x0 '' val=0 (0x0)
|
|
[ 5] 0x180229e90 str=0x4e4f5f53544f4853 '' val=1380013151 (0x5241545f)
|
|
[ 6] 0x180229ea0 str=0x4653534543435553 '' val=1415531605 (0x545f4c55)
|
|
[ 7] 0x180229eb0 str=0x5345 '' val=1314017091 (0x4e524f43)
|
|
[ 8] 0x180229ec0 str=0x4548534e41454c43 '' val=5461061 (0x535445)
|
|
[ 9] 0x180229ed0 str=0x5f474e4953534150 '' val=1129465168 (0x43524550)
|
|
[10] 0x180229ee0 str=0x4547 '' val=1397968720 (0x53534f50)
|
|
[11] 0x180229ef0 str=0x45435245505f4e4f '' val=1195463758 (0x4741544e)
|
|
|
|
==============================================================================
|
|
== pointer arrays containing the sold vocabulary
|
|
==============================================================================
|
|
auctionSoldBid pointer found at: ['0x1802d2928']
|
|
auctionSoldBuyNow pointer found at: ['0x1802d2930']
|
|
auctionWonBuyNow pointer found at: ['0x1802d2940']
|
|
sold@22f524 pointer found at: ['0x1802d3da8']
|
|
sold@228bed pointer found at: NOWHERE
|
|
SoldFromTradePile pointer found at: NOWHERE
|
|
SOLD@20a0c7 pointer found at: NOWHERE
|
|
|
|
==============================================================================
|
|
== the .data array around 0x1802d3da8 (the one DATA xref to 'sold')
|
|
==============================================================================
|
|
0x1802d3d68 -> 0x1801e98ac 'shots'
|
|
0x1802d3d70 -> 0x18022f4c8 'shotsOnTarget'
|
|
0x1802d3d78 -> 0x18022f4d8 'silhouetteName'
|
|
0x1802d3d80 -> 0x18022f4e8 'silName'
|
|
0x1802d3d88 -> 0x1801ec108 'silver'
|
|
0x1802d3d90 -> 0x18022f4f0 'silverQuantity'
|
|
0x1802d3d98 -> 0x18022f500 'sizeBeforeEncode'
|
|
0x1802d3da0 -> 0x18022f518 'slotIndex'
|
|
0x1802d3da8 -> 0x18022f524 'sold' <<<
|
|
0x1802d3db0 -> 0x18022f52c 'sort'
|
|
0x1802d3db8 -> 0x18022f538 'sortPriority'
|
|
0x1802d3dc0 -> 0x18022ed30 'source'
|
|
0x1802d3dc8 -> 0x18022f548 'squad'
|
|
0x1802d3dd0 -> 0x18022f550 'squadActives'
|
|
0x1802d3dd8 -> 0x18022f560 'squadBuildingSetsClientData'
|
|
0x1802d3de0 -> 0x18022f580 'squadBuildingSetsGracePeriodMinutes'
|
|
0x1802d3de8 -> 0x18022f5a8 'squadChallenge'
|
|
0x1802d3df0 -> 0x18022f5b8 'squadId'
|
|
0x1802d3df8 -> 0x18022f5c0 'squadName'
|
|
0x1802d3e00 -> 0x18022f5d0 'squadList'
|