#!/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 = [], [] ok = True for k in range(n): try: rec = q(beg + k * stride + ptr_off) except OSError: ok = False; break if not rec: nulls.append(k); continue d = decode(rec) if d is None: ok = False; break recs.append((k, rec, d)) if not ok: continue print(f" stride {stride} (ptr at +{ptr_off:#x}): {n} slots, {len(recs)} populated, {len(nulls)} null") if not recs and len(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}") # The SLOT INDEX is load-bearing evidence: the squad parser's `actives` # arm writes element i to slot `r15d + i`, and r15d is shared scratch # that other atom handlers clobber. Which slots are filled therefore # reveals the index the parse actually started from. print(f" {'slot':>4} {'ptr':>14} " + " ".join(f"{f:>8}" for f in FIELDS)) for k, rec, d in recs[:8]: print(f" {k:>4} {rec:#14x} " + " ".join(f"{v:>8}" for v in d)) if nulls: print(f" empty slots: {nulls[:16]}") cats = collections.Counter(d[3] for _, _, d in recs) if cats: print(f" CATEGORY (+0x60) distribution: {dict(cats)}") break