"""Q1 cont: the UI-facing readers. GetMaxPileSize -> FUN_18003ff60,
GetAuctionTunables -> FUN_18003fa40, and model vt+0x130 (the object whose +0x30
holds NUM_MAX_AUCTIONS).

HYPOTHESIS: GetMaxPileSize is what the transfer-list UI actually calls, and it
resolves to the same model+0x1fd1c that TRADE_PILE_SIZE publishes -- OR to the
separate (vt+0x130)+0x30 auction cap. These are two different numbers and the
brief conflates them.

CONTROL: FUN_18000d550 (already decompiled) is the known-good reader of
model+0x1fd1c via vt+0xa58. If FUN_18003ff60 reaches vt+0xa58 too, they agree.

Also: form-independent disp32 writer scan for the auction-cap field. I search
.text for the raw little-endian 4-byte displacement, which catches mov/movzx/cmp/
lea in EVERY encoding -- the search form that caught the +0x1fd2e writer. For a
small offset like 0x30 a disp32 scan is useless (it would be disp8), so instead I
enumerate every writer of the object returned by vt+0x130 by decompiling its
allocator/deserialiser.
"""
import struct, traceback

try:
    for a, tag in [
        (0x18003ff60, "GetMaxPileSize script binding"),
        (0x18003fa40, "GetAuctionTunables script binding"),
    ]:
        src = dec(a)
        print("\n\n########## %#x  %s  (len=%d) ##########" % (a, tag, len(src)))
        print(src)
except Exception:
    traceback.print_exc()

try:
    VT = 0x18021c2a0
    print("\n\n########## model vtable slots of interest ##########")
    for slot in (0x08, 0x130, 0x270, 0xa58, 0xa60, 0x988, 0x998, 0xb00):
        t = qword(VT + slot)
        f = fm.getFunctionAt(addr(t)) if 0x180001000 <= t < 0x1801e5000 else None
        stub = read_bytes(t, 16) if f or (0x180001000 <= t < 0x1801e5000) else b""
        print("  vt+%#05x -> %#x  %s  stub=%s" % (slot, t, f.getName() if f else "?", stub.hex()))
        if 0x180001000 <= t < 0x1801e5000:
            s = dec(t)
            print("     ---- decompile (len=%d) ----" % len(s))
            print("     " + s.replace("\n", "\n     "))
except Exception:
    traceback.print_exc()
