fifa17-recon: SOLVED "Send to Club" -- it was the response body all along

Live 2026-08-04 with FUT_MOVE_BODY=ack and FUT_PACK_AUTOCLUB=0. Bought a bronze pack,
opened it, chose Send to Club. The session SURVIVED, the five cards persisted into the
club pile, and there was no ut/delete/auth logout -- the logout that accompanied all
seven previous attempts.

  11:15:48 PUT /item
    req {"itemData":[{"id":100000125,"pile":"club","swap":0,"tradeId":0}, ... x5]}
    res {"itemData":[{"id":100000125,"pile":"club","success":true}, ... x5]}
  11:15:49 GET /user/credits      session alive
  11:15:51 GET /hub               no error dialog
  11:16:06 GET /club?year=2017... MY CLUB opened

PUT ut/%s/item never was an ack endpoint. It builds per-item VERDICT records, and the
completion handler raises EVENT_CARDS_MOVE_CARD_FAILURE when the vector is empty or
success != 1. Every body this project ever returned, {} included, told the client the
move had FAILED, and the client ended the FUT session because that is what that event
does. We were failing our own move.

Defaults flipped: FUT_MOVE_BODY empty -> ack, FUT_PACK_AUTOCLUB 1 -> 0. The autoclub
workaround is retired.

New intel, captured for the first time because the client had never got this far: the
request carries `swap` and `tradeId` beside id/pile. We ignore both and the move
succeeded, so neither is load-bearing for a pending-to-club move.

PROCESS, and this is the part worth keeping. The FIRST attempt at this test produced
no PUT /item at all: FUT_PACK_AUTOCLUB=1 had already emptied the pending pile at
purchase time, so the reveal screen had nothing to assign and the client never issued
the request. The workaround for the bug was hiding the bug. Before testing a fix,
check the configuration still lets the client make the call the fix is for.

Two self-inflicted incidents, both recorded in REBUILD_RESEARCH S17:
- Restarting the server to inject a flag WHILE FIFA was running produced the exact
  "error connecting to FIFA 17 Ultimate Team" dialog this project spent weeks chasing,
  from a plain connection refusal during the ~30s window. Restart only at the main
  menu, and check the log for ProtoHttp requests before blaming a response.
- pgrep -f matched the invoking shell twice, killing it before the restart, because
  the same command contained the literal script name in a later clause.

Docs updated: REBUILD_RESEARCH S17 (the solve), priority-2026-08 S2/S3.1/S6 (next task
is now the MY CLUB counter), PROJECT_REPORT 6a, HANDOFF 5a plus the stale "FutMoveCard
has no skip handler" claim in S3 and a new 5d for Seasons/Draft.

