"""D3 Q8: close out the lifecycle table -- is forSale(5)/offered(6) ever tested, and what is the item-record pile (+0x60) vocabulary? ESTABLISHED SO FAR. itemState lands at item+0x5c; 0x64..0x68 publish the Flash boolean IS_ACTIVE (three independent publishers agree, one of them as the range test `state - 100 < 5`); the equip path FUN_180113870 writes 1 back into +0x5c when it unequips and 0x67 when it equips; the squad code FUN_1801b3640 accepts state 1 and state 2. The earlier precise scan reported the constants compared against [reg+0x5c] anywhere in the DLL as {-1,0,1,2,3,100,101,102,103,104} -- 5 and 6 absent -- but that population mixes several unrelated structs, so this query re-runs it printing EVERY hit with its function, and adds the `SUB/DEC ladder` and `switch jump table` forms that a plain compare scan cannot see. CONTROL: the same three forms, pointed at [reg+0x60], must rediscover the values we have already measured live in the item record (1 for club items, 6 for purchased) and the value 4 we just read in FUN_1801c3480. If 1/4/6 do not come out of the scan it is not seeing item-record pile tests and its silence proves 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) def dump(tag, va, full=7000): 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" % (tag, va, f.getName(), len(src))) print("=" * 78) with open(OUT + "q8_%s_%x.c" % (tag, va), "w") as fh: fh.write(src) print(src if len(src) <= full else " [long; written to file]") return src try: for DISP in (0x5c, 0x60): print() print("###### displacement %#x : every compare / ladder / jump-table dispatch" % DISP) it = listing.getInstructions(True) window = [] seen = [] while it.hasNext(): ins = it.next() window.append(ins) if len(window) > 16: window.pop(0) s0 = str(window[0]) if ("+ %#x]" % DISP) not in s0 or "RSP" in s0 or "RBP" in s0: continue mn = window[0].getMnemonicString().upper() f = fm.getFunctionContaining(window[0].getAddress()) fn = f.getName() if f else "?" a0 = int(window[0].getAddress().getOffset()) if mn == "CMP": try: v = int(window[0].getOpObjects(1)[0].getValue()) except Exception: v = None seen.append((a0, fn, "CMP", v, s0)) continue if mn not in ("MOV", "MOVZX", "MOVSX", "MOVSXD"): continue try: dst = str(window[0].getOpObjects(0)[0]) except Exception: continue key = dst.replace("R", "E") run = 0 for nxt in window[1:]: sn = str(nxt) m2 = nxt.getMnemonicString().upper() if key not in sn.replace("R", "E"): continue if m2 in ("CMP", "TEST"): try: v = int(nxt.getOpObjects(1)[0].getValue()) except Exception: v = None seen.append((a0, fn, m2, v, s0 + " ; " + sn)) break if m2 in ("SUB", "DEC", "ADD", "INC"): try: d = 1 if m2 == "DEC" else (-1 if m2 == "INC" else int(nxt.getOpObjects(1)[0].getValue())) except Exception: break run += d if m2 in ("SUB", "DEC") else -d seen.append((a0, fn, "LADDER@%d" % run, run, s0 + " ; " + sn)) continue if m2 == "JMP": seen.append((a0, fn, "JUMPTABLE", None, s0 + " ; " + sn)) break print(" %d hits" % len(seen)) by_fn = defaultdict(list) for a0, fn, kind, v, txt in seen: by_fn[fn].append((a0, kind, v, txt)) for fn in sorted(by_fn): vals = sorted({v for _, _, v, _ in by_fn[fn] if v is not None}) print(" %-22s n=%-3d constants=%s" % (fn, len(by_fn[fn]), vals)) allv = sorted({v for _, _, _, v, _ in seen if v is not None}) print(" ALL CONSTANTS for disp %#x: %s" % (DISP, allv)) if DISP == 0x60: print(" CONTROL -- are the live-measured pile values 1, 4 and 6 present? %s" % {k: (k in allv) for k in (1, 4, 6)}) if DISP == 0x5c: print(" are forSale(5) and offered(6) present? %s" % {k: (k in allv) for k in (5, 6)}) for a0, fn, kind, v, txt in seen: if v in (5, 6, 3, 0xff, -1): print(" %#x %-22s %s %s | %s" % (a0, fn, kind, v, txt)) print() print("###### the remaining itemState readers") for tag, va in (("s_18011dc50", 0x18011dc50), ("s_180094ae0", 0x180094ae0)): dump(tag, va) except Exception: traceback.print_exc()