"""DIMENSION 2 batch 6: find the concrete FUT request service and the request
builders behind DiscardCard / DiscardCardList / MoveCard / SellCard.

HYPOTHESES
 H1 FUN_1800295d0 is a setter; its caller passes the concrete service object, so
    the caller reveals the vtable.
 H2 0x180123cc0..0x180124910 is a block of tiny per-action factory thunks that
    Ghidra never disassembled. Disassembling them yields, for each action, the
    request class it constructs.
 H3 The action table's base is below 0x1802cb000 and some function indexes it
    with the action enum; that function is the request dispatcher.

CONTROL: FutSquadSaveServerResponse -> 0x180171a60.
"""
import traceback, os

OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/packres"

try:
    print("CONTROL FutSquadSaveServerResponse ->",
          [hex(a) for a, v, e in class_deser("FutSquadSaveServerResponse")])

    print("=" * 78)
    print("SECTION 1 -- callers of the service setter FUN_1800295d0")
    for r in xrefs_to(0x1800295D0):
        print("   %#x %s %s %#x" % (r[0], r[1], r[2], r[3]))
    for e in sorted({r[3] for r in xrefs_to(0x1800295D0) if r[3]}):
        s = dec(e)
        print("---- caller %#x len=%d ----" % (e, len(s)))
        print(s if len(s) < 8000 else s[:8000] + "\n...TRUNCATED, len=%d" % len(s))

    print("=" * 78)
    print("SECTION 2 -- disassembly of the factory thunk block 0x180123900-0x180124950")
    a = 0x180123900
    end = 0x180124950
    while a < end:
        ins = listing.getInstructionAt(addr(a))
        if ins is None:
            b = read_bytes(a, 16)
            print("  %#x  DATA %s" % (a, b.hex()))
            a += 16
            continue
        print("  %#x  %s" % (a, ins))
        a += ins.getLength()

    print("=" * 78)
    print("SECTION 3 -- action table extent and who indexes it")
    # walk backwards from 0x1802cb000 in 0x30 steps while row[0] looks like a string
    base = 0x1802CB000
    while True:
        prev = base - 0x30
        try:
            q = qword(prev)
        except Exception:
            break
        if not (0x1801E5000 <= q <= 0x180290000):
            break
        try:
            s = rd_str(q, 40)
        except Exception:
            break
        if not s or not all(0x20 <= ord(c) < 0x7F for c in s):
            break
        base = prev
    print("  table base ~ %#x" % base)
    for r in xrefs_to(base):
        print("   xref to base %#x %s %s %#x" % (r[0], r[1], r[2], r[3]))
    for e in sorted({r[3] for r in xrefs_to(base) if r[3]}):
        s = dec(e)
        print("---- indexer %#x len=%d ----" % (e, len(s)))
        print(s if len(s) < 9000 else s[:9000] + "\n...TRUNCATED, len=%d" % len(s))

except Exception:
    traceback.print_exc()
