"""Why do all three packs collapse into one store tile, and does displayGroupAssetId fix it? OBSERVED LIVE 2026-08-05. With FUT_STORE_DISPLAYGROUP=1 the store shows three group tiles named Bronze Pack / Gold Pack / Premium Gold, but drilling into ANY of them renders the same single Premium Gold pack. Two of three packs are unbuyable. We send displayGroup {"value": name} per pack and never displayGroupAssetId (0xda). The risk was predicted in utas_server.py before it happened: sending displayGroup may select a grouped RENDER PATH rather than merely filling a caption, and if so the packs need something to group BY. The obvious candidate is displayGroupAssetId, which we omit, so every pack presumably shares a default of 0 and lands in one group. That is a hypothesis. Do not ship a fix on it. Establish: Q1 where does displayGroupAssetId (0xda) store in the pack element deser 0x18013af30, and what is its constructor default? If the default is not a constant, the "everything shares group 0" story is wrong. Q2 who READS that offset. The reader is the grouping code, and whether it lives in CardsDLL or in the packed FIFA17.exe decides whether this is answerable statically at all. Q3 what does displayGroup (0xd9) store, and is there a second slot (the group's own identity) distinct from the +0x00 caption slot that `value` writes? Q4 does anything build a LIST of packs per group, e.g. a loop comparing one pack's group id against another's? That is the function that decides tile membership. CONTROLS * `assetId` 0x23 is a known INT field of the same deser storing to [rbp-0x3c]. It must resolve the same way, or the offset extraction is unreliable. * the "unknown" literal at 0x180223108 is documented as the constructor default of the caption slot written by FUN_180133f60. Reproducing that anchors Q3. COVERAGE RULE: print every decompile in full with its length. No absence claim may be made from a truncated print, and no claim of "X is the only reader" without showing the xref list it came from. """ import traceback PACK_DESER = 0x18013AF30 CTOR = 0x180133F60 UNKNOWN_LIT = 0x180223108 A = {"displayGroup": 0xD9, "displayGroupAssetId": 0xDA, "displayGroupUseDefaultImage": 0xDB, "assetId": 0x23, "value": 0x377, "priority": 0x250} def dump(va, title): try: f = func(va) src = dec(va) print("\n" + "=" * 78) print("%#x %s body %d bytes / decompile %d chars (IN FULL)" % (va, title, f.getBody().getNumAddresses() if f else -1, len(src))) print("=" * 78) print(src) return src except Exception: print("!! failed %#x" % va) traceback.print_exc() return "" try: src = dump(PACK_DESER, "pack element deserializer") print("\n--- atom comparisons present, BOTH == and != forms ---") import re as _re for name, a in sorted(A.items(), key=lambda kv: kv[1]): hits = _re.findall(r"[!=]= 0x%x\b" % a, src) print(" %-30s %#-6x %s" % (name, a, hits or "ABSENT")) print(" (the != form matters: q_hub_1 missed clubPlayers by grepping only for ==)") dump(CTOR, "constructor that writes the caption default") print("\n" + "=" * 78) print("WHO REFERENCES THE 'unknown' LITERAL %#x" % UNKNOWN_LIT) print("=" * 78) for frm, typ, fn, ent in xrefs_to(UNKNOWN_LIT): print(" %#x %s (entry %#x)" % (frm, fn, ent)) print("\n" + "=" * 78) print("CALLERS OF THE PACK DESER (the store root and anything else)") print("=" * 78) for a, n in callers(PACK_DESER): print(" %#x %s" % (a, n)) print("\n" + "=" * 78) print("CALLEES OF THE PACK DESER (sub-object parsers, incl. the displayGroup body)") print("=" * 78) for a, n in callees(PACK_DESER): print(" %#x %s" % (a, n)) # The store root: whatever assembles the tile list must walk the parsed vector. print("\n" + "=" * 78) print("STORE ROOT 0x1801234e0 IN FULL, and its callers") print("=" * 78) dump(0x1801234E0, "FutStoreGetPackTypes root") for a, n in callers(0x1801234E0): print(" caller %#x %s" % (a, n)) except Exception: traceback.print_exc()