"""Q1/Q3: trade-pile capacity literals -- who READS them?

HYPOTHESIS: TRADE_PILE_SIZE is an OUTPUT name only (published by FUN_18000d550),
exactly like IS_TRADING_ENABLED, and is therefore NOT a Blaze client-config key
the DLL ever looks up.

CONTROL (same form): IS_TRADING_ENABLED (0x1801fc118) is PROVEN output-only --
exactly one rip-relative reference in .text, a `lea` inside the publisher. If my
scanner reproduces that exact result for IS_TRADING_ENABLED, the scanner is good.
SECOND CONTROL: a literal that IS read/compared somewhere, to prove the scanner
can see a consumer at all. I use the route string "/transfermarket?..." which must
be referenced by a request builder, and the atom-name strings.

METHOD, form-independent: I do NOT grep for `== 0x` or trust Ghidra's xref db.
For every byte offset in .text I read the 4 bytes as a little-endian int32 and
test whether text_base+i+4+disp equals the target. That catches lea/mov/cmp/push
in EVERY rip-relative encoding. Separately I search .rdata/.data for the absolute
8-byte pointer, which catches vtable slots and pointer tables.
"""
import struct, traceback

try:
    def sect(name):
        for b in mem.getBlocks():
            if b.getName() == name:
                return int(b.getStart().getOffset()), int(b.getEnd().getOffset()) - int(b.getStart().getOffset()) + 1
        return None, None

    TB, TS = sect(".text")
    print("text base %#x size %#x" % (TB, TS))
    TEXT = read_bytes(TB, TS)
    print("read text len", len(TEXT))

    RB, RS = sect(".rdata")
    RDATA = read_bytes(RB, RS)
    DB, DS = sect(".data")
    DATA = read_bytes(DB, DS)
    print("rdata %#x len %d ; data %#x len %d" % (RB, len(RDATA), DB, len(DATA)))

    def riprefs(target):
        """all i such that some 4-byte window at TB+i is a rip-disp32 to target"""
        out = []
        for i in range(0, len(TEXT) - 4):
            d = struct.unpack_from("<i", TEXT, i)[0]
            if TB + i + 4 + d == target:
                out.append(TB + i)
        return out

    def absrefs(target):
        p = struct.pack("<Q", target)
        out = []
        for base, buf in ((RB, RDATA), (DB, DATA), (TB, TEXT)):
            j = buf.find(p)
            while j != -1:
                out.append(base + j)
                j = buf.find(p, j + 1)
        return out

    def strhits(s):
        pat = s.encode() + b"\x00"
        return find_all(pat)

    NAMES = [
        "TRADE_PILE_SIZE",
        "GetMaxPileSize",
        "NUM_MAX_AUCTIONS",
        "IS_MAX_AUCTIONS",
        "pileSizeClientData",
        "TradePileFull",
        "maximumTradePileSize",
        "IS_TRADING_ENABLED",       # CONTROL: proven output-only
        "IS_STORE_ENABLED",         # CONTROL: sibling
        "WATCH_LIST_SIZE",
        "maximumActiveAuctions",
        "MAX_ACTIVE_AUCTIONS",
    ]

    for nm in NAMES:
        hits = strhits(nm)
        print("\n=== %-24s  %d string hit(s): %s" % (nm, len(hits), [hex(h) for h in hits]))
        for h in hits:
            print("     text at %#x = %r" % (h, rd_str(h, 80)))
            rr = riprefs(h)
            print("     rip-disp32 refs in .text: %d" % len(rr))
            for a in rr:
                # print 24 bytes starting 8 before, plus containing function
                ctx = read_bytes(a - 8, 32)
                f = fm.getFunctionContaining(addr(a))
                print("        %#x  in %s  bytes[-8..+24]=%s" % (
                    a, f.getName() if f else "?", ctx.hex()))
            ar = absrefs(h)
            if ar:
                print("     absolute-pointer refs: %s" % [hex(x) for x in ar])
            # also the -4 RS4 style and Ghidra's own view
            gx = xrefs_to(h)
            print("     ghidra xrefs_to: %s" % [(hex(a), t, fn) for a, t, fn, e in gx])

except Exception:
    traceback.print_exc()
