# -*- coding: utf-8 -*-
"""ADVERSARIAL VERIFY 2: who WRITES the gate byte, and who else READS slot +0x2e0.

WHY. Live memory (pid 4048, read-only) says FutDataManagerImpl+0x1fd45 is currently 01,
while utas_server.py is running with FUT_SETTINGS unset, i.e. it serves {"configs": []}.
So either the settings struct default-initialises those fields to 1 and the applier runs
anyway, or something other than FUN_18011dc50 writes the byte. Both possibilities
contradict the reviewed report's premise that "the byte defaults to zero".

H1  FUN_18011dc50 is the ONLY writer of +0x1fd45 / +0x1fd3a in CardsDLL .text.
    (disassembly scan for any memory operand with displacement 0x1fd3a..0x1fd48)
H2  the settings struct handed to the applier is default-constructed with 1s.
    (callers of FUN_18013c6d0, and the allocation site)
H3  of the 8 CALL [reg+0x2e0] sites, only 0x1800aaa43 is a FutDataManagerImpl accessor.
    ATTACK: decompile all 8 and look at what object they call it on.
CONTROL: the same write-scan for 0x1fd3a must find FUN_18011dc50 too.
"""
import traceback, re, struct

OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/packres"

def dump(n, s):
    p = "%s/v_%s.txt" % (OUT, n)
    open(p, "w").write(s)
    print("[wrote %s  %d chars]" % (p, len(s)))

try:
    from ghidra.program.model.address import AddressSet
    blk = [b for b in mem.getBlocks() if b.getName() == ".text"][0]
    aset = AddressSet(blk.getStart(), blk.getEnd())

    print("=" * 78)
    print("H1: every instruction in .text whose operand displacement is 0x1fd28..0x1fd50")
    it = listing.getInstructions(aset, True)
    hits = {}
    n = 0
    rx = re.compile(r"0x1fd([0-9a-f]{2})")
    while it.hasNext():
        ins = it.next()
        n += 1
        t = str(ins)
        m = rx.search(t)
        if not m:
            continue
        d = int("1fd" + m.group(1), 16)
        if not (0x1fd28 <= d <= 0x1fd50):
            continue
        a = int(ins.getAddress().getOffset())
        hits.setdefault(d, []).append((a, fname(a), t))
    print("  instructions walked: %d" % n)
    for d in sorted(hits):
        print("  --- disp %#x : %d sites ---" % (d, len(hits[d])))
        for a, f, t in hits[d]:
            print("      %#x  %-24s %s" % (a, f, t))

    print("=" * 78)
    print("H2: callers of the applier FUN_18011dc50 and of the settings deser FUN_18013c6d0")
    for tgt in (0x18011dc50, 0x18013c6d0):
        print("  callers(%#x):" % tgt)
        try:
            cs = callers(tgt)
        except Exception as e:
            cs = "EXC %s" % e
        print("   ", cs)
        try:
            for r in xrefs_to(tgt):
                print("    xref", r, fname(r[0]) if isinstance(r, tuple) else "")
        except Exception as e:
            print("    xrefs_to EXC", e)

    print("=" * 78)
    print("H2b: FULL decompile of every caller of the applier")
    seen = set()
    try:
        cs = callers(0x18011dc50)
    except Exception:
        cs = []
    for c in cs:
        va = c if isinstance(c, int) else int(c)
        if va in seen:
            continue
        seen.add(va)
        s = dec(va)
        print("----- caller %#x (%s) len=%d -----" % (va, fname(va), len(s)))
        print(s)
        dump("q2_applier_caller_%x" % va, s)

    print("=" * 78)
    print("H3: decompile every CALL [reg+0x2e0] site's containing function, show the line")
    SITES = [(0x1800522ac, 0x180051cd0), (0x18006b3dd, 0x18006ac20),
             (0x18008918b, 0x180088cb0), (0x18008bcc0, 0x18008b6e0),
             (0x1800aaa43, 0x1800aa440), (0x1800d06a4, 0x1800d0600),
             (0x18011a61d, 0x18011a5c0), (0x18011cfdc, 0x18011cfa0)]
    for site, fn in SITES:
        s = dec(fn)
        print("--- site %#x in %s : decompile %d chars ---" % (site, fname(fn), len(s)))
        dump("q2_site_%x" % fn, s)
        for i, ln in enumerate(s.split("\n")):
            if "0x2e0" in ln:
                print("    L%-4d %s" % (i + 1, ln.strip()))
        # what object? print 12 instructions before the call
        a = addr(site)
        ins = listing.getInstructionAt(a)
        back = []
        for _ in range(14):
            ins = ins.getPrevious() if ins else None
            if ins is None:
                break
            back.append("      %#x  %s" % (int(ins.getAddress().getOffset()), ins))
        for l in reversed(back):
            print(l)
        print("      %#x  %s   <== the call" % (site, listing.getInstructionAt(a)))

    print("=" * 78)
    print("H3b: is 0x18011cfa0 / 0x18011a5c0 operating on the same vtable? print them fully")
    for fn in (0x18011cfa0, 0x18011a5c0, 0x1800d0600):
        s = dec(fn)
        print("===== %#x %s (%d chars) =====" % (fn, fname(fn), len(s)))
        print(s)

except Exception:
    traceback.print_exc()
print("QUERY DONE")
