25b7089c54
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.
63 lines
2.1 KiB
Python
Executable File
63 lines
2.1 KiB
Python
Executable File
#!/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}")
|