"""DIMENSION 2 Q1/Q3: slot->disp resolution + READER search per gate byte. HYPOTHESIS: each publisher slot is an accessor stub `0f b6 81 c3` (movzx eax,byte[rcx+disp]; ret) at model vtable 0x18021c2a0. For the refusing modes (season/draft/tournament), the ONLY reader of the gate byte is the publisher FUN_18006cc60, which hands the value to the script layer -- i.e. no native mode gate. CONTROL: slot 0x270 must decode to disp 0x1fd2e (trading), already proven by two prior docs. Reader scan must find FUN_18011dc50 (applier, WRITES 0x1fd2e) and FUN_1801a7260 (TO_TRADE_PILE predicate, READS 0x1fd2e) among the disp-32 hits for 0x1fd2e -- both known, so if either is missing the scan form is wrong. """ import traceback try: MODEL_VT = 0x18021c2a0 slots = { 0x270: "IS_TRADING_ENABLED", 0x280: "IS_STORE_ENABLED", 0x2b0: "IS_FRIENDLY_SEASON_ENABLED", 0x2b8: "IS_TOURNAMENT_QUIT_ENABLED", 0x2c0: "IS_PROCESSING_STATE_ENABLED", 0x2c8: "IS_DRAFT_MODE_ENABLED", 0x2d8: "IS_STORY_MODE_REWARD_ENABLED", 0x2f0: "IS_RETURNING_USER_REWARDS_SCREEN_ENABLED", } print("=" * 70) print("SLOT -> accessor -> displacement (model offset)") print("=" * 70) disp_by_name = {} for slot in sorted(slots): tgt = qword(MODEL_VT + slot) stub = read_bytes(tgt, 8) disp = None # 0f b6 81 c3 -> movzx eax, byte [rcx+disp32] ; ret if stub[0:3] == b"\x0f\xb6\x81" and stub[7] == 0xc3: disp = int.from_bytes(stub[3:7], "little") # 8b 81 c3 -> mov eax, [rcx+disp32] ; ret (int getter, 4-byte) elif stub[0:2] == b"\x8b\x81" and stub[6] == 0xc3: disp = int.from_bytes(stub[2:6], "little") name = slots[slot] disp_by_name[name] = disp print("slot +%#05x %-42s -> %#011x stub=%s disp=%s" % (slot, name, tgt, stub.hex(), ("%#x" % disp) if disp is not None else "??")) print() print("=" * 70) print("READERS: .text hits for each displacement (raw disp32 LE, form-agnostic)") print("catches movzx/mov/cmp/lea/setcc in every encoding") print("=" * 70) for name, disp in disp_by_name.items(): if disp is None: continue pat = disp.to_bytes(4, "little") hits = find_all(pat, blocks=(".text",)) print("\n%-42s disp %#x (%d hit(s))" % (name, disp, len(hits))) for h in hits: f = fm.getFunctionContaining(addr(h)) fn = f.getName() if f else "?" ent = int(f.getEntryPoint().getOffset()) if f else 0 ins = listing.getInstructionAt(addr(h - 3)) or listing.getInstructionAt(addr(h - 2)) or listing.getInstructionAt(addr(h)) print(" %#011x in %-16s (%#x) ins~ %s" % (h, fn, ent, str(ins) if ins else "?")) print() print("=" * 70) print("READERS via vtable slot call: .text scan for call [reg+slot] (ff /2 disp32)") print("=" * 70) # FF /2 with mod=10 (disp32): modrm 0x90..0x97 (rax..rdi), 0x94 needs SIB call_modrm = [0x90, 0x91, 0x92, 0x93, 0x95, 0x96, 0x97] for slot in sorted(slots): pat_disp = slot.to_bytes(4, "little") found = [] for mrm in call_modrm: pat = bytes([0xff, mrm]) + pat_disp for h in find_all(pat, blocks=(".text",)): f = fm.getFunctionContaining(addr(h)) found.append((h, f.getName() if f else "?", int(f.getEntryPoint().getOffset()) if f else 0)) # also REX.W/B variants (41 ff /2, 48/49 not valid for call reg-indirect but include 41) for rex in (0x41,): for mrm in [0x90, 0x91, 0x92, 0x93, 0x95, 0x96, 0x97]: pat = bytes([rex, 0xff, mrm]) + pat_disp for h in find_all(pat, blocks=(".text",)): f = fm.getFunctionContaining(addr(h)) found.append((h, f.getName() if f else "?", int(f.getEntryPoint().getOffset()) if f else 0)) print("\nslot +%#05x %-42s (%d call-site(s))" % (slot, slots[slot], len(found))) for h, fn, ent in found: print(" %#011x in %-16s (%#x)" % (h, fn, ent)) except Exception: traceback.print_exc()