"""Q3: find the common FUT request-dispatch path and any precondition that can make the
client refuse to send. Market builder FUN_180162c90 sits at 0x180214dd8 and seasons
builder FUN_180175bd0 at 0x18022d748; both are vtable members. Identify each vtable
(start + RS4 class name nearby), decode enough slots to see the shape, and find the
call sites of the slot index they occupy.

CONTROL: the model vtable 0x18021c2a0 with slot +0x988 = FUN_18011dc50 is a KNOWN,
already-verified vtable/slot pair. The same routine is applied to it, and it must
reproduce +0x988; otherwise the vtable-start detection is wrong.
"""
import struct, traceback

try:
    blocks = {}
    for b in mem.getBlocks():
        blocks[str(b.getName())] = (int(b.getStart().getOffset()), int(b.getEnd().getOffset()))

    def blk(name):
        s, e = blocks[name]
        out = b""
        a = s
        while a <= e:
            n = min(1 << 20, e - a + 1)
            out += read_bytes(a, n)
            a += n
        return s, out

    ts, text = blk(".text")
    rs, rdata = blk(".rdata")

    def is_code(v):
        return 0x180001000 <= v < 0x1801e5000

    def vtable_start(ptr_site):
        """Walk back while the preceding qword is also a .text pointer."""
        a = ptr_site
        while a - 8 >= rs:
            v = struct.unpack_from("<Q", rdata, a - 8 - rs)[0]
            if not is_code(v):
                break
            a -= 8
        return a

    def vtable_len(start):
        n = 0
        a = start
        while a - rs + 8 <= len(rdata):
            v = struct.unpack_from("<Q", rdata, a - rs)[0]
            if not is_code(v):
                break
            n += 1
            a += 8
        return n

    def rtti_name_near(start):
        """The qword right before a vtable is usually the RTTI/type descriptor
        pointer; also try the EA 'RS4:' literal convention nearby."""
        out = []
        for back in (8, 16):
            a = start - back
            if a - rs < 0:
                continue
            v = struct.unpack_from("<Q", rdata, a - rs)[0]
            out.append((back, hex(v)))
        return out

    CASES = [
        ("CONTROL model applier", 0x18021cc28, 0x18011dc50),
        ("market builder", 0x180214dd8, 0x180162c90),
        ("seasons builder", 0x18022d748, 0x180175bd0),
    ]
    for lbl, site, fn in CASES:
        st = vtable_start(site)
        ln = vtable_len(st)
        print("\n### %s  ptr_site=%#x fn=%#x" % (lbl, site, fn))
        print("   vtable start=%#x slots=%d  slot_of_fn=+%#x" % (st, ln, site - st))
        print("   preceding qwords:", rtti_name_near(st))
        # print slots
        for i in range(min(ln, 40)):
            v = struct.unpack_from("<Q", rdata, st - rs + i * 8)[0]
            f = fm.getFunctionAt(addr(v))
            print("     +%#05x %#x %s" % (i * 8, v, f.getName() if f else ""))
        if ln > 40:
            print("     ... (%d more)" % (ln - 40))

    print("\n\n### decompile market builder FUN_180162c90")
    s = dec(0x180162c90)
    print("len=%d" % len(s))
    print(s)
    print("\n\n### decompile seasons builder FUN_180175bd0")
    s = dec(0x180175bd0)
    print("len=%d" % len(s))
    print(s)
except Exception:
    traceback.print_exc()
