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

A. ABSENCE ATTACK on "atom 0x23d playerType has no arm in 0x18013fe00".
   The reviewed agent grepped the DECOMPILE TEXT. I attack it two other ways:
     A1 decode the actual SWITCH JUMP TABLE from the disassembly (the ground truth
        the decompiler's `case` labels are only a rendering of), and
     A2 scan the WHOLE of .text for any instruction carrying the immediate 0x23d,
        with 0x23f (playStyle, known present) and 0x20e (packOpeningAnimationEnabled,
        known present in the settings deser) as positive controls.

B. NUM_*_IN_PACK: is param_4+0xc0..0xd0 really filled from the pack DEFINITION JSON?
   Full decompile of the pack element deser 0x18013af30, every write to +0xc0..+0xd0.

C. FutCreatePackServerResponse 0x180162880 -- itemList appended in wire order.

D. ABSENCE: USE_ANIMATION_STYLE 0x1801fd580 has exactly one xref.

E. BONUS, the D6 open lead: which path reaches CARDS_CB_ERR_PACK_NOT_IN_DIME?
"""
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

    print("=" * 78)
    print("A1: switch dispatch inside 0x18013fe00 -- find indirect JMPs and their tables")
    f6 = func(0x18013fe00)
    print("  body %s - %s" % (f6.getBody().getMinAddress(), f6.getBody().getMaxAddress()))
    it = listing.getInstructions(f6.getBody(), True)
    jmps = []
    while it.hasNext():
        ins = it.next()
        if ins.getMnemonicString() == "JMP" and "[" in str(ins):
            jmps.append((int(ins.getAddress().getOffset()), str(ins)))
    print("  indirect JMPs: %d" % len(jmps))
    for a, t in jmps:
        print("    %#x  %s" % (a, t))
        # Ghidra's switch recovery: look at the flow refs out of this instruction
        try:
            rs = refs.getReferencesFrom(addr(a))
            tgts = sorted(set(int(r.getToAddress().getOffset()) for r in rs
                              if r.getReferenceType().isFlow()))
            print("      %d computed flow targets" % len(tgts))
        except Exception as e:
            print("      refs failed", e)
        # and the switch's case labels from the listing
    # Ghidra stores case values as labels "caseD_xx" or in the jump table; use
    # the decompiler's own high-level switch instead, but validate arm COUNT
    s6 = dec(0x18013fe00)
    cases = sorted(set(int(x, 16) for x in re.findall(r"case 0x([0-9a-f]+):", s6)))
    print("  decompile arms: %d" % len(cases))

    print("=" * 78)
    print("A2: whole-.text immediate scan for 0x23d / 0x23f / 0x20e / 0xd7")
    blk = [b for b in mem.getBlocks() if b.getName() == ".text"][0]
    aset = AddressSet(blk.getStart(), blk.getEnd())
    it = listing.getInstructions(aset, True)
    want = {0x23d: [], 0x23f: [], 0x20e: [], 0x2c5: []}
    n = 0
    while it.hasNext():
        ins = it.next()
        n += 1
        try:
            nops = ins.getNumOperands()
        except Exception:
            continue
        for oi in range(nops):
            objs = ins.getOpObjects(oi)
            for o in objs:
                try:
                    v = int(o.getValue())
                except Exception:
                    continue
                if v in want:
                    a = int(ins.getAddress().getOffset())
                    want[v].append((a, fname(a), str(ins)))
    print("  instructions walked: %d" % n)
    for v in sorted(want):
        lst = want[v]
        print("  --- immediate %#x : %d sites ---" % (v, len(lst)))
        for a, fn, t in lst[:40]:
            print("      %#x  %-26s %s" % (a, fn, t))
    print("  0x23d anywhere in .text? ", len(want[0x23d]) > 0)
    print("  0x23f (control) sites in 0x18013fe00? ",
          [hex(a) for a, fn, t in want[0x23f] if fn == "FUN_18013fe00"])
    print("  0x20e (control) sites in 0x18013c6d0? ",
          [hex(a) for a, fn, t in want[0x20e] if fn == "FUN_18013c6d0"])

    print("=" * 78)
    print("B: pack element deser 0x18013af30 -- writes to +0xc0..+0xd0")
    sb = dec(0x18013af30)
    print("  len=%d chars, %d lines" % (len(sb), sb.count("\n") + 1))
    dump("q4_pack_elem_deser", sb)
    for i, ln in enumerate(sb.split("\n")):
        if re.search(r"0x(c0|c4|c8|cc|d0)\b", ln) or "0xc0" in ln:
            print("    L%-4d %s" % (i + 1, ln.strip()))
    print("  --- its case arms ---")
    cb = sorted(set(int(x, 16) for x in re.findall(r"case 0x([0-9a-f]+):", sb)))
    print("   ", [hex(c) for c in cb])

    print("=" * 78)
    print("C: FutCreatePackServerResponse deser 0x180162880")
    sc = dec(0x180162880)
    print("  len=%d chars, %d lines" % (len(sc), sc.count("\n") + 1))
    dump("q4_createpack_deser", sc)
    print(sc)

    print("=" * 78)
    print("D: xrefs to USE_ANIMATION_STYLE 0x1801fd580")
    print("  string there:", rd_str(0x1801fd580))
    try:
        for r in xrefs_to(0x1801fd580):
            a = r[0] if isinstance(r, tuple) else int(r)
            print("   ", r, fname(a))
    except Exception as e:
        print("  EXC", e)
    hits = find_all(b"USE_ANIMATION_STYLE", None)
    print("  find_all('USE_ANIMATION_STYLE'):", [hex(int(h)) for h in hits])

    print("=" * 78)
    print("E: CARDS_CB_ERR_PACK_NOT_IN_DIME")
    hits = find_all(b"CARDS_CB_ERR_PACK_NOT_IN_DIME", None)
    print("  string sites:", [hex(int(h)) for h in hits])
    for h in hits:
        try:
            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)
                print("    ---- containing function %s, %d chars ----" % (fname(a), len(se)))
                dump("q4_dime_%s" % fname(a), se)
                print(se[:9000])
        except Exception as e:
            print("    EXC", e)

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