6ddd5e9d47
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
350 lines
21 KiB
Markdown
350 lines
21 KiB
Markdown
# 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 |
|