"""DIMENSION 2 Q1/Q3: slot->disp resolution + READER search per gate byte.

HYPOTHESIS: each publisher slot is an accessor stub `0f b6 81 <disp32> 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 <disp32> 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 <disp32> 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()
