docs(market): recover the FIFA17 sold wire contract from CardsDLL (Ghidra)

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.
This commit is contained in:
funman300
2026-08-18 01:31:27 +00:00
parent cb32fe9b84
commit 571c5f9261
19 changed files with 2059 additions and 4 deletions
+241
View File
@@ -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 |
+2 -2
View File
@@ -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
@@ -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'
@@ -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")
@@ -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'
@@ -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}")
@@ -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'
@@ -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("<Q", target)
hits = find_all(pat, blocks=(".rdata", ".data", ".text"))
print(f" {label:20s} pointer found at: {[hex(h) for h in hits] or 'NOWHERE'}")
print()
print("=" * 78)
print("== the .data array around 0x1802d3da8 (the one DATA xref to 'sold')")
print("=" * 78)
for i in range(-8, 12):
ea = 0x1802D3DA8 + i * 8
try:
p = qword(ea)
except Exception:
continue
s = rd_str(p, 50) if 0x180000000 < p < 0x180400000 else ""
mark = " <<<" if i == 0 else ""
print(f" 0x{ea:x} -> 0x{p:x} {s!r}{mark}")
@@ -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("<Q", sa), blocks=(".rdata", ".data"))
return hits[0] if hits else None
print("=" * 78)
print("== solve for the table base using the twelve known atoms")
print("=" * 78)
bases = {}
for aid, name in sorted(KNOWN.items()):
sa = str_addr(name)
pa = ptr_addr(sa) if sa else None
if pa is None:
print(f" 0x{aid:03x} {name:20s} string=0x{sa or 0:x} pointer=NOT FOUND")
continue
base = pa - 8 * aid
bases[base] = bases.get(base, 0) + 1
print(f" 0x{aid:03x} {name:20s} str=0x{sa:x} ptr=0x{pa:x} -> 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})")
@@ -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 "`'"
@@ -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("<Q", sa), blocks=(".rdata", ".data")):
if TABLE_LO <= pa < TABLE_HI and (pa - BASE) % 8 == 0:
out.append((sa, pa, (pa - BASE) // 8))
return out
print("=" * 78)
print("== atom ids, resolved against the table range only")
print("=" * 78)
for name in ("sold", "count", "offered", "selling", "maxAuctionsAllowed",
"credits", "auctionInfo", "itemData", "bidState", "tradeState",
"coinsProcessed", "offers", "duplicateItemIdList", "total"):
res = atom_id(name)
if not res:
print(f" {name:22s} ABSENT from the atom table")
for sa, pa, aid in res:
print(f" {name:22s} str=0x{sa:x} ptr=0x{pa:x} ATOM ID = 0x{aid:x} ({aid})")
print()
print("=" * 78)
print("== functions that compare against >=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
@@ -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;
}
@@ -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))
@@ -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
@@ -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}")
@@ -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()
@@ -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]
@@ -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("<d", 0.95)),
("double 0.05", struct.pack("<d", 0.05)),
("float 0.95f", struct.pack("<f", 0.95)),
("float 0.05f", struct.pack("<f", 0.05))):
hits = find_all(pat, blocks=(".rdata", ".data"))
print(f" {label:12s} {[hex(h) for h in hits[:10]] or 'ABSENT'}")
for h in hits[:6]:
xs = xrefs_to(h)
for x in xs[:4]:
print(f" used at 0x{x[0]:x} in {x[2]} (entry 0x{x[3]:x})")
print()
print("=" * 78)
print("== functions using BOTH 95 (or 5) and 100 as immediates — integer fee math")
print("=" * 78)
found = []
for f in fm.getFunctions(True):
ent = int(f.getEntryPoint().getOffset())
vals = set()
it = listing.getInstructions(f.getBody(), True)
while it.hasNext():
ins = it.next()
if ins.getMnemonicString() not in ("IMUL", "MOV", "CMP", "ADD", "SUB", "LEA", "SHL"):
continue
for i in range(ins.getNumOperands()):
for o in ins.getOpObjects(i):
try:
v = int(o.getValue())
except Exception:
continue
if v in (95, 100, 5, 20):
vals.add(v)
if 100 in vals and (95 in vals or 5 in vals):
found.append((ent, f.getName(), sorted(vals)))
print(f" {len(found)} candidate(s)")
for ent, nm, vals in found[:40]:
print(f" 0x{ent:x} {nm:26s} immediates {vals}")