"""D3 Q2: the itemState string<->enum functions, and who READS the state slots.

ESTABLISHED BEFORE THIS QUERY (q_cd_state_1 + live read, both controls passed):
  the deser's stack struct base is local_188 (anchored by local_118 = &PTR_LAB_1801eaac0
  landing at record+0x70, the known item vtable), so record_off = 0x188 - N.
  itemState (atom 0x172) -> FUN_180166660(str) -> record +0x5c   (live: 1 == "free")
  owners    (atom 0x207) -> record +0x48 u8                      (live: 1)
  untradeable(atom 0x361)-> record +0x49 u8 = (untradeable == FALSE), a TRADEABLE flag
                                                                 (live: 0 on all 22)
  the enum table is at 0x180229cc0 (NOT 0x180229d20, which is mid-table):
    invalid 0, free 1, WAITING_FOR_GAME 2, inGame 2, forSale 5, offered 6,
    activeBadge 0x64, activeHomeKit 0x65, activeAwayKit 0x66, activeBall 0x67,
    activeStadium 0x68, active 0xff, terminator {NULL, 0xffffffff}

HYPOTHESIS: FUN_180166660 walks that table and returns a default for no-match; there is
a matching enum->string writer used when the client SENDS itemState back; and the UI
gating for "list on transfer market" / "quick sell" reads +0x49 and/or +0x5c.

CONTROL for the reader scan (same syntactic form as the target -- a memory operand with
a small displacement): the scan must also find the KNOWN readers of +0x38/+0x3c
(discardValue) and of +0x70 (the vtable). If displacement 0x38 or 0x70 comes back with
zero hits the scan is broken and every absence below is void.

OUTPUT: full decompiles (len printed), full table walk, full grouped reader census.
"""
import traceback, os

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

STATE_VALUES = {0, 1, 2, 5, 6, 0x64, 0x65, 0x66, 0x67, 0x68, 0xff}


def dump(tag, va):
    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)
    print(src)
    with open(OUT + "q2_%s_%x.c" % (tag, va), "w") as fh:
        fh.write(src)
    return src


try:
    print("###### A. string->enum helpers used by the item deser")
    for tag, va in (("itemState_0x172", 0x180166660),
                    ("injuryType_0x168", 0x1801666f0),
                    ("prefPos_0x24a", 0x180166810),
                    ("resourceId_split_0x287", 0x180166ca0),
                    ("playStyle_0x23f", 0x180136480)):
        dump(tag, va)

    print()
    print("###### B. every xref into the itemState table block 0x180229cc0..0x180229d80")
    for a in range(0x180229cc0, 0x180229d90, 8):
        xs = xrefs_to(a)
        for frm, typ, fn, ent in xs:
            print("  table%+#5x  %#x  xref from %#x %s in %s (%#x)"
                  % (a - 0x180229cc0, a, frm, typ, fn, ent))

    print()
    print("###### C. xrefs to each itemState string literal (finds the enum->string writer)")
    for name in (b"invalid\x00", b"free\x00", b"WAITING_FOR_GAME\x00", b"inGame\x00",
                 b"forSale\x00", b"offered\x00", b"activeBadge\x00", b"activeHomeKit\x00",
                 b"activeAwayKit\x00", b"activeBall\x00", b"activeStadium\x00",
                 b"tradepile\x00", b"club\x00", b"trade\x00", b"pile\x00"):
        hits = find_all(name)
        print("  %-20s occurrences=%d" % (name.decode().strip("\x00"), len(hits)))
        for h in hits:
            for frm, typ, fn, ent in xrefs_to(h):
                print("      @%#x  xref %#x %s in %s (%#x)" % (h, frm, typ, fn, ent))

    print()
    print("###### D. callers of FUN_180166660 (every itemState parse site)")
    try:
        for c in callers(0x180166660):
            print("  caller", c)
    except Exception as e:
        print("  callers() failed:", e)

    print()
    print("###### E. reader census: instructions with memory displacement 0x49 / 0x5c")
    print("           CONTROLS in the same scan: 0x38 and 0x70")
    want = {0x49: [], 0x5c: [], 0x38: [], 0x70: []}
    total = 0
    it = listing.getInstructions(True)
    while it.hasNext():
        ins = it.next()
        total += 1
        try:
            n = ins.getNumOperands()
        except Exception:
            continue
        for i in range(n):
            try:
                objs = ins.getOpObjects(i)
            except Exception:
                continue
            if len(objs) < 2:
                continue
            has_reg = any(hasattr(o, "getName") for o in objs)
            if not has_reg:
                continue
            for o in objs:
                v = None
                try:
                    v = int(o.getValue())
                except Exception:
                    continue
                if v in want:
                    f = fm.getFunctionContaining(ins.getAddress())
                    want[v].append((int(ins.getAddress().getOffset()),
                                    f.getName() if f else "?",
                                    int(f.getEntryPoint().getOffset()) if f else 0,
                                    str(ins)))
    print("  scanned %d instructions" % total)
    for d in (0x38, 0x70, 0x49, 0x5c):
        print("  disp %#04x -> %d hits (CONTROL)" % (d, len(want[d]))
              if d in (0x38, 0x70) else "  disp %#04x -> %d hits" % (d, len(want[d])))

    print()
    print("###### F. functions touching [reg+0x49], grouped (this displacement is rare)")
    from collections import defaultdict
    g = defaultdict(list)
    for a, fn, ent, txt in want[0x49]:
        g[(ent, fn)].append((a, txt))
    for (ent, fn), lst in sorted(g.items()):
        print("  %s (%#x)  n=%d" % (fn, ent, len(lst)))
        for a, txt in lst:
            print("      %#x  %s" % (a, txt))

    print()
    print("###### G. functions touching [reg+0x5c] that ALSO contain an itemState value")
    g2 = defaultdict(list)
    for a, fn, ent, txt in want[0x5c]:
        g2[(ent, fn)].append((a, txt))
    print("  total distinct functions touching +0x5c: %d" % len(g2))
    for (ent, fn), lst in sorted(g2.items()):
        if not ent:
            continue
        f = func(ent)
        if f is None:
            continue
        imms = set()
        it2 = listing.getInstructions(f.getBody(), True)
        while it2.hasNext():
            ins = it2.next()
            for i in range(ins.getNumOperands()):
                for o in ins.getOpObjects(i):
                    try:
                        imms.add(int(o.getValue()))
                    except Exception:
                        pass
        hit = imms & {0x64, 0x65, 0x66, 0x67, 0x68, 0xff, 5, 6}
        if 0x64 in imms or 0x67 in imms or 0x68 in imms:
            print("  *** %s (%#x) n=%d  state-ish immediates=%s"
                  % (fn, ent, len(lst), sorted(hex(x) for x in hit)))
            for a, txt in lst:
                print("        %#x  %s" % (a, txt))

except Exception:
    traceback.print_exc()
