"""D3 run 6: how does a store response leave CardsDLL, and can the packed exe see
the pack record at all?

SETTLED: FutStoreGetPackTypesServerResponse is a 0x60-byte object; ctor 0x180123030
sets vtable 0x18021dd68 and an empty FUT Vector at +0x28/+0x30/+0x38 (allocator
+0x40, "FUT Vector" tag +0x50, timestamp +0x5c). Its vtable has NO accessor: slot 0
and slot +0x40 are deleting destructors, +0x08 is the deserializer, the rest are the
shared base-class slots also present on FutCreatePackServerResponse. So nothing in
the class hands a pack record out.

THIS RUN
  1. The RPC descriptor row: find the data references to the factory 0x180123480 and
     to the command strings, and print the surrounding qwords, so the table that
     binds "STOREPACKTYPES" -> factory -> deserializer is visible.
  2. The shared response virtuals (+0x20 0x180122420, +0x10/+0x18 0x18016cac0,
     +0x28 0x18016ca90, +0x38 0x18016c950, +0x48 0x18016bfc0, +0x58 0x18016ca60):
     is any of them a data accessor rather than plumbing?
  3. CardsDLL EXPORT TABLE. If the packed exe reads pack quantities it must reach
     them through an export or through a pointer an export returned. Enumerate every
     export; that bounds the exe's reach.
  4. Re-confirm the 100-element cap in 0x18013af30 from disassembly.
CONTROL: the export enumeration must at minimum return the DLL's known entry points;
an empty export list means the query is broken, not that the DLL exports nothing.
"""
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. DESCRIPTOR ROW FOR THE STORE RPC")
    for target, nm in ((0x180123480, "store factory"), (0x1801234e0, "store deser"),
                       (0x18021f318, "\"STOREPACKTYPES\" string"),
                       (0x18021de20, "RS4 name literal")):
        pat = target.to_bytes(8, "little")
        hits = find_all(pat, blocks=(".rdata", ".data"))
        print("  %s %#x embedded at: %s" % (nm, target, [hex(x) for x in hits]))
        for h in hits:
            lo = h - 0x40
            print("    context qwords around %#x:" % h)
            for i in range(16):
                a = lo + i * 8
                try:
                    q = qword(a)
                except Exception:
                    continue
                extra = ""
                if 0x1801e5000 <= q < 0x1802e0000:
                    try:
                        s = rd_str(q, 60)
                        if s.isprintable() and len(s) > 2:
                            extra = "  \"%s\"" % s
                    except Exception:
                        pass
                if 0x180001000 <= q < 0x1801e5000:
                    extra = "  fn=%s" % fname(q)
                print("      %#x: %#018x%s%s" % (a, q, extra, "   <== HIT" if a == h else ""))

    print("\n### 2. SHARED RESPONSE VIRTUALS")
    for va in (0x180122420, 0x18016cac0, 0x18016ca90, 0x18016ca40, 0x18016c950,
               0x18016bfc0, 0x18016cb80, 0x18016ca60, 0x18016c110, 0x18016cb20):
        try:
            s = dump("shared virtual", va, OUT + "d3_sv_%x.txt" % va, echo=True)
        except Exception as e:
            print("  !! %#x %s" % (va, e))

    print("\n### 3. EXPORT TABLE")
    st = prog.getSymbolTable()
    it = st.getExternalEntryPointIterator()
    n = 0
    while it.hasNext():
        a = it.next()
        syms = st.getSymbols(a)
        nms = [str(s.getName()) for s in syms]
        print("  %#x  %s" % (int(a.getOffset()), nms))
        n += 1
    print("  total exported entry points: %d  %s"
          % (n, "PASS" if n else "FAIL (query broken)"))

    print("\n### 4. THE 100-PACK CAP")
    f = func(0x18013af30)
    it2 = listing.getInstructions(f.getBody(), True)
    buf = []
    while it2.hasNext():
        i = it2.next()
        buf.append("%#x  %s" % (int(i.getAddress().getOffset()), str(i)))
    for k, ln in enumerate(buf):
        if "0x64" in ln or "0x158" in ln:
            print("  ...")
            for j in range(max(0, k - 6), min(len(buf), k + 7)):
                print("    %s" % buf[j])
except Exception:
    traceback.print_exc()
    sys.stdout.flush()
