"""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()
