"""D3 Q6: the item ACTION vocabulary (TO_TRADE_PILE / DISCARD ...) and every consumer of the tradeable flag; plus a precise search for code that compares item+0x5c against an itemState value. WHAT LED HERE. 'TO_TRADE_PILE' (0x1801f4d48) and 'DISCARD' (0x1801f4d28) are adjacent in .rdata and BOTH are referenced by the single function FUN_18003e370, which also manipulates a byte at +0x49 -- the tradeable flag's offset. That is the action-menu builder, i.e. the thing that greys entries out. CONTROL for the string window: the window must also contain other action names we can recognise as menu entries (not random data); if the neighbourhood is unreadable garbage the window is wrong and the identification is not made. CONTROL for the itemState-value scan: the same look-ahead machinery, pointed at displacement 0x3c with the constant 0, must rediscover the KNOWN discard guard `if ((int)local_150 == 0)`-style tests. If the look-ahead finds nothing anywhere the scan is broken and its silence about +0x5c means nothing. """ import traceback, os from collections import defaultdict OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/cards/" os.makedirs(OUT, exist_ok=True) STATES = {1, 2, 5, 6, 0x64, 0x65, 0x66, 0x67, 0x68, 0xff, 0xffffffff} 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 %s len(src)=%d (FULL)" % (tag, va, f.getName(), len(src))) print("=" * 78) if echo: print(src) with open(OUT + "q6_%s_%x.c" % (tag, va), "w") as fh: fh.write(src) return src try: print("###### A. the action-name neighbourhood in .rdata") a = 0x1801f4a00 while a < 0x1801f5200: s = rd_str(a, 120) if s and all(32 <= ord(c) < 127 for c in s): xs = xrefs_to(a) fns = sorted({(fn, e) for _, _, fn, e in xs if e}) print(" %#x %-46r xrefs=%d %s" % (a, s, len(xs), ["%s(%#x)" % (n, e) for n, e in fns][:5])) a += len(s) + 1 else: a += 1 print() print("###### B. the action-menu builder and the tradeable-flag consumers") for tag, va in (("actionmenu_18003e370", 0x18003e370), ("call_get49_1800bc580", 0x1800bc580), ("call_gate_1800e2a40", 0x1800e2a40), ("call_get48_1800d0600", 0x1800d0600), ("call_get48_1800e4fd0", 0x1800e4fd0)): dump(tag, va) print() print("###### C. precise scan: [reg+DISP] loaded then compared to a constant") print(" targets DISP=0x5c (itemState) and DISP=0x49 (tradeable)") print(" CONTROL DISP=0x3c and DISP=0x4c, which we know are compared") res = defaultdict(list) for DISP in (0x5c, 0x49, 0x3c, 0x4c): it = listing.getInstructions(True) window = [] while it.hasNext(): ins = it.next() window.append(ins) if len(window) > 12: window.pop(0) s0 = str(window[0]) if ("+ %#x]" % DISP) not in s0: continue if "RSP" in s0 or "RBP" in s0: continue mn = window[0].getMnemonicString().upper() if mn == "CMP": # direct compare with immediate try: v = int(window[0].getOpObjects(1)[0].getValue()) except Exception: v = None f = fm.getFunctionContaining(window[0].getAddress()) res[DISP].append((int(window[0].getAddress().getOffset()), f.getName() if f else "?", "DIRECT", v, s0)) continue if mn not in ("MOV", "MOVZX", "MOVSX", "MOVSXD"): continue try: dst = str(window[0].getOpObjects(0)[0]) except Exception: continue for nxt in window[1:]: sn = str(nxt) mn2 = nxt.getMnemonicString().upper() if mn2 in ("CMP", "SUB", "TEST") and dst.replace("R", "E") in sn.replace("R", "E"): try: v = int(nxt.getOpObjects(1)[0].getValue()) except Exception: v = None f = fm.getFunctionContaining(window[0].getAddress()) res[DISP].append((int(window[0].getAddress().getOffset()), f.getName() if f else "?", mn2, v, s0 + " ; " + sn)) break print(" DISP %#04x -> %d load/compare pairs" % (DISP, len(res[DISP]))) for DISP in (0x3c, 0x4c, 0x49, 0x5c): tag = "CONTROL" if DISP in (0x3c, 0x4c) else "TARGET" print(" --- %s DISP %#04x, constants seen: %s" % (tag, DISP, sorted({v for _, _, _, v, _ in res[DISP] if v is not None})[:40])) print() print(" --- every DISP 0x5c compare whose constant is an itemState value") for a, fn, kind, v, txt in res[0x5c]: if v in STATES: print(" %#x %-20s %s %s | %s" % (a, fn, kind, hex(v), txt)) print() print(" --- every DISP 0x49 compare") for a, fn, kind, v, txt in res[0x49]: print(" %#x %-20s %s %s | %s" % (a, fn, kind, v if v is None else hex(v), txt)) except Exception: traceback.print_exc()