"""D3 Q5: the UI view-model accessors over the item record, and the enum tables for
pile / tradeState / bidState.

WHAT LED HERE. FUN_1801a8940 is a nine-byte getter `return *(*(this+0x18) + 0x49)` and
FUN_1801a89f0 is `return *(*(this+0x18) + 0x48)`: there is a wrapper class holding the
item record at +0x18 and exposing its fields one accessor at a time. FUN_1801a7260 is a
PREDICATE in the same region that reads item+0x49, item+0x4c, item+0x50 and item+0x145
and returns 0 or 1 -- exactly the shape of a "is this menu entry enabled" test, which is
the open live question (Place on Transfer List / List on Transfer Market greyed out).

HYPOTHESIS: enumerating every small function of the form `*(*(param_1+0x18) + N)` gives
the COMPLETE list of item fields the UI can see, and the predicates in the same region
give the gating rules. If itemState (+0x5c) has an accessor, the lifecycle table is
reachable; if it has none, itemState reaches the UI some other way and I must say so.

CONTROL: the accessor enumeration must find offsets we already know the UI displays --
+0x38/+0x3c (discardValue, confirmed on screen today) and +0xb4 (rating). If those come
back with no accessor the enumeration is looking at the wrong class and proves nothing.

Also: pile string->enum FUN_180142650 (used by the PUT /item verdict deser),
tradeState FUN_180166bd0, bidState FUN_180166380, with their tables walked.
"""
import traceback, os, re
from collections import defaultdict

OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/cards/"
os.makedirs(OUT, exist_ok=True)


def dump(tag, va, echo=True):
    f = func(va)
    if f is None:
        print("%s %#x -> NO FUNCTION" % (tag, va))
        return ""
    src = dec(va)
    print("=" * 78)
    print("%s  %#x  %s  len(src)=%d  (FULL)" % (tag, va, f.getName(), len(src)))
    print("=" * 78)
    if echo:
        print(src)
    with open(OUT + "q5_%s_%x.c" % (tag, va), "w") as fh:
        fh.write(src)
    return src


def walk_table(base, tag, lo=-0x100, hi=0x200):
    print("  --- table %s at %#x" % (tag, base))
    for off in range(lo, hi, 0x10):
        a = base + off
        try:
            p = qword(a)
            q = qword(a + 8)
        except Exception:
            continue
        s = rd_str(p, 64) if 0x180000000 <= p < 0x181000000 else ""
        print("    %#x (%+#5x) p=%#014x q=%#010x %r" % (a, off, p, q, s))


try:
    print("###### A. accessor enumeration over the whole DLL")
    print("  every function <= 0x40 bytes whose decompile is a single deref of")
    print("  *(param_1 + 0x18) or *(param_1 + 0x10) plus a constant offset")
    pat18 = re.compile(r"\(param_1 \+ 0x18\) \+ (0x[0-9a-f]+|\d+)\)")
    pat18b = re.compile(r"\*\(longlong \*\)\(param_1 \+ 0x18\)\)")
    pat10 = re.compile(r"\(param_1 \+ 0x10\) \+ (0x[0-9a-f]+|\d+)\)")
    acc18 = defaultdict(list)
    acc10 = defaultdict(list)
    n_small = 0
    fit = fm.getFunctions(True)
    while fit.hasNext():
        f = fit.next()
        sz = int(f.getBody().getNumAddresses())
        if sz > 0x40:
            continue
        n_small += 1
        ent = int(f.getEntryPoint().getOffset())
        try:
            src = dec(ent, 30)
        except Exception:
            continue
        for m in pat18.finditer(src):
            acc18[int(m.group(1), 0)].append((ent, f.getName(), sz))
        for m in pat10.finditer(src):
            acc10[int(m.group(1), 0)].append((ent, f.getName(), sz))
        if pat18b.search(src) and "+ 0x18" in src and not pat18.search(src):
            acc18[0].append((ent, f.getName(), sz))
    print("  scanned %d small functions" % n_small)
    print("  --- accessors on *(this+0x18) + N   (N = item record offset)")
    for off in sorted(acc18):
        for ent, nm, sz in acc18[off]:
            print("    +%#-6x %s (%#x) size=%d" % (off, nm, ent, sz))
    print("  CONTROL: accessors exist for +0x38/+0x3c/+0xb4? %s"
          % {hex(k): len(acc18.get(k, [])) for k in (0x38, 0x3c, 0xb4)})
    print("  itemState +0x5c accessor count: %d" % len(acc18.get(0x5c, [])))
    print("  --- accessors on *(this+0x10) + N")
    for off in sorted(acc10):
        for ent, nm, sz in acc10[off]:
            print("    +%#-6x %s (%#x) size=%d" % (off, nm, ent, sz))

    print()
    print("###### B. the gate predicate and its neighbours")
    for tag, va in (("gate_1801a7260", 0x1801a7260), ("helper_1801a8900", 0x1801a8900),
                    ("get49_1801a8940", 0x1801a8940), ("get48_1801a89f0", 0x1801a89f0),
                    ("f5c_1801a5a30", 0x1801a5a30), ("f5c_1801a5a50", 0x1801a5a50),
                    ("f5c_1801a5aa0", 0x1801a5aa0), ("f5c_1801a5ac0", 0x1801a5ac0),
                    ("f5c_1801a5ae0", 0x1801a5ae0), ("f5c_1801a7040", 0x1801a7040)):
        dump(tag, va)

    print()
    print("###### C. who references the gate predicate (vtable slot or direct call)")
    for va in (0x1801a7260, 0x1801a8940, 0x1801a89f0):
        print("  --- xrefs to %#x" % va)
        for frm, typ, fn, ent in xrefs_to(va):
            print("      %#x %s in %s (%#x)" % (frm, typ, fn, ent))
        # vtable membership: any .rdata qword equal to va
        import struct
        hits = find_all(struct.pack("<Q", va), blocks=(".rdata", ".data"))
        for h in hits:
            print("      IN TABLE at %#x ; neighbours:" % h)
            for k in range(-3, 6):
                try:
                    t = qword(h + k * 8)
                except Exception:
                    continue
                print("         %+2d %#x %s" % (k, t, fname(t) if 0x180000000 <= t < 0x181000000 else ""))

    print()
    print("###### D. pile / tradeState / bidState string->enum")
    for tag, va in (("pile_enum_180142650", 0x180142650),
                    ("tradeState_180166bd0", 0x180166bd0),
                    ("bidState_180166380", 0x180166380)):
        src = dump(tag, va)
        m = re.search(r"PTR_[A-Za-z_0-9]*_(1[0-9a-f]{8})", src or "")
        if m:
            walk_table(int(m.group(1), 16), tag, 0, 0x120)
        else:
            m2 = re.search(r"DAT_(1[0-9a-f]{8})", src or "")
            if m2:
                walk_table(int(m2.group(1), 16) - 8, tag, 0, 0x120)

except Exception:
    traceback.print_exc()
