"""ADVERSARIAL BATCH 2. MAIN ATTACK: the claim "cardtype 9 has NO resolver at all, so ball and leaguelogo display strings must come off the wire (localizedName + description)". That claim CHANGES WHAT WE SERVE, so it is priority 1. Counter-evidence to chase: .rdata at 0x1802041d0 holds 'fcc_leaguelogos' and 0x1802041e0 holds 'LeagueName_Abbr_15_%d', sitting immediately beside 'FUT_UC_KITS' (0x180204180) which IS a resolver literal. If some function formats LeagueName_Abbr_15_%d for a league logo, the "must come off the wire" claim is wrong. H6 vtable+0x490 = FUN_18011a860 is a GENERIC name resolver taking (cardtype@+0x4c, cardsubtypeid@+0x50, resourceId@+0x18). Does it have a cardtype-9 arm? H7 'fcc_leaguelogos' / 'LeagueName_Abbr_15_%d' are referenced by some function. H8 FUN_18012ee20 has EXACTLY ONE caller (the club URL builder). [absence claim] H9 FUN_1800fed90 is the ONLY function whose switch case set is exactly {0x91..0x96}. [absence claim -- re-tested here by a DIFFERENT method than the original caseD_ symbol enumeration: I enumerate switch tables from the instruction/flow side via getBasicBlocks + scalar operands, AND repeat the symbol method, and compare the two.] H10 FUN_180141660 (the merge) is called on every deserialized item. CONTROL for the xref questions: 'FUT_UC_KITS' at 0x180204180 MUST come back with >=1 referencing function (we already know FUN_180119bd0 uses it). If the xref method returns 0 for FUT_UC_KITS the method is broken and every negative is void. Same syntactic form (a .rdata string address referenced by a LEA) as the targets. """ import traceback, os OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/cards/adv" try: print("=== CONTROL + targets: xrefs to .rdata string addresses ===") STRS = { "FUT_UC_KITS (CONTROL)": 0x180204180, "FUT_UC_BALL": 0x180239120, "fcc_leaguelogos": 0x1802041d0, "LeagueName_Abbr_15_%d": 0x1802041e0, "leagues": 0x1802041b0, "Badge (0x1802041b8)": 0x1802041b8, "countryid": 0x1802041c0, "fcc_myclubs": 0x180204190, "TeamName_Abbr15_%d?": None, } for name, a in STRS.items(): if a is None: continue try: xs = xrefs_to(a) except Exception as e: print(" %-24s XREF ERROR %s" % (name, e)); continue fns = sorted(set((x[2], x[3]) for x in xs)) print(" %-24s 0x%x %d refs, funcs: %s" % (name, a, len(xs), ["%s@0x%x" % (n, e) for n, e in fns])) print() print("=== find TeamName_Abbr15_%d and StadiumName_%d addresses then xref ===") for lit in (b"TeamName_Abbr15_%d\x00", b"StadiumName_%d\x00", b"LeagueName_Abbr_15_%d\x00", b"fcc_leaguelogos\x00", b"fcc_balls\x00", b"fcc_stadium\x00", b"fcc_badgecards\x00", b"fcc_kitcards\x00", b"fcc_misccards\x00"): hits = find_all(lit, blocks=(".rdata", ".data", ".text")) print(" %-26s %d hit(s) at %s" % (lit.rstrip(b"\x00").decode(), len(hits), [hex(h) for h in hits])) for h in hits: xs = xrefs_to(h) fns = sorted(set((x[2], x[3]) for x in xs)) print(" -> %d refs: %s" % (len(xs), ["%s@0x%x" % (n, e) for n, e in fns])) print() print("=== H6: generic resolver FUN_18011a860 (vtable +0x490) FULL ===") src = dec(0x18011a860) open(os.path.join(OUT, "FUN_18011a860.c"), "w").write(src) print("len=%d" % len(src)) print(src) print() print("=== H8: callers of FUN_18012ee20 (itemState code -> atom) ===") for fa in (0x18012ee20, 0x180141660, 0x180166660, 0x1800fed90): try: cs = callers(fa) except Exception: cs = [(x[0], x[2], x[3]) for x in xrefs_to(fa)] print(" FUN_%x callers: %s" % (fa, cs)) print() print("=== H9: switch case-set enumeration, TWO methods ===") st = prog.getSymbolTable() # method 1: caseD_ symbols grouped by containing function import collections bysym = collections.defaultdict(set) n = 0 for sym in st.getAllSymbols(True): nm = str(sym.getName()) if not nm.startswith("caseD_"): continue n += 1 a2 = sym.getAddress() f = fm.getFunctionContaining(a2) if f is None: continue try: v = int(nm.split("_")[-1], 16) except ValueError: continue bysym[int(f.getEntryPoint().getOffset())].add(v) print(" method1: %d caseD_ symbols over %d functions" % (n, len(bysym))) TARGET = set(range(0x91, 0x97)) exact = [hex(k) for k, v in bysym.items() if v == TARGET] superset = [hex(k) for k, v in bysym.items() if TARGET <= v and v != TARGET] overlap = [hex(k) for k, v in bysym.items() if (TARGET & v) and not (TARGET <= v)] print(" functions with case set EXACTLY {0x91..0x96}: %s" % exact) print(" functions whose case set is a SUPERSET: %s" % superset) print(" functions with PARTIAL overlap: %s" % overlap) print(" CONTROL FUN_1800d8330 present in method1? %s -> %s" % (0x1800d8330 in bysym, sorted(hex(x) for x in bysym.get(0x1800d8330, [])))) print() print("=== H10: callers of the merge FUN_180141660 ===") xs = xrefs_to(0x180141660) print(" %d refs: %s" % (len(xs), sorted(set("%s@0x%x" % (x[2], x[3]) for x in xs)))) except Exception: traceback.print_exc()