"""ADVERSARIAL VERIFICATION BATCH 2.

Q1 COMPLETENESS GAP the D4 report admitted: are there raw, non-accessor reads of
   record+0xb4 anywhere in the binary? 0xb4 cannot be encoded as a signed disp8,
   so EVERY [reg+0xb4] reference must carry the literal disp32 bytes b4 00 00 00.
   Scanning .text for those four bytes and decoding the containing instruction is
   therefore an EXHAUSTIVE search, not a sample. Same scan for 0x54 and 0x88.
   Positive control: the scan must find FUN_1801a87f0 (+0xb4), FUN_180141660's
   ladder (+0xb4 and +0x54) and FUN_1801a85c0 (+0x88).

Q2 FUN_18013f4d0 -- the family-6 handler the deser tail calls with (record,
   resourceId, AMOUNT). If it stores amount in the record, the standing
   "amount is dropped" verdict is wrong.

Q3 the +0xe0 mystery: FUN_1801a8540, FUN_1800e5940 (manager publisher),
   FUN_1800e6e20 (player publisher) in full.

Q4 FUN_180166660 itemState mapper, FUN_1800d7b50/b30/b10/af0 value readers.

Q5 who calls FUN_18013fe00 and FUN_180141660 (is the ladder really on every path).
"""
import traceback

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

try:
    def scan_disp(off):
        pat = bytes([off & 0xFF, (off >> 8) & 0xFF, (off >> 16) & 0xFF, (off >> 24) & 0xFF])
        hits = find_all(pat, blocks=(".text",))
        rows = []
        for h in hits:
            ins = listing.getInstructionContaining(addr(h))
            if ins is None:
                continue
            a = int(ins.getAddress().getOffset())
            txt = str(ins)
            if ("0xb4]" in txt or "0x54]" in txt or "0x88]" in txt or
                    hex(off) in txt.lower()):
                f = fm.getFunctionContaining(ins.getAddress())
                rows.append((a, txt, f.getName() if f else "?"))
        return rows

    for off, label in ((0xB4, "record+0xb4 rating"),
                       (0x54, "record+0x54 disputed"),
                       (0x88, "record+0x88 playStyle")):
        rows = scan_disp(off)
        print("\n==== EXHAUSTIVE disp32 scan for [reg+%#x]  (%s): %d instructions"
              % (off, label, len(rows)))
        seen = {}
        for a, txt, fn in rows:
            seen.setdefault(fn, []).append((a, txt))
        for fn in sorted(seen):
            print("  %-24s" % fn, ["%#x %s" % (a, t) for a, t in seen[fn]])

    bodies = []
    for nm, a in (("f_13f4d0_family6", 0x18013F4D0),
                  ("acc_1a8540", 0x1801A8540),
                  ("acc_1a86b0", 0x1801A86B0),
                  ("acc_1a8590_nation", 0x1801A8590),
                  ("acc_1a86a0_team", 0x1801A86A0),
                  ("pub_mgr_1800e5940", 0x1800E5940),
                  ("pub_player_1800e6e20", 0x1800E6E20),
                  ("itemstate_166660", 0x180166660),
                  ("rd_d7b50", 0x1800D7B50), ("rd_d7b30", 0x1800D7B30),
                  ("rd_d7b10", 0x1800D7B10), ("rd_d7af0", 0x1800D7AF0),
                  ("stamp_d84e0", 0x1800D84E0)):
        f = func(a)
        s = dec(a, 600)
        bodies.append("=" * 78)
        bodies.append("### %s @ %#x  len=%d" % (nm, a, len(s)))
        bodies.append(s)
    open(OUT + "v2_bodies.txt", "w").write("\n".join(bodies))
    print("\nWROTE v2_bodies.txt")

    print("\n==== callers ====")
    for nm, a in (("FUN_18013fe00 item deser", 0x18013FE00),
                  ("FUN_180141660 merge", 0x180141660),
                  ("FUN_180135890 players merge", 0x180135890),
                  ("FUN_1801a87f0 rating acc", 0x1801A87F0),
                  ("FUN_1801a80c0 cardlevel acc", 0x1801A80C0),
                  ("FUN_1801a85c0 playstyle acc", 0x1801A85C0),
                  ("FUN_1801a8550 league acc", 0x1801A8550),
                  ("FUN_1801a8540", 0x1801A8540)):
        xs = xrefs_to(a)
        cs = sorted(set("%s@%#x" % (x[2], x[3]) for x in xs if x[1].startswith("UNCONDITIONAL_CALL") or "CALL" in x[1]))
        print("%-30s xrefs=%d callers=%s" % (nm, len(xs), cs))
except Exception:
    traceback.print_exc()
