"""D3 Q2/Q3: the auction-count surface.

HYPOTHESIS: the third condition is the AUCTION LIMIT. .rdata holds
NUM_CURRENT_AUCTIONS (0x1801f3658), NUM_MAX_AUCTIONS (0x1801f3670),
IS_MAX_AUCTIONS (0x1801f3688) and CARDS_CB_ERR_AUCTION_LIMIT_REACHED
(0x1802142f8). If NUM_MAX_AUCTIONS is fed from model+0x1fd1c (TRADE_PILE_SIZE,
measured 0) then IS_MAX_AUCTIONS is TRUE for every card and the transfer entries
are greyed by a full-trade-pile test, independently of TO_TRADE_PILE.

Find the publishers and decompile them in full.

CONTROL: TO_TRADE_PILE (0x1801f4d48) is resolved by the SAME reference machinery
in the same run; its only referencing function must come out as FUN_18003e370,
which is already established. If that control does not resolve, no absence in
this run means anything.

Both Ghidra's reference manager AND a raw rip-relative displacement scan of
.text are used, because the two miss different things.
"""
import traceback

try:
    TEXT_LO, TEXT_HI = 0x180001000, 0x1801e5000
    text = read_bytes(TEXT_LO, TEXT_HI - TEXT_LO)
    print("text bytes read: %d" % len(text))

    def riprefs(target):
        """Every 4-byte little-endian disp32 in .text whose rip-relative target is
        `target`, for every instruction length 5..9 (covers lea/mov/cmp/push
        encodings without assuming the opcode). Form-independent."""
        out = []
        for i in range(0, len(text) - 4):
            d = int.from_bytes(text[i:i + 4], "little", signed=True)
            va = TEXT_LO + i
            for ilen in range(4, 10):
                if va + ilen + d == target:
                    out.append((va - (ilen - 4), va, ilen, d))
                    break
        return out

    NAMES = [
        ("TO_TRADE_PILE  CONTROL", 0x1801f4d48),
        ("NUM_CURRENT_AUCTIONS", 0x1801f3658),
        ("NUM_MAX_AUCTIONS", 0x1801f3670),
        ("IS_MAX_AUCTIONS", 0x1801f3688),
        ("CARDS_CB_ERR_AUCTION_LIMIT_REACHED", 0x1802142f8),
        ("CARDS_CB_ERR_WATCHLIST_FULL", 0x1802140f8),
    ]
    funcs = {}
    for label, a in NAMES:
        print("=" * 78)
        print("### %s  @ %#x  text=%r" % (label, a, rd_str(a, 60)))
        print("  ghidra xrefs_to:")
        for t in xrefs_to(a):
            print("    from %#x  %s  in %s (%#x)" % t)
            if t[3]:
                funcs.setdefault(t[3], set()).add(label)
        print("  raw rip-relative disp32 hits (form-independent):")
        for start, dispva, ilen, d in riprefs(a):
            f = fm.getFunctionContaining(addr(dispva))
            nm = f.getName() if f else "?"
            ent = int(f.getEntryPoint().getOffset()) if f else 0
            print("    disp@%#x ilen=%d -> in %s (%#x)  raw=%s"
                  % (dispva, ilen, nm, ent, read_bytes(dispva - 3, 10).hex()))
            if ent:
                funcs.setdefault(ent, set()).add(label)

    print()
    print("#" * 78)
    print("### FULL DECOMPILES of every function that touches those names")
    print("#" * 78)
    for ent in sorted(funcs):
        src = dec(ent)
        print("=" * 78)
        print("### %#x  touches %s   len(src)=%d" % (ent, sorted(funcs[ent]), len(src)))
        print("=" * 78)
        print(src)
        print()
except Exception:
    traceback.print_exc()
