diff --git a/docs/FIFA17_SOLD_WIRE_RE.md b/docs/FIFA17_SOLD_WIRE_RE.md new file mode 100644 index 0000000..2c6b0e9 --- /dev/null +++ b/docs/FIFA17_SOLD_WIRE_RE.md @@ -0,0 +1,241 @@ +# FIFA 17 seller-facing SOLD state — static recovery from CardsDLL + +Reverse engineering of `CardsDLL_Win64_retail.dll` +(md5 `4de3493131d7d2ff7f8b360c5ac9b655`), Ghidra 12.1.2 headless via pyghidra, +13,382 functions, image base `0x180000000`. Queries and raw output: +`docs/evidence/market-sold-re-2026-08-17/`. + +Confidence tags: **[PE]** read from the binary · **[PE-PROOF]** a proven *negative* +(an exhaustive absence) · **[PLAN]** corpus prose, not a decompile · **[UNDECIDABLE]** +proven not answerable from this binary. + +--- + +## 1. There is no `sold` token, and now it is exhaustive + +Both vocabularies dumped in full to their sentinels, not sampled. **[PE]** + +`tradeState` table `0x180229e40` — exactly 4 rows, then a `{0,0}` terminator: + +```text +active=1 inactive=2 expired=3 closed=4 +``` + +`itemState` table `0x180229cc0` — exactly 12 rows, then a `{0,-1}` terminator: + +```text +invalid=0 free=1 WAITING_FOR_GAME=2 inGame=2 forSale=5 offered=6 +activeBadge=100 activeHomeKit=101 activeAwayKit=102 activeBall=103 +activeStadium=104 active=255 +``` + +Neither contains `sold`. So the seller's sold state **must** be a combination of +existing atoms. This closes the question that previously rested on a partial dump. + +## 2. What `closed` actually does — the complete flag computation + +Decompiled from the `auctionInfo` deserializer `0x18013e410`. `local_70` is +`tradeState`, `local_40` is `bidState`. **[PE]** + +```c +if (local_70 == 4) { /* tradeState == closed */ + local_3b = local_40 != 0; /* bidState != none */ +} else { + local_3b = (local_40 - 1U & 0xfffffffd) == 0; /* bidState in {1,3} */ +} +local_3a = local_40 - 2U < 2; /* bidState in {2,3} */ +``` + +Evaluated: + +| | `none`(0) | `outbid`(1) | `highest`(2) | `buyNow`(3) | +|---|---|---|---|---| +| `local_3b`, tradeState==closed | 0 | **1** | **1** | **1** | +| `local_3b`, otherwise | 0 | 1 | 0 | 1 | +| `local_3a` (any tradeState) | 0 | 0 | **1** | **1** | + +## 3. The complete record → Flash mapping + +From the publisher `0x1801bf030`, every property it sets, with its record offset. +This supersedes the previous partial list. **[PE]** + +| Flash property | record | meaning | +|---|---|---| +| `TRADEID_LOWER` / `TRADEID_UPPER` | +0x38 | tradeId, split into two 32-bit halves | +| `DURATION` | +0x90 | formatted; **`FUT_AUCTION_EXPIRED`** when the value underflows (i.e. `expires == 0`) | +| `TIME_REMAINING` | +0x90 | expires, seconds | +| `MIN_CREDITS` | +0x78 | currentBid | +| `MAX_CREDITS` | +0x70 | buyNowPrice | +| `RESERVEDPRICE` | +0x74 | startingBid | +| `YOURBID` | +0xb8 | **bidState, passed through verbatim** | +| `STATE` | +0x88 | **tradeState, passed through verbatim** | +| **`COINS_AWARDED`** | **+0xbf** | **the `coinsProcessed` atom (0x2f4), u8** | +| `UUID_UPPER` / `UUID_LOWER` | itemData+8 | | +| `CARD_ID` / `FIFA_ID` | itemData+0x18 | `FIFA_ID` masks `& 0xffffff` | +| `CARD_TYPE` | itemData+0x4c | | +| `CARD_OFFERSTATE` | itemData+0x5c | itemState | +| `IS_WATCHED` | +0xbc | `watched` atom | +| `INBOX` | +0xbe | `local_3a` — bidState ∈ {highest, buyNow} | +| `IS_GLOW` | +0xbd | `local_3b` — the table in §2 | +| `TRADE_DATA_AVAILABLE` | — | constant 1 | + +**`coinsProcessed`'s consumer is now traced.** The corpus recorded its type and +noted that no consumer had ever been found; it is published to the movie as +**`COINS_AWARDED`**. That is a settlement/"you have been paid" signal, exactly as +the corpus guessed but never demonstrated. + +## 4. `highest` vs `buyNow` on a closed row is UNDECIDABLE from CardsDLL + +**[UNDECIDABLE]**, and this is a proof, not a failed search. + +For `tradeState == closed`, §2 gives `IS_GLOW = (bidState != none)` and +`INBOX = (bidState ∈ {highest, buyNow})`. Both `highest`(2) and `buyNow`(3) +therefore produce **`IS_GLOW=1, INBOX=1`** — bit-identical. No native consumer can +tell them apart. + +But §3 sharpens *why* it is undecidable: `bidState` is **not** consumed only through +those flags. It is published verbatim as `YOURBID`, alongside `STATE` and +`COINS_AWARDED`. The movie receives the raw values. So the discrimination exists — +it just lives entirely in the APT/ActionScript front end, which is unread. + +Consequence: **no amount of further CardsDLL work can answer "which `bidState` does +a seller see".** Only an AVM1 read of `external/ion_fut/screens/trading/tradepile`, +or a live behavioural A/B, can. This retires the question as a static target. + +The corpus's own lifecycle table (`fifa17-recon/docs/plan-2026-08-06-transfer-market.md:720-729`) +asserts the seller sees `closed` / **`highest`** with `coinsProcessed 1`, and assigns +`closed` / `buyNow` to the *buyer*. That is **[PLAN]**, and it **contradicts** the +common third-party lore that a sold seller row is `closed` + `buyNow`. Given §4 the +contradiction cannot be resolved statically — but note the corpus reading is the one +that leaves `buyNow` meaning "*I* bought it now", which is self-consistent with +`YOURBID` being a property about the viewer's own bid. + +## 5. The clear-sold verb EXISTS — PE-proven + +The request builder `0x1801647c0`: **[PE]** + +```c +if (*(longlong *)(param_1 + 0x10) == 0) { + FUN_180007f80(&local_38, 0x20, "/sold"); /* no tradeId -> bulk */ +} else { + FUN_180007f80(&local_38, 0x20, "/%lld"); /* one specific tradeId */ +} +``` + +One builder, two forms, on route base `ut/delete/%s/trade` (`DELETETRADE`), response +class `RS4:FutISRemoveTradeServerResponse` (`0x180228bc8`, with the `/sold` literal +at `0x180228bec` immediately after it): + +```text +DELETE ut/delete/{ns}/trade/{tradeId} remove one trade +DELETE ut/delete/{ns}/trade/sold remove ALL sold trades +``` + +Corroborated by the client's own request-name table, where +**`RemoveAllSoldFromTradePile`** (`0x1801efae8`) sits beside `RemoveFromTradePile`, +`AddToWatchList` and `RemoveFromWatchList`. + +**Architectural consequence.** A bulk "remove all sold" verb only makes sense if +sold rows **persist in the seller's pile until explicitly cleared**. That is +incompatible with our current host, where a sold/cancelled listing leaves +`/tradePile` the instant the CAS lands (the Fix A invariant). Implementing the sold +path will require revisiting that invariant — and doing so needs live validation, +because Fix A itself was a live-confirmed correction. + +## 6. The seller-facing SOLD counter is real, and we hardcode it to 0 + +A complete chain, wire atom → struct → Flash → localised caption, with no inference +at any step. **[PE]** + +Hub `tradePile` sub-deserializer `0x18013ead0`: + +| atom | id | writes | Flash slot (publisher `0x1800b1dc0`, tile `0x1c0`) | +|---|---|---|---| +| `count` | 0xbc | +0x1d4 | `TEXT0` with caption `FUT_UC_ITEMS` | +| `notification` | 0x1da | +0x1d6 | `NOTIFICATION`, capped at 99 | +| `selling` | 0x2b8 | +0x1d2 (**and** counts-struct +0x36) | `TEXT2` with caption `FUT_TF_SELLING` | +| **`sold`** | **0x2c9** | **+0x1d8** | **`TEXT3` with caption `FUT_TF_SOLD`** | + +The sibling tile `0x1d0` (Transfer Targets) uses `FUT_TF_WINNING` (+0x1c8) and +`FUT_TF_OUTBID`. `selling` writing two structs independently re-confirms the +corpus's counts-struct offset for `selling`. + +So FIFA 17 renders a **SOLD** count to the seller, sourced from atom `sold` (0x2c9). +Our host and the Python oracle both hardcode `sold: 0`, so that bucket can never +populate. This is direct client evidence about `sold` — the thing +`MARKET_SOLD_SETTLEMENT.md` required before touching `/tradePile/counts`. It +establishes that `sold` is *displayed*; it does **not** yet establish what should be +counted in it (rows awaiting clear? rows sold this session?). + +## 7. Atom-name decoder (method note, reusable) + +Atom IDs are **indices into an alphabetically sorted pointer table** of atom-name +strings, base `0x1802d2760`. Validated against all twelve known `auctionInfo` +atoms — 12/12 agree — and cross-checked against `fifa17-recon/docs/fut_atoms.tsv`. +**[PE]** + +```text +atom_id = (pointer_slot_address - 0x1802d2760) / 8 +``` + +Newly resolved: `sold`=0x2c9, `count`=0xbc, `offered`=0x1e5, `selling`=0x2b8, +`maxAuctionsAllowed`=0x1bf, `credits`=0xc0, `auctionInfo`=0x35, `total`=0x325, +`duplicateItemIdList`=0xec, `itemState`=0x172, `offers`=0x1e6, `coins`=0x95. + +Caution when using it: look up a name by finding the pointer slot **inside the table +range**, not by taking the first matching string in the binary. Common words such as +`sold`, `offered` and `count` appear in several unrelated tables, and taking the +first hit produces confident nonsense (it initially reported `sold` as ABSENT and +`offered` as a negative index). + +## 8. An auction-outcome vocabulary exists but this client ignores it + +The atom table contains a 7-value outcome vocabulary: **[PE]** + +```text +0x36 auctionLostBidRejected 0x39 auctionSoldBid 0x3b auctionWonBid +0x37 auctionLostOutbid 0x3a auctionSoldBuyNow 0x3c auctionWonBuyNow +0x38 auctionLostOutbidSelf +``` + +These would distinguish seller-sold-by-bid from seller-sold-by-buy-now explicitly. +**No CardsDLL deserializer consumes them.** Every candidate function that compares +against three or more of `0x36..0x3c` was checked and none is an atom dispatcher +(no value-SKIP `0x180135ff0`, no atom loop `0x1801c7f10`) — they are small-immediate +coincidences. **[PE-PROOF]** So the vocabulary is server-side or telemetry, and it +does not carry the sold state to this client. + +## 9. The 5% fee is NOT in the client — Task B is undecidable from here + +**[PE-PROOF]**, three independent absences: + +* no `0.95` or `0.05` constant, `double` or `float`, anywhere in the binary; +* no localisation key for tax/fee/net/proceeds/commission/"you will receive" — the + only `FUT_TF_*` keys in the binary are `SELLING`, `SOLD`, `WINNING`, `OUTBID`; +* the 17 functions using both `5`/`95` and `100` as immediates are all unrelated + (they include the item deserializer) — no fee arithmetic exists. + +The client therefore never computes or displays a net. It learns the seller's +balance only from `credits`. + +**This makes the rounding rule unmeasurable by experiment against our own server.** +Whatever we credit is what the client displays; there is no client-side expectation +to compare against, so there is no oracle. The only evidence that could settle +floor-the-fee (150 → 143) versus floor-the-proceeds (150 → 142) is an original +EA-era capture of a seller's balance across a known-price sale, which we do not +have. + +Accordingly the rule stays a **documented choice**: fee = `floor(gross × 5 / 100)`, +proceeds = `gross − fee`, chosen because `fee + proceeds == gross` holds exactly at +every input. It is pinned by regression tests at 0, 1, 19, 20, 21, 39, 40, 100, 101, +119, 120, 149, 150, 151, 199, 200, 1 000, 15 000, 15 000 000 and `i64::MAX`, so it +cannot drift silently. + +## 10. What would close the remaining gaps + +| Gap | Only remaining route | +|---|---| +| `highest` vs `buyNow` on a seller's sold row | AVM1 disassembly of `tradepile.isInActiveAuction` / `PreCheckCardOptions`, or a live behavioural A/B (the movie gets `YOURBID` verbatim, so the two ARE separable by the client's behaviour) | +| What `sold` should count | live observation with a real sold row | +| Whether sold rows persist until cleared | live: does the client issue `DELETE .../trade/sold`? | +| The 5% rounding rule | an original EA-era seller-balance capture; nothing in reach | diff --git a/docs/MARKET_SOLD_SETTLEMENT.md b/docs/MARKET_SOLD_SETTLEMENT.md index fa309c0..19cff6b 100644 --- a/docs/MARKET_SOLD_SETTLEMENT.md +++ b/docs/MARKET_SOLD_SETTLEMENT.md @@ -344,8 +344,8 @@ Two things it found that the unit tests had not: | A sold card leaves the seller's lineup | **TESTED** | | Invalid sales leave the economy unchanged | **TESTED** | | The fee rate is 5% of gross | **FIFA17-HISTORICAL** | -| The fee is floored (150 → 143, not 142) | **NEEDS-LIVE** | -| The seller-facing sold wire state | **NEEDS-LIVE** — not implemented, not guessed | +| The fee is floored (150 → 143, not 142) | **UNDECIDABLE from available evidence** — CardsDLL contains no fee arithmetic, no `0.95`/`0.05` constant and no tax/net caption, so the client never computes or displays a net and there is no oracle to measure against. See `FIFA17_SOLD_WIRE_RE.md` §9. Remains a pinned CHOICE. | +| The seller-facing sold wire state | **PARTIALLY RECOVERED** from the binary — see `FIFA17_SOLD_WIRE_RE.md`. Proven: no `sold` token in either vocabulary (exhaustive); `coinsProcessed` is published to Flash as `COINS_AWARDED`; a bulk clear-sold verb `DELETE …/trade/sold` exists; the seller's SOLD counter is real (atom `sold` 0x2c9 → `FUT_TF_SOLD`). Still UNDECIDABLE statically: `highest` vs `buyNow`. | | The client renders a settled sale correctly | **NEEDS-LIVE** — never exercised | No Core test here establishes anything about the FIFA 17 wire contract. They diff --git a/docs/evidence/market-sold-re-2026-08-17/q_sold_1.out b/docs/evidence/market-sold-re-2026-08-17/q_sold_1.out new file mode 100644 index 0000000..2ffcd5d --- /dev/null +++ b/docs/evidence/market-sold-re-2026-08-17/q_sold_1.out @@ -0,0 +1,308 @@ +============================================================================== +== auctionInfo deserializer 0x18013e410 — full decompilation +============================================================================== + +/* WARNING: Function: __security_check_cookie replaced with injection: security_check_cookie */ + +undefined4 FUN_18013e410(undefined8 param_1,longlong param_2,undefined8 param_3) + +{ + longlong *plVar1; + undefined1 *puVar2; + undefined1 *puVar3; + undefined ***pppuVar4; + undefined ***pppuVar5; + char cVar6; + int iVar7; + int iVar8; + undefined8 uVar9; + longlong *plVar10; + undefined4 uVar11; + undefined1 auStack_138 [32]; + undefined **local_118; + undefined8 local_110; + longlong local_108; + undefined8 local_100; + undefined **local_f8; + undefined1 local_f0 [32]; + undefined1 local_d0; + longlong *local_c8; + undefined8 local_c0; + undefined1 local_b0 [36]; + undefined4 local_8c; + undefined4 local_88; + undefined4 local_84; + undefined4 local_80; + int local_70; + undefined8 local_68; + undefined1 local_58 [8]; + longlong local_50; + longlong *local_48; + int local_40; + undefined1 local_3c; + undefined1 local_3b; + undefined1 local_3a; + undefined1 local_39; + ulonglong local_38; + + local_100 = 0xfffffffffffffffe; + local_38 = DAT_1802db6e0 ^ (ulonglong)auStack_138; + iVar8 = 0x38c; + FUN_18010f5f0(&local_f8); + iVar7 = FUN_1801c7f10(param_2); + while (iVar7 != 10) { + if (*(int *)(param_2 + 0xd0) == 0xb) { + iVar8 = FUN_180180d00(*(undefined8 *)(param_2 + 0xf8)); + iVar7 = FUN_1801c7f10(param_2); + if (iVar7 != 6) goto LAB_18013e498; + } + else { +LAB_18013e498: + if (iVar8 < 0x2b8) { + if (iVar8 == 0x2b7) { + uVar9 = FUN_1801c7aa0(param_2); + FUN_180008120(local_b0,uVar9,0x1e); + } + else if (iVar8 < 0x117) { + if (iVar8 == 0x116) { + local_68 = FUN_1801c79d0(param_2); + } + else if (iVar8 == 0x57) { + uVar9 = FUN_1801c7aa0(param_2); + local_40 = FUN_180166380(uVar9); + } + else if (iVar8 == 0x65) { + uVar9 = FUN_1801c79d0(param_2); + local_88 = FUN_1800d7b30(uVar9); + } + else { + if (iVar8 != 0xc1) goto LAB_18013e59b; + uVar9 = FUN_1801c79d0(param_2); + local_80 = FUN_1800d7b30(uVar9); + } + } + else if (iVar8 == 0x16b) { + FUN_18013fe00(local_58,param_2,0); + } + else if (iVar8 == 0x2b6) { + uVar9 = FUN_1801c79d0(param_2); + local_8c = FUN_1800d7b30(uVar9); + } + else { +LAB_18013e59b: + FUN_180135ff0(iVar8,param_2); + } + } + else if (iVar8 == 0x2e6) { + uVar9 = FUN_1801c79d0(param_2); + local_84 = FUN_1800d7b30(uVar9); + } + else if (iVar8 == 0x2f4) { + uVar9 = FUN_1801c79d0(param_2); + local_39 = FUN_1800d7b50(uVar9); + } + else if (iVar8 == 0x331) { + local_c0 = FUN_1801c79d0(param_2); + } + else if (iVar8 == 0x335) { + uVar9 = FUN_1801c7aa0(param_2); + local_70 = FUN_180166bd0(uVar9); + } + else { + if (iVar8 != 0x380) goto LAB_18013e59b; + cVar6 = FUN_1801c7620(param_2); + local_3c = cVar6 != '\0'; + } + } + iVar7 = FUN_1801c7f10(param_2); + } + if (local_70 == 4) { + local_3b = local_40 != 0; + } + else { + local_3b = (local_40 - 1U & 0xfffffffd) == 0; + } + local_3a = local_40 - 2U < 2; + local_d0 = 1; + FUN_180007f60(local_f0,param_3,1); + plVar10 = (longlong *)FUN_18011a830(); + if (local_48 == (longlong *)0x0) { + local_110 = 0; + local_118 = &PTR_LAB_180214de8; + local_108 = 0; + (**(code **)(*plVar10 + 0xa00))(plVar10,&local_118,local_c0); + if (local_108 == 0) { + uVar11 = 1; + goto LAB_18013e795; + } + if ((local_58 != (undefined1 *)(local_108 + 0xa0)) && + (plVar1 = *(longlong **)(local_108 + 0xb0), local_48 != plVar1)) { + if (local_48 != (longlong *)0x0) { + puVar2 = (undefined1 *)*local_48; + puVar3 = *(undefined1 **)(puVar2 + 8); + if (local_58 == puVar2) { + *local_48 = (longlong)puVar3; + } + else { + for (; puVar3 != (undefined1 *)0x0; puVar3 = *(undefined1 **)(puVar3 + 8)) { + if (puVar3 == local_58) { + *(undefined8 *)(puVar2 + 8) = *(undefined8 *)(puVar3 + 8); + break; + } + puVar2 = puVar3; + } + } + } + local_48 = plVar1; + if (plVar1 != (longlong *)0x0) { + local_50 = *plVar1; + *plVar1 = (longlong)local_58; + } + } + local_118 = &PTR_LAB_180214de8; + if (local_108 != 0) { + pppuVar4 = *(undefined ****)(local_108 + 0x30); + pppuVar5 = (undefined ***)pppuVar4[1]; + if (&local_118 == pppuVar4) { + *(undefined ****)(local_108 + 0x30) = pppuVar5; + } + else { + for (; pppuVar5 != (undefined ***)0x0; pppuVar5 = (undefined ***)pppuVar5[1]) { + if (pppuVar5 == &local_118) { + pppuVar4[1] = pppuVar5[1]; + break; + } + pppuVar4 = pppuVar5; + } + } + } + } + (**(code **)(*plVar10 + 0x9f8))(plVar10,param_1,&local_f8); + uVar11 = 10; +LAB_18013e795: + FUN_18000a3c0(local_58); + local_f8 = &PTR_FUN_18021b520; + while (local_c8 != (longlong *)0x0) { + plVar10 = (longlong *)local_c8[1]; + (**(code **)(*local_c8 + 8))(); + local_c8 = plVar10; + } + return uVar11; +} + + +============================================================================== +== Flash publisher 0x1801bf030 — full decompilation +============================================================================== + +void FUN_1801bf030(longlong *param_1,longlong param_2,undefined8 param_3,undefined8 param_4) + +{ + longlong lVar1; + longlong *plVar2; + longlong lVar3; + int iVar4; + undefined4 local_res8 [2]; + undefined4 local_res10 [2]; + undefined4 local_res18 [2]; + undefined4 local_res20 [2]; + undefined1 *local_b0; + undefined1 *local_a8; + undefined1 *local_a0; + longlong *local_98; + undefined4 local_90; + char *local_88; + longlong local_80 [2]; + longlong local_70; + longlong *local_68; + + (**(code **)(*param_1 + 8)) + (param_1,(*(longlong *)(param_2 + 0x38) - *(longlong *)(param_2 + 0x30)) / 0x18,*param_1 + ,param_4,0xfffffffffffffffe); + iVar4 = 0; + lVar3 = *(longlong *)(param_2 + 0x30); + if (lVar3 != *(longlong *)(param_2 + 0x38)) { + do { + lVar1 = *(longlong *)(lVar3 + 0x10); + FUN_1800d8fa0(*(undefined8 *)(lVar1 + 0x38),local_res10,local_res8); + (**(code **)(*param_1 + 0x20))(param_1,iVar4,"TRADEID_LOWER",local_res8[0]); + (**(code **)(*param_1 + 0x20))(param_1,iVar4,"TRADEID_UPPER",local_res10[0]); + FUN_1801a9200(local_80,*(undefined4 *)(lVar1 + 0x6c)); + local_98 = (longlong *)FUN_1800d7370(); + local_90 = 1; + local_88 = "FUT String"; + local_b0 = &DAT_1802e13a0; + local_a8 = &DAT_1802e13a0; + local_a0 = &DAT_1802e13a1; + if (*(longlong *)(lVar1 + 0x90) - 1U < 0x20f5800) { + FUN_1801c2e50(&local_b0); + } + else { + plVar2 = (longlong *)FUN_18019e320(); + (**(code **)(*plVar2 + 400))(plVar2,&local_b0,"FUT_AUCTION_EXPIRED",1); + } + (**(code **)(*param_1 + 0x30))(param_1,iVar4,"DURATION",local_b0); + (**(code **)(*param_1 + 0x20))(param_1,iVar4,"TIME_REMAINING",*(undefined4 *)(lVar1 + 0x90)); + (**(code **)(*param_1 + 0x20))(param_1,iVar4,"MIN_CREDITS",*(undefined4 *)(lVar1 + 0x78)); + (**(code **)(*param_1 + 0x20))(param_1,iVar4,"MAX_CREDITS",*(undefined4 *)(lVar1 + 0x70)); + (**(code **)(*param_1 + 0x20))(param_1,iVar4,"RESERVEDPRICE",*(undefined4 *)(lVar1 + 0x74)); + (**(code **)(*param_1 + 0x20))(param_1,iVar4,"YOURBID",*(undefined4 *)(lVar1 + 0xb8)); + (**(code **)(*param_1 + 0x20))(param_1,iVar4,"STATE",*(undefined4 *)(lVar1 + 0x88)); + (**(code **)(*param_1 + 0x20))(param_1,iVar4,"COINS_AWARDED",*(undefined1 *)(lVar1 + 0xbf)); + FUN_1800d8fa0(*(undefined8 *)(*(longlong *)(lVar1 + 0xb0) + 8),local_res18,local_res20); + (**(code **)(*param_1 + 0x20))(param_1,iVar4,"UUID_UPPER",local_res18[0]); + (**(code **)(*param_1 + 0x20))(param_1,iVar4,"UUID_LOWER",local_res20[0]); + (**(code **)(*param_1 + 0x20)) + (param_1,iVar4,"CARD_ID",*(undefined4 *)(*(longlong *)(lVar1 + 0xb0) + 0x18)); + (**(code **)(*param_1 + 0x20)) + (param_1,iVar4,"FIFA_ID",*(uint *)(*(longlong *)(lVar1 + 0xb0) + 0x18) & 0xffffff); + (**(code **)(*param_1 + 0x20)) + (param_1,iVar4,"CARD_TYPE",*(undefined4 *)(*(longlong *)(lVar1 + 0xb0) + 0x4c)); + (**(code **)(*param_1 + 0x20)) + (param_1,iVar4,"CARD_OFFERSTATE",*(undefined4 *)(*(longlong *)(lVar1 + 0xb0) + 0x5c) + ); + (**(code **)(*param_1 + 0x20))(param_1,iVar4,"IS_WATCHED",*(undefined1 *)(lVar1 + 0xbc)); + (**(code **)(*param_1 + 0x20))(param_1,iVar4,"INBOX",*(undefined1 *)(lVar1 + 0xbe)); + (**(code **)(*param_1 + 0x20))(param_1,iVar4,"IS_GLOW",*(undefined1 *)(lVar1 + 0xbd)); + (**(code **)(*param_1 + 0x18))(param_1,iVar4,"TRADE_DATA_AVAILABLE",1); + if (1 < (longlong)local_a0 - (longlong)local_b0) { + if (local_b0 != (undefined1 *)0x0) { + (**(code **)(*local_98 + 0x18))(local_98,local_b0,(int)local_a0 - (int)local_b0); + } + } + if ((1 < local_70 - local_80[0]) && (local_80[0] != 0)) { + (**(code **)(*local_68 + 0x18))(local_68,local_80[0],(int)local_70 - (int)local_80[0]); + } + lVar3 = lVar3 + 0x18; + iVar4 = iVar4 + 1; + } while (lVar3 != *(longlong *)(param_2 + 0x38)); + } + return; +} + + +============================================================================== +== strings: sold / clear / inbox / glow / collect +============================================================================== + sold 0x180228bed 'sold' + sold 0x18022f524 'sold' + Sold 0x1801efaf1 'SoldFromTradePile' + Sold 0x1802302cf 'SoldBid' + Sold 0x1802302df 'SoldBuyNow' + SOLD 0x18020a0c7 'SOLD' + clearSold ABSENT + ClearSold ABSENT + INBOX 0x180215044 'INBOX' + inbox ABSENT + IS_GLOW 0x1802151a0 'IS_GLOW' + glow ABSENT + Glow ABSENT + collect 0x1802307e0 'collector' + Collect 0x1801ec3c1 'CollectedCards' + Collect 0x1801ec3f8 'Collection' + Collect 0x1801ec422 'Collection' + Collect 0x1801ec449 'Collection' + unclaimed 0x18022fd68 'unclaimedPrizesChampionEvents' + claim 0x1802099d1 'claimedPack' + claim 0x1802099e9 'claimedPack2' + claim 0x18022fd6a 'claimedPrizesChampionEvents' diff --git a/docs/evidence/market-sold-re-2026-08-17/q_sold_1.py b/docs/evidence/market-sold-re-2026-08-17/q_sold_1.py new file mode 100644 index 0000000..2bd5783 --- /dev/null +++ b/docs/evidence/market-sold-re-2026-08-17/q_sold_1.py @@ -0,0 +1,30 @@ +"""Q1 — what does FIFA17 do with tradeState == closed(4)? + +Targets the ONE native tradeState consumer branch (cmp [rbp-0x38],0x4 at +0x18013e619) inside the auctionInfo deserializer 0x18013e410, and the Flash +publisher 0x1801bf030. +""" + +print("=" * 78) +print("== auctionInfo deserializer 0x18013e410 — full decompilation") +print("=" * 78) +print(dec(0x18013E410)) + +print("=" * 78) +print("== Flash publisher 0x1801bf030 — full decompilation") +print("=" * 78) +print(dec(0x1801BF030)) + +print("=" * 78) +print("== strings: sold / clear / inbox / glow / collect") +print("=" * 78) +for needle in (b"sold", b"Sold", b"SOLD", b"clearSold", b"ClearSold", + b"INBOX", b"inbox", b"IS_GLOW", b"glow", b"Glow", + b"collect", b"Collect", b"unclaimed", b"claim"): + hits = find_all(needle, blocks=(".rdata", ".data", ".text")) + for h in hits[:14]: + s = rd_str(h, 90) + if s and len(s) > 2: + print(f" {needle.decode():12s} 0x{h:x} {s!r}") + if not hits: + print(f" {needle.decode():12s} ABSENT") diff --git a/docs/evidence/market-sold-re-2026-08-17/q_sold_2.out b/docs/evidence/market-sold-re-2026-08-17/q_sold_2.out new file mode 100644 index 0000000..c79263b --- /dev/null +++ b/docs/evidence/market-sold-re-2026-08-17/q_sold_2.out @@ -0,0 +1,114 @@ +============================================================================== +== 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' diff --git a/docs/evidence/market-sold-re-2026-08-17/q_sold_2.py b/docs/evidence/market-sold-re-2026-08-17/q_sold_2.py new file mode 100644 index 0000000..214333f --- /dev/null +++ b/docs/evidence/market-sold-re-2026-08-17/q_sold_2.py @@ -0,0 +1,50 @@ +"""Q2 — the sold vocabulary: SoldBid / SoldBuyNow / SoldFromTradePile / sold, +the coinsProcessed -> COINS_AWARDED path, and the route table (clear-sold?). +""" + +print("=" * 78) +print("== neighbourhood of SoldBid/SoldBuyNow (0x1802302cf) — read the whole table") +print("=" * 78) +a = 0x180230280 +while a < 0x180230400: + s = rd_str(a, 60) + if s and len(s) > 1 and s.isprintable(): + print(f" 0x{a:x} {s!r}") + a += len(s.encode()) + 1 + else: + a += 1 + +print() +print("=" * 78) +print("== xrefs to each sold-ish string") +print("=" * 78) +for name, sa in (("sold@228bed", 0x180228BED), ("sold@22f524", 0x18022F524), + ("SoldFromTradePile", 0x1801EFAF1), ("SoldBid", 0x1802302CF), + ("SoldBuyNow", 0x1802302DF), ("SOLD@20a0c7", 0x18020A0C7)): + xs = xrefs_to(sa) + print(f" {name:20s} {len(xs)} xref(s)") + for x in xs[:8]: + print(f" from 0x{x[0]:x} {x[1]} in {x[2]} (entry 0x{x[3]:x})") + +print() +print("=" * 78) +print("== who reads record +0xbf (COINS_AWARDED) / +0xbe (INBOX) / +0xbd (IS_GLOW)") +print("== i.e. the deserializer's coinsProcessed + the two computed flags") +print("=" * 78) +print("-- decoder for atom 0x2f4 (coinsProcessed): FUN_1800d7b50") +print(dec(0x1800D7B50)) + +print() +print("=" * 78) +print("== route table 0x18021df80 — all 45 rows, looking for a clear/sold route") +print("=" * 78) +for i in range(48): + ea = 0x18021DF80 + i * 8 + try: + p = qword(ea) + except Exception: + break + s = "" + if 0x180000000 < p < 0x180400000: + s = rd_str(p, 120) + print(f" [{i:2d}] 0x{ea:x} -> 0x{p:x} {s!r}") diff --git a/docs/evidence/market-sold-re-2026-08-17/q_sold_3.out b/docs/evidence/market-sold-re-2026-08-17/q_sold_3.out new file mode 100644 index 0000000..8e98960 --- /dev/null +++ b/docs/evidence/market-sold-re-2026-08-17/q_sold_3.out @@ -0,0 +1,123 @@ +============================================================================== +== 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' diff --git a/docs/evidence/market-sold-re-2026-08-17/q_sold_3.py b/docs/evidence/market-sold-re-2026-08-17/q_sold_3.py new file mode 100644 index 0000000..4cd785d --- /dev/null +++ b/docs/evidence/market-sold-re-2026-08-17/q_sold_3.py @@ -0,0 +1,68 @@ +"""Q3 — full route table, the itemState/tradeState enum tables, and the pointer +arrays that reference the auctionSold* vocabulary and the bare 'sold' string. +""" +import struct + +print("=" * 78) +print("== route table continued from 0x18021e0f8 until it stops looking like one") +print("=" * 78) +a = 0x18021E100 +misses = 0 +while a < 0x18021E400 and misses < 6: + p = qword(a) + s = rd_str(p, 120) if 0x180000000 < p < 0x180400000 else "" + if s and s.isprintable(): + print(f" 0x{a:x} -> 0x{p:x} {s!r}") + misses = 0 + else: + misses += 1 + a += 8 + +print() +print("=" * 78) +print("== itemState table 0x180229cc0 (prior work: 12 rows) — dump generously") +print("=" * 78) +for i in range(20): + ea = 0x180229CC0 + i * 16 + p, v = qword(ea), dword(ea + 8) + s = rd_str(p, 40) if 0x180000000 < p < 0x180400000 else "" + print(f" [{i:2d}] 0x{ea:x} str=0x{p:x} {s!r:24s} val={v} (0x{v:x})") + +print() +print("=" * 78) +print("== tradeState table 0x180229e40 — dump generously") +print("=" * 78) +for i in range(12): + ea = 0x180229E40 + i * 16 + p, v = qword(ea), dword(ea + 8) + s = rd_str(p, 40) if 0x180000000 < p < 0x180400000 else "" + print(f" [{i:2d}] 0x{ea:x} str=0x{p:x} {s!r:24s} val={v} (0x{v:x})") + +print() +print("=" * 78) +print("== pointer arrays containing the sold vocabulary") +print("=" * 78) +for label, target in (("auctionSoldBid", 0x1802302C8), + ("auctionSoldBuyNow", 0x1802302D8), + ("auctionWonBuyNow", 0x180230300), + ("sold@22f524", 0x18022F524), + ("sold@228bed", 0x180228BED), + ("SoldFromTradePile", 0x1801EFAF1), + ("SOLD@20a0c7", 0x18020A0C7)): + pat = struct.pack(" 0x{p:x} {s!r}{mark}") diff --git a/docs/evidence/market-sold-re-2026-08-17/q_sold_4.py b/docs/evidence/market-sold-re-2026-08-17/q_sold_4.py new file mode 100644 index 0000000..2a2a032 --- /dev/null +++ b/docs/evidence/market-sold-re-2026-08-17/q_sold_4.py @@ -0,0 +1,67 @@ +"""Q4 — is the atom ID the index into the alphabetical atom-name pointer table? + +Validate against the twelve KNOWN auctionInfo atoms. If all twelve agree on one +base, the table is the atom dictionary and we can read the ID of any name. +""" +import struct + +KNOWN = { # atom id -> name, from the confirmed auctionInfo deserializer + 0x57: "bidState", 0x65: "buyNowPrice", 0xC1: "currentBid", 0x116: "expires", + 0x16B: "itemData", 0x2B6: "sellerEstablished", 0x2B7: "sellerName", + 0x2E6: "startingBid", 0x2F4: "coinsProcessed", 0x331: "tradeId", + 0x335: "tradeState", 0x380: "watched", +} + + +def str_addr(name): + """Address of the exact NUL-terminated string `name`.""" + for h in find_all(name.encode() + b"\x00", blocks=(".rdata", ".data")): + return h + return None + + +def ptr_addr(sa): + hits = find_all(struct.pack(" base 0x{base:x}") + +print() +print(" base histogram:", {hex(b): n for b, n in sorted(bases.items(), key=lambda kv: -kv[1])}) +if not bases: + raise SystemExit("no bases resolved") +BASE = max(bases, key=bases.get) +print(f" CONSENSUS BASE = 0x{BASE:x} ({bases[BASE]}/{len(KNOWN)} atoms agree)") + +print() +print("=" * 78) +print("== read the atom id of every sold/counts-related name") +print("=" * 78) +for name in ("sold", "selling", "offered", "count", "maxAuctionsAllowed", + "auctionSoldBid", "auctionSoldBuyNow", "auctionWonBid", + "auctionWonBuyNow", "auctionLostOutbid", "auctionLostOutbidSelf", + "auctionLostBidRejected", "credits", "coins", "tradeIdStr", + "itemState", "offers", "bids", "watched", "expires"): + sa = str_addr(name) + pa = ptr_addr(sa) if sa else None + if pa is None: + print(f" {name:24s} ABSENT from the table") + continue + off = pa - BASE + if off % 8: + print(f" {name:24s} ptr=0x{pa:x} misaligned (off {off})") + continue + print(f" {name:24s} ptr=0x{pa:x} ATOM ID = 0x{off // 8:x} ({off // 8})") diff --git a/docs/evidence/market-sold-re-2026-08-17/q_sold_5.out b/docs/evidence/market-sold-re-2026-08-17/q_sold_5.out new file mode 100644 index 0000000..5ce93ad --- /dev/null +++ b/docs/evidence/market-sold-re-2026-08-17/q_sold_5.out @@ -0,0 +1,80 @@ +============================================================================== +== atom ids, resolved against the table range only +============================================================================== + sold str=0x18022f524 ptr=0x1802d3da8 ATOM ID = 0x2c9 (713) + count str=0x180230abc ptr=0x1802d2d40 ATOM ID = 0xbc (188) + offered str=0x18022a2d8 ptr=0x1802d3688 ATOM ID = 0x1e5 (485) + selling str=0x18022f460 ptr=0x1802d3d20 ATOM ID = 0x2b8 (696) + maxAuctionsAllowed str=0x1802318a8 ptr=0x1802d3558 ATOM ID = 0x1bf (447) + credits str=0x180230af8 ptr=0x1802d2d60 ATOM ID = 0xc0 (192) + auctionInfo str=0x180230270 ptr=0x1802d2908 ATOM ID = 0x35 (53) + itemData str=0x180231438 ptr=0x1802d32b8 ATOM ID = 0x16b (363) + bidState str=0x180230458 ptr=0x1802d2a18 ATOM ID = 0x57 (87) + tradeState str=0x18022fb48 ptr=0x1802d4108 ATOM ID = 0x335 (821) + coinsProcessed str=0x18022f768 ptr=0x1802d3f00 ATOM ID = 0x2f4 (756) + offers str=0x180231ae4 ptr=0x1802d3690 ATOM ID = 0x1e6 (486) + duplicateItemIdList str=0x180230d18 ptr=0x1802d2ec0 ATOM ID = 0xec (236) + total str=0x18022fa4c ptr=0x1802d4088 ATOM ID = 0x325 (805) + +============================================================================== +== functions that compare against >=3 of the auction-outcome atoms 0x36..0x3c +============================================================================== + scanned 13308 functions + 0x180043b90 FUN_180043b90 sees ['0x36', '0x37', '0x38', '0x3c'] + 0x1800455b0 FUN_1800455b0 sees ['0x37', '0x38', '0x39'] + 0x18006b4c0 FUN_18006b4c0 sees ['0x38', '0x39', '0x3c'] + 0x180094ce0 FUN_180094ce0 sees ['0x36', '0x37', '0x38'] + 0x180097c70 FUN_180097c70 sees ['0x36', '0x37', '0x38'] + 0x1800cabb0 FUN_1800cabb0 sees ['0x38', '0x39', '0x3a'] + 0x1800cf3b0 FUN_1800cf3b0 sees ['0x36', '0x38', '0x3a', '0x3c'] + 0x1800e6410 FUN_1800e6410 sees ['0x36', '0x37', '0x38', '0x39'] + 0x1800ee7c0 FUN_1800ee7c0 sees ['0x36', '0x37', '0x38', '0x39', '0x3a', '0x3c'] + 0x1800fb5b0 FUN_1800fb5b0 sees ['0x38', '0x39', '0x3a', '0x3b'] + 0x1801071f0 FUN_1801071f0 sees ['0x36', '0x37', '0x38', '0x3a', '0x3b', '0x3c'] + 0x18012b370 FUN_18012b370 sees ['0x36', '0x37', '0x38', '0x39', '0x3a', '0x3b', '0x3c'] + 0x18012fd40 FUN_18012fd40 sees ['0x36', '0x37', '0x38', '0x3c'] + 0x18013fe00 FUN_18013fe00 sees ['0x36', '0x38', '0x3a', '0x3b'] + 0x180163770 FUN_180163770 sees ['0x36', '0x38', '0x3a'] + 0x180172d00 FUN_180172d00 sees ['0x38', '0x3a', '0x3c'] + +============================================================================== +== string neighbourhoods: SoldFromTradePile / SOLD / sold +============================================================================== +-- SoldFromTradePile + 0x1801efa40 'CardsRequestStaffBonuses' + 0x1801efa60 'CardsGetStaffBonuses' + 0x1801efa78 'WatchListRequest' + 0x1801efa90 'WatchListResultsRequest' + 0x1801efaa8 'AddToWatchList' + 0x1801efab8 'RemoveFromWatchList' + 0x1801efad0 'RemoveFromTradePile' + 0x1801efae8 'RemoveAllSoldFromTradePile' + 0x1801efb08 'GetPhishingQuestion' + 0x1801efb20 'SetPhishingAnswer' + 0x1801efb38 'ValidatePhishingAnswer' + 0x1801efb50 'RetrieveTrustedConsoleList' + 0x1801efb70 'EnableLiveStreaming' +-- SOLD + 0x18020a040 'D_PRIZES' + 0x18020a050 'EVENT_ID' + 0x18020a05c 'TEXT0' + 0x18020a068 'FUT_UC_SQUAD' + 0x18020a078 'TEXT1' + 0x18020a080 'FUT_TOTAL_AUCTIONS' + 0x18020a098 'FUT_UC_ITEMS' + 0x18020a0a8 'FUT_TF_SELLING' + 0x18020a0b8 'TEXT2' + 0x18020a0c0 'FUT_TF_SOLD' + 0x18020a0cc 'TEXT3' + 0x18020a0d8 'FUT_TF_WINNING' + 0x18020a0e8 'FUT_TF_OUTBID' + 0x18020a0f8 'FUT_GH_TOTAL_PLAYERS_0' + 0x18020a110 'FUT_GH_TOTAL_PLAYERS_1' + 0x18020a128 'FUT_GH_TOP_SCORER' +-- sold@228bed + 0x180228bc8 'RS4:FutISRemoveTradeServerResponse' + 0x180228bec '/sold' + 0x180228c00 '\\�&' + 0x180228c11 "�'" + 0x180228c28 "X�'" + 0x180228c38 "`�'" diff --git a/docs/evidence/market-sold-re-2026-08-17/q_sold_5.py b/docs/evidence/market-sold-re-2026-08-17/q_sold_5.py new file mode 100644 index 0000000..94de688 --- /dev/null +++ b/docs/evidence/market-sold-re-2026-08-17/q_sold_5.py @@ -0,0 +1,83 @@ +"""Q5 — fix the atom lookup, then find the CONSUMER of atoms 0x36..0x3c +(auctionLost*/auctionSold*/auctionWon*) and read the sold-related string +neighbourhoods. +""" +import struct + +BASE = 0x1802D2760 +TABLE_LO, TABLE_HI = 0x1802D2760, 0x1802D4800 + + +def atom_id(name): + """Every string with this exact spelling, then the pointer that lies INSIDE + the atom table. Taking the first string match is wrong: common words appear + in several unrelated tables.""" + out = [] + for sa in find_all(name.encode() + b"\x00", blocks=(".rdata", ".data")): + for pa in find_all(struct.pack("=3 of the auction-outcome atoms 0x36..0x3c") +print("=" * 78) +WANT = set(range(0x36, 0x3D)) +hits = {} +fi = fm.getFunctions(True) +n = 0 +for f in fi: + n += 1 + ent = int(f.getEntryPoint().getOffset()) + body = f.getBody() + seen = set() + it = listing.getInstructions(body, True) + while it.hasNext(): + ins = it.next() + m = ins.getMnemonicString() + if m not in ("CMP", "SUB", "MOV", "LEA"): + continue + for i in range(ins.getNumOperands()): + for o in ins.getOpObjects(i): + try: + v = int(o.getValue()) + except Exception: + continue + if v in WANT: + seen.add(v) + if len(seen) >= 3: + hits[ent] = (f.getName(), sorted(hex(v) for v in seen)) +print(f" scanned {n} functions") +for ent, (nm, vals) in sorted(hits.items()): + print(f" 0x{ent:x} {nm:28s} sees {vals}") + +print() +print("=" * 78) +print("== string neighbourhoods: SoldFromTradePile / SOLD / sold") +print("=" * 78) +for label, lo, hi in (("SoldFromTradePile", 0x1801EFA40, 0x1801EFB80), + ("SOLD", 0x18020A040, 0x18020A140), + ("sold@228bed", 0x180228B60, 0x180228C60)): + print(f"-- {label}") + a = lo + while a < hi: + s = rd_str(a, 70) + if s and len(s) > 1 and s.isprintable(): + print(f" 0x{a:x} {s!r}") + a += len(s.encode()) + 1 + else: + a += 1 diff --git a/docs/evidence/market-sold-re-2026-08-17/q_sold_6.out b/docs/evidence/market-sold-re-2026-08-17/q_sold_6.out new file mode 100644 index 0000000..8cdbfcf --- /dev/null +++ b/docs/evidence/market-sold-re-2026-08-17/q_sold_6.out @@ -0,0 +1,469 @@ +============================================================================== +== (a) xrefs: request names + the /sold path suffix +============================================================================== + RemoveAllSoldFromTradePile 1 xref(s) + 0x1800293fe DATA in FUN_180028b50 (entry 0x180028b50) + RemoveFromTradePile 1 xref(s) + 0x1800293d4 DATA in FUN_180028b50 (entry 0x180028b50) + AddToWatchList 1 xref(s) + 0x180029380 DATA in FUN_180028b50 (entry 0x180028b50) + /sold suffix 1 xref(s) + 0x180164813 DATA in FUN_1801647c0 (entry 0x1801647c0) + RS4:FutISRemoveTradeServerResponse 2 xref(s) + 0x1801defe9 DATA in Unwind@1801defd0 (entry 0x1801defd0) + 0x18016488d DATA in FUN_180164860 (entry 0x180164860) + +============================================================================== +== (b) xrefs: the Transfer List caption keys +============================================================================== + FUT_TOTAL_AUCTIONS 1 xref(s) + 0x1800b1ef8 DATA in FUN_1800b1dc0 (entry 0x1800b1dc0) + FUT_UC_ITEMS 4 xref(s) + 0x1800fbbdc DATA in FUN_1800fbae0 (entry 0x1800fbae0) + 0x1800fbe5a DATA in FUN_1800fbae0 (entry 0x1800fbae0) + 0x1800b1fe7 DATA in FUN_1800b1dc0 (entry 0x1800b1dc0) + 0x1800b21a9 DATA in FUN_1800b1dc0 (entry 0x1800b1dc0) + FUT_TF_SELLING 1 xref(s) + 0x1800b2023 DATA in FUN_1800b1dc0 (entry 0x1800b1dc0) + FUT_TF_SOLD 1 xref(s) + 0x1800b208e DATA in FUN_1800b1dc0 (entry 0x1800b1dc0) + FUT_TF_WINNING 1 xref(s) + 0x1800b21e5 DATA in FUN_1800b1dc0 (entry 0x1800b1dc0) + FUT_TF_OUTBID 1 xref(s) + 0x1800b2250 DATA in FUN_1800b1dc0 (entry 0x1800b1dc0) + + caption publisher candidates (entry -> #caption refs): {'0x1800b1dc0': 7, '0x1800fbae0': 2} + +============================================================================== +== decompiled caption publisher 0x1800b1dc0 +============================================================================== + +void FUN_1800b1dc0(undefined8 param_1,longlong *param_2) + +{ + char cVar1; + undefined8 uVar2; + longlong lVar3; + longlong *plVar4; + ushort uVar5; + longlong *local_res10; + undefined1 *local_90; + undefined1 *local_88; + undefined1 *local_80; + longlong *local_78; + undefined4 local_70; + char *local_68; + undefined1 *local_60; + undefined1 *local_58; + undefined1 *local_50; + longlong *local_48; + undefined4 local_40; + char *local_38; + + uVar2 = FUN_1800d7170(); + FUN_180009c80(&local_res10,uVar2); + lVar3 = (**(code **)(*local_res10 + 0x1f8))(local_res10); + local_78 = (longlong *)FUN_1800d7370(); + local_70 = 1; + local_68 = "FUT String"; + local_90 = &DAT_1802e13a0; + local_88 = &DAT_1802e13a0; + local_80 = &DAT_1802e13a1; + local_48 = (longlong *)FUN_1800d7370(); + local_40 = 1; + local_38 = "FUT String"; + local_60 = &DAT_1802e13a0; + local_58 = &DAT_1802e13a0; + local_50 = &DAT_1802e13a1; + (**(code **)(*param_2 + 0x58))(param_2,4); + (**(code **)(*param_2 + 0x78))(param_2,0,"UPDATE_TYPE",PTR_s_PANEL_UPDATE_1802a4fb0); + (**(code **)(*param_2 + 0x70))(param_2,0,"TILE_ID",0x1b0); + (**(code **)(*param_2 + 0x70))(param_2,0,"ADDON_ENUM",5); + plVar4 = (longlong *)FUN_18019e320(); + (**(code **)(*plVar4 + 0xb8))(plVar4,&local_90,*(undefined4 *)(lVar3 + 0x38)); + (**(code **)(*param_2 + 0x78))(param_2,0,"TEXT0",local_90); + plVar4 = (longlong *)FUN_18019e320(); + (**(code **)(*plVar4 + 400))(plVar4,&local_90,"FUT_TOTAL_AUCTIONS",1); + (**(code **)(*param_2 + 0x78))(param_2,0,"TEXT1",local_90); + (**(code **)(*param_2 + 0x78))(param_2,1,"UPDATE_TYPE",PTR_s_PANEL_UPDATE_1802a4fb0); + (**(code **)(*param_2 + 0x70))(param_2,1,"TILE_ID",0x1c0); + uVar5 = *(ushort *)(lVar3 + 0x1d6); + if (0x62 < uVar5) { + uVar5 = 99; + } + (**(code **)(*param_2 + 0x70))(param_2,1,"NOTIFICATION",uVar5); + (**(code **)(*param_2 + 0x70))(param_2,1,"ADDON_ENUM"); + plVar4 = (longlong *)FUN_18019e320(); + (**(code **)(*plVar4 + 0xb8))(plVar4,&local_90,*(undefined2 *)(lVar3 + 0x1d4)); + (**(code **)(*param_2 + 0x78))(param_2,1,"TEXT0",local_90); + plVar4 = (longlong *)FUN_18019e320(); + (**(code **)(*plVar4 + 400))(plVar4,&local_90,"FUT_UC_ITEMS",1); + (**(code **)(*param_2 + 0x78))(param_2,1,"TEXT1",local_90); + plVar4 = (longlong *)FUN_18019e320(); + (**(code **)(*plVar4 + 400))(plVar4,&local_90,"FUT_TF_SELLING",1); + plVar4 = (longlong *)FUN_18019e320(); + (**(code **)(*plVar4 + 0xb8))(plVar4,&local_60,*(undefined2 *)(lVar3 + 0x1d2)); + FUN_18000fbd0(&local_90,local_90,local_60); + (**(code **)(*param_2 + 0x78))(param_2,1,"TEXT2",local_90); + plVar4 = (longlong *)FUN_18019e320(); + (**(code **)(*plVar4 + 400))(plVar4,&local_90,"FUT_TF_SOLD",1); + plVar4 = (longlong *)FUN_18019e320(); + (**(code **)(*plVar4 + 0xb8))(plVar4,&local_60,*(undefined2 *)(lVar3 + 0x1d8)); + FUN_18000fbd0(&local_90,local_90,local_60); + (**(code **)(*param_2 + 0x78))(param_2,1,"TEXT3",local_90); + (**(code **)(*param_2 + 0x78))(param_2,2,"UPDATE_TYPE",PTR_s_PANEL_UPDATE_1802a4fb0); + (**(code **)(*param_2 + 0x70))(param_2,2,"TILE_ID",0x1d0); + uVar5 = 99; + if (*(ushort *)(lVar3 + 0x1ce) < 99) { + uVar5 = *(ushort *)(lVar3 + 0x1ce); + } + (**(code **)(*param_2 + 0x70))(param_2,2,"NOTIFICATION",uVar5); + (**(code **)(*param_2 + 0x70))(param_2,2,"ADDON_ENUM"); + plVar4 = (longlong *)FUN_18019e320(); + (**(code **)(*plVar4 + 0xb8))(plVar4,&local_90,*(undefined2 *)(lVar3 + 0x1ca)); + (**(code **)(*param_2 + 0x78))(param_2,2,"TEXT0",local_90); + plVar4 = (longlong *)FUN_18019e320(); + (**(code **)(*plVar4 + 400))(plVar4,&local_90,"FUT_UC_ITEMS",1); + (**(code **)(*param_2 + 0x78))(param_2,2,"TEXT1",local_90); + plVar4 = (longlong *)FUN_18019e320(); + (**(code **)(*plVar4 + 400))(plVar4,&local_90,"FUT_TF_WINNING",1); + plVar4 = (longlong *)FUN_18019e320(); + (**(code **)(*plVar4 + 0xb8))(plVar4,&local_60,*(undefined2 *)(lVar3 + 0x1c8)); + FUN_18000fbd0(&local_90,local_90,local_60); + (**(code **)(*param_2 + 0x78))(param_2,2,"TEXT2",local_90); + plVar4 = (longlong *)FUN_18019e320(); + (**(code **)(*plVar4 + 400))(plVar4,&local_90,"FUT_TF_OUTBID",1); + plVar4 = (longlong *)FUN_18019e320(); + (**(code **)(*plVar4 + 0xb8))(plVar4,&local_60,*(undefined2 *)(lVar3 + 0x1cc)); + FUN_18000fbd0(&local_90,local_90,local_60); + (**(code **)(*param_2 + 0x78))(param_2,2,"TEXT3",local_90); + uVar2 = FUN_1800264e0(); + cVar1 = FUN_180026510(uVar2); + if (cVar1 == '\0') { + (**(code **)(*param_2 + 0x78))(param_2,3,"UPDATE_TYPE",PTR_s_PANEL_UPDATE_1802a4fb0); + (**(code **)(*param_2 + 0x70))(param_2,3,"TILE_ID",0x200); + (**(code **)(*param_2 + 0x70))(param_2,3,"ADDON_ENUM"); + } + if ((1 < (longlong)local_50 - (longlong)local_60) && (local_60 != (undefined1 *)0x0)) { + (**(code **)(*local_48 + 0x18))(local_48,local_60,(int)local_50 - (int)local_60); + } + if ((1 < (longlong)local_80 - (longlong)local_90) && (local_90 != (undefined1 *)0x0)) { + (**(code **)(*local_78 + 0x18))(local_78,local_90,(int)local_80 - (int)local_90); + } + (**(code **)(*local_res10 + 8))(local_res10); + return; +} + + + +============================================================================== +== decompiled caption publisher 0x1800fbae0 +============================================================================== + +/* WARNING: Function: __security_check_cookie replaced with injection: security_check_cookie */ +/* WARNING: Globals starting with '_' overlap smaller symbols at the same address */ + +uint FUN_1800fbae0(undefined8 param_1,undefined4 param_2,undefined8 *param_3) + +{ + undefined1 *puVar1; + longlong lVar2; + uint uVar3; + int iVar4; + longlong *plVar5; + undefined8 uVar6; + longlong *plVar7; + longlong lVar8; + size_t sVar9; + size_t sVar10; + undefined1 auStack_2e8 [32]; + undefined4 local_2c8; + undefined1 local_2b8; + uint local_2b4; + undefined4 local_2b0; + undefined4 uStack_2ac; + undefined1 *local_2a8; + undefined1 *local_2a0; + undefined1 *local_298; + longlong *local_290; + undefined4 local_288; + char *local_280; + undefined1 *local_278; + undefined1 *local_270; + undefined1 *local_268; + longlong *local_260; + undefined4 local_258; + char *local_250; + undefined1 *local_248; + undefined1 *local_240; + undefined1 *local_238; + longlong *local_230; + undefined4 local_228; + char *local_220; + longlong local_218; + undefined8 local_210; + longlong local_208; + longlong *local_200; + undefined4 local_1f8; + char *local_1f0; + void *local_1e8; + longlong local_1e0; + longlong local_1d8; + longlong *local_1d0; + undefined8 local_1b8; + longlong *local_1a8; + undefined8 local_1a0; + undefined8 local_198; + undefined8 local_190; + undefined4 local_188; + undefined2 local_184; + undefined8 local_180; + undefined8 local_178; + undefined8 local_170; + undefined8 local_168; + undefined2 local_160; + undefined4 local_15c; + ulonglong local_158; + undefined8 uStack_150; + undefined4 local_148; + undefined2 local_144; + undefined4 local_140; + undefined **local_138; + undefined8 local_130; + undefined8 local_128; + undefined8 local_120; + undefined8 local_118; + undefined8 local_110; + undefined8 local_108; + undefined8 local_100; + undefined4 local_f8; + undefined2 local_f4; + undefined1 local_f2; + undefined1 local_f0 [168]; + ulonglong local_48; + + local_1b8 = 0xfffffffffffffffe; + local_48 = DAT_1802db6e0 ^ (ulonglong)auStack_2e8; + local_200 = (longlong *)FUN_1800d7370(); + local_218 = 0; + local_208 = 0; + local_1f8 = 1; + local_1f0 = "FUT Vector"; + FUN_18004fff0(0,0,local_2b8); + local_210 = 0; + local_2b0 = 0; + uVar3 = FUN_1800fb8c0(param_1,param_2,&local_218,&local_2b0); + if ((undefined1 *)*param_3 != (undefined1 *)param_3[1]) { + *(undefined1 *)*param_3 = 0; + param_3[1] = *param_3; + } + local_2b4 = uVar3; + local_260 = (longlong *)FUN_1800d7370(); + lVar2 = local_218; + local_258 = 1; + local_250 = "FUT String"; + local_278 = &DAT_1802e13a0; + local_270 = &DAT_1802e13a0; + local_268 = &DAT_1802e13a1; + if (uVar3 < 2) { + if (uVar3 == 1) { + local_1a8 = (longlong *)0x0; + local_1a0 = 0; + local_198 = 0; + local_190 = 0; + local_188 = 0; + local_184 = 0; + local_180 = 0; + local_178 = 0; + local_170 = 0; + local_168 = 0; + local_160 = 0x100; + lVar8 = -1; + local_15c = 0xffffffff; + local_158 = _DAT_1801f66a0; + uStack_150 = _UNK_1801f66a8; + local_148 = 0; + local_144 = 0; + local_140 = 0; + local_130 = 0; + local_138 = &PTR_LAB_1801eaac0; + local_128 = 0; + local_120 = 0; + local_118 = 0; + local_f4 = 0; + local_f2 = 0; + local_110 = 0; + local_108 = 0; + local_100 = 0; + local_f8 = 0; + memset(local_f0,0,0xa0); + local_190 = CONCAT44(local_190._4_4_,*(undefined4 *)(lVar2 + 4)) & 0xffffffff00ffffff; + FUN_1801362e0(&local_1a8); + FUN_180141660(&local_1a8); + uVar6 = FUN_1800d7170(); + FUN_180009c80(&local_2b0,uVar6); + plVar5 = (longlong *)CONCAT44(uStack_2ac,local_2b0); + local_2c8 = (undefined4)local_190; + (**(code **)(*plVar5 + 0x490))(plVar5,&local_1e8,local_15c,local_158 & 0xffffffff); + sVar10 = local_1e0 - (longlong)local_1e8; + sVar9 = sVar10; + if (0 < (longlong)sVar10) { + sVar9 = 0; + } + iVar4 = memcmp(local_1e8,&DAT_1801e9caf,sVar9); + if (((iVar4 == 0) && (-1 < (longlong)sVar10)) && ((longlong)sVar10 < 1)) { + local_230 = (longlong *)FUN_1800d7370(); + local_228 = 1; + local_220 = "FUT String"; + local_248 = &DAT_1802e13a0; + local_240 = &DAT_1802e13a0; + local_238 = &DAT_1802e13a1; + local_290 = (longlong *)FUN_1800d7370(); + local_288 = 1; + local_280 = "FUT String"; + local_2a8 = &DAT_1802e13a0; + local_2a0 = &DAT_1802e13a0; + local_298 = &DAT_1802e13a1; + FUN_18000fbd0(&local_248,"AWARD_LABEL_%i",*(undefined4 *)(lVar2 + 8)); + plVar7 = (longlong *)FUN_18019e320(); + puVar1 = local_248; + (**(code **)(*plVar7 + 400))(plVar7,&local_2a8,local_248,1); + sVar10 = (longlong)local_2a0 - (longlong)local_2a8; + sVar9 = sVar10; + if (0 < (longlong)sVar10) { + sVar9 = 0; + } + iVar4 = memcmp(local_2a8,&DAT_1801e9caf,sVar9); + if (((iVar4 == 0) && (-1 < (longlong)sVar10)) && ((longlong)sVar10 < 1)) { + plVar7 = (longlong *)FUN_18019e320(); + (**(code **)(*plVar7 + 400))(plVar7,&local_2a8,"FUT_UC_ITEMS",1); + } + FUN_18000fbd0(param_3,&DAT_18021940c,*(undefined4 *)(lVar2 + 0xc)); + do { + lVar8 = lVar8 + 1; + } while (local_2a8[lVar8] != '\0'); + FUN_18000f650(param_3,local_2a8,local_2a8 + lVar8); + if (1 < (longlong)local_298 - (longlong)local_2a8) { + if (local_2a8 != (undefined1 *)0x0) { + (**(code **)(*local_290 + 0x18))(local_290,local_2a8,(int)local_298 - (int)local_2a8); + } + } + if (1 < (longlong)local_238 - (longlong)puVar1) { + if (puVar1 != (undefined1 *)0x0) { + (**(code **)(*local_230 + 0x18))(local_230,puVar1,(int)local_238 - (int)local_248); + } + } + } + else { + FUN_18000fbd0(param_3,&DAT_18021940c,*(undefined4 *)(lVar2 + 0xc)); + do { + lVar8 = lVar8 + 1; + } while (*(char *)((longlong)local_1e8 + lVar8) != '\0'); + FUN_18000f650(param_3,local_1e8,(longlong)local_1e8 + lVar8); + } + if ((1 < local_1d8 - (longlong)local_1e8) && (local_1e8 != (void *)0x0)) { + (**(code **)(*local_1d0 + 0x18))(local_1d0,local_1e8,(int)local_1d8 - (int)local_1e8); + } + (**(code **)(*plVar5 + 8))(plVar5); + FUN_18000a3c0(&local_138); + plVar5 = local_1a8; + while (uVar3 = local_2b4, plVar5 != (longlong *)0x0) { + plVar7 = (longlong *)plVar5[1]; + (**(code **)(*plVar5 + 8))(); + plVar5 = plVar7; + } + } + } + else { + plVar5 = (longlong *)FUN_18019e320(); + (**(code **)(*plVar5 + 400))(plVar5,&local_278,"FUT_UC_ITEMS",1); + FUN_18000fbd0(param_3,"%i %s",local_2b0,local_278); + } + if (1 < (longlong)local_268 - (longlong)local_278) { + if (local_278 != (undefined1 *)0x0) { + (**(code **)(*local_260 + 0x18))(local_260,local_278,(int)local_268 - (int)local_278); + } + } + FUN_18004fff0(lVar2,local_210,local_2b8); + if (lVar2 != 0) { + (**(code **)(*local_200 + 0x18))(local_200,lVar2,((local_208 - lVar2) / 0x38) * 0x38); + } + return uVar3; +} + + + +============================================================================== +== (c) GetAuctionCount deserializer FUN_180163770 +============================================================================== + +/* WARNING: Function: __security_check_cookie replaced with injection: security_check_cookie */ + +undefined8 FUN_180163770(undefined8 param_1,undefined8 *param_2) + +{ + undefined2 uVar1; + int iVar2; + undefined4 uVar3; + undefined8 uVar4; + longlong *plVar5; + longlong lVar6; + undefined1 auStack_168 [32]; + int local_148 [2]; + undefined8 local_140; + undefined1 local_138 [288]; + ulonglong local_18; + + local_140 = 0xfffffffffffffffe; + local_18 = DAT_1802db6e0 ^ (ulonglong)auStack_168; + FUN_1801c63e0(local_138,0,0); + local_148[0] = 0x38c; + uVar4 = FUN_180008130(*param_2); + FUN_1801c8270(local_138,*param_2,uVar4,0); + FUN_1801c7f10(local_138); + FUN_1801c7f10(local_138); + plVar5 = (longlong *)FUN_18011a830(); + lVar6 = (**(code **)(*plVar5 + 0x130))(plVar5); + *(undefined4 *)(lVar6 + 0x30) = 0xffffffff; + *(undefined8 *)(lVar6 + 0x34) = 0; + iVar2 = FUN_1801c7f10(local_138); + while (iVar2 != 10) { + iVar2 = FUN_180141ee0(local_148,local_138); + if (iVar2 != 6) { + if (local_148[0] == 0xbc) { + uVar4 = FUN_1801c79d0(local_138); + uVar1 = FUN_1800d7b10(uVar4); + *(undefined2 *)(lVar6 + 0x34) = uVar1; + } + else if (local_148[0] == 0x1bf) { + uVar4 = FUN_1801c79d0(local_138); + uVar3 = FUN_1800d7af0(uVar4); + *(undefined4 *)(lVar6 + 0x30) = uVar3; + } + else if (local_148[0] == 0x1e5) { + uVar4 = FUN_1801c79d0(local_138); + uVar1 = FUN_1800d7b10(uVar4); + *(undefined2 *)(lVar6 + 0x3a) = uVar1; + } + else if (local_148[0] == 0x2b8) { + uVar4 = FUN_1801c79d0(local_138); + uVar1 = FUN_1800d7b10(uVar4); + *(undefined2 *)(lVar6 + 0x36) = uVar1; + } + else if (local_148[0] == 0x2c9) { + uVar4 = FUN_1801c79d0(local_138); + uVar1 = FUN_1800d7b10(uVar4); + *(undefined2 *)(lVar6 + 0x38) = uVar1; + } + else { + FUN_180135ff0(local_148[0],local_138); + } + } + iVar2 = FUN_1801c7f10(local_138); + } + *(undefined1 *)(lVar6 + 0x28) = 1; + FUN_1801c6560(local_138); + return 1; +} + + diff --git a/docs/evidence/market-sold-re-2026-08-17/q_sold_6.py b/docs/evidence/market-sold-re-2026-08-17/q_sold_6.py new file mode 100644 index 0000000..34cbbdd --- /dev/null +++ b/docs/evidence/market-sold-re-2026-08-17/q_sold_6.py @@ -0,0 +1,51 @@ +"""Q6 — the decisive reads: + (a) who builds the /sold request (clear-all-sold) and what it is called; + (b) who publishes FUT_TF_SELLING / FUT_TF_SOLD / FUT_TF_WINNING / FUT_TF_OUTBID, + i.e. which count feeds the seller's SOLD bucket; + (c) the GetAuctionCount deserializer, to pin the five counts atoms. +""" + +print("=" * 78) +print("== (a) xrefs: request names + the /sold path suffix") +print("=" * 78) +for label, a in (("RemoveAllSoldFromTradePile", 0x1801EFAE8), + ("RemoveFromTradePile", 0x1801EFAD0), + ("AddToWatchList", 0x1801EFAA8), + ("/sold suffix", 0x180228BEC), + ("RS4:FutISRemoveTradeServerResponse", 0x180228BC8)): + xs = xrefs_to(a) + print(f" {label:36s} {len(xs)} xref(s)") + for x in xs[:10]: + print(f" 0x{x[0]:x} {x[1]:12s} in {x[2]} (entry 0x{x[3]:x})") + +print() +print("=" * 78) +print("== (b) xrefs: the Transfer List caption keys") +print("=" * 78) +caps = (("FUT_TOTAL_AUCTIONS", 0x18020A080), ("FUT_UC_ITEMS", 0x18020A098), + ("FUT_TF_SELLING", 0x18020A0A8), ("FUT_TF_SOLD", 0x18020A0C0), + ("FUT_TF_WINNING", 0x18020A0D8), ("FUT_TF_OUTBID", 0x18020A0E8)) +owners = {} +for label, a in caps: + xs = xrefs_to(a) + print(f" {label:20s} {len(xs)} xref(s)") + for x in xs[:10]: + print(f" 0x{x[0]:x} {x[1]:12s} in {x[2]} (entry 0x{x[3]:x})") + owners[x[3]] = owners.get(x[3], 0) + 1 + +print() +print(" caption publisher candidates (entry -> #caption refs):", + {hex(k): v for k, v in sorted(owners.items(), key=lambda kv: -kv[1])}) + +for ent in sorted(owners, key=lambda e: -owners[e])[:2]: + print() + print("=" * 78) + print(f"== decompiled caption publisher 0x{ent:x}") + print("=" * 78) + print(dec(ent)) + +print() +print("=" * 78) +print("== (c) GetAuctionCount deserializer FUN_180163770") +print("=" * 78) +print(dec(0x180163770)) diff --git a/docs/evidence/market-sold-re-2026-08-17/q_sold_7.out b/docs/evidence/market-sold-re-2026-08-17/q_sold_7.out new file mode 100644 index 0000000..b449907 --- /dev/null +++ b/docs/evidence/market-sold-re-2026-08-17/q_sold_7.out @@ -0,0 +1,178 @@ +============================================================================== +== (a) hub tradePile sub-deserializer 0x18013ead0 +== atom ids of interest: count=0xbc notification=0x1da selling=0x2b8 sold=0x2c9 +============================================================================== + +undefined4 FUN_18013ead0(longlong param_1,longlong param_2,longlong param_3) + +{ + undefined2 uVar1; + int iVar2; + int iVar3; + undefined8 uVar4; + + iVar3 = 0x38c; + iVar2 = FUN_1801c7f10(param_3); + do { + if (iVar2 == 10) { + *(undefined1 *)(param_1 + 0x1da) = 1; + return 10; + } + if (*(int *)(param_3 + 0xd0) == 0xb) { + iVar3 = FUN_180180d00(*(undefined8 *)(param_3 + 0xf8)); + iVar2 = FUN_1801c7f10(param_3); + if (iVar2 != 6) goto LAB_18013eb2e; + } + else { +LAB_18013eb2e: + if (iVar3 == 0xbc) { + uVar4 = FUN_1801c79d0(param_3); + uVar1 = FUN_1800d7b10(uVar4); + *(undefined2 *)(param_1 + 0x1d4) = uVar1; + } + else if (iVar3 == 0x1da) { + uVar4 = FUN_1801c79d0(param_3); + uVar1 = FUN_1800d7b10(uVar4); + *(undefined2 *)(param_1 + 0x1d6) = uVar1; + } + else if (iVar3 == 0x2b8) { + uVar4 = FUN_1801c79d0(param_3); + uVar1 = FUN_1800d7b10(uVar4); + *(undefined2 *)(param_1 + 0x1d2) = uVar1; + uVar4 = FUN_1801c79d0(param_3); + uVar1 = FUN_1800d7b10(uVar4); + *(undefined2 *)(param_2 + 0x36) = uVar1; + } + else if (iVar3 == 0x2c9) { + uVar4 = FUN_1801c79d0(param_3); + uVar1 = FUN_1800d7b10(uVar4); + *(undefined2 *)(param_1 + 0x1d8) = uVar1; + } + else { + FUN_180135ff0(iVar3,param_3); + } + } + iVar2 = FUN_1801c7f10(param_3); + } while( true ); +} + + + +============================================================================== +== (b) /sold request builder 0x1801647c0 +============================================================================== + +/* WARNING: Function: __security_check_cookie replaced with injection: security_check_cookie */ + +void FUN_1801647c0(longlong param_1,undefined8 param_2,undefined4 param_3) + +{ + undefined1 auStack_58 [32]; + undefined1 local_38; + undefined8 local_37; + undefined8 local_2f; + undefined8 local_27; + undefined4 local_1f; + undefined2 local_1b; + undefined1 local_19; + ulonglong local_18; + + local_18 = DAT_1802db6e0 ^ (ulonglong)auStack_58; + local_38 = 0; + local_37 = 0; + local_2f = 0; + local_27 = 0; + local_1f = 0; + local_1b = 0; + local_19 = 0; + if (*(longlong *)(param_1 + 0x10) == 0) { + FUN_180007f80(&local_38,0x20,"/sold"); + } + else { + FUN_180007f80(&local_38,0x20,"/%lld"); + } + FUN_180008100(param_2,&local_38,param_3); + return; +} + + + +============================================================================== +== (c) FutGetAuctionCount deserializer 0x180163770 +============================================================================== + +/* WARNING: Function: __security_check_cookie replaced with injection: security_check_cookie */ + +undefined8 FUN_180163770(undefined8 param_1,undefined8 *param_2) + +{ + undefined2 uVar1; + int iVar2; + undefined4 uVar3; + undefined8 uVar4; + longlong *plVar5; + longlong lVar6; + undefined1 auStack_168 [32]; + int local_148 [2]; + undefined8 local_140; + undefined1 local_138 [288]; + ulonglong local_18; + + local_140 = 0xfffffffffffffffe; + local_18 = DAT_1802db6e0 ^ (ulonglong)auStack_168; + FUN_1801c63e0(local_138,0,0); + local_148[0] = 0x38c; + uVar4 = FUN_180008130(*param_2); + FUN_1801c8270(local_138,*param_2,uVar4,0); + FUN_1801c7f10(local_138); + FUN_1801c7f10(local_138); + plVar5 = (longlong *)FUN_18011a830(); + lVar6 = (**(code **)(*plVar5 + 0x130))(plVar5); + *(undefined4 *)(lVar6 + 0x30) = 0xffffffff; + *(undefined8 *)(lVar6 + 0x34) = 0; + iVar2 = FUN_1801c7f10(local_138); + while (iVar2 != 10) { + iVar2 = FUN_180141ee0(local_148,local_138); + if (iVar2 != 6) { + if (local_148[0] == 0xbc) { + uVar4 = FUN_1801c79d0(local_138); + uVar1 = FUN_1800d7b10(uVar4); + *(undefined2 *)(lVar6 + 0x34) = uVar1; + } + else if (local_148[0] == 0x1bf) { + uVar4 = FUN_1801c79d0(local_138); + uVar3 = FUN_1800d7af0(uVar4); + *(undefined4 *)(lVar6 + 0x30) = uVar3; + } + else if (local_148[0] == 0x1e5) { + uVar4 = FUN_1801c79d0(local_138); + uVar1 = FUN_1800d7b10(uVar4); + *(undefined2 *)(lVar6 + 0x3a) = uVar1; + } + else if (local_148[0] == 0x2b8) { + uVar4 = FUN_1801c79d0(local_138); + uVar1 = FUN_1800d7b10(uVar4); + *(undefined2 *)(lVar6 + 0x36) = uVar1; + } + else if (local_148[0] == 0x2c9) { + uVar4 = FUN_1801c79d0(local_138); + uVar1 = FUN_1800d7b10(uVar4); + *(undefined2 *)(lVar6 + 0x38) = uVar1; + } + else { + FUN_180135ff0(local_148[0],local_138); + } + } + iVar2 = FUN_1801c7f10(local_138); + } + *(undefined1 *)(lVar6 + 0x28) = 1; + FUN_1801c6560(local_138); + return 1; +} + + + +============================================================================== +== callers of the hub tradePile sub-deserializer (which response embeds it) +============================================================================== + 0x180139610 FUN_180139610 diff --git a/docs/evidence/market-sold-re-2026-08-17/q_sold_7.py b/docs/evidence/market-sold-re-2026-08-17/q_sold_7.py new file mode 100644 index 0000000..759b32f --- /dev/null +++ b/docs/evidence/market-sold-re-2026-08-17/q_sold_7.py @@ -0,0 +1,31 @@ +"""Q7 — close it: + (a) hub tradePile sub-deserializer 0x18013ead0: which ATOM writes +0x1d8 (the + offset the FUT_TF_SOLD caption reads)? + (b) the /sold request builder 0x1801647c0 — bulk clear, or per-trade? + (c) FutGetAuctionCount deserializer 0x180163770 — the five counts atoms. +""" + +print("=" * 78) +print("== (a) hub tradePile sub-deserializer 0x18013ead0") +print("== atom ids of interest: count=0xbc notification=0x1da selling=0x2b8 sold=0x2c9") +print("=" * 78) +print(dec(0x18013EAD0)) + +print() +print("=" * 78) +print("== (b) /sold request builder 0x1801647c0") +print("=" * 78) +print(dec(0x1801647C0)) + +print() +print("=" * 78) +print("== (c) FutGetAuctionCount deserializer 0x180163770") +print("=" * 78) +print(dec(0x180163770)) + +print() +print("=" * 78) +print("== callers of the hub tradePile sub-deserializer (which response embeds it)") +print("=" * 78) +for a, n in callers(0x18013EAD0): + print(f" 0x{a:x} {n}") diff --git a/docs/evidence/market-sold-re-2026-08-17/q_sold_8.py b/docs/evidence/market-sold-re-2026-08-17/q_sold_8.py new file mode 100644 index 0000000..4a012b6 --- /dev/null +++ b/docs/evidence/market-sold-re-2026-08-17/q_sold_8.py @@ -0,0 +1,25 @@ +"""Q8 — are atoms 0x36..0x3c (auctionLost*/auctionSold*/auctionWon*) real response +keys? If a deserializer dispatches on them, the server tells the client HOW an +auction concluded via its own atom, and bidState is not the carrier. + +Signature of a genuine atom dispatcher: the SAME local compared against many atom +ids, with FUN_180135ff0 (value-SKIP) as the else, and FUN_1801c7f10 driving the loop. +""" + +CANDIDATES = (0x18012B370, 0x1801071F0, 0x1800EE7C0, 0x1800FB5B0, 0x1800CF3B0) + +for ent in CANDIDATES: + print("=" * 78) + print(f"== 0x{ent:x} callees include value-SKIP? " + f"{any(c[0] == 0x180135FF0 for c in callees(ent))}" + f" atom-loop? {any(c[0] == 0x1801C7F10 for c in callees(ent))}") + print("=" * 78) + body = dec(ent) + # Only print if it smells like an atom dispatcher, else just report. + if "FUN_180135ff0" in body or "FUN_1801c7f10" in body: + print(body[:6000]) + else: + print(" NOT an atom dispatcher (no value-SKIP, no atom loop) — small-immediate noise") + print(f" size {len(body)} chars; first 400:") + print(body[:400]) + print() diff --git a/docs/evidence/market-sold-re-2026-08-17/q_tax.out b/docs/evidence/market-sold-re-2026-08-17/q_tax.out new file mode 100644 index 0000000..2c41c10 --- /dev/null +++ b/docs/evidence/market-sold-re-2026-08-17/q_tax.out @@ -0,0 +1,62 @@ +============================================================================== +== localisation / string keys about tax, fee, net proceeds +============================================================================== + TAX (no printable hits) + tax (no printable hits) + Tax (no printable hits) + FEE 0x18023a3a8 'FEET_ATTEMPTED' + FEE 0x18023a3c0 'FEET_COMPLETED' + _FEE 0x18023a3a7 '_FEET_ATTEMPTED' + _FEE 0x18023a3bf '_FEET_COMPLETED' + fee (no printable hits) + NET 0x180209b1c 'NETOURNAMENT_0' + NET 0x180209b3c 'NETOURNAMENT_1' + NET 0x18020dd10 'NET' + _NET 0x18020dd0f '_NET' + RECEIVE 0x18023933f 'RECEIVER' + RECEIVE 0x180239350 'RECEIVER' + RECEIVE 0x18023983f 'RECEIVER' + RECEIVE 0x18023990d 'RECEIVER' + RECEIVE 0x180239a0b 'RECEIVER' + RECEIVE 0x18023a06f 'RECEIVED' + Receive (no printable hits) + PROCEEDS (no printable hits) + COMMISSION (no printable hits) + EA_TAX (no printable hits) + FUT_TF_ 0x18020a0a8 'FUT_TF_SELLING' + FUT_TF_ 0x18020a0c0 'FUT_TF_SOLD' + FUT_TF_ 0x18020a0d8 'FUT_TF_WINNING' + FUT_TF_ 0x18020a0e8 'FUT_TF_OUTBID' + AFTER_TAX (no printable hits) + earnings (no printable hits) + Earnings (no printable hits) + +============================================================================== +== float constants 0.95 / 0.05 / 0.95f / 0.05f +============================================================================== + double 0.95 ABSENT + double 0.05 ABSENT + float 0.95f ABSENT + float 0.05f ABSENT + +============================================================================== +== functions using BOTH 95 (or 5) and 100 as immediates — integer fee math +============================================================================== + 17 candidate(s) + 0x18000b5c0 FUN_18000b5c0 immediates [5, 100] + 0x180051cd0 FUN_180051cd0 immediates [5, 20, 100] + 0x180057b00 FUN_180057b00 immediates [5, 100] + 0x180082c30 FUN_180082c30 immediates [5, 100] + 0x180088cb0 FUN_180088cb0 immediates [5, 100] + 0x1800a3cb0 FUN_1800a3cb0 immediates [5, 100] + 0x1800a47b0 FUN_1800a47b0 immediates [5, 100] + 0x1800d5050 FUN_1800d5050 immediates [5, 20, 100] + 0x1800d5450 FUN_1800d5450 immediates [5, 20, 100] + 0x1801129f0 FUN_1801129f0 immediates [5, 100] + 0x18013af30 FUN_18013af30 immediates [5, 20, 100] + 0x18013ec10 FUN_18013ec10 immediates [5, 100] + 0x18013fe00 FUN_18013fe00 immediates [5, 100] + 0x180147070 FUN_180147070 immediates [5, 100] + 0x180180ea0 FUN_180180ea0 immediates [5, 100] + 0x1801adae0 FUN_1801adae0 immediates [5, 100] + 0x1801b9e60 FUN_1801b9e60 immediates [5, 100] diff --git a/docs/evidence/market-sold-re-2026-08-17/q_tax.py b/docs/evidence/market-sold-re-2026-08-17/q_tax.py new file mode 100644 index 0000000..6e88c8e --- /dev/null +++ b/docs/evidence/market-sold-re-2026-08-17/q_tax.py @@ -0,0 +1,69 @@ +"""Task B — does FIFA17.exe itself compute the transfer fee / net proceeds? + +If the list-item confirmation screen shows "you will receive N", the rounding rule +is IN THE BINARY and needs no live measurement. Look for: + * localisation keys mentioning tax/fee/net/receive + * float constants 0.95 / 0.05 + * integer 95 or 5 used with 100 in the same function +""" +import struct + +print("=" * 78) +print("== localisation / string keys about tax, fee, net proceeds") +print("=" * 78) +for needle in (b"TAX", b"tax", b"Tax", b"FEE", b"_FEE", b"fee", b"NET", b"_NET", + b"RECEIVE", b"Receive", b"PROCEEDS", b"COMMISSION", b"EA_TAX", + b"FUT_TF_", b"AFTER_TAX", b"earnings", b"Earnings"): + hits = find_all(needle, blocks=(".rdata", ".data")) + shown = 0 + for h in hits: + s = rd_str(h, 70) + if s and len(s) > 2 and s.isprintable(): + print(f" {needle.decode():10s} 0x{h:x} {s!r}") + shown += 1 + if shown >= 12: + break + if not shown: + print(f" {needle.decode():10s} (no printable hits)") + +print() +print("=" * 78) +print("== float constants 0.95 / 0.05 / 0.95f / 0.05f") +print("=" * 78) +for label, pat in (("double 0.95", struct.pack(" 1 (40, 2, 38), // exactly 2.0 (100, 5, 95), - (150, 7, 143), // 7.5 -> 7: THE discriminating case (alternative: 8/142) - (200, 10, 190), + (101, 5, 96), // 5.05 -> 5 + (119, 5, 114), // 5.95 -> 5, last price paying 5 + (120, 6, 114), // exactly 6.0 + (149, 7, 142), // 7.45 -> 7 + (150, 7, 143), // 7.5 -> 7: THE discriminating case (alternative: 8/142) + (151, 7, 144), // 7.55 -> 7 (a HALF-UP rule would pay 8 here too) + (199, 9, 190), // 9.95 -> 9 + (200, 10, 190), // exactly 10.0 (1_000, 50, 950), (15_000, 750, 14_250), // the canonical fixture (15_000_000, 750_000, 14_250_000), // FUT's practical price ceiling