"""TRANSFER LIST shows 0/0. What feeds the trade-pile capacity? STATE. tradingEnabled (gate byte 0x1fd2e) is now 1, proven live, delivered via the Blaze client-config store after /settings was proven incapable of reaching the applier. Every card ships untradeable:false. Both documented gates of TO_TRADE_PILE (FUN_1801a7260) are therefore satisfied, and yet "Place on Transfer List" and "List on Transfer Market" are STILL greyed. The header reads TRANSFER LIST 0/0 in red, so the capacity is zero and that is the obvious third condition. We send maximumTradePileSize=77 through /settings. That cannot work: the applier FUN_18011dc50 has no caller Ghidra can see, is unreachable from the settings deserializer, and a live probe showed no int gate field carrying 77 (+0x1fd14=0, +0x1fd4c=0, +0x1fd54=480). So the capacity must arrive some other way, or default to 0. CANDIDATE LITERALS found in CardsDLL: TRADE_PILE_SIZE, GetMaxPileSize, NUM_MAX_AUCTIONS, IS_MAX_AUCTIONS, pileSizeClientData, TradePileFull, maximumTradePileSize. QUESTIONS Q1 Find the reader of the trade-pile capacity. Start from GetMaxPileSize and TradePileFull and walk back to the field they read. Which struct offset holds it? Q2 Is that offset the SAME one the settings applier would write (i.e. one of the int fields at 0x1fd14 / 0x1fd4c / 0x1fd54), or a different home entirely? If it is an applier field then capacity is unreachable for the same reason tradingEnabled was, and the Blaze store is the only remaining lever. Q3 Is TRADE_PILE_SIZE a CLIENT-CONFIG key, read the same way IS_TRADING_ENABLED is? That is the decisive question, because IS_TRADING_ENABLED via the Blaze store worked on the first try and the same delivery path would fix this. Find its xrefs and the lookup that consumes it. Do NOT assume: an UPPER_SNAKE literal could equally be a localisation key or a telemetry tag. Q4 pileSizeClientData smells like the ut/%s/clientdata route, which we already serve. Check whether the capacity is read from a clientdata blob instead. If so that is a UTAS-side fix and needs no Blaze restart, which makes it much cheaper to test. CONTROL: IS_TRADING_ENABLED is known to work through the Blaze client-config path. Find how IT is consumed, and use that as the template for judging whether TRADE_PILE_SIZE is consumed the same way. A candidate that is NOT read by the same mechanism is not a candidate, however plausible the name looks. COVERAGE: print decompiles in full with lengths. Enumerate all four dispatch forms before any absence claim: == , != , case labels, and sub/dec ladders. """ import traceback LITERALS = [b"TRADE_PILE_SIZE\x00", b"GetMaxPileSize\x00", b"NUM_MAX_AUCTIONS\x00", b"IS_MAX_AUCTIONS\x00", b"pileSizeClientData\x00", b"TradePileFull\x00", b"maximumTradePileSize\x00", b"IS_TRADING_ENABLED\x00"] def dump(va, title, limit=None): try: f = func(va) src = dec(va) print("\n" + "=" * 78) print("%#x %s body %d / decompile %d chars%s" % (va, title, f.getBody().getNumAddresses() if f else -1, len(src), "" if limit is None else " (first %d shown)" % limit)) print("=" * 78) print(src if limit is None else src[:limit]) except Exception: print("!! failed %#x" % va) traceback.print_exc() try: print("=" * 78) print("LITERAL LOCATIONS AND XREFS") print("=" * 78) interesting = {} for lit in LITERALS: name = lit[:-1].decode() hits = find_all(lit) print("\n%-24s %d hit(s)" % (name, len(hits))) for h in hits: print(" at %#x : %r" % (h, rd_str(h, 40))) xs = xrefs_to(h) if not xs: print(" no direct xref (may be reached via a table)") for frm, typ, fn, ent in xs: print(" xref %#x in %s (entry %#x)" % (frm, fn, ent)) if ent: interesting.setdefault(name, set()).add(ent) print("\n" + "=" * 78) print("CONSUMERS, decompiled") print("=" * 78) # IS_TRADING_ENABLED first: it is the working control and the template. for name in ("IS_TRADING_ENABLED", "TRADE_PILE_SIZE", "pileSizeClientData", "GetMaxPileSize", "TradePileFull", "NUM_MAX_AUCTIONS"): for ent in sorted(interesting.get(name, []))[:2]: dump(ent, "consumer of %s" % name, limit=6000) except Exception: traceback.print_exc()