"""Q2: the card-accessor family and the OTHER card publishers.

FUN_1800e5940 is the MANAGER card publisher (gated on CARD_TYPE, emits
ManagerCardBio / TACTICAL_KNOWLEDGE / ATTRIB_TEAM_TALKS). It reads every value it
publishes through a family of tiny accessors at 0x1801a8xxx taking a 4-qword stack
wrapper, and pushes them to a UI sink via param_3 vtable slots +0x08 (bool),
+0x10 (int), +0x20 (string).

HYPOTHESIS: those accessors are thin field reads off the 0x180-stride parsed item
record, so FUN_1801a87f0 (OVERALL_RATING) reads record+0xb4, which is where atom
0x274 rating is stored. If so the DISPLAYED rating is OUR wire value.

CONTROL, same syntactic form: FUN_1801a8540 is published as LEAGUE_ID. LEAGUE_ID
is KNOWN to be client-derived -- FUN_180135890 "always recomputes leagueid +0x154"
on a DB hit. And FUN_1801a8480 is CONTRACT_REMAINING, known-ours (+0x8c, we send
contract 7 and the manager card printed "CONTRACT 7"). So the accessor family must
show BOTH kinds reading the SAME record: that proves the accessors are record
reads and that "ours vs theirs" is decided upstream in the merge, not here. If the
league accessor instead performs a DB query while the rating one does a field read,
that is the opposite answer and it is equally visible.

Also: enumerate every function in [0x1801a7000,0x1801a9000) with its decompiled
body, which is the whole accessor table, and find the sibling publishers by xref
on the UI key literals.
"""
import traceback

try:
    print("=== A: UI key literals and their xrefs (finds sibling publishers) ===")
    keys = [b"OVERALL_RATING", b"CARD_LEVEL", b"CARD_RARITY", b"CARD_TYPE",
            b"ASSET_ID", b"LEAGUE_ID", b"TEAM_ID", b"NATIONALITY",
            b"CONTRACT_REMAINING", b"FIRST_NAME", b"LAST_NAME", b"KNOWN_AS",
            b"PREFERRED_POSITION", b"POSITION", b"FITNESS", b"PLAYSTYLE",
            b"PLAY_STYLE", b"CHEMISTRY", b"ATTRIBUTE", b"ATTR", b"MORALE",
            b"TRAINING", b"DISCARD", b"QUICK_SELL", b"UNTRADEABLE",
            b"RESOURCE_ID", b"DEFINITION_ID", b"CARD_ASSET", b"ITEM_STATE",
            b"ITEM_TYPE", b"SUBTYPE", b"RARE", b"PHOTO", b"HEADSHOT", b"IS_"]
    for k in keys:
        for a in find_all(k):
            s = rd_str(a, 64)
            if not s.startswith(k.decode()):
                continue
            xs = xrefs_to(a)
            if not xs:
                continue
            print("  %-34s @%#x" % (s, a))
            for frm, typ, fn, ent in xs:
                print("      from %#x %-10s %s(%#x)" % (frm, typ, fn, ent))

    print()
    print("=== B: every function in [0x1801a7000,0x1801a9000) decompiled ===")
    it = fm.getFunctions(addr(0x1801A7000), True)
    n = 0
    while it.hasNext():
        f = it.next()
        e = int(f.getEntryPoint().getOffset())
        if e >= 0x1801A9000:
            break
        n += 1
        src = dec(e)
        body = " ".join(src.split())
        print("--- %s @%#x len=%d" % (f.getName(), e, len(src)))
        print("    %s" % (body[:900]))
    print("total functions in range: %d" % n)

    print()
    print("=== C: FUN_1801a78f0 (builds the wrapper) in full ===")
    s = dec(0x1801A78F0)
    print("len=%d" % len(s))
    print(s)
except Exception:
    traceback.print_exc()
