Files
OpenFUT/docs/plans/FIFA17_EMPTY_MYPACKS_CLIENT_FIX.md
T
funman300 e8ee6c34e7 docs(fifa17): record empty My Packs client contract
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).
2026-08-13 01:08:47 +00:00

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 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_18007dab0FUN_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 guardcrash 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].

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.