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.
115 lines
4.5 KiB
Plaintext
115 lines
4.5 KiB
Plaintext
==============================================================================
|
|
== neighbourhood of SoldBid/SoldBuyNow (0x1802302cf) — read the whole table
|
|
==============================================================================
|
|
0x180230280 'auctionLostBidRejected'
|
|
0x180230298 'auctionLostOutbid'
|
|
0x1802302b0 'auctionLostOutbidSelf'
|
|
0x1802302c8 'auctionSoldBid'
|
|
0x1802302d8 'auctionSoldBuyNow'
|
|
0x1802302f0 'auctionWonBid'
|
|
0x180230300 'auctionWonBuyNow'
|
|
0x180230318 'authToken'
|
|
0x180230328 'available'
|
|
0x180230338 'awaykit'
|
|
0x180230340 'awardCount'
|
|
0x180230350 'awardedPrizes'
|
|
0x180230360 'awardItemData'
|
|
0x180230370 'awardMappings'
|
|
0x180230380 'awardType'
|
|
0x180230390 'awardSet'
|
|
0x1802303a0 'awardSetId'
|
|
0x1802303ac 'awards'
|
|
0x1802303b8 'awardValue'
|
|
0x1802303c8 'badgeDBid'
|
|
0x1802303d4 'badges'
|
|
0x1802303dc 'balls'
|
|
0x1802303e8 'BEGINNER'
|
|
0x1802303f8 'bestBuilderScore'
|
|
|
|
==============================================================================
|
|
== xrefs to each sold-ish string
|
|
==============================================================================
|
|
sold@228bed 0 xref(s)
|
|
sold@22f524 1 xref(s)
|
|
from 0x1802d3da8 DATA in ? (entry 0x0)
|
|
SoldFromTradePile 0 xref(s)
|
|
SoldBid 0 xref(s)
|
|
SoldBuyNow 0 xref(s)
|
|
SOLD@20a0c7 0 xref(s)
|
|
|
|
==============================================================================
|
|
== who reads record +0xbf (COINS_AWARDED) / +0xbe (INBOX) / +0xbd (IS_GLOW)
|
|
== i.e. the deserializer's coinsProcessed + the two computed flags
|
|
==============================================================================
|
|
-- decoder for atom 0x2f4 (coinsProcessed): FUN_1800d7b50
|
|
|
|
ulonglong FUN_1800d7b50(longlong param_1)
|
|
|
|
{
|
|
ulonglong in_RAX;
|
|
byte bVar1;
|
|
|
|
if (0 < param_1) {
|
|
bVar1 = 0xff;
|
|
if (param_1 < 0xff) {
|
|
bVar1 = (byte)param_1;
|
|
}
|
|
return (ulonglong)bVar1;
|
|
}
|
|
return in_RAX & 0xffffffffffffff00;
|
|
}
|
|
|
|
|
|
|
|
==============================================================================
|
|
== route table 0x18021df80 — all 45 rows, looking for a clear/sold route
|
|
==============================================================================
|
|
[ 0] 0x18021df80 -> 0x18021e308 'ut/%s/auctionhouse'
|
|
[ 1] 0x18021df88 -> 0x18021e320 'AUCTIONHOUSE'
|
|
[ 2] 0x18021df90 -> 0x18021e330 'ut/%s/clubUser'
|
|
[ 3] 0x18021df98 -> 0x18021e340 'CLUB_USER'
|
|
[ 4] 0x18021dfa0 -> 0x18021e350 'ut/%s/user/list'
|
|
[ 5] 0x18021dfa8 -> 0x1801fa0b8 'CLUB_INFO'
|
|
[ 6] 0x18021dfb0 -> 0x18021e360 'ut/%s/club'
|
|
[ 7] 0x18021dfb8 -> 0x1801f6438 'CLUB'
|
|
[ 8] 0x18021dfc0 -> 0x18021e370 'ut/%s/defid'
|
|
[ 9] 0x18021dfc8 -> 0x18021e37c 'DREAM'
|
|
[10] 0x18021dfd0 -> 0x18021e388 'ut/%s/squad'
|
|
[11] 0x18021dfd8 -> 0x18021e394 'SQUAD'
|
|
[12] 0x18021dfe0 -> 0x18021e3a0 'ut/delete/%s/squad'
|
|
[13] 0x18021dfe8 -> 0x18021e3b8 'DELETE_SQUAD'
|
|
[14] 0x18021dff0 -> 0x18021e3c8 'ut/%s/leaderboards/options'
|
|
[15] 0x18021dff8 -> 0x18021e3e8 'LBOPTIONS'
|
|
[16] 0x18021e000 -> 0x18021e3f8 'ut/%s/leaderboards'
|
|
[17] 0x18021e008 -> 0x18021e410 'LBDEFAULT'
|
|
[18] 0x18021e010 -> 0x18021e420 'ut/%s/activeMessage'
|
|
[19] 0x18021e018 -> 0x18021e438 'PAFPRACTICE'
|
|
[20] 0x18021e020 -> 0x18021e444 'ut/%s'
|
|
[21] 0x18021e028 -> 0x18021e44c 'UT'
|
|
[22] 0x18021e030 -> 0x18021e450 'ut/%s/user'
|
|
[23] 0x18021e038 -> 0x18021e45c 'USER'
|
|
[24] 0x18021e040 -> 0x18021e468 'ut/delete/%s/user'
|
|
[25] 0x18021e048 -> 0x18021e480 'DELETEUSER'
|
|
[26] 0x18021e050 -> 0x18021e490 'ut/%s/item'
|
|
[27] 0x18021e058 -> 0x18021e49c 'ITEMS'
|
|
[28] 0x18021e060 -> 0x18021e4a8 'ut/%s/item/resource'
|
|
[29] 0x18021e068 -> 0x18021e4c0 'ITEMS_BY_RES'
|
|
[30] 0x18021e070 -> 0x18021e4d0 'ut/delete/%s/item'
|
|
[31] 0x18021e078 -> 0x18021e4e8 'DELETEITEMS'
|
|
[32] 0x18021e080 -> 0x18021e4f8 'ut/%s/match'
|
|
[33] 0x18021e088 -> 0x1801ecc34 'MATCH'
|
|
[34] 0x18021e090 -> 0x18021e508 'ut/%s/sbs'
|
|
[35] 0x18021e098 -> 0x18021e514 'SBC'
|
|
[36] 0x18021e0a0 -> 0x18021e518 'ut/%s/tournament'
|
|
[37] 0x18021e0a8 -> 0x18021e530 'TOURNAMENT'
|
|
[38] 0x18021e0b0 -> 0x18021e540 'ut/%s/tournament/user'
|
|
[39] 0x18021e0b8 -> 0x18021e558 'TOURNAMENTUSER'
|
|
[40] 0x18021e0c0 -> 0x18021e568 'ut/delete/%s/tournament/user'
|
|
[41] 0x18021e0c8 -> 0x18021e588 'TOURNAMENTQUIT'
|
|
[42] 0x18021e0d0 -> 0x18021e598 'ut/%s/season'
|
|
[43] 0x18021e0d8 -> 0x180209fc4 'SEASON'
|
|
[44] 0x18021e0e0 -> 0x18021e5a8 'ut/%s/season/user'
|
|
[45] 0x18021e0e8 -> 0x18021e5c0 'SEASONUSER'
|
|
[46] 0x18021e0f0 -> 0x18021e5d0 'ut/%s/season/%%s/user'
|
|
[47] 0x18021e0f8 -> 0x18021e5e8 'SEASONUSER_ALTER'
|