"""D3 run 8: what actually sizes the pack reveal.

FOUND IN RUN 7: the CreatePack deserializer 0x180162880 push_backs EVERY parsed item
TWICE, once into the response object's own vector (obj+0x30/0x38/0x40) and once into
a singleton's vector reached as
    mgr = FUN_18011a830()  ->  vtbl[0x160](mgr)   (call it PACKMGR)
    PACKMGR+0x30 / +0x38 / +0x40   item vector, 0x18-byte elements
    PACKMGR+0x28  byte set to 1 after the whole body is parsed  ("contents ready")
    vtbl[0x10](PACKMGR) called BEFORE parsing  (presumably clear)
numberItems (atom 0x1dd) is written to the RESPONSE at obj+0x28 and is never used to
size either vector: both grow one element per item actually present in itemList.

THIS RUN
  1. Resolve FUN_18011a830 and the vtbl+0x160 accessor so PACKMGR's class is named.
  2. Decompile vtbl+0x10 (the pre-parse call) to confirm it is a clear.
  3. Find readers of PACKMGR's vector and of the +0x28 ready flag: that is the reveal.
  4. Disassemble the unanalysed RPC handler thunks 0x180124810 (StorePackTypes),
     0x180124800 (StorePackQuantities), 0x180124250 (PurchasePack) -- Ghidra created
     no functions there, so read the bytes directly.
CONTROL: FUN_18011a830 must resolve to a singleton getter (a DAT_ load or a
  create-on-first-use), and slot 0x160 must be a plain accessor. If either
  decompiles to something unrelated the chain is misread.
"""
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("### 1. SINGLETON CHAIN")
    dump("FUN_18011a830", 0x18011a830, OUT + "d3_mgr_getter.txt", echo=True)
    print("  callers of 0x18011a830: %d" % len(callers(0x18011a830)))

    print("\n### 4. RPC HANDLER THUNK BYTES")
    for va, nm in ((0x180124810, "STOREPACKTYPES"), (0x180124800, "STOREPACKQUANTITIES"),
                   (0x180124250, "PURCHASEPACK"), (0x180124260, "PURCHASEDITEMS"),
                   (0x180124240, "PURCHASEITEMS")):
        b = read_bytes(va, 32)
        print("  %#x %-22s %s" % (va, nm, b.hex()))
        f = fm.getFunctionContaining(addr(va))
        print("      containing function: %s" % (f.getName() if f else "NONE"))
        ins = listing.getInstructionContaining(addr(va))
        print("      instruction: %s" % (str(ins) if ins else "NONE (undisassembled)"))
        # decode a rel32 jmp/call if present
        if b[0] == 0xE9:
            t = va + 5 + int.from_bytes(b[1:5], "little", signed=True)
            print("      JMP rel32 -> %#x %s" % (t, fname(t)))
        if b[0] == 0x48 and b[1] == 0xFF and b[2] == 0x25:
            t = va + 7 + int.from_bytes(b[3:7], "little", signed=True)
            print("      JMP [rip+..] -> slot %#x = %#x" % (t, qword(t)))

    print("\n### 5. READERS OF THE RESPONSE-SIDE numberItems obj+0x28")
    # obj+0x28 is disp8-encodable so a byte scan is useless; instead enumerate
    # everything that can hold a FutCreatePackServerResponse: only its ctor names the
    # vtable, and the factory has no callers, so the object is dispatched generically.
    for tgt in (0x180228260, 0x1802282f0, 0x180228268):
        print("  xrefs to %#x: %s" % (tgt, [(hex(f), t, n) for f, t, n, e in xrefs_to(tgt)]))

    print("\n### 6. duplicateItemIdList sub-parser 0x180138e10")
    dump("dupe id list parser", 0x180138e10, OUT + "d3_dupe_parser.txt", echo=True)
except Exception:
    traceback.print_exc()
    sys.stdout.flush()
