"""D3 run 9: CORRECTION RUN. There IS a reader, and I nearly missed it. WHAT WENT WRONG IN RUNS 3-8. I triaged the disp32 scan by ADDRESS BAND, treating everything below ~0x180100000 as "engine noise", and on that basis dismissed FUN_18002c3c0. It is in fact a pack-record -> view-model adapter that reads all five packContentInfo slots, plus `start` (+0xb4) and `unopened` (+0xcd). Address band is not evidence. This run replaces the band heuristic with a FIELD FINGERPRINT. FINGERPRINT. The pack record's distinctive, disp32-encodable field offsets are 0xb0 state, 0xb4 start, 0xbc quantity, 0xc0, 0xc4, 0xc8 saleType, 0xcc useDefaultImage, 0xcd unopened, 0xce isPremium, 0xcf dealType-free, 0xd0 dealType-promo, 0x138 visible, 0x13c bonus, 0x140, 0x144 itemQuantity, 0x148 gold, 0x14c silver, 0x150 bronze, 0x154 rare. Any unrelated struct may collide on one or two of these. Colliding on five or more, especially on the tight run 0xcd/0xce/0xcf/0xd0, is not chance. CONTROLS. The known-good members must score at the top: 0x1801340e0 (copy-assign), 0x180133210 (uninitialised_copy), 0x18002c3c0 (the adapter just found). 0x180133af0 (stride 0x168) and 0x180134b50 (extends to +0x163) must NOT, since their strides prove they are other classes. THEN follow the view model FUN_18002c3c0 builds: its callers, FUN_18002cc90 which it tail-calls, and every reader of the view model's copies of the quantities at vm+0xc0/0xc4/0xc8/0xcc/0xd0, to see whether any of them counts an item list. """ import traceback, sys, os OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/packres/" os.makedirs(OUT, exist_ok=True) FP = [0x0b0, 0x0b4, 0x0bc, 0x0c0, 0x0c4, 0x0c8, 0x0cc, 0x0cd, 0x0ce, 0x0cf, 0x0d0, 0x138, 0x13c, 0x140, 0x144, 0x148, 0x14c, 0x150, 0x154] TIGHT = [0x0cd, 0x0ce, 0x0cf, 0x0d0, 0x13c, 0x144, 0x14c, 0x154] VM = [0x0c0, 0x0c4, 0x0c8, 0x0cc, 0x0d0, 0x084, 0x0b0, 0x0b5, 0x0b6, 0x0b7, 0x0b8] def dump(tag, va, path, echo=True): src = dec(va) hdr = "%s %#x fname=%s len(src)=%d (FULL, NOT TRUNCATED)" % ( tag, va, fname(va), len(src)) if echo: print("=" * 78) print(hdr) print("=" * 78) print(src) with open(path, "w") as fh: fh.write("// " + hdr + "\n" + src) return src def scan_mem(val): pat = bytes([val & 0xFF, (val >> 8) & 0xFF, (val >> 16) & 0xFF, (val >> 24) & 0xFF]) tag = "+ %#x]" % val out = {} seen = set() for h in find_all(pat, blocks=(".text",)): ins = None for back in range(0, 12): i2 = listing.getInstructionContaining(addr(h - back)) if i2 is not None: ins = i2 break if ins is None: continue a = int(ins.getAddress().getOffset()) if a in seen: continue seen.add(a) t = str(ins) if tag not in t: continue f = fm.getFunctionContaining(ins.getAddress()) if f is None or f.getName().startswith("Unwind@"): continue out.setdefault(int(f.getEntryPoint().getOffset()), []).append((a, t)) return out try: print("### 1. PACK-RECORD FIELD FINGERPRINT OVER ALL OF .text") hits = {} for off in FP: for k in scan_mem(off): hits.setdefault(k, set()).add(off) ranked = sorted(hits.items(), key=lambda kv: -len(kv[1])) print(" functions scoring >=5 fingerprint offsets:") strong = [] for k, offs in ranked: if len(offs) < 5: break t = sorted(o for o in offs if o in TIGHT) print(" %#x %-22s score=%2d tight=%d %s" % (k, fname(k), len(offs), len(t), [hex(x) for x in sorted(offs)])) strong.append(k) print("\n CONTROLS: 0x1801340e0 in=%s 0x180133210 in=%s 0x18002c3c0 in=%s" " | must NOT be strong: 0x180133af0 in=%s 0x180134b50 in=%s" % (0x1801340e0 in strong, 0x180133210 in strong, 0x18002c3c0 in strong, 0x180133af0 in strong, 0x180134b50 in strong)) print("\n full decompile of every strong function not already understood:") KNOWN = {0x1801340e0, 0x180133210, 0x1801342d0, 0x18013af30, 0x18002c3c0} for k in strong: if k in KNOWN: continue dump("STRONG FINGERPRINT", k, OUT + "d3_fp_%x.txt" % k, echo=True) print("\n### 2. THE ADAPTER AND ITS VIEW MODEL") print(" callers of adapter 0x18002c3c0: %s" % [(hex(a), n) for a, n in callers(0x18002c3c0)]) for a, n in callers(0x18002c3c0): dump("ADAPTER CALLER", a, OUT + "d3_ad_caller_%x.txt" % a, echo=True) dump("FUN_18002cc90 (tail call from adapter)", 0x18002cc90, OUT + "d3_vm_18002cc90.txt", echo=True) print(" callers of 0x18002cc90: %s" % [(hex(a), n) for a, n in callers(0x18002cc90)]) print("\n### 3. VIEW-MODEL QUANTITY READERS (vm+0xc0..0xd0)") vmhits = {} for off in (0x0c0, 0x0c4, 0x0c8, 0x0cc, 0x0d0): for k, v in scan_mem(off).items(): vmhits.setdefault(k, {})[off] = v cands = [(k, o) for k, o in vmhits.items() if len(o) >= 4] print(" functions reading >=4 of vm+0xc0..0xd0: %d" % len(cands)) for k, o in sorted(cands): print(" %#x %-22s %s" % (k, fname(k), [hex(x) for x in sorted(o)])) print("\n decompiles:") for k, o in sorted(cands): if k in KNOWN: continue dump("VM QTY READER", k, OUT + "d3_vm_%x.txt" % k, echo=True) except Exception: traceback.print_exc() sys.stdout.flush()