380 + 51 checks green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
This commit is contained in:
funman300
2026-08-04 11:20:03 -07:00
parent 18d864908e
commit 5b139864ee
5 changed files with 222 additions and 96 deletions
+56 -37
View File
@@ -74,9 +74,12 @@ at `0x1801c7f1a`. This is the single most common way to break the game, and it h
this project repeatedly. Arrays must be arrays; nested objects must be objects.
**Unknown keys are usually skipped safely** — most deserializers route an unrecognised
atom to a value-skip handler (`FUN_180135ff0`), so extra fields are inert. **But not
always**: at least one deserializer (`FutMoveCard`, `0x180128600`) has *no* skip handler
at all, so any unexpected key leaves its value unconsumed and desyncs the reader.
atom to a value-skip handler (`FUN_180135ff0`), so extra fields are inert. This document
previously named `FutMoveCard` (`0x180128600`) as an exception with no skip handler at
all. **That was wrong**, and the retraction is in §5a: it has two skip-handler call sites
and parses seven atoms. No deserializer in this project is currently known to lack one.
Treat any future "this class has no skip handler" claim as unproven until the search is
shown to have covered the whole function.
### Working method
@@ -97,7 +100,7 @@ omission is skip-safe, a wrong shape freezes the game.
| Squad building — `PUT /squad/<id>` fires, persists across relaunch | ✅ |
| Squad roster ("MY SQUADS") | ✅ |
| Transfer market — browse, bid, buy-now, sell, watchlist | ✅ |
| Packs — buy, cards land in club | ✅ (via a workaround, see §5) |
| Packs — buy, reveal, Send to Club | ✅ (the workaround is retired, see §5a) |
| Quick sell — destroys cards, credits coins | ✅ |
| Match loop — create/ready/play/destroy + coin rewards | ✅ implemented, **never played in-game** |
| Online/EASFC status bar (no "servers unreachable") | ✅ when enabled |
@@ -114,37 +117,34 @@ screens were broken by shipping "corrections" on by default.
## 5. Open problems
### 5a. "Send to Club" kills the FUT session — UNSOLVED
### 5a. "Send to Club" kills the FUT session — SOLVED 2026-08-04
Opening a pack shows the cards correctly. Choosing **Send to Club** results in:
Opening a pack shows the cards correctly; choosing **Send to Club** used to produce
*"there has been an error connecting to FIFA 17 Ultimate Team"* and a logout, seven
attempts running. The cards always moved server-side; only the acknowledgement was
rejected.
> *"We are sorry but there has been an error connecting to FIFA 17 Ultimate Team. You
> will be returned to the FIFA 17 Main Menu."*
The cause was the response body. `PUT ut/%s/item` returns per-item **verdict** records,
not an acknowledgement, and the completion handler raises
`EVENT_CARDS_MOVE_CARD_FAILURE` when the record vector is empty or when `success != 1`.
Every body the project returned, `{}` included, reported the move as failed. The fix is
the real shape:
The client then POSTs `ut/delete/auth` (logout) about a second later. The cards **do**
move correctly server-side every time — only the acknowledgement is rejected.
```json
{"itemData":[{"id":100000125,"pile":"club","success":true}, ...]}
```
**Eliminated by live test:**
Live: five cards to the club, session survived, cards persisted, no `ut/delete/auth`.
The autoclub workaround is retired.
| Hypothesis | Result |
|---|---|
| Missing `chemistry` field | Failed without it too |
| Extra keys + no skip handler in the deserializer | Real finding; sending *only* the parsed key still failed |
| The response body at all | **A bare `{}` also fails** |
| A network failure ("error connecting") | Zero non-loopback connections during a failure — that string is FIFA's *generic* session-error text |
| Missing per-call service URLs | 146 were genuinely missing and are now served; unchanged |
| The POW layer saturating HTTP | Same failure with POW fully disabled |
| The reveal screen's exit path | **"Quick Sell All" from the same screen works** |
| The club not being loaded | Loading MY CLUB first, then moving, still failed |
Two corrections this closed, both worth carrying forward:
**The key asymmetry:** quick sell (`POST ut/delete/<sku>/item`) and move
(`PUT ut/<sku>/item`) both receive an identical bare `{}` from the same server, with the
same headers and status. Quick sell succeeds; move kills the session. Whatever decides
this is **client-side state, not the wire**.
**Workaround in place:** pack contents are deposited straight into the club at open time
and the pending pile is kept empty, so the client is never offered a move. Packs are
fully usable; the cost is that the reveal screen shows nothing to assign.
- The repo's claim that this deserializer had **no skip handler** and parsed only two
keys was false. It came from searching a truncated decompile. It implied the body
could not be at fault, which is what sent seven attempts after client-side state.
Never conclude an absence from a truncated or unverified-length extraction.
- The quick-sell asymmetry was not evidence of client state. Quick sell's callbacks
read only the transport status code and never touch the body.
### 5b. "MY CLUB" counter always reads 0 — UNSOLVED
@@ -171,6 +171,24 @@ element parser**. Sending it populated **froze the store** (the type-desync busy
it is behind a flag, default off. Doing it properly needs the group's own field set worked
out rather than a self-referential copy of the pack.
### 5d. Seasons and Draft refuse — UNSOLVED, and not obviously server-side
Selecting **single-player Seasons** raises *"There was a problem communicating with the
FIFA Ultimate Team servers"* while making **zero requests to any layer**. UTAS, Blaze and
POW logs show only pings and one census subscription across the whole failure window. No
response can be wrong because no request was made. POW is eliminated (same failure with it
enabled and disabled).
**Online Draft** hangs the client rather than crashing it (process alive, no dump). The
one suspicious thing on the wire is `GET ut/%s/squad/mode/draft/state`, which our generic
`/squad` route answers with a full active-squad object: 23 slots, nested `itemData`, a
33-integer formation string. The real class wants `roundsInfo` plus a state enum, so this
is a textbook type-desync candidate and the timing matches. **Nothing has isolated it**;
it is a suspect, not a cause.
Both matter beyond themselves, because they are the only two routes into a match, and the
`/match` request shape has therefore never been captured.
---
## 6. Notable reverse-engineering findings
@@ -230,16 +248,17 @@ out rather than a self-referential copy of the pack.
## 9. Where help would be most valuable
1. **The move-vs-quick-sell asymmetry (§5a).** Two sibling endpoints, identical responses,
one works. What client-side precondition could a "move item between piles" operation
have that a "discard item" operation does not — a loaded destination collection, a
known pile capacity, a valid target index?
2. **The MY CLUB counter (§5b).** Given the client demonstrably *has* the items and
1. **The MY CLUB counter (§5b).** Given the client demonstrably *has* the items and
*renders* them, what else could a tab counter read from? Note that a sibling counter in
the same bar works correctly.
3. **Whether either is fixable server-side at all**, or whether the honest answer is that
the deciding logic lives in the packed executable and only live instrumentation can
settle it.
2. **Seasons refusing with zero requests to any server (§5d).** The client raises a
"problem communicating with the FIFA Ultimate Team servers" without contacting
anything. Nothing on the wire can be wrong because nothing went on the wire.
3. **Whether the remaining problems are fixable server-side at all**, or whether the
deciding logic lives in the Denuvo-packed executable and only live instrumentation can
settle it. Note that this question was asked about `Send to Club` too, and there the
answer turned out to be a plain wire fix, so treat "it must be client-side" as a
hypothesis needing evidence rather than a fallback explanation.
Useful framing: this project's failures have almost always come from proposing a fix
before testing the assumption under it. Hypotheses that come with a cheap way to
+30 -14
View File
@@ -177,24 +177,40 @@ two working screens — once freezing the store outright.
## 6. Open problems
### 6a. "Send to Club" ends the FUT session
### 6a. "Send to Club" ends the FUT session — SOLVED 2026-08-04
Opening a pack displays the cards correctly. Choosing **Send to Club** produces *"there
has been an error connecting to FIFA 17 Ultimate Team"* and a logout. The cards **do**
move correctly server-side; only the acknowledgement is rejected.
Opening a pack displayed the cards correctly, but choosing **Send to Club** produced
*"there has been an error connecting to FIFA 17 Ultimate Team"* and a logout, seven
attempts running. The cards always moved correctly server-side; only the
acknowledgement was rejected.
Eliminated by live test: the response body (a bare `{}` fails identically), a missing
field, unknown-key desync, a network failure (zero external connections during a
failure — that error string is FIFA's *generic* session error), missing service URLs,
the online layer, the reveal screen's exit path, and the club not being loaded.
**It was the response body all along.** `PUT ut/%s/item` does not parse an
acknowledgement, it builds per-item **verdict** records, and the completion handler
raises `EVENT_CARDS_MOVE_CARD_FAILURE` when the record vector is empty or when
`success != 1`. Every body this project returned, `{}` included, therefore told the
client the move had failed, and the client ended the FUT session because that is what
that event does. Serving the real shape fixed it in one launch:
**The asymmetry:** quick sell and move receive an *identical* bare `{}` from the same
server. Quick sell succeeds. Move ends the session. The deciding factor is client-side
state, not the wire.
```json
{"itemData":[{"id":100000125,"pile":"club","success":true}, ...]}
```
**Workaround:** pack contents are deposited straight into the club at open time, so the
client is never offered a move. Packs are fully usable; the reveal screen just doesn't
offer assignment.
Live result: five cards sent to the club, session survived, cards persisted, no
`ut/delete/auth` logout. Both flags are now defaults and the autoclub workaround is
retired.
**Why it took seven attempts,** which is the part worth keeping: the project had
recorded that this deserializer had *no skip handler* and parsed only two keys. That
was false, produced by searching a **truncated** decompile (the first 4,000 characters
of a 6,193-character function). It implied "the body cannot be the problem", which is
what redirected the investigation to client-side state. The quick-sell asymmetry that
seemed to confirm it has a mundane explanation: quick sell's callbacks read only the
transport status code and never touch the body, so its tolerance of `{}` said nothing
about this endpoint.
The general lesson, now a standing rule: **never conclude an absence from a truncated
or unverified-length extraction**, and treat every negative claim in the endpoint docs
as weaker than the corresponding positive one.
### 6b. "MY CLUB" counter reads 0
+71
View File
@@ -643,3 +643,74 @@ wire the whole time.
**Status: necessary condition identified, sufficiency UNTESTED.** Staged behind
`FUT_MOVE_BODY=ack`, default still `empty`. One launch settles it.
---
## 17. SOLVED: "Send to Club" (2026-08-04, one launch)
`FUT_MOVE_BODY=ack`, `FUT_PACK_AUTOCLUB=0`. Bought a bronze pack, opened it, chose
Send to Club. **The session survived.** The five cards persisted into the club pile
and the client carried on to the hub and then into MY CLUB.
```
11:15:42 POST /purchased/items pack bought, 8400 -> 8000
11:15:43 GET /purchased/items 5 items in the pending pile
11:15:48 PUT /item
req {"itemData":[{"id":100000125,"pile":"club","swap":0,"tradeId":0}, ... x5]}
res {"itemData":[{"id":100000125,"pile":"club","success":true}, ... x5]}
11:15:49 GET /user/credits session alive
11:15:51 GET /hub no error dialog
11:16:05 GET /club/stats/{staff,year,consumables}
11:16:06 GET /club?year=2017&type=player&count=11&... MY CLUB opened
```
**No `ut/delete/auth`.** Every one of the seven previous attempts logged that logout
within a second or two. This run has none. Profile on disk afterwards: 109 items, 86
in the club pile, all five new ids present.
### What this settles
The failure was always the response body. The client was told, by every body this
project ever returned including a bare `{}`, that the move had **failed**, and it
ended the FUT session because that is what `EVENT_CARDS_MOVE_CARD_FAILURE` does. The
"deciding factor is client-side state, not the wire" premise recorded in §14c was
wrong, and §16 explains exactly which truncated decompile produced it.
Both defaults are flipped in `utas_server.py`: `FUT_MOVE_BODY=ack`, and
`FUT_PACK_AUTOCLUB` now defaults **off**.
### New intel: the request shape
Captured for the first time (the client had never successfully reached this path):
```json
{"itemData":[{"id":100000125,"pile":"club","swap":0,"tradeId":0}, ...]}
```
`swap` and `tradeId` accompany `id` and `pile`. We ignore both and the move
succeeded, so neither is load-bearing for a pending-pile-to-club move. `TODO/CONFIRM`
what `swap` means for a squad-slot exchange, where it plausibly is.
### Process note, and it is not a small one
The first attempt at this test produced **no `PUT /item` at all**. `FUT_PACK_AUTOCLUB=1`
had already emptied the pending pile at purchase time, so the reveal screen had
nothing to assign and the client never issued the request. The workaround for the bug
was hiding the bug: it removed the only path that exercises the broken endpoint.
The lesson generalises. A workaround that suppresses a request suppresses the evidence
too. Before testing a fix, check that the configuration still lets the client make the
call the fix is for.
Two self-inflicted incidents in the same session, both worth recording:
- The server was restarted to inject the flag **while FIFA was already running**, and
the user walked into the FUT hub during the roughly 30-second window with nothing on
:8099. That produced the exact "error connecting to FIFA 17 Ultimate Team" dialog
this project has spent weeks chasing, from a plain connection refusal. Restart only
when the client is at the main menu, and check the log for `ProtoHttp` requests
before attributing a failure to a response.
- `pgrep -f`/`pkill -f` matched the running shell twice, because the command that ran
the pattern also contained the literal script name in a later clause. It killed the
invoking shell before it reached the restart. Split the kill and the start into
separate commands, or obfuscate every occurrence.
+23 -3
View File
@@ -109,6 +109,20 @@ prove there is no second one.
## 2. The next task
> **RESOLVED 2026-08-04, the same day this was written.** The launch happened and it
> worked: five cards sent to the club, session survived, no logout, cards persisted.
> `FUT_MOVE_BODY=ack` and `FUT_PACK_AUTOCLUB=0` are now the defaults. Full account in
> `REBUILD_RESEARCH.md` §17. The section below is left as written, because the
> reasoning that put this first is the part worth reusing, not the outcome.
>
> One correction it earned: the pre-launch instruction to leave `FUT_PACK_AUTOCLUB=1`
> alone was wrong. Autoclub empties the pending pile at purchase time, so the reveal
> screen has nothing to assign and the client never sends the request. The first
> attempt produced no `PUT /item` at all. A workaround that suppresses a request
> suppresses the evidence for the bug it works around.
>
> **The next task is now §3.2, the MY CLUB counter.**
**One launch. Set `FUT_MOVE_BODY=ack`. Buy a pack, reveal it, press Send to Club.**
Everything about this is cheap. One flag, one existing menu path, one binary outcome
@@ -163,7 +177,13 @@ it, because an entry that cannot be disconfirmed does not belong on this list.
### 3.1 `Send to Club` under `FUT_MOVE_BODY=ack`
Covered in §2. Cost: one launch, one flag. Status: staged, unrun.
Covered in §2. Cost: one launch, one flag. **Status: run, and it worked.** Both flags
are now defaults. The measurement cost one pack and about four minutes.
Worth noting against the "what could make this plan wrong" section below: the outcome
was the good one, but §7's warning still stands unchanged for everything else. This
result does not make the rest of the queue more likely to be right; it makes the
method more likely to be right.
### 3.2 The MY CLUB counter, `GET /club/stats/<mode>`
@@ -417,8 +437,8 @@ corrected and must be fixed before they propagate into `ENDPOINT_MAP.md`.
|---|---|---|
| `FUT_MASSINFO` | `full` | live-proven; boot-critical |
| `FUT_USERINFO` | `roster` | live-proven; `packs` and `full` are untested rungs |
| `FUT_PACK_AUTOCLUB` | `1` | the `Send to Club` workaround; revisit after §2 |
| `FUT_MOVE_BODY` | `empty` | `ack` is correct-by-decompiler but sufficiency is unproven |
| `FUT_PACK_AUTOCLUB` | `0` | the workaround is retired; it emptied the pending pile and suppressed the move request entirely |
| `FUT_MOVE_BODY` | `ack` | **live-proven 2026-08-04**; `empty`, `full` and `dreamsquads` all report the move as failed |
| `FUT_MARKET` | `sample` | live-proven |
| `FUT_MODES` | off | changes `/season`, whose array-root freeze risk cannot fire unless the client asks, and it has never asked |
| `FUT_ACCOUNTINFO` | off | queued at §3.4 |
+42 -42
View File
@@ -617,53 +617,53 @@ def defs_route(h):
return 200, {"itemData": [item_def(i) for i in ids]}
# ---- pack reveal workaround ---------------------------------------------------
# THE REVEAL HAND-OFF IS UNSOLVED. Live 2026-08-04, five packs, three different
# response shapes for FutMoveCard (full card objects / +chemistry / dreamSquads-only):
# every time the client moved the cards, then POSTed ut/delete/auth ~1s later and
# dropped to the main menu. No crash dump; Blaze keeps pinging afterwards, so the
# game is alive and it is the FUT SESSION that ends. The cards always arrive
# server-side -- only the acknowledgement is rejected.
# ---- pack reveal: SOLVED 2026-08-04 -------------------------------------------
# The reveal hand-off worked live: five cards, Send to Club, session survived, cards
# persisted, no ut/delete/auth. See MOVE_BODY below for what actually fixed it.
#
# What is known: FutMoveCard's deserializer 0x180128600 contains NO skip handler
# (FUN_180135ff0 appears zero times, unique among FUT deserializers) and parses only
# itemData(0x16b) -> element -> dreamSquads(0xe9). Sending exactly that still failed,
# so the trigger is elsewhere and remains unidentified.
#
# WORKAROUND (default ON, FUT_PACK_AUTOCLUB=0 disables): deposit pack contents
# STRAIGHT into the club at open time and keep the pending pile empty, so the client
# is never offered a move to make and never sends the request that kills the session.
# Cost: the reveal screen shows no cards to assign. Benefit: packs are usable and the
# cards are in the club, which is the point of buying one. Turn this off when the
# real hand-off is understood.
PACK_AUTOCLUB = os.environ.get("FUT_PACK_AUTOCLUB", "1") == "1"
# The workaround this block used to describe (deposit pack contents straight into
# the club at open time, keep the pending pile empty) is now DEFAULT OFF. It was
# always a cost, not a fix: with the pending pile empty the client has nothing to
# assign, so the reveal screen shows no cards AND the move request is never sent.
# That second effect made the real bug untestable -- the first live attempt at the
# correct response shape produced no PUT /item at all because autoclub had already
# emptied the pile. Leave this off unless the move path regresses.
PACK_AUTOCLUB = os.environ.get("FUT_PACK_AUTOCLUB", "0") == "1"
# FUT_MOVE_BODY -- what PUT ut/%s/item answers. Made switchable so the shape can be
# bisected in one relaunch each instead of a code edit per attempt.
# ack -> {"itemData":[{"id":N,"pile":"club","success":true}, ...]}
# the CORRECT shape per the deserializer. UNTESTED LIVE.
# empty (default) -> {} KNOWN-BROKEN, see below
# full -> echo the moved card objects (known-broken)
# dreamsquads -> {"itemData":[{"dreamSquads":[]} x N]} (known-broken)
# FUT_MOVE_BODY -- what PUT ut/%s/item answers.
# ack (default) -> {"itemData":[{"id":N,"pile":"club","success":true}, ...]}
# LIVE-PROVEN 2026-08-04. Five cards sent to club, session
# survived, cards persisted, NO ut/delete/auth logout.
# empty -> {} known-broken
# full -> echo the moved card objects known-broken
# dreamsquads -> {"itemData":[{"dreamSquads":[]} x N]} known-broken
#
# `empty` REMAINS THE DEFAULT ONLY BECAUSE `ack` HAS NOT BEEN LIVE-TESTED.
# It is not a good default: 0x180128600 builds per-item VERDICT records and the
# completion handler raises EVENT_CARDS_MOVE_CARD_FAILURE when the record vector is
# empty, so {} fails unconditionally. Every rung above except `ack` is now known to
# report the move as failed. Flip the default to `ack` the moment one launch
# confirms it.
# WHY. 0x180128600 does not parse an acknowledgement, it builds per-item VERDICT
# records, and the completion handler raises EVENT_CARDS_MOVE_CARD_FAILURE when the
# record vector is EMPTY or when record+0x0c != 1. success(0x2fa) is initialised to
# '\0' per element. So {} and every echo shape reported the move as FAILED -- the
# session died because we told it to.
#
# The earlier note here argued the opposite -- that a bare {} was "demonstrably
# acceptable" because Quick Sell survives one. That inference was wrong: quick
# sell's callbacks check only the transport code and never read the body, so its
# tolerance says nothing about this endpoint. Retained as a caution: a sibling
# endpoint accepting a body is not evidence that this one will.
# The request the client actually sends (captured live, first time ever):
# {"itemData":[{"id":100000125,"pile":"club","swap":0,"tradeId":0}, ...]}
# so `swap` and `tradeId` accompany id/pile. We ignore both; the move succeeded
# without honouring them.
#
# Still true and still useful from that round: the netwatch recorded ZERO
# non-loopback connections during a failure, so "error connecting to FIFA 17
# Ultimate Team" is FIFA's generic FUT-session failure text and must not be read as
# a network event.
MOVE_BODY = os.environ.get("FUT_MOVE_BODY", "empty")
# HISTORY, kept because the wrong version of it cost seven attempts. This file used
# to claim 0x180128600 had NO skip handler and parsed only itemData -> dreamSquads.
# Both false: two skip-handler sites, seven atoms. The claim came from searching a
# TRUNCATED decompile (src[:4000] of 6193 chars). It implied "the body cannot be the
# problem", which sent the investigation after client-side state. It was the body.
# See REBUILD_RESEARCH.md S16.
#
# Also retracted: the argument that a bare {} was "demonstrably acceptable" because
# Quick Sell survives one. Quick sell's callbacks read only the transport code and
# never touch the body, so its tolerance said nothing about this endpoint.
#
# Still true from that round: the netwatch recorded ZERO non-loopback connections
# during a failure, so "error connecting to FIFA 17 Ultimate Team" is FIFA's generic
# FUT-session failure text and must never be read as a network event.
MOVE_BODY = os.environ.get("FUT_MOVE_BODY", "ack")
# FUT_STORE_GROUPS: send displayGroup(0xd9) in the pack catalogue. DEFAULT OFF --
# it FROZE the store screen live on 2026-08-04 (recursive nested array through the