Files
OpenFUT/fifa17-recon/tools/fut_flow.md
T
funman300 6ddd5e9d47 fifa17-recon: offline FUT squad-shell working + full card-system RE
Milestone: FIFA 17 Ultimate Team boots end-to-end on our offline backend
past every EA gate into the hub and a live Squads editor (correct 4-4-2,
5-star squad, no freezes).

Key findings this session:
- userMassInfo MUST stay {} (any content desyncs the massinfo parser
  0x180174630 -> tokenizer busy-loop freeze). Deliver the squad via
  GET /squad/0 (fetched on Squads-tab entry) instead.
- Player cards render generic because the card view-model (0x1800d7920)
  reads identity/rating/face from a resolved record at item+0x10, filled
  by a lookup (0x18011cca0) in the FUT item-definition std::map at
  CardsDb+0x160c0 -- which is EMPTY offline -> default blank record.
- Version advertising (itemDbVersion/checkServerDbVersion) is proven inert
  (JSON fields routed to the skip handler). Owned items don't auto-trigger
  a definition fetch. In-place map overwrite is dead (map stays empty).
- Definition-serving endpoints (item/resource, defid, item?idList) built +
  ready; the fetch trigger lives in the packed FIFA17.exe.

New: docs/CARD_SYSTEM.md (findings + ordered next-steps plan for real
player cards: patch-POC, dbdata extractor, drive FIFA17.exe fetch, or
live-memory store injection). Plus tools: fut_seed.py (squad ladder +
definition serving), fifadrive.sh, vgamepad.py, and the login-RE toolset.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PN5bmpDVQR1aXgefyWAt7o
2026-08-01 20:24:30 -07:00

