"""D3 run 5: the response objects' own virtuals, and who sizes the pack reveal. SETTLED SO FAR Pack record (0x158 bytes) lifecycle inside CardsDLL is a CLOSED graph: 0x1801234e0 root deser -> 0x18013af30 element deser -> ctor 0x1801342d0, push_back 0x180132180 (-> uninit_copy 0x180133210, copy-assign 0x1801340e0), stack copy destroyed by 0x1801232a0; vector freed by 0x180123200, whose only caller is the response object's scalar_deleting_destructor 0x1801233e0. Nothing else in .text constructs, copies or destroys one. FutStoreGetPackTypesServerResponse vtable = 0x18021dd68 (referenced only by its ctor 0x180123030). Pack vector at obj+0x28/0x30/0x38, timestamp obj+0x5c. FutCreatePackServerResponse vtable = 0x180228260, deser 0x180162880: numberItems(0x1dd) -> obj+0x28 (raw 8-byte store), itemList(0x16e) -> vector obj+0x30/0x38/0x40 with 0x18-byte elements, purchasedPackId(0x264) -> obj+0x70. THIS RUN A. Decompile every class-specific virtual of both response objects. The two classes share slots +0x10..+0x38 and +0x48..+0x68 (generic base) but differ at +0x00 and +0x40, so +0x40 is where per-class behaviour lives. B. Find the RPC/command strings STOREPACKTYPES and CREATEPACK and their xrefs, to reach the code that consumes each response. C. Ask directly whether the reveal is sized from numberItems (obj+0x28) or from the itemList vector length: enumerate readers of the CreatePack object. CONTROL for B: the string "CREATEPACK" is written into the pack record by its own ctor at rec+0xd8, so at least that xref must come back; if the string search returns nothing at all the search is broken. """ 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) def dump(tag, va, path, echo=True): src = dec(va) hdr = "%s %#x fname=%s len(src)=%d (FULL, NOT TRUNCATED)" % ( tag, va, fname(va), len(src)) if echo: print("=" * 78) print(hdr) print("=" * 78) print(src) with open(path, "w") as fh: fh.write("// " + hdr + "\n" + src) return src try: print("### A. CLASS-SPECIFIC VIRTUALS") for va, tag in ((0x180123030, "FutStoreGetPackTypes ctor"), (0x1801233e0, "FutStoreGetPackTypes scalar_deleting_dtor"), (0x1801233a0, "FutStoreGetPackTypes vtbl+0x40"), (0x180123100, "0x158-stride helper near store class"), (0x180122420, "shared vtbl+0x20"), (0x180162420, "FutCreatePack ctor"), (0x1801624e0, "FutCreatePack scalar_deleting_dtor"), (0x1801624a0, "FutCreatePack vtbl+0x40"), (0x18014c990, "0x158 ADD (unclassified)")): try: dump(tag, va, OUT + "d3_v_%x.txt" % va, echo=True) print(" callers: %s" % [(hex(a), n) for a, n in callers(va)]) except Exception as e: print(" !! %s: %s" % (tag, e)) print("\n### B. COMMAND STRINGS") for s in (b"STOREPACKTYPES\x00", b"CREATEPACK\x00", b"V2STORE\x00", b"STOREPACKQUANTITIES\x00", b"PURCHASEDITEMS\x00"): hits = find_all(s) print(" %-24s hits=%s" % (s.decode(errors="replace").strip("\x00"), [hex(x) for x in hits])) for h in hits: for frm, typ, fn, ent in xrefs_to(h): print(" xref %#x %s in %s" % (frm, typ, fn)) print("\n### C. WHO READS THE RESPONSE OBJECTS") for vt, nm in ((0x18021dd68, "FutStoreGetPackTypes vtable"), (0x180228260, "FutCreatePack vtable")): print(" xrefs to %s %#x:" % (nm, vt)) for frm, typ, fn, ent in xrefs_to(vt): print(" %#x %s %s" % (frm, typ, fn)) print("\n### C2. every .text reference to the two vtable ADDRESSES as immediates") for vt in (0x18021dd68, 0x180228260): pat = vt.to_bytes(8, "little") for h in find_all(pat, blocks=(".text", ".rdata", ".data")): f = fm.getFunctionContaining(addr(h)) print(" vtbl %#x embedded at %#x in %s" % (vt, h, f.getName() if f else "(data)")) except Exception: traceback.print_exc() sys.stdout.flush()