"""ADVERSARIAL BATCH 3.

My batch-2 displacement census turned up FOUR +0x5c sites the other agent's
constant-collecting scan did not report, including MOV dword [RDI+0x5c],0x5 and
MOV dword [RDI+0x5c],0x6 in FUN_180147070 -- i.e. the client WRITES forSale and
offered. Their claim "forSale(5) and offered(6): NEVER TESTED ANYWHERE" and the
action "nothing reads them" are under direct attack here.

Also under attack:
 - "no other code path can produce the greyout from wire data": FUN_1800bc580 is a
   THIRD +0x49 consumer (it counts untradeable squad members). What uses that count?
 - the FUN_1800e2a40 <-> FUN_18003e370 vtable link the agent flagged as a gap.
 - the +0x23f playStyle mapper, the 0x226 pile mapper, and the record-offset anchor
   inside FUN_18013fe00 (printed IN FULL, with len).

CONTROL for the vtable hunt: I search for the 8-byte pointer to FUN_1800e2a40 AND,
in the same pass, for the pointer to FUN_1801a7260 (which the agent reported has NO
8-byte pointer, only 4-byte .pdata RVAs) and to FUN_18003e370. A hunt that finds all
three or none tells me the search itself is sound.
"""
import traceback, struct

OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/cards/adv2/q3_raw.txt"
try:
    f = open(OUT, "w")

    def P(*a):
        f.write(" ".join(str(x) for x in a) + "\n")

    P("=" * 25, "A. itemState WRITERS/READERS the other scan missed", "=" * 25)
    for a in (0x180147070, 0x1801A6FC0, 0x1800A47B0, 0x18011DC50, 0x1800D73D0):
        d = dec(a)
        P("### %#x  len=%d  xrefs:" % (a, len(d)))
        for frm, t, cf, e in xrefs_to(a):
            P("    %#x %s in %s@%#x" % (frm, t, cf, e))
        P(d)
        P()

    P("=" * 25, "B. the third +0x49 consumer: who calls FUN_1800bc580", "=" * 25)
    for frm, t, cf, e in xrefs_to(0x1800BC580):
        P("  %#x %s in %s@%#x" % (frm, t, cf, e))
    P("--- FUN_1801a8890 (the sibling predicate counted into param_2):")
    P(dec(0x1801A8890))
    P("--- FUN_1801a80a0:")
    P(dec(0x1801A80A0))

    P()
    P("=" * 25, "C. vtable link FUN_1800e2a40 <- FUN_18003e370 slot 0x40", "=" * 25)
    for nm, a in (("FUN_1800e2a40", 0x1800E2A40), ("FUN_1801a7260", 0x1801A7260),
                  ("FUN_18003e370", 0x18003E370), ("FUN_1800eb850", 0x1800EB850)):
        pat = struct.pack("<Q", a)
        hits = find_all(pat, blocks=(".rdata", ".data"))
        P("  %s ptr8 hits: %s" % (nm, [hex(h) for h in hits]))
        for h in hits:
            # walk backwards to find the table start (first qword that is not a .text ptr)
            start = h
            while True:
                try:
                    v = qword(start - 8)
                except Exception:
                    break
                if not (0x180001000 <= v < 0x1801E5000):
                    break
                start -= 8
            P("      table start %#x, slot +%#x" % (start, h - start))
            for i in range(0, 40):
                try:
                    v = qword(start + i * 8)
                except Exception:
                    break
                if not (0x180001000 <= v < 0x1801E5000):
                    P("        +%#04x  %#x  <END>" % (i * 8, v))
                    break
                fn = fm.getFunctionAt(addr(v))
                P("        +%#04x  %#x  %s%s" % (i * 8, v, fn.getName() if fn else "",
                                                 "   <== TARGET" if v == a else ""))

    P()
    P("=" * 25, "D. FUN_18013fe00 FULL", "=" * 25)
    d = dec(0x18013FE00, timeout=600)
    P("len(src) =", len(d))
    P(d)

    P()
    P("=" * 25, "E. mappers", "=" * 25)
    for nm, a in (("playStyle FUN_180136480", 0x180136480),
                  ("pile FUN_180142650", 0x180142650),
                  ("owners helper FUN_1800d7b50", 0x1800D7B50),
                  ("BOUGHT_FOR mapper FUN_1800d7b30", 0x1800D7B30),
                  ("family FUN_1800d8330", 0x1800D8330)):
        P("### " + nm)
        dd = dec(a)
        P("   len=%d" % len(dd))
        P(dd)
        P()

    f.close()
    print("WROTE", OUT)
except Exception:
    traceback.print_exc()
