"""VERIFY-2. Attack the ABSENCE claims by asm-level immediate enumeration.

HYPOTHESES UNDER ATTACK:
  H6  extPrice finalPrice (0x180139070) / originalPrice (0x18013aae0) read ONLY
      atom 0x11a (externalPriceId). They do NOT read 0x1b (amount) or 0xc4 (currency).
  H7  currency element deser 0x180138bd0 reads ONLY 0x1d0/0x134/0x124, stride 0x30.
  H8  FutCreatePackServerResponse deser 0x180162880 has arms ONLY for
      0x16e,0x1dd,0x264,0xec -- no reason/errorCode/state.
  H9  FUN_18002c3c0 has exactly ONE caller (0x1800150d0) and does pure copies of
      +0x144..+0x154.
  H10 the 0x20f..0x298 jump table in 0x18013af30 really does bind 0x26b quantity,
      0x298 saleType, 0x20f packType to real arms.

METHOD: instead of reading the decompiler, I enumerate EVERY scalar immediate that
appears in a CMP/SUB/LEA/MOV inside each function's real instruction listing. If an
atom id is nowhere in that set, it cannot be dispatched on. This is an exhaustive
upper bound over the function body and is a different method from reading C output.

CONTROL: 0x18013af30 must yield 0x20c, 0x2e3, 0x35d in its immediate set (known
present) and must yield the 5 packContentInfo ids. If the technique misses those,
it is broken and every absence below is void.
"""
import traceback
OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/packres/"

