From 0997dd3b6091dad1e582bb66d658f586bf8c30b4 Mon Sep 17 00:00:00 2001 From: funman300 Date: Mon, 24 Aug 2026 16:08:02 +0000 Subject: [PATCH] tools(re): census FIFA 17's resident club-item vector Read-only /proc/PID/mem census of the client's resident club-item store, which is what the pre-match kit selector actually consumes. No writes. Resolves the whole chain from static RE rather than guessing offsets: [CardsDLL+0x2e6398] -> owner object (FUN_18011a830 is a plain global read) owner->vtable[0x4e8] -> lea rax,[rcx+0x1f9d8]; ret, i.e. mgr is an EMBEDDED subobject, not a pointer mgr+0x108 .. mgr+0x110 -> club-item vector, stride 24 element+0x10 -> the item record (FUN_1800d73d0) CardsDLL is located by its NEAREST PRECEDING NAMED mapping, because Wine maps PE sections anonymously and the Wine heap is also rwx, so permissions do not discriminate code from heap. The base is then sanity-checked against a known immediate (mov edx,0x7575 at 0x180026fea) and the tool aborts rather than reporting from a wrong base. Field offsets are the ones already proven, and nothing else is interpreted: +0x4c cardtype, +0x50 cardsubtypeid, +0x5c itemState, +0x60 category, +0x94 teamid, +0xba teamkittypetechid (u16). FIRST RESULT, live on the client parked at the pre-match kit selector: players mgr+0x0d8: 23 slots, 18 non-null, all (cardtype 1, subtype 0) club items mgr+0x108: 5 slots, 0 non-null Five slots, every item pointer NULL. Five is the club's active club-item set -- home kit, away kit, badge, stadium, ball -- so the client knows it should hold five and holds none. That is why FUN_1800d73d0 returns the 0x1802c2a28 sentinel whose +0x10 is NULL, and why the tiles are untextured. --- fifa17-recon/tools/live/census_clubitems.py | 114 ++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100755 fifa17-recon/tools/live/census_clubitems.py diff --git a/fifa17-recon/tools/live/census_clubitems.py b/fifa17-recon/tools/live/census_clubitems.py new file mode 100755 index 0000000..16efc01 --- /dev/null +++ b/fifa17-recon/tools/live/census_clubitems.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +"""Read-only census of FIFA 17's RESIDENT club-item vector. + +Chain, every link from CardsDLL static RE: + [CardsDLL+0x2e6398] -> owner object (FUN_18011a830) + owner->vtable[0x4e8] -> getter returning mgr (call *0x4e8(%rdx)) + mgr+0x108 .. mgr+0x110 -> club-item vector, stride 24 + element+0x10 -> the item record pointer (FUN_1800d73d0) + record+0x4c cardtype (derived from cardsubtypeid by FUN_1800d8330: 9/10/11 -> 7) + record+0x50 cardsubtypeid + record+0x5c itemState (101 activeHomeKit, 102 activeAwayKit) + record+0x60 category (clone driver FUN_1801c3480 requires 4) + record+0x94 teamid + record+0xba teamkittypetechid (u16) +Offsets not in that list are labelled UNVERIFIED and only dumped raw. +No writes. Ever. +""" +import re, struct, sys, collections + +PID = int(sys.argv[1]) if len(sys.argv) > 1 else 44405 +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(" 48 8b 81 off32 c3 or 48 8b 41 off8 c3 +b = rd(getter, 12) +mgr = None +if b[0:3] == bytes.fromhex("488d81"): + off = struct.unpack_from(" mgr = {mgr:#x}") +elif b[0:3] == bytes.fromhex("488d41"): + off = b[3]; mgr = owner + off + print(f" getter returns owner+{off:#x} (EMBEDDED subobject) -> mgr = {mgr:#x}") +elif b[0:3] == bytes.fromhex("488b81"): + off = struct.unpack_from(" mgr = {mgr:#x}") +elif b[0:3] == bytes.fromhex("488b41"): + off = b[3]; mgr = q(owner + off) + print(f" getter returns [owner+{off:#x}] -> mgr = {mgr:#x}") +elif b[0:2] == bytes.fromhex("488b") and b[2] == 0xc1: + mgr = owner; print(" getter returns owner itself") +else: + print(" getter shape unrecognised; trying owner as mgr") + mgr = owner + +for label, mgr_try in (("resolved", mgr), ("owner", owner)): + try: + beg, end = q(mgr_try + 0x108), q(mgr_try + 0x110) + except OSError: + print(f" [{label}] +0x108/0x110 unreadable"); continue + if not (0 < beg <= end) or (end - beg) % 24 or (end - beg) > 24*100000: + print(f" [{label}] vector implausible: {beg:#x}..{end:#x}") + continue + n = (end - beg) // 24 + print(f"\n === club-item vector via {label}: {beg:#x}..{end:#x} {n} slot(s) ===") + hist = collections.Counter(); rows = [] + for k in range(n): + try: + rec = q(beg + k*24 + 0x10) + except OSError: + continue + if not rec: + hist[("", None)] += 1; continue + try: + r = rd(rec, 0xC0) + except OSError: + continue + if len(r) < 0xC0: continue + ct, sub, st, cat = i32(r,0x4c), i32(r,0x50), i32(r,0x5c), i32(r,0x60) + team = i32(r,0x94); kt = struct.unpack_from("14} {'ctype':>5} {'subtype':>7} {'state':>5} {'cat':>4} {'team':>5} {'kittype':>7}") + for rec, ct, sub, st, cat, team, kt in rows[:12]: + print(f" {rec:#14x} {ct:>5} {sub:>7} {st:>5} {cat:>4} {team:>5} {kt:>7}") + break