tools(re): walk the record pool embedded in the club-model owner
Records are 0x180 bytes starting at owner+0x162d8, below the embedded manager subobject at owner+0x1f9d8. Walking that pool answers whether the client ever builds a record for an item it was served, independently of whether the record is filed into a collection. Result on the live client with the kit selector open: 21 records exist - 18 squad players with category 1, plus one cardtype 10 and two cardtype 4 staff with category 0 - and no cardtype 7 record anywhere, despite two kit items having been served three times in the same boot.
This commit is contained in:
Executable
+62
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Read-only scan of the record pool embedded in the club-model owner object.
|
||||
|
||||
The 18 resident player records sit at a fixed stride of 0x180 inside the owner
|
||||
object, below the embedded manager subobject at owner+0x1f9d8. This walks that
|
||||
pool to see whether storage for the five club items exists and what it holds.
|
||||
Read-only. Never writes.
|
||||
"""
|
||||
import re, struct, sys
|
||||
|
||||
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("<Q", rd(a, 8))[0]
|
||||
|
||||
named = []
|
||||
for ln in open(f"/proc/{PID}/maps"):
|
||||
m = re.match(r"([0-9a-f]+)-([0-9a-f]+) \S{4} \S+ \S+ \S+\s+(.+)", ln)
|
||||
if m:
|
||||
named.append((int(m.group(1), 16), m.group(3).strip()))
|
||||
named.sort()
|
||||
base = next(s for s, p in named if p.endswith("CardsDLL_Win64_retail.dll"))
|
||||
def live(s):
|
||||
return base + (s - 0x180000000)
|
||||
|
||||
owner = q(live(0x1802e6398))
|
||||
mgr = owner + 0x1f9d8
|
||||
beg, end = q(mgr + 0xd8), q(mgr + 0xe0)
|
||||
first = None
|
||||
for k in range((end - beg) // 24):
|
||||
r = q(beg + k * 24 + 0x10)
|
||||
if r:
|
||||
first = r; break
|
||||
if first is None:
|
||||
sys.exit("no populated player record to anchor the pool")
|
||||
|
||||
print(f" owner = {owner:#x} mgr = {mgr:#x} first record = {first:#x}")
|
||||
print(f" record - owner = {first - owner:#x} pool room to mgr = {(mgr - first) // 0x180} slots of 0x180")
|
||||
print()
|
||||
hdr = f" {'idx':>3} {'addr':>12} {'ctype':>6} {'subtyp':>6} {'state':>6} {'cat':>4} {'team':>5} {'kittyp':>6} set"
|
||||
print(hdr)
|
||||
n = (mgr - first) // 0x180
|
||||
for k in range(min(n, 40)):
|
||||
a = first + k * 0x180
|
||||
try:
|
||||
r = rd(a, 0xC0)
|
||||
except OSError:
|
||||
print(f" {k:>3} {a:#12x} unreadable"); break
|
||||
if len(r) < 0xC0:
|
||||
break
|
||||
ct, sub, st, cat, team = (struct.unpack_from("<i", r, o)[0] for o in (0x4c, 0x50, 0x5c, 0x60, 0x94))
|
||||
kt = struct.unpack_from("<H", r, 0xba)[0]
|
||||
nz = sum(1 for b in r if b)
|
||||
flag = ""
|
||||
if ct == 7:
|
||||
flag = " <== CARDTYPE 7"
|
||||
elif nz == 0:
|
||||
flag = " (all zero)"
|
||||
print(f" {k:>3} {a:#12x} {ct:>6} {sub:>6} {st:>6} {cat:>4} {team:>5} {kt:>6} {nz:>3}/192{flag}")
|
||||
Reference in New Issue
Block a user