"""D3 Q1: the itemState vocabulary and the lifecycle-field arms in the shared item deser. HYPOTHESIS: FUN_18013fe00 (shared ITEM element deser) has an arm for atom 0x172 (itemState) that reads a STR and maps it through the enum table documented at 0x180229d20 (stride 0x10: WAITING_FOR_GAME, inGame, forSale, offered, activeBadge, activeHomeKit, activeAwayKit, activeBall, activeStadium, active). That documented list omits "free", which we send on every card, so either the table is longer than recorded or "free" is the default/no-match value. Also locate the arms for pile 0x226, pileType 0x228, owners 0x207, untradeable 0x361, untradeableCount 0x362, tradeId 0x331, loans 0x19b, itemLoans 0x16f, duplicateItemLoans 0xed, tradeState 0x335, contract 0xa9?(unknown, resolved from tsv below). CONTROL (same syntactic form as the target): the item deser is known to store our discardValue (atom 0xd7) at item+0x38. If the immediate-scan below does not find 0xd7 in FUN_18013fe00 in the SAME form (case label / cmp / ladder) then the scan is broken and every absence claim in this batch is void. Second control: 0x172 itself appears in docs as reaching the enum table, so xrefs_to(0x180229d20) must be non-empty. ABSENCE TRAP GUARD: we do not grep "== 0x". We enumerate every immediate operand of every instruction in the function (scalar operands of any size), which catches `cmp ==`, `cmp !=`, jump-table `case` labels only indirectly, and running-sum sub/dec ladders (the ladder deltas are computed and searched too). Jump tables are handled separately by walking every switch construct Ghidra knows about. OUTPUT: full decompiles printed untruncated with len(src); full table dump; full immediate 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) ATOMS = { 0x172: "itemState", 0x226: "pile", 0x227: "pileSizeClientData", 0x228: "pileType", 0x207: "owners", 0x361: "untradeable", 0x362: "untradeableCount", 0x331: "tradeId", 0x332: "tradepile", 0x333: "tradePile", 0x19b: "loans", 0x16f: "itemLoans", 0xed: "duplicateItemLoans", 0x335: "tradeState", 0xd7: "discardValue(CONTROL)", 0x19: "allowUntradeableForSquadBuildingSets", } def dump(tag, va, echo=True): f = func(va) if f is None: print("%s %#x -> NO FUNCTION" % (tag, va)) return "" src = dec(va) print("=" * 78) print("%s %#x fname=%s len(src)=%d (FULL, NOT TRUNCATED)" % (tag, va, f.getName(), len(src))) print("=" * 78) if echo: print(src) with open(OUT + "q1_%s_%x.c" % (tag, va), "w") as fh: fh.write(src) return src def immediates(va): """Every scalar operand of every instruction in the function containing va.""" f = func(va) out = {} if f is None: return out body = f.getBody() it = listing.getInstructions(body, True) n = 0 while it.hasNext(): ins = it.next() n += 1 for i in range(ins.getNumOperands()): for o in ins.getOpObjects(i): try: v = int(o.getValue()) except Exception: continue out.setdefault(v & 0xFFFFFFFFFFFFFFFF, []).append( (int(ins.getAddress().getOffset()), str(ins))) print("[immediates] %s %#x: %d instructions, %d distinct scalars" % (f.getName(), va, n, len(out))) return out def report_atoms(va, imms): """Direct hits + running-sum ladder reconstruction.""" print("--- atom census for %#x (%s)" % (va, fname(va))) for a, name in sorted(ATOMS.items()): hits = imms.get(a, []) if hits: print(" DIRECT 0x%-4x %-38s x%d first@%#x %s" % (a, name, len(hits), hits[0][0], hits[0][1])) # ladder reconstruction: walk instructions in address order, keep a running # sum of sub/dec/add immediates on the dispatch register, report any partial # sum that equals one of our atoms. f = func(va) if f is None: return it = listing.getInstructions(f.getBody(), True) running = {} while it.hasNext(): ins = it.next() m = ins.getMnemonicString().lower() if m not in ("sub", "add", "dec", "inc", "cmp", "lea"): continue try: reg = str(ins.getOpObjects(0)[0]) except Exception: continue val = None for o in ins.getOpObjects(1) if ins.getNumOperands() > 1 else []: try: val = int(o.getValue()) except Exception: pass if m == "dec": val = 1 if m == "inc": val = -1 if val is None: continue if m in ("sub", "dec"): running[reg] = running.get(reg, 0) + val elif m in ("add", "inc"): running[reg] = running.get(reg, 0) - val cur = running.get(reg) if cur in ATOMS: print(" LADDER 0x%-4x %-38s @%#x %s (running sum on %s)" % (cur, ATOMS[cur], int(ins.getAddress().getOffset()), ins, reg)) def switch_labels(va): """Every switch case label Ghidra resolved inside the function.""" f = func(va) if f is None: return from ghidra.program.model.symbol import FlowType # noqa it = listing.getInstructions(f.getBody(), True) tot = 0 while it.hasNext(): ins = it.next() ft = ins.getFlowType() if ft is not None and ft.isJump() and ft.isComputed(): tgts = ins.getFlows() print(" SWITCH @%#x %s -> %d targets" % (int(ins.getAddress().getOffset()), ins, len(tgts))) tot += len(tgts) if tot: print(" (computed-jump targets total %d)" % tot) try: print("###### PART A: the itemState enum table at 0x180229d20") # walk generously in both directions; entries are (char* name, ...) stride 0x10 base = 0x180229d20 for off in range(-0x200, 0x400, 0x10): a = base + off try: p = qword(a) q = qword(a + 8) except Exception: continue s = "" if 0x180000000 <= p < 0x181000000: try: s = rd_str(p, 64) except Exception: s = "" print(" %#x (%+#5x) p=%#018x q=%#018x str=%r" % (a, off, p, q, s)) print() print("###### PART B: who references the table") for t in (0x180229d20,): for frm, typ, fn, ent in xrefs_to(t): print(" xref %#x %s in %s (%#x)" % (frm, typ, fn, ent)) print() print("###### PART C: shared item deser FUN_18013fe00") src = dump("itemdeser", 0x18013fe00) imms = immediates(0x18013fe00) report_atoms(0x18013fe00, imms) switch_labels(0x18013fe00) print() print("###### PART D: full sorted immediate dump for 0x18013fe00 (values < 0x1000)") for v in sorted(k for k in imms if k < 0x1000): print(" 0x%-4x n=%-3d %s" % (v, len(imms[v]), imms[v][0][1])) print() print("###### PART E: callees of the item deser") try: for c in callees(0x18013fe00): print(" callee", c) except Exception as e: print(" callees() failed:", e) except Exception: traceback.print_exc()