"""D3 Q6: the item ACTION vocabulary (TO_TRADE_PILE / DISCARD ...) and every consumer
of the tradeable flag; plus a precise search for code that compares item+0x5c against
an itemState value.

WHAT LED HERE. 'TO_TRADE_PILE' (0x1801f4d48) and 'DISCARD' (0x1801f4d28) are adjacent
in .rdata and BOTH are referenced by the single function FUN_18003e370, which also
manipulates a byte at +0x49 -- the tradeable flag's offset. That is the action-menu
builder, i.e. the thing that greys entries out.

CONTROL for the string window: the window must also contain other action names we can
recognise as menu entries (not random data); if the neighbourhood is unreadable garbage
the window is wrong and the identification is not made.

CONTROL for the itemState-value scan: the same look-ahead machinery, pointed at
displacement 0x3c with the constant 0, must rediscover the KNOWN discard guard
`if ((int)local_150 == 0)`-style tests. If the look-ahead finds nothing anywhere the
scan is broken and its silence about +0x5c means nothing.
"""
import traceback, os
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)
STATES = {1, 2, 5, 6, 0x64, 0x65, 0x66, 0x67, 0x68, 0xff, 0xffffffff}


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 + "q6_%s_%x.c" % (tag, va), "w") as fh:
        fh.write(src)
    return src


try:
    print("###### A. the action-name neighbourhood in .rdata")
    a = 0x1801f4a00
    while a < 0x1801f5200:
        s = rd_str(a, 120)
        if s and all(32 <= ord(c) < 127 for c in s):
            xs = xrefs_to(a)
            fns = sorted({(fn, e) for _, _, fn, e in xs if e})
            print("  %#x %-46r xrefs=%d %s" % (a, s, len(xs),
                                               ["%s(%#x)" % (n, e) for n, e in fns][:5]))
            a += len(s) + 1
        else:
            a += 1

    print()
    print("###### B. the action-menu builder and the tradeable-flag consumers")
    for tag, va in (("actionmenu_18003e370", 0x18003e370),
                    ("call_get49_1800bc580", 0x1800bc580),
                    ("call_gate_1800e2a40", 0x1800e2a40),
                    ("call_get48_1800d0600", 0x1800d0600),
                    ("call_get48_1800e4fd0", 0x1800e4fd0)):
        dump(tag, va)

    print()
    print("###### C. precise scan: [reg+DISP] loaded then compared to a constant")
    print("           targets DISP=0x5c (itemState) and DISP=0x49 (tradeable)")
    print("           CONTROL DISP=0x3c and DISP=0x4c, which we know are compared")
    res = defaultdict(list)
    for DISP in (0x5c, 0x49, 0x3c, 0x4c):
        it = listing.getInstructions(True)
        window = []
        while it.hasNext():
            ins = it.next()
            window.append(ins)
            if len(window) > 12:
                window.pop(0)
            s0 = str(window[0])
            if ("+ %#x]" % DISP) not in s0:
                continue
            if "RSP" in s0 or "RBP" in s0:
                continue
            mn = window[0].getMnemonicString().upper()
            if mn == "CMP":
                # direct compare with immediate
                try:
                    v = int(window[0].getOpObjects(1)[0].getValue())
                except Exception:
                    v = None
                f = fm.getFunctionContaining(window[0].getAddress())
                res[DISP].append((int(window[0].getAddress().getOffset()),
                                  f.getName() if f else "?", "DIRECT", v, s0))
                continue
            if mn not in ("MOV", "MOVZX", "MOVSX", "MOVSXD"):
                continue
            try:
                dst = str(window[0].getOpObjects(0)[0])
            except Exception:
                continue
            for nxt in window[1:]:
                sn = str(nxt)
                mn2 = nxt.getMnemonicString().upper()
                if mn2 in ("CMP", "SUB", "TEST") and dst.replace("R", "E") in sn.replace("R", "E"):
                    try:
                        v = int(nxt.getOpObjects(1)[0].getValue())
                    except Exception:
                        v = None
                    f = fm.getFunctionContaining(window[0].getAddress())
                    res[DISP].append((int(window[0].getAddress().getOffset()),
                                      f.getName() if f else "?", mn2, v, s0 + " ; " + sn))
                    break
        print("  DISP %#04x -> %d load/compare pairs" % (DISP, len(res[DISP])))
    for DISP in (0x3c, 0x4c, 0x49, 0x5c):
        tag = "CONTROL" if DISP in (0x3c, 0x4c) else "TARGET"
        print("  --- %s DISP %#04x, constants seen: %s"
              % (tag, DISP, sorted({v for _, _, _, v, _ in res[DISP] if v is not None})[:40]))
    print()
    print("  --- every DISP 0x5c compare whose constant is an itemState value")
    for a, fn, kind, v, txt in res[0x5c]:
        if v in STATES:
            print("    %#x %-20s %s %s  | %s" % (a, fn, kind, hex(v), txt))
    print()
    print("  --- every DISP 0x49 compare")
    for a, fn, kind, v, txt in res[0x49]:
        print("    %#x %-20s %s %s  | %s" % (a, fn, kind, v if v is None else hex(v), txt))

except Exception:
    traceback.print_exc()
