docs+tools: measure the FIFA 17 market gate bytes in the live client

Adds trade_gate_probe.py (read-only: /proc/<pid>/mem O_RDONLY + pread, slide proven
against the on-disk FNV prologue), extending gate_byte_probe.py to vtable slot
+0x270 exactly as the transfer-market analysis asked for.

Measured: IS_TRADING_ENABLED=1 (was 0 in the Python era), TRADE_PILE_SIZE=100
(was 0), watchListSize=50 (was 0), with four controls reading 1. So every
CardsDLL-supplied input that analysis named as a market blocker is now OPEN, which
the Rust host achieves by construction -- it emits userInfo.feature as {} so the
kill switch at 0x180174f19 never arms, and it already sends pileSizeClientData
keys 2 and 4.

This narrows the Actions-panel question to the exe-side UI script term, and rules
out ownership fields, the gate bytes, the cancel route and the state vocabularies
as candidates -- each on measured or PE-derived evidence rather than inference.
This commit is contained in:
funman300
2026-08-17 19:09:03 +00:00
parent 772f8a615a
commit dcbef721f2
2 changed files with 125 additions and 0 deletions
+49
View File
@@ -147,3 +147,52 @@ today (Core models no untradeable items) and necessary — a hardcoded `true` gr
out both list buttons — but it will misrepresent SBC / promo / loan rewards once
those exist. `untradeable` belongs on the owned-item instance as authoritative
state, not inferred from definition, resourceId or rarity.
---
## MEASURED in the live client — 2026-08-17
Read out of the running `FIFA17.exe` (pid-resolved, CardsDLL slide proven against
the on-disk FNV prologue) with `fifa17-recon/tools/trade_gate_probe.py`, which
extends `gate_byte_probe.py` to vtable slot `+0x270` as the transfer-market
analysis asked for. Read-only: `/proc/<pid>/mem` `O_RDONLY` + `pread`.
| Gate | Python era (2026-08-06) | Now | Owner |
|---|---|---|---|
| `IS_TRADING_ENABLED` `model+0x1fd2e` (slot `+0x270`) | **0** | **1** | settings struct `+0x28`; was zeroed by `userInfo.feature.trade` |
| `TRADE_PILE_SIZE` `model+0x1fd1c` | **0** | **100** | `userMassInfo.pileSizeClientData` key 2 |
| watch-list size `model+0x1fd20` | **0** | **50** | same member, key 4 |
| `storeEnabled` `model+0x1fd2f` | 1 | 1 | control |
| `IS_FRIENDLY_SEASON` / `IS_DRAFT_MODE` / `packOpeningAnimation` | 1 | 1 | controls |
**CONFIRMED: every CardsDLL-supplied input the transfer-market analysis named as a
blocker is now open.** The Rust host does this by construction — it emits
`userInfo.feature` as `{}` (no `trade` member, so the kill switch at `0x180174f19`
never arms: it fires only when atom `0x330` inside `0x11c` parses as exactly 1) and
it already sends `pileSizeClientData` keys 2 and 4. Serving `tradingEnabled: 1` in
the settings `configs` array would NOT have worked, because that tail runs after
every member is parsed and would overwrite it.
### What this rules out
The Transfer List Actions panel not opening on an own listing is therefore **not**:
* an ownership field — FIFA 17's auctionInfo has no `tradeOwner`/`sellerId` atom;
* `IS_TRADING_ENABLED`, `TRADE_PILE_SIZE` or the watch-list size — all measured open;
* the cancel route — `DELETE ut/delete/{ns}/trade/{tradeId}` is the PE's spelling and
is what we serve;
* `tradeState` / `bidState` / `expires` spellings — all three are the PE's own
vocabularies and values.
Per the analysis's own falsifier ("if the byte reads 1 and the screen still refuses,
the exe-side predicate has a term we have not enumerated"), the remaining term is
**exe-side UI script**, which CardsDLL does not own and the server cannot set.
Status: **UNKNOWN**, and it is now the narrowest it has ever been.
### Confirmed fidelity bug found on the way
`expires` was a frozen `3600` on every poll, so the client's live countdown never
moved and an auction could never run out. Now derived from `created_at + duration`
(duration taken from the `ISStart` body), clamped at 0, with an aged-out active
listing projecting as `expired`/`none` — FIFA 17's relistable state. Verified live:
the standing listing correctly reads `expires: 0` once past its hour.