# -*- coding: utf-8 -*-
"""ADVERSARIAL VERIFY 5.

A. THE LADDER. My immediate-scan in q_pack_v3_4 FAILED ITS OWN CONTROL: neither 0x23f
   (a case Ghidra shows in 0x18013fe00) nor 0x20e (a case in 0x18013c6d0) exists as a
   raw immediate. That proves the dispatch is a running SUB/DEC ladder and that any
   "grep for the constant" method is invalid here. So: dump the FULL disassembly of
   0x18013fe00 and reconstruct the ladder from the SUB/JZ chain, validating the
   reconstruction against the 52 arms Ghidra's decompiler reports. Only if the
   reconstruction reproduces those 52 do I get to say anything about 0x23d.

B. Who WRITES pack-definition +0xc0..+0xd0? D4 claim 13 grades the NUM_*_IN_PACK
   fields authority=SERVER but its evidence only shows the READ. Find every function
   that writes ALL of 0xc0/0xc4/0xc8/0xcc/0xd0 as dwords, and the callers of
   FUN_180015d80 so param_4 can be identified.

C. CARDS_CB_ERR_PACK_NOT_IN_DIME xref (the D6 open lead). q4 crashed here because I
   passed blocks=None to find_all; fixed.
"""
import traceback, re

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

    print("=" * 78)
    print("A: full disassembly of 0x18013fe00, and ladder reconstruction")
    f6 = func(0x18013fe00)
    it = listing.getInstructions(f6.getBody(), True)
    lines = []
    insns = []
    while it.hasNext():
        ins = it.next()
        a = int(ins.getAddress().getOffset())
        lines.append("%#x  %s" % (a, ins))
        insns.append((a, ins.getMnemonicString(), str(ins)))
    dump("q5_item_deser_disasm", "\n".join(lines))
    print("  %d instructions" % len(insns))

    # ladder: SUB reg,imm (or DEC reg) whose NEXT instruction is a conditional jump
    acc = {}
    atoms = []
    for i, (a, mn, t) in enumerate(insns):
        nxt = insns[i + 1][1] if i + 1 < len(insns) else ""
        m = re.match(r"^(SUB|CMP|DEC|MOV)\s+([A-Z0-9]+),?\s*(.*)$", t)
        if not m:
            continue
        op, reg, rest = m.group(1), m.group(2), m.group(3)
        imm = None
        mi = re.match(r"^(0x[0-9a-fA-F]+|\d+)$", rest.strip())
        if mi:
            imm = int(mi.group(1), 0)
        if op == "MOV":
            # a fresh load of the dispatch register resets the running sum
            acc[reg] = 0
            continue
        if op == "DEC":
            imm = 1
            rest = "1"
        if imm is None:
            continue
        cond = nxt.startswith("J") and nxt not in ("JMP",)
        if op == "SUB":
            acc[reg] = acc.get(reg, 0) + imm
            if cond:
                atoms.append((a, acc[reg], reg, t, nxt))
        elif op == "CMP":
            if cond:
                atoms.append((a, acc.get(reg, 0) + imm, reg, t, nxt))
    vals = sorted(set(v for _, v, _, _, _ in atoms))
    s6 = dec(0x18013fe00)
    cases = sorted(set(int(x, 16) for x in re.findall(r"case 0x([0-9a-f]+):", s6)))
    print("  reconstructed ladder values (%d): %s" % (len(vals), [hex(v) for v in vals]))
    print("  decompiler case arms      (%d): %s" % (len(cases), [hex(c) for c in cases]))
    inter = sorted(set(vals) & set(cases))
    print("  RECONSTRUCTION CONTROL: %d/%d decompiler arms reproduced" % (len(inter), len(cases)))
    print("  arms the ladder found that the decompiler did not: %s"
          % [hex(v) for v in sorted(set(vals) - set(cases))][:60])
    print("  0x23d in reconstructed ladder? ", 0x23d in vals)
    print("  0x23f in reconstructed ladder? ", 0x23f in vals)
    print("  --- ladder trace ---")
    for a, v, reg, t, nxt in atoms:
        print("    %#x  atom=%#-6x %-28s next=%s" % (a, v, t, nxt))

    print("=" * 78)
    print("B: functions writing dwords at +0xc0/+0xc4/+0xc8/+0xcc/+0xd0")
    blk = [b for b in mem.getBlocks() if b.getName() == ".text"][0]
    it = listing.getInstructions(AddressSet(blk.getStart(), blk.getEnd()), True)
    per = {}
    rx = re.compile(r"MOV\s+dword ptr \[([A-Z0-9]+) \+ (0x(?:c0|c4|c8|cc|d0))\]")
    while it.hasNext():
        ins = it.next()
        t = str(ins)
        m = rx.match(t)
        if not m:
            continue
        a = int(ins.getAddress().getOffset())
        per.setdefault(fname(a), set()).add(m.group(2))
    full = [(k, sorted(v)) for k, v in per.items() if len(v) >= 4]
    print("  functions writing >=4 of the five: %d" % len(full))
    for k, v in full:
        print("    %-26s %s" % (k, v))
    print("  callers of FUN_180015d80:")
    try:
        for c in callers(0x180015d80):
            print("   ", c)
    except Exception as e:
        print("   EXC", e)
    print("  xrefs_to(0x180015d80):")
    try:
        for r in xrefs_to(0x180015d80):
            print("   ", r)
    except Exception as e:
        print("   EXC", e)

    print("=" * 78)
    print("C: CARDS_CB_ERR_PACK_NOT_IN_DIME")
    hits = find_all(b"CARDS_CB_ERR_PACK_NOT_IN_DIME")
    print("  string sites:", [hex(int(h)) for h in hits])
    for h in hits:
        for r in xrefs_to(int(h)):
            a = r[0] if isinstance(r, tuple) else int(r)
            print("    xref %s in %s" % (r, fname(a)))
            se = dec(a)
            dump("q5_dime_%s" % (fname(a) or "unk"), se)
            print("    ---- %s  %d chars ----" % (fname(a), len(se)))
            print(se)

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