"""DIMENSION 4 (duplicates), pass 4: the item class, and the one unresolved
atom-serialisation site.

Open items from pass 3:
 (a) the single UNRESOLVED FUN_180180cd0 call site, 0x1801438e2 in FUN_180143760,
     takes its atom from [RBP+0x60]. Until it is characterised, "no request ever
     serialises 0xeb/0xec/0xed/0x16f" is not airtight.
 (b) which class implements GetCardDuplicate (DAT_1802def18 vtable slot 0x18).
 (c) the item-model manager: DAT_1802e6398 is written by FUN_18011d780. Get the
     concrete vtable so slots 0x160 / 0x7d8 / 0xa08 can be named, and so the item
     constructor (which must zero item+0x10) can be found.

CONTROL for the vtable walk: slot 0x08 of any of these vtables must decode to a
real function in .text, and we print the raw qwords so a bogus vtable is visible.
"""
import traceback

OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/store/dup4_out.txt"

def w(fh, s=""):
    fh.write(str(s) + "\n")

def dump(fh, a, label):
    s = dec(a)
    w(fh, "")
    w(fh, "#" * 70)
    w(fh, "# %s   %#x   len(src)=%d   FULL" % (label, a, len(s)))
    w(fh, "#" * 70)
    w(fh, s)

try:
    fh = open(OUT, "w")

    dump(fh, 0x180143760, "(a) dynamic-atom serialiser")

    w(fh, "")
    w(fh, "=" * 70)
    w(fh, "(b) xrefs to DAT_1802def18 (the script card-info provider pointer)")
    w(fh, "=" * 70)
    for frm, typ, fn, ent in xrefs_to(0x1802DEF18):
        w(fh, "   %#x %s in %s @ %#x" % (frm, typ, fn, ent))

    dump(fh, 0x18011D780, "(c) manager singleton installer FUN_18011d780")

    w(fh, "")
    w(fh, "=" * 70)
    w(fh, "(c2) candidate manager vtables referenced from FUN_18011d780")
    w(fh, "=" * 70)
    f = func(0x18011D780)
    seen = set()
    if f is not None:
        it = listing.getInstructions(f.getBody(), True)
        while it.hasNext():
            ins = it.next()
            for r in ins.getReferencesFrom():
                t = int(r.getToAddress().getOffset())
                if 0x180000000 <= t < 0x181000000 and t not in seen:
                    seen.add(t)
                    try:
                        q0 = qword(t)
                        q1 = qword(t + 8)
                    except Exception:
                        continue
                    f0 = fm.getFunctionAt(addr(q0)) if 0x180000000 <= q0 < 0x181000000 else None
                    f1 = fm.getFunctionAt(addr(q1)) if 0x180000000 <= q1 < 0x181000000 else None
                    if f0 and f1:
                        w(fh, "  possible vtable %#x : [0]=%#x %s  [8]=%#x %s" %
                          (t, q0, f0.getName(), q1, f1.getName()))
                        for off in (0x160, 0x7D8, 0xA08, 0xA40, 0x5B8):
                            try:
                                q = qword(t + off)
                            except Exception:
                                continue
                            ff = fm.getFunctionAt(addr(q)) if 0x180000000 <= q < 0x181000000 else None
                            w(fh, "      +%#05x -> %#x %s" % (off, q, ff.getName() if ff else "(not a function)"))

    fh.close()
    print("WROTE", OUT)
except Exception:
    traceback.print_exc()