try:
    def all_scalars(entry):
        f = func(entry)
        if f is None: return None, None
        out = {}
        it = listing.getInstructions(f.getBody(), True)
        n = 0
        while it.hasNext():
            ins = it.next(); n += 1
            for i in range(ins.getNumOperands()):
                try: sc = ins.getScalar(i)
                except Exception: sc = None
                if sc is None: continue
                v = int(sc.getUnsignedValue())
                out.setdefault(v, []).append((int(ins.getAddress().getOffset()), str(ins)))
        return out, n

    def dump(entry, tag):
        src = dec(entry)
        p = OUT + "v2_%s_%x.txt" % (tag, entry)
        open(p, "w").write(src)
        print("[%s] %#x  len(src)=%d  lines=%d -> %s" % (tag, entry, len(src), src.count("\n"), p))
        return src

    def asm(entry, tag):
        f = func(entry)
        lines = []
        it = listing.getInstructions(f.getBody(), True)
        while it.hasNext():
            i = it.next()
            lines.append("%010x  %s" % (int(i.getAddress().getOffset()), str(i)))
        p = OUT + "v2_%s_%x.asm" % (tag, entry)
        open(p, "w").write("\n".join(lines) + "\n")
        return p

    ATOMS = {0x1b: "amount", 0xc4: "currency", 0x11a: "externalPriceId",
             0x124: "finalFunds", 0x134: "funds", 0x1d0: "name",
             0x16e: "itemList", 0x1dd: "numberItems", 0x264: "purchasedPackId",
             0xec: "duplicateItemIdList", 0x2eb: "state", 0x28b: "reason",
             0x20b: "packId", 0x127: "firstPartyStoreId", 0x26b: "quantity",
             0x298: "saleType", 0x20f: "packType", 0x176: "isPremium",
             0x15c: "id", 0x20c: "packContentInfo", 0x2e3: "start", 0x35d: "unopened",
             0x63: "bronzeQuantity", 0x149: "goldQuantity", 0x170: "itemQuantity",
             0x273: "rareQuantity", 0x2c6: "silverQuantity", 0x240: "points",
             0x265: "purchaseLimit", 0x261: "purchaseCount", 0x37d: "visible",
             0x36a: "useDefaultImage", 0x102: "end", 0xcc: "dealType",
             0x2cb: "sortPriority", 0x33a: "transactionId", 0x367: "useAuth",
             0x368: "useCount", 0x375: "useTime", 0x369: "useCredits",
             0x36b: "usePreOrder", 0x258: "productId", 0x14e: "groupName",
             0x266: "purchasePackType", 0x260: "purchase"}

    for entry, tag in [(0x18013af30, "CONTROL_packelem"),
                       (0x180139070, "finalPrice"),
                       (0x18013aae0, "originalPrice"),
                       (0x180138bd0, "currencyElem"),
                       (0x180162880, "createpack_deser"),
                       (0x180162530, "createpack_reqser"),
                       (0x1801269f0, "purchaseitems_deser")]:
        sc, n = all_scalars(entry)
        if sc is None:
            print("!! no function at %#x" % entry); continue
        present = sorted(a for a in ATOMS if a in sc)
        print()
        print("=" * 74)
        print("%s  %#x   instructions=%d  distinct scalars=%d" % (tag, entry, n, len(sc)))
        print("  ATOM IDS PRESENT AS IMMEDIATES:")
        for a in present:
            sites = sc[a][:3]
            print("    %-6s %-22s  %s" % (hex(a), ATOMS[a],
                                          "; ".join("%010x %s" % s for s in sites)))
        missing = sorted(a for a in ATOMS if a not in sc)
        print("  ABSENT: " + ", ".join("%s(%s)" % (hex(a), ATOMS[a]) for a in missing))

    print()
    print("=" * 74)
    print("H10: jump table behind LEA EAX,[R15-0x20f]; CMP EAX,0x89")
    # find R13 base
    f = func(0x18013af30)
    it = listing.getInstructions(f.getBody(), True)
    while it.hasNext():
        i = it.next()
        s = str(i)
        if "R13" in s and i.getMnemonicString() in ("LEA", "MOV") and s.split(',')[0].endswith("R13"):
            print("  R13 set:", "%010x  %s" % (int(i.getAddress().getOffset()), s))
    idx = read_bytes(0x18013bcb4, 0x8a)
    print("  index table @0x18013bcb4 (%d bytes):" % len(idx), idx.hex())
    offs = [dword(0x18013bc98 + 4 * k) for k in range(max(idx) + 1)]
    print("  offset table @0x18013bc98:", ["%08x" % o for o in offs])
    print("  atom -> target:")
    for k in range(0x8a):
        atom = 0x20f + k
        t = (offs[idx[k]] + 0x180000000) & 0xFFFFFFFFFFFF
        nm = ATOMS.get(atom, "")
        if nm or t != (offs[idx[0x8a - 1]] + 0x180000000):
            pass
    # group atoms by target
    from collections import defaultdict
    g = defaultdict(list)
    for k in range(0x8a):
        g[offs[idx[k]] + 0x180000000].append(0x20f + k)
    for t in sorted(g):
        ats = g[t]
        named = [("%s=%s" % (hex(a), ATOMS[a])) for a in ats if a in ATOMS]
        print("    target %010x  n=%-3d  %s" % (t, len(ats), ", ".join(named) if named else ""))

    print()
    print("=" * 74)
    print("H9: xrefs to FUN_18002c3c0")
    for r in xrefs_to(0x18002c3c0):
        print("   from %010x %-14s in %s (%010x)" % (r[0], r[1], r[2], r[3]))
    s = dump(0x18002c3c0, "adapter")
    import re
    print("  --- lines mentioning 0x14[4-9c]/0x15[04]/0xb4/0xcd ---")
    for ln in s.split("\n"):
        if any(k in ln for k in ("0x144", "0x148", "0x14c", "0x150", "0x154", "+ 0xb4", "+ 0xcd")):
            print("   ", ln.strip())

    print()
    print("=" * 74)
    print("STRING LITERALS referenced by the pack element deser")
    for a in (0x1801e98c8, 0x180223228, 0x1801fd44c, 0x180223238, 0x180221c04,
              0x1801efea0, 0x1801ec008):
        print("   %010x = %r" % (a, rd_str(a, 40)))

    for e, t in [(0x180139070, "finalPrice"), (0x18013aae0, "originalPrice"),
                 (0x180138bd0, "currencyElem"), (0x180162880, "createpack_deser")]:
        dump(e, t); asm(e, t)

except Exception:
    traceback.print_exc()
