"""DIMENSION 4 (duplicates), pass 6: exhaustive census, matcher generation 2.

Pass 5's matcher M1 (single-expression nested +0x10) MISSED the IS-list consumer
0x18013e7f0, which splits the access across two statements:
    lVar6 = *(longlong *)(*(longlong *)(lVar14 + 0x10) + 0xb0);
    ... *(longlong *)(lVar6 + 0x10) = plVar13[2];
So M1 alone cannot support an absence claim. M3 below does a textual
def-use: any local assigned from a qword load, then used as base of a +0x10
qword access.

M3 = for every assignment  `<var> = *(longlong *)(<anything>);`  remember <var>;
     then flag the function if `<var> + 0x10)` appears anywhere.
     PLUS the M1 nested form. This is deliberately over-broad; the output is
     reviewed by hand.

CONTROL: all six hand-known sites must be flagged:
  writers 0x180162880, 0x18013bd40, 0x1801293d0, 0x18013e7f0
  readers 0x180043880, 0x180094220
If any is missed the pass is void for absence purposes.

Also dumps the three functions M1 newly found (0x180094ae0, 0x180096490,
0x18009bc40) and their callers, to name the UI screens involved.
"""
import re, traceback, time

OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/store/dup6_out.txt"

ASSIGN = re.compile(r"(\w+) = \*\(longlong \*\)\([^;\n]{0,120}\);")
M1 = re.compile(r"\*\(longlong \*\)\(\*\(longlong \*\)\([^;\n]{0,80}?\) \+ 0x10\)")
CONTROLS = [0x180162880, 0x18013BD40, 0x1801293D0, 0x18013E7F0, 0x180043880, 0x180094220]

def hitlines(s, var):
    out = []
    pat = re.compile(r"\b%s \+ 0x10\b" % re.escape(var))
    for ln in s.split("\n"):
        if pat.search(ln):
            out.append(ln.strip())
    return out

try:
    fh = open(OUT, "w")
    t0 = time.time()
    funcs = []
    it = fm.getFunctions(True)
    while it.hasNext():
        funcs.append(it.next())
    fh.write("total functions: %d\n" % len(funcs))

    flagged = {}
    n = 0
    for f in funcs:
        n += 1
        ent = int(f.getEntryPoint().getOffset())
        try:
            s = dec(ent, timeout=25)
        except Exception:
            continue
        if s.startswith("// decompile"):
            continue
        why = []
        if M1.search(s):
            why.append("M1:" + M1.search(s).group(0)[:70])
        for m in ASSIGN.finditer(s):
            v = m.group(1)
            hl = hitlines(s, v)
            if hl:
                why.append("M3[%s]: %s" % (v, hl[0][:110]))
                break
        if why:
            flagged[ent] = (f.getName(), why)
        if n % 2000 == 0:
            fh.write("... %d/%d %.0fs flagged=%d\n" % (n, len(funcs), time.time() - t0, len(flagged)))
            fh.flush()

    fh.write("\nSWEPT %d in %.0fs, flagged %d\n" % (n, time.time() - t0, len(flagged)))
    fh.write("\n=== CONTROL CHECK ===\n")
    ok = True
    for c in CONTROLS:
        fh.write("  %#x flagged=%s\n" % (c, c in flagged))
        ok = ok and (c in flagged)
    fh.write("ALL CONTROLS FLAGGED: %s\n" % ok)

    fh.write("\n=== flagged functions (%d) ===\n" % len(flagged))
    for ent in sorted(flagged):
        nm, why = flagged[ent]
        fh.write("  %#x %s\n" % (ent, nm))
        for x in why:
            fh.write("        %s\n" % x)

    fh.write("\n\n=== decompiles of the three NEW M1 functions ===\n")
    for a in (0x180094AE0, 0x180096490, 0x18009BC40):
        s = dec(a)
        fh.write("\n" + "#" * 68 + "\n# %#x len(src)=%d FULL\n" % (a, len(s)) + "#" * 68 + "\n")
        fh.write(s + "\n")
        fh.write("-- callers:\n")
        for frm, typ, fn, e in xrefs_to(a):
            fh.write("     %#x %s in %s @ %#x\n" % (frm, typ, fn, e))

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