Full investigation record for bug 6c: baseline + Experiments A/B/C', Candidate F (contradicted), the explicit active-placeholder selection test, the minidump-confirmed CardsDLL crash, and the P2 decision. Marks ROOT CAUSE ESTABLISHED and documents the known UX limitations and the client-side follow-up. Files: docs/evidence/STORE_TILE_6C.md, docs/evidence/FIFA17_EMPTY_MYPACKS_CLIENT_CONTRACT.md, the four genuine /store/purchasegroup captures (baseline, mypacks70, empty_no_sentinel, active_placeholder), and docs/plans/FIFA17_EMPTY_MYPACKS_CLIENT_FIX.md (client-side design/research).
6.6 KiB
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 frompurchase[]; amypacksgroup exists iff some pack hasdisplayGroup.value=="mypacks".group+0x104=(value=="mypacks"), tiles ingroup+0x40, ordinal ingroup+0x00(1-based creation order).FUN_18007dab0→FUN_1800147f0(model, screen+0x290, …)— renders/resolves a category.screen+0x290==0lists group tiles (FUN_180014610); otherwiseFUN_180014420exact-matches the ordinal and returns NULL on a miss, after whichFUN_1800147f0dereferences[RAX+0x48]with no null guard → crash at0x180014882(ACCESS_VIOLATIONread of0x48, minidump-confirmed).screen+0x290is written in exactly two CardsDLL sites: ctorFUN_18007d1a0writes0;FUN_18007e7f0case0x7551copies the Flash movie message fieldCATEGORY_IDverbatim into it. So the category is chosen by the Scaleform movie.FUN_18007e5e0binds the six store tabs (FUN_180014580:mypacks, points, bronze, silver, gold, special) toPANEL_ID= matching group ordinal, or hides the panel.- Unopened-pack count signals (server, already correct at 0 when empty):
userInfo.unopenedPacks.recoveredPacksand/user/credits .unopenedPacks. The hubCentralUnclaimedPacktile (destinationGOTO_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 theCentralUnclaimedPacktile). - 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_IDis 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
mypacksis never resolved when absent. - Evidence:
screen+0x290 ← CATEGORY_ID(FUN_18007e7f0case0x7551); 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_180014420returns 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
mypacksgroup 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_IDselection 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
mypacksgroup 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.