"""ESTABLISHED: model+0x1fd2e = (settings response+0x50 == 1), fed by deser case
0x336 = atom "tradingEnabled" as a configs[] type-string. The applier DOES run
(model+0x1fd28=60 live and the applier is its only writer), so with configs:[] every
field keeps the response-object CONSTRUCTOR DEFAULT.
NOW: enumerate every CALLER of the trading gate accessor vt+0x270 (FUN_18011c670),
form-independently, by scanning .text for all ff /2 encodings of call [reg+0x270],
and decompile the market/tradepile surfaces.
CONTROL: also scan for call [reg+0xa58] (TRADE_PILE_SIZE, known to be read by the
publisher FUN_18000d550) -- if that scan finds FUN_18000d550 the scan form is good.
"""
import traceback, struct
try:
    tstart = None
    for b in mem.getBlocks():
        if b.getName() == ".text":
            tstart = int(b.getStart().getOffset())
            tb = read_bytes(tstart, int(b.getEnd().getOffset()) - tstart + 1)
    print(".text len", len(tb))

    def call_sites(slot):
        """all `call [reg+disp32]` (ff /2) with disp32 == slot"""
        out = []
        d = struct.pack("<i", slot)
        for modrm in (0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97):
            pat = bytes([0xff, modrm]) + d
            i = tb.find(pat)
            while i != -1:
                a = tstart + i
                # modrm 0x94 needs a SIB; skip mismatched
                f = fm.getFunctionContaining(addr(a))
                out.append((a, modrm, f.getName() if f else "?",
                            int(f.getEntryPoint().getOffset()) if f else 0))
                i = tb.find(pat, i + 1)
        # rex-prefixed forms are the same modrm bytes, already covered
        return sorted(out)

    for slot, tag in [(0x270, "IS_TRADING_ENABLED accessor"),
                      (0xa58, "TRADE_PILE_SIZE accessor (CONTROL)"),
                      (0x280, "IS_STORE_ENABLED accessor"),
                      (0xa60, "watch-list size accessor")]:
        print("\n=== call [reg+%#x]  %s ===" % (slot, tag))
        for a, m, fn, ent in call_sites(slot):
            print("   %#x  modrm=%#04x  in %-30s ent=%#x" % (a, m, fn, ent))

    print("\n=== xrefs to /transfermarket literal 0x180228490 ===")
    for frm, typ, fn, ent in xrefs_to(0x180228490):
        print("   %#x %-12s %s ent=%#x" % (frm, typ, fn, ent))
    print("=== xrefs to tradepile 0x18022fb20 / watchlist 0x18022ffa8 ===")
    for lit in (0x18022fb20, 0x18022ffa8):
        print("  lit %#x = %r" % (lit, rd_str(lit, 60)))
        for frm, typ, fn, ent in xrefs_to(lit):
            print("     %#x %-12s %s ent=%#x" % (frm, typ, fn, ent))

    print("\n=== FUN_18013adb0  pileSizeClientData deser ===")
    s = dec(0x18013adb0); print("len=%d" % len(s)); print(s)
except Exception:
    traceback.print_exc()
