"""Q19: every constant the code compares a +0x50 (cardsubtypeid) or +0x4c (cardtype)
field against.

The parsed item record has cardsubtypeid at +0x50 and cardtype at +0x4c. Instead of
searching for a constant (which misses jump tables) or for a syntactic form (which
misses != and ladders), search for the FIELD ACCESS and then collect every immediate
that touches the loaded register within the next 8 instructions, whatever the
mnemonic. Both the direct form (CMP dword [reg+0x50], imm) and the load-then-test
form (MOV r32,[reg+0x50]; SUB r32,imm; CMP r32,imm) are covered.

CONTROL: FUN_18011e3c0 is known to do `*(int *)(x + 0x50) - 0x91U < 6`, so it must
appear with 0x91 (and 6) attached to a +0x50 access. If the control is absent the
scan is broken and nothing may be concluded from what it does not find.
"""
import traceback

try:
    block = None
    for b in mem.getBlocks():
        if b.getName() == ".text":
            block = b
            break
    per = {}
    it = listing.getInstructions(block.getStart(), True)
    window = []          # [(reg_name, remaining_instrs)]
    n = 0
    while it.hasNext():
        ins = it.next()
        n += 1
        txt = str(ins)
        # 1) direct: memory operand with disp 0x50/0x4c and an immediate
        for disp in ("0x50", "0x4c"):
            if ("+ " + disp + "]") in txt or ("+" + disp + "]") in txt:
                imms = []
                for i in range(ins.getNumOperands()):
                    for o in ins.getOpObjects(i):
                        try:
                            imms.append(int(o.getValue()))
                        except Exception:
                            pass
                f = fm.getFunctionContaining(ins.getAddress())
                key = (f.getName(), int(f.getEntryPoint().getOffset())) if f else ("?", 0)
                rec = per.setdefault(key, {"direct": set(), "near": set()})
                for v in imms:
                    if v not in (0x50, 0x4C) and 0 <= v < 0x1000:
                        rec["direct"].add((disp, v))
                # start a window: whatever register this instruction defines
                for r in ins.getResultObjects():
                    window.append([str(r), 8, key, disp])
        # 2) decay window and attach immediates that touch the tracked register
        nxt = []
        for w in window:
            reg, left, key, disp = w
            if left <= 0:
                continue
            if reg in txt:
                for i in range(ins.getNumOperands()):
                    for o in ins.getOpObjects(i):
                        try:
                            v = int(o.getValue())
                        except Exception:
                            continue
                        if 0 <= v < 0x1000:
                            per.setdefault(key, {"direct": set(), "near": set()})
                            per[key]["near"].add((disp, v))
            w[1] = left - 1
            nxt.append(w)
        window = nxt[-40:]

    print("instructions scanned: %d" % n)
    print()
    CAND = {9, 10, 11, 0x1E, 0x1F, 7, 0x91}
    print("=== functions whose +0x50 / +0x4c constants meet {9,10,11,0x1e,0x1f,7,0x91} ===")
    for (name, ent), rec in sorted(per.items()):
        vals = rec["direct"] | rec["near"]
        hit = {v for _d, v in vals} & CAND
        if not hit:
            continue
        print("  %-24s %#x  hits=%s" % (name, ent, sorted("%#x" % h for h in hit)))
        print("        direct=%s" % sorted("%s:%#x" % (d, v) for d, v in rec["direct"]))
        print("        near  =%s" % sorted("%s:%#x" % (d, v) for d, v in rec["near"])[:40])
    print()
    print("=== control FUN_18011e3c0 ===")
    for (name, ent), rec in per.items():
        if ent == 0x18011E3C0:
            print("  direct=%s" % sorted("%s:%#x" % (d, v) for d, v in rec["direct"]))
            print("  near  =%s" % sorted("%s:%#x" % (d, v) for d, v in rec["near"]))
except Exception:
    traceback.print_exc()