350 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# FIFA 17 — FUT online-login flow graph + auth state machine (2026-07-30)
**Target:** FIFA17.exe, ImageBase `0x140000000`, Wine flat map. Live PID 3362053 (read-only
`/proc/pid/mem`) until it exited mid-session; the rest is from captured dumps
(`navregion.bin`, `loginreg.asm`) and the on-disk `.srdata` (plaintext in the file; `.text`
is packer-encrypted on disk, so no further static disassembly is possible without a live PID).
**Clean-room:** everything below comes from our own `FIFA17.exe` (live memory + its own
plaintext `.srdata`), our own nav JSON as loaded by that binary, and our own captured
LSX/Blaze traffic. No leak material.
---
## 0. Answer in one paragraph
`origin.nav` **passes** and the flow **does** reach `startFutBlazeLogin`. `onlineLoginFlow.nav`
turns out to be a pure UI shell — it contains **no login logic at all**; it only emits
`sendScreenEvent ["OnlineLogin","0"]` and then waits for the C++ to fire `loginSuccess` /
`loginFail` / `evt_onlineLoginFailurePopup`. The real state machine is the **OSDK
`LoginController` / `LoginStateMachineImpl`** (OSDK `8.01.03.00-fifa.01`) inside FIFA17.exe.
That machine ran `LoginStateConnect` (Blaze connect + `Util::preAuth` — observed on the wire)
and `LoginStateLoadConfig` (`Util::fetchClientConfig` ×6 — observed), **but our Blaze
responder answers all six `fetchClientConfig` calls with an EMPTY payload.** The next step is
an *account-info* step that needs values from that config (`blazeSdkClientId`,
`blazeSdkClientSecret`, `blazeServerClientId`, `identityRedirectUri`). With an empty config it
fails **locally, emitting zero network traffic** (no further LSX verb, no HTTP to the Nucleus
stub, no further Blaze RPC), raises the OSDK event `EVENT_LOGIN_FETCH_ACCOUNT_INFO_FAILURE`
("Unable to retrieve account information. Please try again."), and short-circuits to
`LoginStateLogout``Authentication::logout` (1/0x46) → disconnect. **`GetAuthCode` and
`Authentication::login` (1/0x0A) live downstream in `LoginStatePCLogin` and are therefore
never reached.** The prerequisite is not a pushed LSX event and not a persona/entitlement
check — it is **a populated Blaze client configuration**.
---
## 1. The full node path (recovered verbatim from live nav JSON)
Nav JSON is resident in one heap region (`0xba5b0000-0xbc620000` this run, dumped to
`scratchpad/navregion.bin`). 152 `.nav` files are referenced; all are resident. Frostbite path
of the login flow: `data/ui/nav/online/onlineLoginFlow.nav`.
```
mainMenu
└─ launchFUTFlow external /online/origin.nav
outputs: OriginIsOnlineTrue -> startFutBlazeLogin
quit -> mainMenu
└─ futBlazeLogin external /online/onlineLoginFlow.nav
inputs: startFutBlazeLogin -> startLoginWithoutMultiplayerCheck
outputs: loginSuccess -> CheckFUTRosters
loginFail -> mainMenu
└─ CheckFUTRosters external /checkFUTRostersFlow.nav
outputs: advance -> postFUTBlazeLogin
back -> mainMenu
└─ postFUTBlazeLogin onEnter: invoke evt_sign_out_flow_ready, evt_invite_flow_not_ready
transitions: advanceRequest -> futFlow
└─ futFlow external /fut/futFlow.nav
```
### 1a. `origin.nav` — complete (2 nodes). This gate is PASSING now.
```json
{ "name":"origin", "states":[
{ "name":"preCheckCoopOrigin",
"onEnter":[ ["sendAction",["checkOriginConnected"]] ],
"transitions":[ {"event":"OriginIsOnline","targets":["OriginIsOnlineTrue"]},
{"event":"OriginIsOffline","targets":["OriginOfflinePopup"]} ] },
{ "name":"OriginOfflinePopup",
"onEnter":[ ["loadView",["popup","OriginOfflinePopup",
"TXT_HUB_SCREEN_ORIGIN_ONLINE_CHECK|OK|popupYes"]] ],
"transitions":[ {"event":"popupYes","targets":["quit"]} ] } ] }
```
FeFlow action ids: `checkOriginConnected` = 0x27e1, `OriginIsOnline` = 0x27e2,
`OriginIsOffline` = 0x27e3. Backing predicate `g_originOnline @0x1443337f8`, written only by
the LSX `GetInternetConnectedState` callback `0x146f1e6b0`, which also broadcasts
`FE::FIFA::OriginOnlineEvent`.
### 1b. `onlineLoginFlow.nav` — complete, and it contains NO login logic
```
onlineLoginFlow
onEnter: loadViewModel OnlineLoginViewModel
onExit : unloadViewModel OnlineLoginViewModel
(outer transitions, i.e. events the C++ can raise at any time)
onlineLoginToEaPopup -> onlineLoginToEaPopup
onlineBootLoginToEaPopup -> onlineBootLoginToEaPopup
evt_onlineAlertPopup -> onlineAlertLoginPopup
evt_onlineBootLoginFailurePopup -> onlineFailureLoginPopup
evt_onlineLoginFailurePopup -> onlineFailureLoginPopup <<< OUR PATH
evt_online_disconnected -> processLoginFailure
loginIdle / membershipCheck / firstPartyCommerceCheck
processBootLoginFailure / processLoginFailure
states:
startLoginWithMultiplayerCheck onEnter: sendScreenEvent ["OnlineLogin","1"]
startLoginWithoutMultiplayerCheck onEnter: sendScreenEvent ["OnlineLogin","0"] <<< ENTRY
in_startSilentSignIn -> skipSilentSignInCheck (conditionAardvark SKIP_SILENT_SIGN_IN)
true -> loginFail
false -> executeSilentSignIn (sendScreenEvent SilentSignIn)
onlineLoginToEaPopup onEnter: sendAction onlineLoginPopupShow
onExit : onlineLoginPopupHide "LOGIN_POPUP"
cancelLogin -> cancelLoginToEA (sendScreenEvent CancelLoginToEA)
onlineBootLoginToEaPopup (same, boot variant)
onlineAlertLoginPopup onEnter: onlineLoginPopupShow / onExit: hide "ALERT_POPUP"
processLoginAlert (sendScreenEvent ProcessLoginAlert)
onlineFailureLoginPopup onEnter: sendAction onlineLoginPopupShow <<< THE POPUP
onExit : onlineLoginPopupHide "ALERT_POPUP"
processLoginFailure -> sendScreenEvent ProcessLoginFailure
processBootLoginFailure -> sendScreenEvent ProcessBootLoginFailure
loginIdle (empty)
membershipCheck popup MembershipCheckPopup / TXT_CHECKING_MEMBERSHIP_LEVEL
firstPartyCommerceCheck popup FirstPartyCommerceCheckPopup
TrialWelcome -> TrialWelcomeCheck (sendAction trialCheck welcomeScreenCheck)
evt_goTrialWelcomeScreen -> TrialWelcomeScreen -> advance -> loginSuccess
evt_skipTrialWelcomeScreen-> loginSuccess
transitions: loginSuccess -> TrialWelcomeCheck ; loginFail -> loginFail(output)
```
**Key structural finding:** every node here is a popup or a `sendScreenEvent`. There is no
`GetAuthCode` node, no persona node, no entitlement node. The nav delegates 100 % of the login
to the C++ via one screen event, and only reacts to C++-raised events. So the question
"why no GetAuthCode" cannot be answered in the flow graph — it is answered in the OSDK login
state machine (§2).
### 1c. `checkFUTRostersFlow.nav` (downstream, never reached)
`LoadFUTDatabase` (condition `loadFUTDatabase`) → `LoadFUTSquad` (`AutoLoadFUTSquad`) →
`CheckFUTRosterUpdateXML` (`isFUTRosterXMLAvailable`) → `CheckFUTSquadBinFile` → … ;
failure → `FailFUTRosterXMLDownloadPopup` (`FUT_SQUAD_DOWNLOAD_FAIL`) / `unloadFUTDatabaseOnFail`.
`onEnter` loads `FIFAFutLoginViewModel`.
---
## 2. The real state machine: OSDK `LoginController`
Build tag in the binary: `E:/p4/fifafb/rl/empatch/TnT/Code/fifa/gamemodes/extern/OSDK/
8.01.03.00-fifa.01/source/...`
### 2a. States recovered (name string, vtable, registered id)
Each `LoginState*` class has an 8-byte `GetStateName()` stub (`lea rax,[str]; ret`) in the
block `0x14719b360-0x14719b4e8`; the stub sits at **vtable+0x08**, which pins vtable→name.
The registration function `0x147158e00-0x1471597xx` (dumped: `scratchpad/loginreg.asm`) does
`alloc → set vtable → set name → map-insert(machine, obj, id)`.
| id | state | vtable | name str |
|---|---|---|---|
| 50 | (unnamed, vt `0x14395bb90`) | `0x14395bb90` | — |
| 100 | `LoginStateCheckUser` | `0x14395bc30` | `0x14395c850` |
| 200 | `LoginStateIsp` | `0x14395bc78` | `0x14395bd08` |
| 300 | `LoginStateRecheckUser` | `0x14395bc30` | `0x14395c868` |
| **310** | **`LoginStateLoadIspAccountInfo`** | `0x14395bd18` | `0x14395bd60` |
| **400** | **`LoginStateConnect`** | `0x14395bd80` | `0x14395be08` |
| 500 | `LoginStateLogout` | `0x14395be80` | `0x14395bf28` |
| **700** | **`LoginStateVersionCheck`** | `0x14395bf40` | `0x14395bf88` |
| **800** | **`LoginStatePCLogin`** | `0x14395c180` | `0x14395c398` |
| 1000 | `LoginStateLoginComplete` | `0x14395c580` | `0x14395c5c8` |
| 1300 | `LoginStateVerifyAccount` | `0x14395c3b0` | `0x14395c4c8` |
| 1350 | `LoginStateUpgradeAccount` | `0x14395c4e0` | `0x14395c560` |
| — | `LoginStateLogin` (generic, non-PC) | `0x14395bfa0` | `0x14395c170` |
| — | `LoginStateLoadConfig` | `0x14395be20` | `0x14395be68` |
| — | `LoginStateShowMaintenance` / `LoginStateUnsuspend` / `LoginStateWebOffer` | `0x14395c5e0` / … | `0x14395bc10` / `0x14395c640` / `0x14395d2c0` |
Ctors: `0x1471610xx` = `LoginStateLogin`, `0x147161250` = `LoginStatePCLogin`,
`0x147161340` = `LoginStateVerifyAccount`, `0x147165ea0` = `LoginStateLoadConfig`.
State-machine map global: `0x144b86c08`; insert helper `0x14717c4f0(machine, state, id, 1)`.
NOTE: ids are an enum, **not** strictly a sequence (500 = Logout is a failure/teardown state).
### 2b. `LoginStateLoadIspAccountInfo` (id 310) — what it actually is
Its event handler is `0x1471b4b40` (vtable+0x20; vtable+0x10 = `0x1472243c0` = `[this+0x20]=0`
i.e. sub-state reset). It switches on a 4-way sub-state `[this+0x20]` and, in sub-states 0 and
1, does `GetComponent('cnnc')` on the OSDK singleton `0x144b86bf8` (`call [vt+0x60]`), then
`[vt+0x38]`, then `[vt+0xb0]` → returns an int country code, and tests it against `0` and
`0x5a5a` (= ASCII `"ZZ"`, the unknown-country sentinel). It also reads a 2-char country string
from `[0x144b86bf8 + 0xb0]` and validates `'A'..'Z'`/`'a'..'z'`.
**So "Isp account info" here = the ISP/connection-derived country/geo (feeds `SetPingSiteLatency`
/ ping-site selection), not the EA account.** That is consistent with it running *before*
`LoginStateConnect` (id 310 < 400) and with it having succeeded this run (Blaze connect
happened, `Country="US"` is served by our LSX `GetProfile`).
### 2c. The account-info fetch + its two failure exits (binary-pinned)
* `0x14727dc00`**starts** the fetch. It resolves a component/service
(`call [r8+0x60]`, obfuscated fourcc arg), then:
* success: `call [rax+0x40]` (kick async op) → store handle via `0x147173690` into `[this+0x1e0]`;
* **null service → immediately dispatches `EVENT_LOGIN_FETCH_ACCOUNT_INFO_FAILURE`
(`0x1439840b8`) at `0x14727dcbe`, with no network I/O at all.**
* `0x14727dd70` — the **completion callback** `(this, status, data)`:
* `status == 0`: copies 4 bytes from `data[0..3]` into `[this+0xa0..0xa3]`, then dispatches
`EVENT_LOGIN_FETCH_ACCOUNT_INFO_SUCCESS` (`0x1439840e0`, lea @ `0x14727de34`);
* `status != 0`: dispatches `EVENT_LOGIN_FETCH_ACCOUNT_INFO_FAILURE` (lea @ `0x14727de7b`).
* Dispatcher: `[[0x144b8f498]] + 0x8`, event-source string `0x14354b5f0`.
* Related adaptor-level events also present: `EVENT_ACCOUNT_FETCH_INFO_SUCCESS/FAILURE`
(`0x143983648` / `0x143983670`), adaptor actions `FetchAccountInfo` (`0x1439623c0`),
`UpdateAccountInfo` (`0x1439623d8`), `GetAccountInfo` (`0x14398aac8`),
`GetNucleusAccountInfo` (`0x14398a920`), `OSDK_NucleusAdaptor` (`0x14398b038`).
### 2d. The `GetAuthCode` chain (only reachable from `LoginStatePCLogin`)
```
FifaOnline::FirstPartyAuthTokenRetriever::DoTick 0x146f199c0
walks pending list [this+8]; per entry:
0x1470da6d0 OriginGetDefaultUser/singleton getter
0x1470db3c0 OriginRequestAuthCodeSync (log str 0x143936158)
gate 0x1470e2840 "Origin SDK available?" -> false: return 0xa0010000
else 0x1470e3560 get SDK
0x1470e67f0 Origin::OriginSDK::RequestAuthCodeSync (0x143937d68)
-> LSX <GetAuthCode ClientId="..."/> -> <AuthCode .../>
status != 0 -> "[%s] Origin Error(%d)" 0x1438f5e18 (lea @0x146f19ab2)
len==0 || ptr==0 -> "[%s] Invalid authcode" 0x1438f5e00 (lea @0x146f19a85)
ok -> build FifaOnline::FirstPartyAuthCodeFutureImpl (0x1438f5d98, lea @0x146f19a39),
store [entry+0xd8], set [entry+0xe8]=1
```
`DoTick` only does anything if the pending list `[this+8]` is non-empty — i.e. only if some
upstream state actually *requested* a first-party token. Nothing requested one this run
(`/tmp/openfut_authcode.txt` empty, no `GetAuthCode` in `/tmp/lsx.log`).
Auth-code consumer parameters, all present in `.rdata`:
`client_id=` `&client_secret=` `&scope=` `&redirect_uri=` `&code=` `&grant_type=`
`authorization_code` `connect/token` (`0x456f58-0x457010` file offsets), scope literal
`signin basic.identity basic.persona basic.domaindata offline`, redirect
`http://127.0.0.1/login_successful.html` (`0x143b04870`), `Nucleus::gNucleusBaseUrl` /
`gNucleusClientSideRedirectUri` (`0x143b8b718` / `0x143b8b778`).
---
## 3. Where it dead-ends, and on what it waits
### Observed reality this run
| layer | evidence |
|---|---|
| LSX | `/tmp/lsx.log` ends at id 19 (`GetGameInfo UPTODATE -> "true"`). **No further LSX request of any kind.** No `GetAuthCode`. |
| Nucleus/HTTP | zero requests to the `nucleusConnect` stub on `127.0.0.1:42131`; no dial to accounts/gateway/nucleus.ea.com. |
| Blaze | connect → `Util::preAuth` (9/0x07) → `Util::ping``Util::fetchClientConfig` (9/0x01) ×6 for `OSDK_CORE`, `OSDK_CLIENT`, `OSDK_NUCLEUS`, `OSDK_WEBOFFER`, `OSDK_ABUSE_REPORTING`, `OSDK_XMS_ABUSE_REPORTING`**all answered EMPTY by `blaze_responder_v3.py`**`Authentication` 1/0x46 = **`logout`**, empty payload → socket closed → 3-second transport-PING reconnect loop (`/tmp/blaze_responder.log`, still looping at the time of writing). **No `Authentication::login` (1/0x0A) ever.** |
| UI | popup text `"Unable to retrieve account information. Please try again."` resident at `0xb79ad020` / `0x78f5380` / `0xbc81ba70`, UI-side id string `KEY_2002` adjacent at `0xb79ace58`/`0xb79ad068`. |
### Mapping that onto the state machine
`LoginStateConnect` (400) ran and **succeeded** (preAuth on the wire). `LoginStateLoadConfig`
ran and **completed** (six replies received) but with **empty config maps**. The very next
step is the account-info step, and it failed **without producing a single byte of network
traffic on any of the three transports**. A timeout or a rejected request would have produced
traffic. A local precondition failure would not — and that is exactly the shape of the
`0x14727dc00` early-out (`service == null → EVENT_LOGIN_FETCH_ACCOUNT_INFO_FAILURE`, no I/O).
The state machine then entered `LoginStateLogout` (500), which is what emits the observed
`Authentication::logout`.
### The prerequisite, precisely
**`startFutBlazeLogin` does require a prerequisite before `GetAuthCode`, and it is the Blaze
client configuration delivered by `Util::fetchClientConfig` — specifically the identity block.**
The four config keys the client needs are literals in `.rdata`, adjacent, right after
`QueryEbisuCallback`:
| key | file offset | what it feeds |
|---|---|---|
| `blazeServerClientId` | 0x458c90 | Blaze-side identity |
| `blazeSdkClientId` | 0x458ca8 | the `ClientId` attribute of the LSX `<GetAuthCode ClientId="…"/>` request (attribute name confirmed in the LSX attribute pool at `0x14394e098`) |
| `blazeSdkClientSecret` | 0x458cc0 | `&client_secret=` in the Nucleus `connect/token` exchange |
| `identityRedirectUri` | 0x458cd8 | `&redirect_uri=` (pairs with `http://127.0.0.1/login_successful.html`) |
With `fetchClientConfig` returning empty, none of these exist, so:
1. the account-info/identity service has nothing to initialise from → fails locally →
`EVENT_LOGIN_FETCH_ACCOUNT_INFO_FAILURE`**"Unable to retrieve account information."**;
2. the `FirstPartyAuthTokenRetriever` pending list is never fed → `DoTick` never calls
`OriginRequestAuthCodeSync`**LSX `GetAuthCode` is never sent**;
3. `LoginRequest.AUTH` (`Blaze::Authentication::LoginRequest` @ `0x14487ca10`, members
`AUTH`/`EXTB`/`EXTI`) can never be filled → `LoginStatePCLogin` (800) is skipped →
**`Authentication::login` (1/0x0A) is never sent**;
4. `LoginStateLogout` (500) tears the session down → the observed `1/0x46`.
### Verdict on the three prior hypotheses
* **(a) a pushed LSX `<Event>` "user logged in"** — **not supported.** Origin is already
`connected="1"`, the flow demonstrably got past `origin.nav` into Blaze, and the client
stopped asking LSX anything at all after `UPTODATE`. A pushed event is not what it is
waiting on. (`IsLoggedIn` does exist in the LSX attribute pool at `0x14394e0f0` and
`Origin::EventHandler<lsx::LoginT,unsigned int>::HandleMessage` at `0x14393f900`, so a
`Login`/`IsLoggedIn` LSX event *is* implementable — but nothing here indicates it is the gate.)
* **(b) an account-info step fails first and aborts before `GetAuthCode`** — **CONFIRMED**,
and its missing input is identified: the Blaze client config.
* **(c) `origin.nav` / `OriginOnlineEvent` not firing** — **REFUTED.** `origin.nav` is a
two-node flow, and everything downstream of `OriginIsOnlineTrue` (Blaze connect, preAuth,
fetchClientConfig) demonstrably ran.
---
## 4. Recommended next action (single change, testable)
Stop returning empty `Util::fetchClientConfig` (9/0x01) replies in
`fifa17-recon/tools/blaze_responder_v3.py`. Return a populated `CONF` string→string map per
CFID, minimally:
* `OSDK_NUCLEUS`: `blazeSdkClientId`, `blazeSdkClientSecret`, `blazeServerClientId`,
`identityRedirectUri` (= `http://127.0.0.1/login_successful.html`), plus the
`NUCLEUS_*_URL` set (`NUCLEUS_CREATE_URL`, `NUCLEUS_ADDED_URL`, `NUCLEUS_INCOMPLETE_URL`,
`NUCLEUS_CREATE_INFO_URL`, `NUCLEUS_DUPACCT_INFO_URL`, `NUCLEUS_DEACTIVATED_INFO_URL`)
pointed at our own stub.
* `OSDK_CORE`: at minimum `SV_ENABLE_SERVER_VERSIONING=0` (else `LoginStateVersionCheck`
(700) can trip "Client/server version mismatch! Client is at version (%08d)…",
`0x14395d1d0`), plus `netres`.
* `OSDK_CLIENT`, `OSDK_WEBOFFER`, `OSDK_ABUSE_REPORTING`, `OSDK_XMS_ABUSE_REPORTING`:
non-empty but can stay minimal. (`OSDK_TICKER` is a seventh CFID the client knows about.)
**Success signals, in order:** (1) the client stops sending `1/0x46` right after the six
config fetches; (2) `/tmp/lsx.log` shows a `GetAuthCode` request carrying `ClientId=` and
`/tmp/openfut_authcode.txt` becomes non-empty; (3) Blaze receives `Authentication::login`
(1/0x0A) with `AUTH=<our auth code>`; (4) after our `LoginResponse` (5 members
ANON/NTOS/SESS/SPAM/UNDR, `0x14487d170`) plus `UserSessions` notifications `UserAdded` (0x02),
`UserSessionExtendedDataUpdate` (0x01), `UserAuthenticated` (0x08), the nav advances
`loginSuccess → TrialWelcomeCheck → CheckFUTRosters → postFUTBlazeLogin → futFlow`.
Keep identity consistent everywhere: PersonaId/UserId `33068179`, Persona `CAGE`, `en_US`,
contentId `1027460`, entitlement `ONLINE_ACCESS`.
---
## 5. Open / not pinned (be honest)
* The exact **emitting state** for the popup was not binary-pinned: the live PID exited before
I could disassemble the `OnlineLoginViewModel` message-index → loc-key mapping
(`0x147c3c050` online branch → `0x147d92a80(this, idx)` with idx 0x21/0x23/0x24/0x25 chosen
by `[vm+0x158]`, then `0x147c4bfd0` / `0x147c4ca50` / `0x147c4a4b0`). Attribution of the
string to `EVENT_LOGIN_FETCH_ACCOUNT_INFO_FAILURE` is by (i) exact semantic match, (ii) the
zero-network-traffic failure shape matching `0x14727dc00`'s early-out, (iii) elimination of
every other observed step. The alternative emitter is the adaptor-level
`EVENT_ACCOUNT_FETCH_INFO_FAILURE` (`0x143983670`) — same root cause either way.
* Whether `blazeSdkClientId` & co. arrive in `OSDK_NUCLEUS` vs `OSDK_CORE` is an inference from
the CFID set + key naming; the fastest resolution is empirical (put them in *both* and watch
which one the client consumes).
* The obfuscated fourcc component ids in `0x14727dc00` / `0x14727dd70` / `0x1471b4b40`
(`'cnnc'` = 0x636E6E63 for the ISP/connection component, and 0x6E756D67 for the one used by
the account-info starter) were reconstructed from `mov r8d/edx,K; lea …,[r+C]` pairs and are
worth re-checking on a live PID.
* `LoginStateLogin` (generic) vs `LoginStatePCLogin` (800): on PC the machine is expected to
use `PCLogin`; not re-verified at runtime.
## 6. Artifacts produced
| file | contents |
|---|---|
| `scratchpad/navscan.py` | live-memory nav/JSON keyword + blob scanner |
| `scratchpad/xr.py` | numpy rip-rel + absolute xref finder (FIFA17 module range) |
| `scratchpad/dis.sh` | dump live VA range + objdump at correct VA |
| `scratchpad/navregion.bin` | 34 MB heap region containing every loaded `.nav` (this run) |
| `scratchpad/navseg_origin_login.txt` | `onlineLoginFlow.nav` + `origin.nav` as text |
| `scratchpad/nav_root_fut.txt` | root.nav FUT chain (`launchFUTFlow``futFlow`) |
| `scratchpad/nav_checkfutrosters.txt` | `checkFUTRostersFlow.nav` |
| `scratchpad/loginreg.asm` | disassembly of the LoginState registration function |