"""ADVERSARIAL BATCH 2 -- the ABSENCE claims, re-tested with a DIFFERENT method.

The other agent tested "+0x49 is compared in exactly two places" and "itemState 5/6
are never tested" with a LOAD/COMPARE-PAIR scan keyed on displacement. That method
has a structural blind spot: a compare performed on a value RETURNED BY AN ACCESSOR
never shows the displacement at the compare site. FUN_1801a8940 is exactly such an
accessor for +0x49 and it has a caller (FUN_1800bc580) the agent never opened.

MY METHOD (different): enumerate EVERY instruction in .text whose textual form
contains the displacement, with no filter on opcode class at all -- so ==, !=, switch
case labels and sub/dec ladders are all caught at the LOAD, and the containing
function is then read. Plus a byte-pattern census of the two-instruction accessor
shape  48 8b 4x 18 / <load disp>  which finds getters my displacement scan would
attribute to the getter rather than to its caller.

CONTROLS (same syntactic form as the targets -- a raw displacement load):
  0x38 and 0x3c  : known-live fields, must come back non-zero
  0x4c           : the other agent reported 37 pairs, must come back >= 37
  0xdeadbe       : impossible displacement, must come back 0  (proves the scan can
                   return zero for a real absence rather than always finding noise)
"""
import re, traceback

OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/cards/adv2/q2_raw.txt"
try:
    f = open(OUT, "w")

    def P(*a):
        f.write(" ".join(str(x) for x in a) + "\n")

    TARGETS = [0x38, 0x3c, 0x48, 0x49, 0x4c, 0x54, 0x58, 0x5c, 0x60, 0x88, 0x90]
    pats = {d: re.compile(r"\+\s*0x%x\s*\]" % d) for d in TARGETS}
    impossible = re.compile(r"\+\s*0xdeadbe\s*\]")

    hits = {d: [] for d in TARGETS}
    imp = []
    n = 0
    it = listing.getInstructions(True)
    while it.hasNext():
        i = it.next()
        s = i.toString()
        n += 1
        for d, p in pats.items():
            if p.search(s):
                hits[d].append((int(i.getAddress().getOffset()), s))
        if impossible.search(s):
            imp.append(int(i.getAddress().getOffset()))
    P("instructions scanned:", n)
    P("IMPOSSIBLE-DISPLACEMENT CONTROL 0xdeadbe hits:", len(imp), "(must be 0)")
    P()
    for d in TARGETS:
        fns = {}
        for a, s in hits[d]:
            fn = fm.getFunctionContaining(addr(a))
            k = (fn.getName(), int(fn.getEntryPoint().getOffset())) if fn else ("?", 0)
            fns.setdefault(k, []).append((a, s))
        P("### displacement +0x%02x : %d instructions in %d functions" % (d, len(hits[d]), len(fns)))
        if d in (0x49, 0x48):
            for (nm, e), lst in sorted(fns.items(), key=lambda x: x[0][1]):
                P("    %s @%#x  (%d)" % (nm, e, len(lst)))
                for a, s in lst:
                    P("        %#x  %s" % (a, s))
        elif d == 0x5c:
            P("    functions:")
            for (nm, e), lst in sorted(fns.items(), key=lambda x: x[0][1]):
                P("      %s @%#x n=%d" % (nm, e, len(lst)))
    P()

    P("=" * 30, "+0x5c FULL instruction list (itemState 5/6 absence retest)", "=" * 30)
    for a, s in hits[0x5C]:
        fn = fm.getFunctionContaining(addr(a))
        P("  %#x  %-52s  %s" % (a, s, fn.getName() if fn else "?"))
    P()

    P("=" * 30, "ACCESSOR CENSUS: byte pattern 48 8b 4x 18 followed by a load", "=" * 30)
    seen = {}
    for reg in (0x41, 0x51, 0x49, 0x59, 0x71, 0x79):
        pat = bytes([0x48, 0x8B, reg, 0x18])
        for a in find_all(pat, blocks=(".text",)):
            try:
                nxt = read_bytes(a + 4, 8)
            except Exception:
                continue
            seen.setdefault(a, nxt)
    P("call-shape candidates:", len(seen))
    interest = {}
    for a, nxt in seen.items():
        disp = None
        if nxt[0] == 0x8B and (nxt[1] & 0xC0) == 0x40:
            disp = nxt[2]
        elif nxt[0] == 0x0F and nxt[1] in (0xB6, 0xB7) and (nxt[2] & 0xC0) == 0x40:
            disp = nxt[3]
        elif nxt[0] == 0x83 and (nxt[1] & 0xC0) == 0x40:
            disp = nxt[2]
        elif nxt[0] == 0x8A and (nxt[1] & 0xC0) == 0x40:
            disp = nxt[2]
        if disp in (0x38, 0x3C, 0x48, 0x49, 0x4C, 0x54, 0x58, 0x5C, 0x60, 0x88, 0x90):
            fn = fm.getFunctionContaining(addr(a))
            interest.setdefault(disp, []).append((a, fn.getName() if fn else "?",
                                                  int(fn.getEntryPoint().getOffset()) if fn else 0))
    for d in sorted(interest):
        P("### accessor-shape loads of +0x%02x : %d" % (d, len(interest[d])))
        for a, nm, e in sorted(interest[d], key=lambda x: x[2]):
            P("    %#x in %s @%#x" % (a, nm, e))
            if e:
                nc = [(fr, t, cf, ce) for fr, t, cf, ce in xrefs_to(e) if "CALL" in t]
                P("        callers: %d -> %s" % (len(nc), sorted(set(cf for _, _, cf, _ in nc))))
    P()

    P("=" * 30, "THE UNOPENED +0x49 CONSUMER: FUN_1800bc580", "=" * 30)
    d = dec(0x1800BC580)
    P("len(src) =", len(d))
    P(d)

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