From 8983998707f25e425bbf2aec7c217d6eba6cdd04 Mon Sep 17 00:00:00 2001 From: funman300 Date: Mon, 24 Aug 2026 16:54:04 +0000 Subject: [PATCH] tools(re): dump resident record fields to settle the kit category gate The kit clone driver FUN_1801c3480 gates on record+0x60 == 4, and no instruction stores that immediate, so the value had to be read off a genuinely resident record. This dumps cardtype, cardsubtypeid, itemState, category, teamid and teamkittypetechid for both the player vector and the club-item vector, resolving the store through the same chain as the census. Result: all 18 resident players carry category 1 and the five club-item slots are null, which identifies +0x60 as a per-collection tag rather than item data. --- .../tools/live/probe_resident_fields.py | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100755 fifa17-recon/tools/live/probe_resident_fields.py diff --git a/fifa17-recon/tools/live/probe_resident_fields.py b/fifa17-recon/tools/live/probe_resident_fields.py new file mode 100755 index 0000000..c707135 --- /dev/null +++ b/fifa17-recon/tools/live/probe_resident_fields.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +"""Read-only dump of RESIDENT record fields, for both the player and club-item vectors. + +Purpose: the kit clone driver FUN_1801c3480 gates on record+0x60 (category) == 4. +No instruction in CardsDLL writes immediate 4 there, so this reads what value a +genuinely resident record actually carries. Read-only. Never writes. + + mgr+0x0c0 cardtype-2 single slot + mgr+0x0d8..0x0e0 cardtype-1 (player) vector + mgr+0x108..0x110 club-item vector + record+0x4c cardtype +0x50 cardsubtypeid +0x5c itemState + record+0x60 category +0x94 teamid +0xba teamkittypetechid (u16) +""" +import re, struct, sys, collections + +PID = int(sys.argv[1]) +mem = open(f"/proc/{PID}/mem", "rb", buffering=0) + +def rd(a, n): + mem.seek(a); return mem.read(n) +def q(a): + return struct.unpack(" 24 * 100000: + print(" implausible vector, skipping") + continue + # resolve stride: the element must contain a plausible heap pointer + for stride, ptr_off in ((24, 0x10), (16, 0x08), (8, 0x00)): + if span % stride: + continue + n = span // stride + recs, nulls = [], 0 + ok = True + for k in range(n): + try: + rec = q(beg + k * stride + ptr_off) + except OSError: + ok = False; break + if not rec: + nulls += 1; continue + d = decode(rec) + if d is None: + ok = False; break + recs.append((rec, d)) + if not ok: + continue + print(f" stride {stride} (ptr at +{ptr_off:#x}): {n} slots, {len(recs)} populated, {nulls} null") + if not recs and nulls != n: + continue + hist = collections.Counter(d[0:2] for _, d in recs) + for key, c in sorted(hist.items(), key=lambda x: -x[1]): + print(f" (cardtype,subtype)={key} x{c}") + print(f" {'ptr':>14} " + " ".join(f"{f:>8}" for f in FIELDS)) + for rec, d in recs[:8]: + print(f" {rec:#14x} " + " ".join(f"{v:>8}" for v in d)) + cats = collections.Counter(d[3] for _, d in recs) + if cats: + print(f" CATEGORY (+0x60) distribution: {dict(cats)}") + break