"""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()