tools(fifa17): answer "who writes item +0x60" — nothing does

The plan called this "the single blocker between 'we can mark a kit equipped'
and 'we can equip a kit'", and recorded that two attempts to find the writer
drowned at 1688 and 4144 instructions.

They drowned because +0x60 is a common struct offset. Two filters make it
readable: only an IMMEDIATE store can introduce a constant (a register store
just propagates one), and item-record code is recognisable by touching +0x4c
(cardtype) or +0x5c (itemState) within a few instructions.

Measured read-only against pid 6580:
  - live +0x60 over all 27 resident records: {1: 23 players, 0: 4 staff}, never 4
  - CardsDLL has 4 comparisons of +0x60 (0, 0, 1, 4); the 4 is the kit gate and
    is the ONLY such comparison in the process
  - CardsDLL has 29 immediate stores to +0x60, constants {-2,0,1,908,0x3f800000}
  - FIFA17.exe, across 79 MB of code: ZERO stores of 4, zero comparisons with 4
  - the gate function has one xref (a jmp) and its address is never taken
  - every register store to +0x60 in CardsDLL is a struct copy or an init

So the gate is not a wire field we failed to send: the value it demands is never
produced by anything. Decoding it fully also shows every OTHER input is already
served — cardtype 7, itemState 101/102, teamid — leaving only the +0xba variant
selector beneath it, which makes a client-side patch the only remaining avenue.
This commit is contained in:
funman300
2026-08-21 21:10:35 +00:00
parent 7f17cfe439
commit a842c5ffb0
2 changed files with 200 additions and 13 deletions
@@ -528,13 +528,6 @@ effects move in the permissive direction. There is also a second escape hatch in
that gate -- `svc->0x308()` on service `0xed80ed8` -- that nobody resolved, so if
squad submission behaves oddly afterwards, that is where to look.
**"List on Transfer Market" as a separate menu entry was not found.** The eight
flags contain `TO_TRADE_PILE` and no listing action. `FUN_18003e550` publishes
`DURATION` / `START_PRICE` / `ASKING_PRICE`, which is the listing panel, but
whether it has its own enable predicate was not chased. The likely explanation is
that listing is only reachable from the trade pile, so both entries share one root
cause, but that is an inference and it is not established.
### Equipping club items
`itemState` really is the equip mechanism for the `IS_ACTIVE` tick:
@@ -553,12 +546,29 @@ will not change the kit.
### Needs decompiling only
**Who writes item `+0x60`.** It gates the kit swap at value 4 and we can produce 1
and 6. Both attempts to scan for it drowned: `+0x60` returns 1688 and 4144
instructions depending on method. The narrower anchor is the `/club` and
`/purchased` response handlers -- find the list-insert that assigns it, read the
constants. This is the single blocker between "we can mark a kit equipped" and "we
can equip a kit".
**Who writes item `+0x60`. ANSWERED 2026-08-21 — NOTHING DOES.** It gates the kit
swap at value 4 and we can produce 1 and 6. Both earlier scans drowned (`+0x60`
returns 1688 and 4144 instructions) because it is a common struct offset. Two
filters cut it to a readable set: only an IMMEDIATE store can introduce a
constant, and item-record code is recognisable by touching `+0x4c`/`+0x5c`
nearby. Measured with `fifa17-recon/tools/kit_gate_probe.py` against pid 6580:
| evidence | result |
|---|---|
| live `+0x60`, all 27 resident records | `{1: 23 players, 0: 4 staff}` — never 4 |
| `cmp dword [reg+0x60], imm8` in CardsDLL | 4 sites: `0`, `0`, `1`, `4`; the `4` is the gate and is UNIQUE in the process |
| immediate stores to `[reg+0x60]`, CardsDLL | 29; constants `{-2, 0, 1, 908, 0x3f800000}` — no 4 |
| immediate stores of 4, FIFA17.exe (79 MB) | 0; also 0 comparisons against 4 |
| xrefs to the gate function | 1 (`jmp` from `0x1801a5329`); address never taken |
| register stores to `+0x60`, CardsDLL | all struct copies or inits to 0/1/-2 |
So the blocker is not a wire field we have not learned to send: the value the
gate demands is never produced by anything. Every OTHER input to the gate is
already served — `+0x4c == 7` (subtype 9), `+0x5c` 101/102
(`activeHomeKit`/`activeAwayKit`), `+0x94` teamid — leaving only the `+0xba`
variant selector below it. A client-side patch is therefore the only remaining
avenue, and a small one; it is not proposed here.
**The kit variant selector.** `FUN_1801bfac0` distinguishes home, away and third
kits from `FUN_1801a8800` (`+0xba`, u16) and `FUN_1801a8040` (`+0xbf`, signed