# FIFA 17 — Clean Empty-My-Packs client fix (DESIGN / RESEARCH ONLY) Status: **design only — no client binary/movie changes made.** This is the client-side follow-up to bug 6c. The backend already ships a compatibility workaround (P2, active non-openable sentinel 65534; see `docs/evidence/STORE_TILE_6C.md` §17 and `FIFA17_EMPTY_MYPACKS_CLIENT_CONTRACT.md`). This plan describes what a *client-side* fix would need to change so the backend shim can eventually become unnecessary for patched clients. Do NOT patch the executable, DLLs, or Scaleform movies in this task. ## 1. Established client-side evidence Binary: `CardsDLL_Win64_retail.dll` SHA-256 `4706a881ae1fc7b5769fd810b25a868d29d2b16a8e65a7513436327ef645573c` (dump load base `0x00006FFFFC120000`; RE-space base `0x180000000`). `FIFA17.exe` (`29c31cef…`) is Denuvo-packed (decrypts only in live memory). Store category pipeline (all decompiled; see `docs/plan-2026-08-05-store-subsystem.md`): - `FUN_1800150d0` — builds display groups from `purchase[]`; a `mypacks` group exists iff some pack has `displayGroup.value=="mypacks"`. `group+0x104=(value=="mypacks")`, tiles in `group+0x40`, ordinal in `group+0x00` (1-based creation order). - `FUN_18007dab0` → `FUN_1800147f0(model, screen+0x290, …)` — renders/resolves a category. `screen+0x290==0` lists group tiles (`FUN_180014610`); otherwise `FUN_180014420` exact-matches the ordinal and **returns NULL on a miss**, after which `FUN_1800147f0` dereferences `[RAX+0x48]` with **no null guard** → **crash at `0x180014882`** (`ACCESS_VIOLATION` read of `0x48`, minidump-confirmed). - `screen+0x290` is written in exactly two CardsDLL sites: ctor `FUN_18007d1a0` writes `0`; **`FUN_18007e7f0` case `0x7551` copies the Flash movie message field `CATEGORY_ID` verbatim** into it. So the category is chosen by the Scaleform movie. - `FUN_18007e5e0` binds the six store tabs (`FUN_180014580`: `mypacks, points, bronze, silver, gold, special`) to `PANEL_ID` = matching group ordinal, or hides the panel. - Unopened-pack count signals (server, already correct at 0 when empty): `userInfo.unopenedPacks.recoveredPacks` and `/user/credits .unopenedPacks`. The hub `CentralUnclaimedPack` tile (destination `GOTO_STORE_MYPACK`) is gated by this count in the hub model (`model+0x20950`). **Candidate F (a server count gating the STORE's My-Packs resolution) was CONTRADICTED**: the count is correct at 0 yet the store still resolves My Packs, because the decision is movie-side. ## 2. Desired clean client behavior ``` unopened-pack count == 0: Store defaults to Browse Packs (e.g. a real category such as bronze/gold) My Packs is NOT selected/resolved no synthetic placeholder tile is required from the server unopened-pack count > 0: existing My Packs behavior unchanged ``` ## 3. Candidate insertion points (ranked) Ranking favors fixing the UX (not merely preventing the crash) and the smallest, lowest-risk change that achieves it. ### Rank 1 (preferred, best UX) — Scaleform / category-selection layer Prevent the movie from emitting `CATEGORY_ID == mypacks` (and from defaulting the store into My Packs) when the unopened-pack count is 0; default to Browse Packs instead. - **Where:** the FUT Store Scaleform movie / ActionScript (`StoreFront`, `CATEGORY_ID`/`ACTION_GET_PACKLIST`, `GOTO_STORE_MYPACK`), which the packed exe hosts and which reads the hub model (it already knows the count for the `CentralUnclaimedPack` tile). - **Behavior changed:** the store's initial/selected category when empty. - **Scope:** movie asset edit (client-side), no native-code patch. - **Risk:** medium — Scaleform RE/editing is fiddly; must find where the default `CATEGORY_ID` is chosen and gate it on the count without breaking the count>0 path. - **Compatibility:** per-client asset change; does not touch protocol or other clients. - **Fixes UX or just crash?** **UX** — no fake tile, correct default; the crash also disappears because `mypacks` is never resolved when absent. - **Evidence:** `screen+0x290 ← CATEGORY_ID` (`FUN_18007e7f0` case `0x7551`); count already available client-side (hub model / `unopenedPacks`). ### Rank 2 — Native Store resolver fallback (CardsDLL) Make `FUN_1800147f0`/`FUN_180014420` fall back to a safe category (e.g. list-tiles `N==0`, or the first existing group) when the requested ordinal misses, instead of dereferencing NULL. - **Behavior changed:** category-miss handling for ALL categories, not just mypacks. - **Scope:** small, localized CardsDLL binary patch near `0x180014420`/`0x180014882`. - **Risk:** medium — alters native store behavior globally; could mask other legitimate misses; the movie may still believe it is in My Packs (empty/odd view). - **Compatibility:** binary patch to the shipped DLL (client-side). - **Fixes UX or just crash?** Crash + partial UX (no crash, but the empty-My-Packs view may still be awkward). - **Evidence:** the no-guard deref at `0x180014882`; `FUN_180014420` returns NULL on miss. ### Rank 3 (cheapest, crash-only) — CardsDLL null guard Insert a null check before the `[RAX+0x48]` dereference in `FUN_1800147f0` (a single `TEST/JZ` around the deref) so a NULL group is skipped/returned safely. - **Behavior changed:** only the crash path. - **Scope:** minimal (a few bytes) binary patch at `~0x180014882`. - **Risk:** low — smallest change; but purely crash-prevention. With no `mypacks` group the resulting empty view is unverified (could be a blank/empty-category state). - **Compatibility:** binary patch (client-side). - **Fixes UX or just crash?** Crash only. - **Evidence:** minidump faulting instruction `CardsDLL+0x14882`, `[NULL+0x48]`. ## 4. Recommended long-term outcome Rank 1 (Scaleform default-category gating) is the clean fix: with count 0 the store opens on Browse Packs, no `mypacks` resolution, no fake tile — and the **backend sentinel 65534 can be dropped for patched clients** (the server would simply omit the `mypacks` group when empty, which is safe once the client no longer resolves it). Rank 3 (null guard) is a cheap universal crash-safety net that could ship alongside. Until a client-side fix exists, the backend P2 sentinel remains the required compatibility behavior for unpatched retail clients. ## 5. Open questions / next research (no execution here) - Locate the Store movie's default/initial `CATEGORY_ID` selection and confirm it can read the unopened count (Rank 1 feasibility). - Confirm, via a guarded-resolver experiment, what the empty-My-Packs view degrades to if the `mypacks` group is simply absent + a null guard is present (Rank 2/3). - Determine whether the Browse-Packs→My-Packs navigation gate (observed with the active sentinel) also resolves under Rank 1.