"""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()