"""D4 Q1c/Q2/Q3: (a) map FutDataManagerImpl vtable slots to the settings gate bytes so we can tell whether byte 0x1fd45 (packOpeningAnimationEnabled) has an accessor at all, and (b) open the reveal path via the USE_ANIMATION_STYLE / gmLoadFUTPackOpenSublevel / CREATE_PACK_STATUS strings. HYPOTHESIS: FutDataManagerImpl publishes one bool accessor per settings gate byte in a contiguous vtable band; the publisher FUN_18006cc60 uses slots 0x270..0x2f0. If a slot returns [this+0x1fd45] then packOpeningAnimationEnabled is readable, and its call sites tell us what it gates. CONTROLS: slot +0x2b0 MUST decompile to a read of 0x1fd3a (IS_FRIENDLY_SEASON_ENABLED) and slot +0x2c8 MUST read 0x1fd3d (IS_DRAFT_MODE_ENABLED). Those two are already proven by FUN_18006cc60's string arguments. If the vtable I pick does not reproduce them, I have the wrong vtable and every other slot reading is worthless. """ import traceback OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/packres/" BUF = [] def p(*a): s = " ".join(str(x) for x in a) print(s) BUF.append(s) try: # ---- ctor, to find the vtable ---- ct = dec(0x18010CDC0, 300) p("=== ctor FUN_18010cdc0 len=%d ; first 1200 chars ===" % len(ct)) p(ct[:1200]) with open(OUT + "d4_fdm_ctor.txt", "w") as f: f.write(ct) # candidate vtables: any .rdata address referenced by the ctor whose first # two qwords are functions cands = [] f0 = func(0x18010CDC0) for ad in f0.getBody().getAddresses(True): ins = listing.getInstructionAt(ad) if ins is None: continue for r in ins.getReferencesFrom(): t = int(r.getToAddress().getOffset()) if 0x1801E5000 <= t <= 0x1802891FF: try: v0, v1 = qword(t), qword(t + 8) except Exception: continue if fm.getFunctionAt(addr(v0)) and fm.getFunctionAt(addr(v1)): if t not in cands: cands.append(t) p("=== vtable candidates from ctor: %s ===" % [hex(c) for c in cands]) for vt in cands: try: s2b0 = qword(vt + 0x2B0) s2c8 = qword(vt + 0x2C8) except Exception: continue if not (fm.getFunctionAt(addr(s2b0)) and fm.getFunctionAt(addr(s2c8))): continue d2b0 = dec(s2b0, 120) d2c8 = dec(s2c8, 120) ok = ("1fd3a" in d2b0.lower()) and ("1fd3d" in d2c8.lower()) p("--- vtable %#x : slot2b0=%#x slot2c8=%#x CONTROL_OK=%s ---" % (vt, s2b0, s2c8, ok)) p(" slot 0x2b0 body: %s" % d2b0.replace("\n", " ")[:300]) p(" slot 0x2c8 body: %s" % d2c8.replace("\n", " ")[:300]) if not ok: continue p("=== CONTROL PASSED for vtable %#x ; dumping slots 0x250..0x320 ===" % vt) for off in range(0x250, 0x328, 8): try: t = qword(vt + off) except Exception: break fn = fm.getFunctionAt(addr(t)) if fn is None: p(" +%#05x %#x (not a function)" % (off, t)) continue body = dec(t, 120).replace("\n", " ") # squeeze body = " ".join(body.split()) p(" +%#05x %#x %s :: %s" % (off, t, fn.getName(), body[:260])) # ---- reveal-path strings ---- for sname, sva in (("USE_ANIMATION_STYLE", 0x1801FD580), ("gmLoadFUTPackOpenSublevel", 0x1801EE860), ("gmUnloadFUTPackOpenAnimation", 0x180208828), ("CREATE_PACK_STATUS", 0x180205F28), ("PACK_CREATE_UNOPENED_PACK", 0x1801EC1B8), ("NUM_RARES_IN_PACK", 0x1801EBF90)): xs = xrefs_to(sva) p("=== xrefs to %s (%#x): %d ===" % (sname, sva, len(xs))) for frm, typ, fn, ent in xs: p(" from %#x %s in %s @ %#x" % (frm, typ, fn, ent)) except Exception: traceback.print_exc() finally: try: with open(OUT + "d4_vtable_and_xrefs.txt", "w") as f: f.write("\n".join(BUF)) print("WROTE d4_vtable_and_xrefs.txt") except Exception: traceback.print_exc()