"""D3 Q1: where do the seven packContentInfo fields land, and what object owns them? HYPOTHESIS: the pack element deser 0x18013af30 dispatches atom 0x20c (packContentInfo) into a nested object sub-deser, which writes seven scalars into a struct. A prior note (docs/plan-2026-08-04-blockers.md:201) claims the slots are +0x144..+0x154 and that nothing in cardsdll reads them back. Verify the offsets first-hand and find the sub-deser. CONTROL: class_deser("FutSquadSave") must return 0x180171a60 and class_deser("FutSquadList") must return 0x180172140. If those come back empty the whole batch is suspect. OUTPUT: full decompiles (len printed, never truncated) + raw disassembly of the pack element deser and of EVERY callee, so the store offsets are read off instructions, not off the decompiler's guessed structure. Also scores each callee by how many of the seven packContentInfo atom immediates (and their sub-ladder deltas) it contains, so the nested sub-deser is identified mechanically. """ import traceback, sys, os OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/packres/" os.makedirs(OUT, exist_ok=True) PCI_ATOMS = {0x63: "bronzeQuantity", 0x2c6: "silverQuantity", 0x149: "goldQuantity", 0x273: "rareQuantity", 0x170: "itemQuantity", 0x2e3: "start", 0x35d: "unopened"} # running-sum sub/dec ladder deltas between consecutive sorted atoms _s = sorted(PCI_ATOMS) PCI_DELTAS = {_s[i + 1] - _s[i] for i in range(len(_s) - 1)} def dump(tag, va, path, echo=True): src = dec(va) if echo: print("=" * 78) print("%s %#x fname=%s len(src)=%d (PRINTED IN FULL, NOT TRUNCATED)" % (tag, va, fname(va), len(src))) print("=" * 78) print(src) with open(path, "w") as fh: fh.write("// %s %#x len=%d\n" % (tag, va, len(src))) fh.write(src) return src def insns(va, limit=200000): f = func(va) out = [] if f is None: return out it = listing.getInstructions(f.getBody(), True) n = 0 while it.hasNext() and n < limit: ins = it.next() out.append((int(ins.getAddress().getOffset()), str(ins))) n += 1 return out def disasm(va, path): lines = ["%#x %s" % (a, s) for a, s in insns(va)] with open(path, "w") as fh: fh.write("\n".join(lines)) return lines def scalars(va): """set of every scalar immediate appearing in the function's instructions""" out = set() f = func(va) if f is None: return out it = listing.getInstructions(f.getBody(), True) while it.hasNext(): ins = it.next() for i in range(ins.getNumOperands()): for o in ins.getOpObjects(i): try: out.add(int(o.getValue()) & 0xFFFFFFFF) except Exception: pass return out try: print("### CONTROLS") for c, expect in (("FutSquadSave", 0x180171a60), ("FutSquadList", 0x180172140), ("FutCreateMatch", 0x180120380)): r = class_deser(c) print(" %-16s -> %s expect %#x %s" % (c, [hex(x[0]) for x in r], expect, "PASS" if any(x[0] == expect for x in r) else "FAIL/UNKNOWN")) PACK_DESER = 0x18013AF30 src = dump("PACK ELEMENT DESER", PACK_DESER, OUT + "d3_pack_elem_deser.txt") print("\n### CALLERS OF PACK ELEMENT DESER") for a, n in callers(PACK_DESER): print(" %#x %s" % (a, n)) dl = disasm(PACK_DESER, OUT + "d3_pack_elem_deser.asm") print("\n### DISASM %d instructions -> d3_pack_elem_deser.asm" % len(dl)) print("\n### CALLEES OF PACK ELEMENT DESER, scored for packContentInfo atoms") cand = [] for a, n in callees(PACK_DESER): sc = scalars(a) hit_atoms = sorted(x for x in sc if x in PCI_ATOMS) hit_delta = sorted(x for x in sc if x in PCI_DELTAS) score = len(hit_atoms) + len(hit_delta) print(" %#x %-28s natoms=%d %s ndelta=%d %s" % (a, n, len(hit_atoms), [hex(x) for x in hit_atoms], len(hit_delta), [hex(x) for x in hit_delta])) cand.append((score, a, n)) dump("CALLEE", a, OUT + "d3_callee_%x.txt" % a, echo=False) disasm(a, OUT + "d3_callee_%x.asm" % a) cand.sort(reverse=True) print("\n### CALL SITES INSIDE PACK ELEM DESER (address -> target)") for ad, s in dl: if s.startswith("CALL"): t = s.split()[-1] try: tv = int(t, 16) print(" %#x %s -> %s" % (ad, s, fname(tv))) except Exception: print(" %#x %s" % (ad, s)) print("\n### TOP CANDIDATE SUB-DESERS") for score, a, n in cand[:3]: print(" score=%d %#x %s" % (score, a, n)) if cand and cand[0][0] >= 3: best = cand[0][1] s2 = dump("PACKCONTENTINFO SUB-DESER (best candidate)", best, OUT + "d3_pci_subdeser.txt") d2 = disasm(best, OUT + "d3_pci_subdeser.asm") print("\n### FULL DISASM OF %#x (%d instructions)" % (best, len(d2))) for ln in d2: print(" " + ln) except Exception: traceback.print_exc() sys.stdout.flush()