"""D3 run 6: how does a store response leave CardsDLL, and can the packed exe see the pack record at all? SETTLED: FutStoreGetPackTypesServerResponse is a 0x60-byte object; ctor 0x180123030 sets vtable 0x18021dd68 and an empty FUT Vector at +0x28/+0x30/+0x38 (allocator +0x40, "FUT Vector" tag +0x50, timestamp +0x5c). Its vtable has NO accessor: slot 0 and slot +0x40 are deleting destructors, +0x08 is the deserializer, the rest are the shared base-class slots also present on FutCreatePackServerResponse. So nothing in the class hands a pack record out. THIS RUN 1. The RPC descriptor row: find the data references to the factory 0x180123480 and to the command strings, and print the surrounding qwords, so the table that binds "STOREPACKTYPES" -> factory -> deserializer is visible. 2. The shared response virtuals (+0x20 0x180122420, +0x10/+0x18 0x18016cac0, +0x28 0x18016ca90, +0x38 0x18016c950, +0x48 0x18016bfc0, +0x58 0x18016ca60): is any of them a data accessor rather than plumbing? 3. CardsDLL EXPORT TABLE. If the packed exe reads pack quantities it must reach them through an export or through a pointer an export returned. Enumerate every export; that bounds the exe's reach. 4. Re-confirm the 100-element cap in 0x18013af30 from disassembly. CONTROL: the export enumeration must at minimum return the DLL's known entry points; an empty export list means the query is broken, not that the DLL exports nothing. """ 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) 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 try: print("### 1. DESCRIPTOR ROW FOR THE STORE RPC") for target, nm in ((0x180123480, "store factory"), (0x1801234e0, "store deser"), (0x18021f318, "\"STOREPACKTYPES\" string"), (0x18021de20, "RS4 name literal")): pat = target.to_bytes(8, "little") hits = find_all(pat, blocks=(".rdata", ".data")) print(" %s %#x embedded at: %s" % (nm, target, [hex(x) for x in hits])) for h in hits: lo = h - 0x40 print(" context qwords around %#x:" % h) for i in range(16): a = lo + i * 8 try: q = qword(a) except Exception: continue extra = "" if 0x1801e5000 <= q < 0x1802e0000: try: s = rd_str(q, 60) if s.isprintable() and len(s) > 2: extra = " \"%s\"" % s except Exception: pass if 0x180001000 <= q < 0x1801e5000: extra = " fn=%s" % fname(q) print(" %#x: %#018x%s%s" % (a, q, extra, " <== HIT" if a == h else "")) print("\n### 2. SHARED RESPONSE VIRTUALS") for va in (0x180122420, 0x18016cac0, 0x18016ca90, 0x18016ca40, 0x18016c950, 0x18016bfc0, 0x18016cb80, 0x18016ca60, 0x18016c110, 0x18016cb20): try: s = dump("shared virtual", va, OUT + "d3_sv_%x.txt" % va, echo=True) except Exception as e: print(" !! %#x %s" % (va, e)) print("\n### 3. EXPORT TABLE") st = prog.getSymbolTable() it = st.getExternalEntryPointIterator() n = 0 while it.hasNext(): a = it.next() syms = st.getSymbols(a) nms = [str(s.getName()) for s in syms] print(" %#x %s" % (int(a.getOffset()), nms)) n += 1 print(" total exported entry points: %d %s" % (n, "PASS" if n else "FAIL (query broken)")) print("\n### 4. THE 100-PACK CAP") f = func(0x18013af30) it2 = listing.getInstructions(f.getBody(), True) buf = [] while it2.hasNext(): i = it2.next() buf.append("%#x %s" % (int(i.getAddress().getOffset()), str(i))) for k, ln in enumerate(buf): if "0x64" in ln or "0x158" in ln: print(" ...") for j in range(max(0, k - 6), min(len(buf), k + 7)): print(" %s" % buf[j]) except Exception: traceback.print_exc() sys.stdout.flush()