"""D4 Q1e: many FutDataManagerImpl accessors are 8-byte leaf stubs that Ghidra never
turned into functions, so dec() returned nothing for them in q_pack_reveal_4. Decode
their bytes directly instead: `0f b6 81 <disp32> c3` = movzx eax,byte ptr [rcx+disp32].

Goal: the full slot -> gate-byte map for vtable 0x18021c2a0, and specifically which
slot (if any) returns byte 0x1fd45, the byte written from settings field [0x1d], which
is the packOpeningAnimationEnabled arm.

CONTROL: slot +0x2b0 must decode to 0x1fd3a and slot +0x2c8 to 0x1fd3d, because
FUN_18006cc60 calls exactly those two slots to publish IS_FRIENDLY_SEASON_ENABLED and
IS_DRAFT_MODE_ENABLED, and FUN_18011dc50 writes those two bytes from fields [0x16] and
[0x17], the two documented worked examples.

Then: xrefs to whichever stub returns 0x1fd45.
"""
import traceback, struct

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)


def stub_offset(t):
    """decode a leaf accessor stub -> (kind, byte offset) or (None, raw hex)."""
    b = read_bytes(t, 24)
    h = b.hex()
    # movzx eax, byte ptr [rcx+disp32] ; ret
    if b[0:3] == b"\x0f\xb6\x81" and b[7:8] == b"\xc3":
        return ("movzx byte", struct.unpack("<I", b[3:7])[0], h)
    # mov eax, dword ptr [rcx+disp32] ; ret
    if b[0:2] == b"\x8b\x81" and b[6:7] == b"\xc3":
        return ("mov dword", struct.unpack("<I", b[2:6])[0], h)
    # lea rax,[rcx+disp32] ; ret
    if b[0:3] == b"\x48\x8d\x81" and b[7:8] == b"\xc3":
        return ("lea", struct.unpack("<I", b[3:7])[0], h)
    # movzx eax, byte [rcx+disp8]
    if b[0:3] == b"\x0f\xb6\x41" and b[4:5] == b"\xc3":
        return ("movzx byte8", b[3], h)
    return (None, -1, h)


try:
    VT = 0x18021C2A0
    found = {}
    for off in range(0x00, 0x400, 8):
        try:
            t = qword(VT + off)
        except Exception:
            continue
        if not (0x180001000 <= t < 0x1801E5000):
            continue
        kind, o, h = stub_offset(t)
        if kind:
            p("  +%#05x -> %#x   %-12s field_byte=%#x" % (off, t, kind, o))
            found[off] = (t, kind, o)
        else:
            fn = fm.getFunctionAt(addr(t))
            p("  +%#05x -> %#x   NOT-A-STUB %s bytes=%s" % (off, t, fn.getName() if fn else "?", h[:32]))

    p("=== CONTROL CHECK ===")
    for slot, want, name in ((0x2B0, 0x1FD3A, "IS_FRIENDLY_SEASON_ENABLED"),
                             (0x2C8, 0x1FD3D, "IS_DRAFT_MODE_ENABLED")):
        got = found.get(slot, (0, "?", -1))[2]
        p("  slot %#x expect %#x got %#x  %s   %s" %
          (slot, want, got, "PASS" if got == want else "FAIL", name))

    p("=== slots returning the settings gate bytes 0x1fd2c..0x1fd48 ===")
    for off, (t, kind, o) in sorted(found.items()):
        if 0x1FD00 <= o <= 0x1FD70:
            p("  slot +%#05x  stub %#x  byte %#x" % (off, t, o))

    p("=== who reads 0x1fd45 ? ===")
    hits = [(off, t) for off, (t, k, o) in found.items() if o == 0x1FD45]
    p("  stubs returning 0x1fd45: %s" % [(hex(a), hex(b)) for a, b in hits])
    for off, t in hits:
        xs = xrefs_to(t)
        p("  xrefs to stub %#x : %d" % (t, len(xs)))
        for frm, typ, fn, ent in xs:
            p("     from %#x %s in %s @ %#x" % (frm, typ, fn, ent))
    # also: xrefs to the vtable slot address itself (indirect call sites are in the
    # packed exe, so expect few/none)
    for off, t in hits:
        xs = xrefs_to(VT + off)
        p("  xrefs to vtable slot %#x : %d -> %s" % (VT + off, len(xs), xs[:10]))

except Exception:
    traceback.print_exc()
finally:
    with open(OUT + "d4_fdm_stubs.txt", "w") as f:
        f.write("\n".join(BUF))
    print("WROTE d4_fdm_stubs.txt")
