"""VERIFY-3. Attack the two biggest absence claims and the actionable tables.

  H11 (D3 #4) NOTHING in CardsDLL compares the delivered itemList against the
      declared packContentInfo quantities. Original method: disp32 BYTE SCAN.
      MY METHOD: whole-.text instruction-operand scalar census via Ghidra's own
      decoded operands, which sees disp8, disp32, SIB and LEA forms alike and does
      not care how the displacement was encoded. Strictly wider than a byte scan.
  H12 (D3 #5) numberItems at FutCreatePackServerResponse+0x28 is read by NOTHING.
      MY METHOD: enumerate every xref to the response vtable 0x180228260, the
      factory 0x180162770, the class literal, and the owning ServerCall vtable
      0x180228270, then look at the completion consumer.
  H13 FUN_18002c3c0 has DATA xrefs at 0x1802f1620 and 0x180244880 that the D3
      report did not mention. Are they vtable slots (=> a second, indirect caller)?
  H14 the HTTP status table FUN_1801844c0 maps only 200 to success.
  H15 the 9-entry transaction state table at 0x1802d02c0.
  H16 the RPC descriptor table at 0x1802cb500, 0x30-byte rows.

CONTROL for the census: FUN_18002c3c0 (the known adapter) MUST appear with all
five offsets. If it does not, the census is broken and every absence is void.
"""
import traceback
from collections import defaultdict
OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/packres/"

try:
    TARGETS = {0x144, 0x148, 0x14c, 0x150, 0x154}
    print("=" * 74)
    print("H11 CENSUS: every function whose decoded operands carry a displacement/")
    print("            scalar in {0x144,0x148,0x14c,0x150,0x154}")
    hits = defaultdict(set)
    sites = defaultdict(list)
    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()
            for i in range(ins.getNumOperands()):
                try: sc = ins.getScalar(i)
                except Exception: sc = None
                if sc is None: continue
                v = int(sc.getUnsignedValue())
                if v in TARGETS:
                    hits[ent].add(v)
                    if len(sites[ent]) < 8:
                        sites[ent].append("%010x %s" % (int(ins.getAddress().getOffset()), str(ins)))
    print("  functions scanned:", nfun)
    ranked = sorted(hits.items(), key=lambda kv: (-len(kv[1]), kv[0]))
    print("  functions carrying ALL FIVE offsets:")
    allfive = [e for e, s in ranked if len(s) == 5]
    for e in allfive:
        print("    %010x  %s" % (e, fname(e) if callable(globals().get("fname")) else ""))
        for s in sites[e]:
            print("        ", s)
    print("  CONTROL 0x18002c3c0 present with 5 offsets:", 0x18002c3c0 in allfive,
          " (offsets seen: %s)" % sorted(hex(x) for x in hits.get(0x18002c3c0, set())))
    print("  functions with 4 offsets:", ["%010x" % e for e, s in ranked if len(s) == 4])
    print("  functions with 3 offsets:", ["%010x" % e for e, s in ranked if len(s) == 3])
    print("  total functions with >=1 of the five: %d" % len(hits))
    with open(OUT + "v3_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()
    print("=" * 74)
    print("H13 DATA xrefs to FUN_18002c3c0")
    for a in (0x1802f1620, 0x180244880):
        blk = mem.getBlock(addr(a))
        print("  %010x in block %s" % (a, blk.getName() if blk else "?"))
        for k in range(-4, 6):
            q = qword(a + k * 8)
            f = fm.getFunctionAt(addr(q)) if 0x180000000 <= q < 0x181000000 else None
            print("     [%+3d] %016x  %s" % (k * 8, q, f.getName() if f else ""))
        print("     xrefs to that slot address:", xrefs_to(a))

    print()
    print("=" * 74)
    print("H12 who touches FutCreatePackServerResponse")
    for lit in find_all(b"RS4:FutCreatePackServerResponse\x00"):
        print("  literal at %010x  xrefs:" % lit, xrefs_to(lit))
    for v in (0x180228260, 0x180228270):
        print("  vtable %010x xrefs: %s" % (v, xrefs_to(v)))
        for slot, t, n in vtable(v, 20):
            if t == 0: break
            print("      +%03x %016x %s" % (slot, t, n))
    print("  xrefs to factory 0x180162770:", xrefs_to(0x180162770))
    print("  xrefs to ctor 0x180162420:", xrefs_to(0x180162420))
    print("  callers of deser 0x180162880:", xrefs_to(0x180162880))

    print()
    print("=" * 74)
    print("H14 HTTP status table FUN_1801844c0")
    s = dec(0x1801844c0)
    open(OUT + "v3_http_1801844c0.txt", "w").write(s)
    print("  len(src)=%d lines=%d" % (len(s), s.count("\n")))
    print(s)

    print()
    print("=" * 74)
    print("H15 transaction state table 0x1802d02c0")
    for k in range(12):
        a = 0x1802d02c0 + k * 16
        v = dword(a); p = qword(a + 8)
        nm = rd_str(p, 40) if 0x180000000 <= p < 0x181000000 else "<%016x>" % p
        print("   [%2d] %010x  value=%-6d name=%r" % (k, a, v if v < 0x80000000 else v - (1 << 32), nm))

    print()
    print("=" * 74)
    print("H16 RPC descriptor table 0x1802cb500 (0x30 rows)")
    bad = 0
    for k in range(40):
        r = 0x1802cb500 + k * 0x30
        try:
            p0 = qword(r); f1 = qword(r + 8); p2 = qword(r + 0x10)
            z3 = qword(r + 0x18); z4 = qword(r + 0x20); fn = qword(r + 0x28)
        except Exception:
            print("   row %d unreadable" % k); break
        n0 = rd_str(p0, 48) if 0x180000000 <= p0 < 0x181000000 else ""
        n2 = rd_str(p2, 48) if 0x180000000 <= p2 < 0x181000000 else ""
        ok = n2.isupper() and n2.isalpha() if n2 else False
        if not ok: bad += 1
        print("   %010x  %-28r flags=%-6x %-28r z=%d,%d fn=%010x %s"
              % (r, n0, f1, n2, z3, z4, fn, "" if ok else "  <-- third qword not an UPPER token"))
    print("   rows whose third qword is NOT an uppercase token: %d/40" % bad)

except Exception:
    traceback.print_exc()
