"""VERIFY-1. Adversarial re-derivation of the pack element deserializer.

HYPOTHESES UNDER ATTACK (from D3/D5 reports):
  H1  0x18013af30 parses packContentInfo (atom 0x20c) INLINE with EXACTLY five
      children: 0x170,0x149,0x2c6,0x63,0x273 -> rec+0x144..+0x154.
  H2  atoms 0x2e3 (start) and 0x35d (unopened) are TOP-LEVEL, not inside 0x20c.
  H3  atoms 0x15c,0x26b,0x298,0x20f,0x176 have REAL arms (not SKIP).
  H4  record stride 0x158, and a CMP against 0x64 caps the store at 100 packs.
  H5  firstPartyStoreId (0x127) in the PACK element uses the STR getter 0x1801c7aa0
      + atoi, NOT the INT getter.

METHOD DELIBERATELY DIFFERENT FROM THE ORIGINALS: I dump the FULL RAW DISASSEMBLY
of the function (every instruction, address + mnemonic + operands) and analyse the
ladder from bytes/asm, not from the decompiler's frame locals. I also
cross-check with the decompile but the asm is primary.

CONTROL: the function must contain a call to the known SKIP primitive 0x180135ff0
and to the known INT/BOOL/STR primitives; and the FNV hasher 0x180180d00 must
disassemble to the known prologue.
"""
import traceback, sys

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

try:
    def dump_asm(entry, path, label):
        f = func(entry)
        if f is None:
            print("NO FUNCTION at %#x" % entry); return None
        body = f.getBody()
        lines = []
        it = listing.getInstructions(body, True)
        n = 0
        while it.hasNext():
            ins = it.next()
            a = int(ins.getAddress().getOffset())
            lines.append("%010x  %-8s %s" % (a, ins.getMnemonicString(),
                                             str(ins).split(None, 1)[1] if ' ' in str(ins) else ''))
            n += 1
        open(path, "w").write("\n".join(lines) + "\n")
        print("[%s] %s entry=%#x  instructions=%d  bodysize=%#x -> %s"
              % (label, f.getName(), int(f.getEntryPoint().getOffset()), n,
                 int(body.getNumAddresses()), path))
        return lines

    print("=" * 78)
    print("CONTROL: FNV hasher prologue at 0x180180d00")
    print("bytes:", read_bytes(0x180180d00, 16).hex())
    f = func(0x180180d00)
    print("ghidra fn:", f.getName() if f else None)

    print()
    print("=" * 78)
    print("PACK ELEMENT DESERIALIZER 0x18013af30")
    src = dec(0x18013af30)
    open(OUT + "v1_packelem_dec.txt", "w").write(src)
    print("decompile len(src) =", len(src), " lines =", src.count("\n"))
    lines = dump_asm(0x18013af30, OUT + "v1_packelem.asm", "packelem")

    # ---- ladder reconstruction from ASM ----
    # Find every CMP/SUB against an immediate in the atom range, in address order,
    # together with the following conditional jump target.
    print()
    print("--- ATOM LADDER (SUB/CMP against immediates, address order) ---")
    f = func(0x18013af30)
    it = listing.getInstructions(f.getBody(), True)
    seq = []
    while it.hasNext():
        ins = it.next()
        m = ins.getMnemonicString()
        if m in ("SUB", "CMP", "ADD"):
            try:
                sc = ins.getScalar(1)
            except Exception:
                sc = None
            if sc is not None:
                v = int(sc.getUnsignedValue())
                if 1 <= v <= 0x400:
                    seq.append((int(ins.getAddress().getOffset()), m, str(ins), v))
    run = 0
    for a, m, s, v in seq:
        if m == "SUB":
            run += v
        elif m == "CMP":
            run += v
        print("%010x  %-40s imm=%#x  running=%#x" % (a, s, v, run))

    print()
    print("--- CALLS to known primitives, with address ---")
    PRIM = {0x1801c79d0: "INT", 0x1801c7620: "BOOL", 0x1801c7aa0: "STR",
            0x180135ff0: "SKIP", 0x1801c7f10: "NEXTTOK", 0x1801c8270: "BEGINOBJ",
            0x18013fe00: "ITEMDESER", 0x1800d7af0: "clampI32", 0x1800d7b30: "clampNonNegI32",
            0x1800d7b10: "toU16", 0x180138bd0: "CURRENCYELEM", 0x180139070: "FINALPRICE",
            0x18013aae0: "ORIGPRICE"}
    it = listing.getInstructions(f.getBody(), True)
    while it.hasNext():
        ins = it.next()
        if ins.getMnemonicString() == "CALL":
            for r in ins.getFlows():
                t = int(r.getOffset())
                if t in PRIM:
                    print("%010x  CALL %-14s (%#x)" % (int(ins.getAddress().getOffset()), PRIM[t], t))

    print()
    print("--- STRIDE / CAP evidence: instructions between 0x18013bad0 and 0x18013bb60 ---")
    a = 0x18013ad0 and 0x18013bad0
    while a < 0x18013bb60:
        ins = listing.getInstructionAt(addr(a))
        if ins is None:
            a += 1; continue
        print("%010x  %s" % (a, str(ins)))
        a += ins.getLength()

    print()
    print("=" * 78)
    print("PACK RECORD CTOR 0x1801342d0")
    src2 = dec(0x1801342d0)
    open(OUT + "v1_packctor_dec.txt", "w").write(src2)
    print("len =", len(src2))
    print(src2)
    dump_asm(0x1801342d0, OUT + "v1_packctor.asm", "packctor")

except Exception:
    traceback.print_exc()
