"""Final batch.

Q-A  How many places consume each getter?
     FUN_1801a8620 = item+0x38 (wire discardValue, UI name DISCARD_CREDITS)
     FUN_1801a8090 = item+0x3c (client fcc_discardcoins result, UI name
                                CALCULATED_DISCARD_CREDITS)
     If +0x3c has exactly ONE consumer and it is the UI property push, then no
     native code ever falls back from the wire value to the computed one, and
     which number the tile shows is a Flash-asset decision. That is H1.

Q-B  What is FUN_1800eb850 registered as? Its only xrefs are DATA slots at
     0x180216130 / 0x1802658a0 / 0x1802f85ec, so it sits in a dispatch table.
     Dump the qwords either side and resolve any string pointers to name it.

Q-C  item+0x34 feeds the UI property "BOUGHT_FOR" and is NOT on the field map.
     Print the deserializer sites that write +0x194 (= 0x34 + 0x160, the frame
     form inside FUN_18013fe00) so the wire atom can be identified later.

CONTROL: FUN_1801a8620 and FUN_1801a8090 are 8-byte leaf functions whose bodies
were printed in q7, so their identity is not in doubt; the xref counts below are
the only new claim. A getter with zero xrefs would mean the xref index is stale,
which is checkable against FUN_1801a80c0 (CARD_LEVEL), known to be called from
FUN_1800eb850.
"""
import traceback

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

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

    P("=== Q-A getter consumers ===")
    for nm, a in (("+0x38 DISCARD_CREDITS getter FUN_1801a8620", 0x1801A8620),
                  ("+0x3c CALCULATED getter    FUN_1801a8090", 0x1801A8090),
                  ("+0x54 CARD_LEVEL getter    FUN_1801a80c0", 0x1801A80C0),
                  ("+0x58 rare==1 getter       FUN_1801a88c0", 0x1801A88C0),
                  ("+0x58 rare==0xc getter     FUN_1801a8880", 0x1801A8880)):
        xs = xrefs_to(a)
        P("%-44s %d xrefs" % (nm, len(xs)))
        for frm, typ, fn, ent in xs:
            P("      %#x %-12s %s @ %#x" % (frm, typ, fn, ent))

    P("")
    P("=== Q-B dispatch-table context around the FUN_1800eb850 slots ===")
    for slot in (0x180216130, 0x1802658A0, 0x1802F85EC):
        P("-- slot %#x --" % slot)
        for off in range(-0x40, 0x48, 8):
            a = slot + off
            try:
                v = qword(a)
            except Exception:
                P("   %#x  <unreadable>" % a)
                continue
            tag = ""
            f = fm.getFunctionAt(addr(v)) if 0x180000000 <= v < 0x181000000 else None
            if f:
                tag = "-> FUNC %s" % f.getName()
            elif 0x1801E5000 <= v <= 0x180290000:
                try:
                    s = rd_str(v, 60)
                except Exception:
                    s = ""
                if s and all(32 <= ord(c) < 127 for c in s):
                    tag = "-> %r" % s
            P("   %#x  %016x  %s%s" % (a, v, tag, "   <== the slot" if off == 0 else ""))
        P("")

    P("=== Q-C writes to the parsed item's +0x34 (frame form RBP+0x194) ===")
    f = func(0x18013FE00)
    lo = int(f.getBody().getMinAddress().getOffset())
    hi = int(f.getBody().getMaxAddress().getOffset())
    p = lo
    while p <= hi:
        ins = listing.getInstructionAt(addr(p))
        if ins is None:
            p += 1
            continue
        t = str(ins)
        if "0x194]" in t or "0x190]" in t or "0x198]" in t or "0x19c]" in t:
            P("   %#x  %s" % (p, t))
        p += ins.getLength()

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