"""ADVERSARIAL BATCH 4 -- the remaining serve-changing and absence claims.

 - FUN_180141660: is the +0x54 level write really on the COMMON tail, or only on the
   "DB Error" path? If only on the error path the whole level story changes.
 - FUN_1801b3640: CMP dword [RAX+0x5c],R15D -- a REGISTER compare the other agent's
   constant-collecting scan could not evaluate. If R15D can be 5 or 6 their
   "forSale/offered are never tested" absence claim dies.
 - FUN_18003e550: the listing panel. Does "List on Transfer Market" have its own
   enable predicate the eight-flag array does not cover?
 - FUN_1800eb850: are DISCARD_CREDITS / CALCULATED_DISCARD_CREDITS really the two
   names, pushed from 0x1801a8620 / 0x1801a8090?
 - 0x226 pile census, re-tested by xrefs to the mapper FUN_180142650 (a DIFFERENT
   method from decompiling all 134 skip-callers).
 - itemState string-writer absence, re-tested by xrefs to every one of the 12 string
   literals, with the ITEM-TYPE table strings ('player','staff') as a control that
   has known extra users.
 - FUN_180008190: resolve the indirect string compare through the global vtable.
"""
import traceback, struct

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

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

    P("=" * 25, "A. FUN_180141660 -- is the level write a common tail?", "=" * 25)
    fn = fm.getFunctionAt(addr(0x180141660))
    body = fn.getBody()
    P("body:", body, " min %#x max %#x" % (int(body.getMinAddress().getOffset()),
                                           int(body.getMaxAddress().getOffset())))
    # every RET in the function, and every branch target landing at/after 0x180141e77
    rets, brs = [], []
    it = listing.getInstructions(body, True)
    while it.hasNext():
        i = it.next()
        m = i.getMnemonicString()
        a = int(i.getAddress().getOffset())
        if m == "RET":
            rets.append(a)
        if m.startswith("J"):
            for r in i.getFlows():
                t = int(r.getOffset())
                if 0x180141E70 <= t <= 0x180141EB0:
                    brs.append((a, m, t))
    P("RET sites:", [hex(x) for x in rets])
    P("branches into the tail 0x180141e70..0x180141eb0:")
    for a, m, t in brs:
        P("   %#x %s -> %#x" % (a, m, t))
    P()
    P("FUN_180141660 decompile:")
    d = dec(0x180141660, timeout=600)
    P("len =", len(d))
    P(d)

    P()
    P("=" * 25, "B. FUN_1801b3640 -- the register compare on +0x5c", "=" * 25)
    ins = listing.getInstructions(addr(0x1801B3860), True)
    n = 0
    while ins.hasNext() and n < 90:
        i = ins.next()
        a = int(i.getAddress().getOffset())
        if a > 0x1801B38E0:
            break
        P("  %#x  %s" % (a, i))
        n += 1
    P()
    P("R15 setup search 0x1801b3640..0x1801b3894:")
    ins = listing.getInstructions(addr(0x1801B3640), True)
    while ins.hasNext():
        i = ins.next()
        a = int(i.getAddress().getOffset())
        if a > 0x1801B3894:
            break
        s = i.toString()
        if "R15" in s:
            P("  %#x  %s" % (a, s))
    P()
    d = dec(0x1801B3640, timeout=600)
    P("FUN_1801b3640 len =", len(d))
    P(d)

    P()
    P("=" * 25, "C. FUN_18003e550 listing panel + FUN_1800eb850 discard push", "=" * 25)
    for a in (0x18003E550, 0x1800EB850):
        d = dec(a, timeout=600)
        P("### %#x len=%d" % (a, len(d)))
        P(d)
        P()

    P("=" * 25, "D. pile mapper xrefs (different method for the 0x226 census)", "=" * 25)
    for frm, t, cf, e in xrefs_to(0x180142650):
        P("  %#x %s in %s@%#x" % (frm, t, cf, e))

    P()
    P("=" * 25, "E. itemState string literals: every xref", "=" * 25)
    names = ["invalid", "free", "WAITING_FOR_GAME", "inGame", "forSale", "offered",
             "activeBadge", "activeHomeKit", "activeAwayKit", "activeBall",
             "activeStadium", "active",
             "player", "staff"]  # last two = CONTROL, known to be used elsewhere
    for nm in names:
        hits = find_all(nm.encode() + b"\x00", blocks=(".rdata", ".data"))
        P("### %-18s literal hits: %s" % (nm, [hex(h) for h in hits]))
        for h in hits:
            for frm, t, cf, e in xrefs_to(h):
                P("      ref %#x %s in %s@%#x" % (frm, t, cf, e))

    P()
    P("=" * 25, "F. FUN_180008190 indirect compare + FUN_180130d10 + FUN_1801c3480", "=" * 25)
    for a in (0x180008190, 0x180130D10, 0x1801C3480):
        d = dec(a, timeout=600)
        P("### %#x len=%d" % (a, len(d)))
        P(d)
        P()

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