"""D3 Q2: the itemState string<->enum functions, and who READS the state slots. ESTABLISHED BEFORE THIS QUERY (q_cd_state_1 + live read, both controls passed): the deser's stack struct base is local_188 (anchored by local_118 = &PTR_LAB_1801eaac0 landing at record+0x70, the known item vtable), so record_off = 0x188 - N. itemState (atom 0x172) -> FUN_180166660(str) -> record +0x5c (live: 1 == "free") owners (atom 0x207) -> record +0x48 u8 (live: 1) untradeable(atom 0x361)-> record +0x49 u8 = (untradeable == FALSE), a TRADEABLE flag (live: 0 on all 22) the enum table is at 0x180229cc0 (NOT 0x180229d20, which is mid-table): invalid 0, free 1, WAITING_FOR_GAME 2, inGame 2, forSale 5, offered 6, activeBadge 0x64, activeHomeKit 0x65, activeAwayKit 0x66, activeBall 0x67, activeStadium 0x68, active 0xff, terminator {NULL, 0xffffffff} HYPOTHESIS: FUN_180166660 walks that table and returns a default for no-match; there is a matching enum->string writer used when the client SENDS itemState back; and the UI gating for "list on transfer market" / "quick sell" reads +0x49 and/or +0x5c. CONTROL for the reader scan (same syntactic form as the target -- a memory operand with a small displacement): the scan must also find the KNOWN readers of +0x38/+0x3c (discardValue) and of +0x70 (the vtable). If displacement 0x38 or 0x70 comes back with zero hits the scan is broken and every absence below is void. OUTPUT: full decompiles (len printed), full table walk, full grouped reader census. """ import traceback, os OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/cards/" os.makedirs(OUT, exist_ok=True) STATE_VALUES = {0, 1, 2, 5, 6, 0x64, 0x65, 0x66, 0x67, 0x68, 0xff} def dump(tag, va): f = func(va) if f is None: print("%s %#x -> NO FUNCTION" % (tag, va)) return "" src = dec(va) print("=" * 78) print("%s %#x %s len(src)=%d (FULL)" % (tag, va, f.getName(), len(src))) print("=" * 78) print(src) with open(OUT + "q2_%s_%x.c" % (tag, va), "w") as fh: fh.write(src) return src try: print("###### A. string->enum helpers used by the item deser") for tag, va in (("itemState_0x172", 0x180166660), ("injuryType_0x168", 0x1801666f0), ("prefPos_0x24a", 0x180166810), ("resourceId_split_0x287", 0x180166ca0), ("playStyle_0x23f", 0x180136480)): dump(tag, va) print() print("###### B. every xref into the itemState table block 0x180229cc0..0x180229d80") for a in range(0x180229cc0, 0x180229d90, 8): xs = xrefs_to(a) for frm, typ, fn, ent in xs: print(" table%+#5x %#x xref from %#x %s in %s (%#x)" % (a - 0x180229cc0, a, frm, typ, fn, ent)) print() print("###### C. xrefs to each itemState string literal (finds the enum->string writer)") for name in (b"invalid\x00", b"free\x00", b"WAITING_FOR_GAME\x00", b"inGame\x00", b"forSale\x00", b"offered\x00", b"activeBadge\x00", b"activeHomeKit\x00", b"activeAwayKit\x00", b"activeBall\x00", b"activeStadium\x00", b"tradepile\x00", b"club\x00", b"trade\x00", b"pile\x00"): hits = find_all(name) print(" %-20s occurrences=%d" % (name.decode().strip("\x00"), len(hits))) for h in hits: for frm, typ, fn, ent in xrefs_to(h): print(" @%#x xref %#x %s in %s (%#x)" % (h, frm, typ, fn, ent)) print() print("###### D. callers of FUN_180166660 (every itemState parse site)") try: for c in callers(0x180166660): print(" caller", c) except Exception as e: print(" callers() failed:", e) print() print("###### E. reader census: instructions with memory displacement 0x49 / 0x5c") print(" CONTROLS in the same scan: 0x38 and 0x70") want = {0x49: [], 0x5c: [], 0x38: [], 0x70: []} total = 0 it = listing.getInstructions(True) while it.hasNext(): ins = it.next() total += 1 try: n = ins.getNumOperands() except Exception: continue for i in range(n): try: objs = ins.getOpObjects(i) except Exception: continue if len(objs) < 2: continue has_reg = any(hasattr(o, "getName") for o in objs) if not has_reg: continue for o in objs: v = None try: v = int(o.getValue()) except Exception: continue if v in want: f = fm.getFunctionContaining(ins.getAddress()) want[v].append((int(ins.getAddress().getOffset()), f.getName() if f else "?", int(f.getEntryPoint().getOffset()) if f else 0, str(ins))) print(" scanned %d instructions" % total) for d in (0x38, 0x70, 0x49, 0x5c): print(" disp %#04x -> %d hits (CONTROL)" % (d, len(want[d])) if d in (0x38, 0x70) else " disp %#04x -> %d hits" % (d, len(want[d]))) print() print("###### F. functions touching [reg+0x49], grouped (this displacement is rare)") from collections import defaultdict g = defaultdict(list) for a, fn, ent, txt in want[0x49]: g[(ent, fn)].append((a, txt)) for (ent, fn), lst in sorted(g.items()): print(" %s (%#x) n=%d" % (fn, ent, len(lst))) for a, txt in lst: print(" %#x %s" % (a, txt)) print() print("###### G. functions touching [reg+0x5c] that ALSO contain an itemState value") g2 = defaultdict(list) for a, fn, ent, txt in want[0x5c]: g2[(ent, fn)].append((a, txt)) print(" total distinct functions touching +0x5c: %d" % len(g2)) for (ent, fn), lst in sorted(g2.items()): if not ent: continue f = func(ent) if f is None: continue imms = set() it2 = listing.getInstructions(f.getBody(), True) while it2.hasNext(): ins = it2.next() for i in range(ins.getNumOperands()): for o in ins.getOpObjects(i): try: imms.add(int(o.getValue())) except Exception: pass hit = imms & {0x64, 0x65, 0x66, 0x67, 0x68, 0xff, 5, 6} if 0x64 in imms or 0x67 in imms or 0x68 in imms: print(" *** %s (%#x) n=%d state-ish immediates=%s" % (fn, ent, len(lst), sorted(hex(x) for x in hit))) for a, txt in lst: print(" %#x %s" % (a, txt)) except Exception: traceback.print_exc()