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