"""Q: which value does the UI actually receive for the quick-sell price?

LEAD: the UI layer is bound by NAME. q4 showed calls of the shape
    (**(code **)(*view + 0x48))(view, "LATEST_GAMES_WON", value)
so every number the client renders is pushed through a named setter. If the
quick-sell price is pushed under a name, finding that name finds the reader, and
the reader tells us whether it reads item+0x38 (the wire discardValue) or
item+0x3c (the client's own fcc_discardcoins result).

METHOD: enumerate every printable string in .rdata/.data whose text contains
DISCARD / discard / QUICK / Quick / SELL / Sell / COIN / Coin, print it with its
xrefs and the containing function, then decompile the functions that push a
discard-ish name.

CONTROL: the search must find the strings we already know exist -- the SQL
fragments "fcc_discardcoins" (0x1802231f0) and "price" (0x1802231e4) both
contain the target substrings, and the known UI name "LATEST_GAMES_WON" must
show up under COIN?? no -- it must show up in a separate positive check that the
string enumerator sees UI names at all. Both checks are printed explicitly. If
either fails the enumerator is broken and its silence means nothing.
"""
import re
import traceback

OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/cards/q5_out.txt"

try:
    lines = []
    def P(*a):
        lines.append(" ".join(str(x) for x in a))

    NEEDLES = ("DISCARD", "discard", "Discard", "QUICK", "Quick", "quick",
               "SELL", "Sell", "sell", "COIN", "Coin", "coin")

    # gather printable C strings out of .rdata/.data
    blocks = {}
    for b in mem.getBlocks():
        if b.getName() in (".rdata", ".data") and b.isInitialized():
            s = int(b.getStart().getOffset())
            n = int(b.getEnd().getOffset()) - s + 1
            blocks[b.getName()] = (s, n)
    P("blocks: %s" % {k: (hex(v[0]), v[1]) for k, v in blocks.items()})

    RE_STR = re.compile(rb"[ -~]{4,120}\x00")
    found = []
    for bn, (s, n) in blocks.items():
        off = 0
        CH = 1 << 20
        while off < n:
            ln = min(CH, n - off)
            data = read_bytes(s + off, ln)
            for m in RE_STR.finditer(data):
                txt = m.group()[:-1].decode("ascii")
                if any(x in txt for x in NEEDLES):
                    found.append((s + off + m.start(), bn, txt))
            off += ln - 130 if ln == CH else ln
    # dedupe
    seen = set()
    found = [f for f in found if not (f[0] in seen or seen.add(f[0]))]
    P("strings matching %s: %d" % (list(NEEDLES), len(found)))

    P("")
    P("=== CONTROL 1: the two known SQL literals must be in the hit list ===")
    hits = {a for a, _, _ in found}
    P("  0x1802231f0 'fcc_discardcoins' present: %s" % (0x1802231F0 in hits))
    P("  0x1802231e4 'price' present (should be False, no needle): %s" % (0x1802231E4 in hits))

    P("")
    P("=== CONTROL 2: the enumerator sees UI names -- LATEST_GAMES_WON ===")
    la = find_all(b"LATEST_GAMES_WON\x00", blocks=(".rdata", ".data"))
    P("  LATEST_GAMES_WON found at %s" % [hex(x) for x in la])

    P("")
    P("=== HITS WITH XREFS ===")
    for a, bn, txt in sorted(found):
        xs = xrefs_to(a)
        fns = sorted({(fn, ent) for _, _, fn, ent in xs if ent})
        P("%#x [%s] %-46r  xrefs=%d  %s"
          % (a, bn, txt, len(xs), ", ".join("%s@%#x" % f for f in fns[:8])))

    with open(OUT, "w") as fh:
        fh.write("\n".join(lines))
    print("wrote %s (%d lines)" % (OUT, len(lines)))
except Exception:
    traceback.print_exc()
