"""VERIFY-4. Re-run the failed census with a WORKING method, plus the remaining
actionable D5 claims.

WHY A RERUN: q_pack_v2_3's census used Instruction.getScalar(), which returns null
for the displacement inside a memory operand. Its own control (FUN_18002c3c0, known
to read +0x144..+0x154) scored ZERO, so the technique was void and no absence could
be concluded from it. Here I match against the printed operand text instead, which
shows the displacement whatever its encoding (disp8, disp32, SIB, LEA).

  H11 nothing besides the known lifecycle+adapter set touches +0x144..+0x154.
  H12 FutCreatePackServerResponse+0x28 (numberItems) is read by nothing.
  H17 FUN_18002cc90 early-returns when tile+0x6c == -1.
  H18 PurchaseItems req serializer 0x180126440 + URL builder 0x180126720.
  H19 FUN_1801267b0 turns 409 + "User already has a transaction" into 0x70.
  H20 purchaseitems response deser field map.
  H21 the pack element's firstPartyStoreId call target qword[0x1801e51d0] is atoi.

CONTROL: the census must list FUN_18002c3c0 with all five offsets, and must list
the pack-record copy-assign 0x1801340e0 and uninit-copy 0x180133210. If those three
are missing the census is broken again.
"""
import traceback, re
from collections import defaultdict
OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/packres/"

try:
    PAT = re.compile(r"0x(144|148|14c|150|154)\b")
    hits = defaultdict(set); sites = defaultdict(list)
    ninst = 0; nfun = 0
    fi = fm.getFunctions(True)
    while fi.hasNext():
        f = fi.next(); nfun += 1
        ent = int(f.getEntryPoint().getOffset())
        it = listing.getInstructions(f.getBody(), True)
        while it.hasNext():
            ins = it.next(); ninst += 1
            s = str(ins)
            m = PAT.findall(s)
            if m:
                for v in m:
                    hits[ent].add(int(v, 16))
                if len(sites[ent]) < 10:
                    sites[ent].append("%010x %s" % (int(ins.getAddress().getOffset()), s))
    print("=" * 74)
    print("H11 CENSUS (operand-text method). functions=%d instructions=%d" % (nfun, ninst))
    ranked = sorted(hits.items(), key=lambda kv: (-len(kv[1]), kv[0]))
    ctl = {0x18002c3c0, 0x1801340e0, 0x180133210}
    print("  CONTROLS: " + ", ".join("%010x=%d offsets" % (c, len(hits.get(c, ()))) for c in sorted(ctl)))
    print("  functions with >=4 of the five offsets:")
    for e, s in ranked:
        if len(s) < 4: break
        print("    %010x  n=%d  %s" % (e, len(s), sorted(hex(x) for x in s)))
        for t in sites[e]:
            print("        ", t)
    print("  functions with exactly 3: %s" % ["%010x" % e for e, s in ranked if len(s) == 3])
    print("  functions with exactly 2: %d, with exactly 1: %d"
          % (sum(1 for _, s in ranked if len(s) == 2), sum(1 for _, s in ranked if len(s) == 1)))
    with open(OUT + "v4_census.txt", "w") as fh:
        for e, s in ranked:
            fh.write("%010x  n=%d  %s\n" % (e, len(s), sorted(hex(x) for x in s)))
            for t in sites[e]:
                fh.write("      %s\n" % t)
    print("  full census -> " + OUT + "v4_census.txt")

    print()
    print("=" * 74)
    print("H12 hunt the FutCreatePackServerResponse consumer")
    print("  callers of ServerCall ctor 0x1801623d0:", xrefs_to(0x1801623d0))
    print("  callers of pool builder 0x18010cdc0:", xrefs_to(0x18010cdc0)[:10])
    for s in (b"OnPurchasePackResponse", b"OnCreatePackResponse", b"PurchasePack",
              b"OnPackPurchase", b"CREATEPACK\x00"):
        h = find_all(s)
        print("  string %r at %s" % (s, ["%010x" % a for a in h]))
        for a in h:
            for r in xrefs_to(a):
                print("        xref %010x %s in %s" % (r[0], r[1], r[2]))

    print()
    print("=" * 74)
    print("H17/H18/H19/H20 decompiles")
    for e, tag in [(0x18002cc90, "price_formatter"),
                   (0x180126440, "purchaseitems_reqser"),
                   (0x180126720, "purchaseitems_url"),
                   (0x1801267b0, "purchaseitems_httperr"),
                   (0x180126900, "purchaseitems_state1body")]:
        s = dec(e)
        open(OUT + "v4_%s_%x.txt" % (tag, e), "w").write(s)
        print()
        print("---- %s %#x  len=%d lines=%d ----" % (tag, e, len(s), s.count("\n")))
        print(s if len(s) < 4200 else s[:4200] + "\n...TRUNCATED, full text in file...")

    print()
    print("=" * 74)
    print("H20 purchaseitems_deser 0x1801269f0 ladder (raw asm, dispatch region)")
    f = func(0x1801269f0)
    it = listing.getInstructions(f.getBody(), True)
    lines = []
    while it.hasNext():
        i = it.next()
        lines.append("%010x  %s" % (int(i.getAddress().getOffset()), str(i)))
    open(OUT + "v4_purchaseitems_deser.asm", "w").write("\n".join(lines) + "\n")
    for l in lines:
        a = int(l[:10], 16)
        if 0x180126ab0 <= a <= 0x180126e60:
            print("   " + l)

    print()
    print("=" * 74)
    print("H21 import at 0x1801e51d0")
    t = qword(0x1801e51d0)
    print("  qword[0x1801e51d0] = %016x" % t)
    d = listing.getDataAt(addr(0x1801e51d0))
    print("  ghidra data/label:", d.getLabel() if d else None,
          [str(s) for s in prog.getSymbolTable().getSymbols(addr(0x1801e51d0))])

except Exception:
    traceback.print_exc()
