# FIFA 17 — "account information" retrieval path, end to end **Date:** 2026-07-30 · **Target:** live `FIFA17.exe` (base `0x140000000`, Wine flat PE map), plus the saved live-string dump `modstrings.txt` after the process exited mid-analysis. **Clean-room provenance:** every fact below comes from (a) static/dynamic analysis of binaries we own (`FIFA17.exe` as mapped in our own process, `stp-origin_emu.dll`), (b) our own client's observed LSX/Blaze wire traffic (`/tmp/lsx.log`, `/tmp/blaze_responder.log`), and (c) the client's own runtime reflection/metadata. **No 2021 EA/FIFA leak material was used or consulted.** Builds on `auth_schema_reflection.md`, `auth_statemachine.md`, `origin_nucleus.md`. --- ## 0. Verdict (short) **Account information is carried on the Blaze channel, but it is *gated* by the LSX/Origin channel. The Nucleus/account HTTP channel is not part of the PC login path at all.** * **LSX/Origin (`127.0.0.1:4216`)** supplies the *identity* and the *credential*: `GetProfile` → the OriginSDK's default **user id / persona id**; `QueryEntitlements` → `ONLINE_ACCESS`; **`GetAuthCode` → the one-time Origin auth code**. * **Blaze (Fire2, `:42130`)** supplies the *account record*: `Authentication::login (1/0x0A)` takes the auth code in `LoginRequest.AUTH` and returns `LoginResponse.SESS` (session key, blazeUserId, userId, email, persona details) — this **is** "account information" for the login flow. The explicit account RPCs (`getAccount 1/0x1E → AccountInfo`, `getPersona 1/0x5A`, `listPersonas 1/0x64`, `listUserEntitlements2 1/0x1D`) all live *behind* that login. * **Nucleus/account HTTP (`:42131`)** is unused **by design of this code path**, for two independent reasons proven below (§4). It is not a missing piece; it is a dead end for PC/Origin login. **The exact missing piece:** `LoginRequest.AUTH` (`authCode`, a plain string) can only be filled by LSX `GetAuthCode`, and the client never issues it. No auth code → no `Authentication::login` → no `LoginResponse` → nothing that can answer "account information" → popup. Everything downstream (`getAccount`, `listPersonas`, FUT `user/accountinfo`) is unreachable. --- ## 1. Channel 1 — LSX / Origin on `127.0.0.1:4216` ### 1.1 Complete OriginSDK LSX surface (from the client's own template instantiations) `Origin::LSXRequest<>` / `LSXEnumeration<>` instantiations present in the image: | Request | Response | Notes | |---|---|---| | `GetConfigT` | `GetConfigResponseT` | | | `GetProfileT` | `GetProfileResponseT` | **sets the default user/persona — see §1.2** | | `GetSettingT` | `GetSettingResponseT` | | | `GetGameInfoT` | `GetGameInfoResponseT` | `UPTODATE` gate | | `GetInternetConnectedStateT` | `InternetConnectedStateT` | the online gate | | **`GetAuthCodeT`** | **`AuthCodeT`** | handler `0x1439385b0`, callback `0x143938660` | | `QueryEntitlementsT` | `QueryEntitlementsResponseT` → `OriginItemT` | enumeration, `0x1439484e0` | | `GetUserProfileByEmailorEAIDT` | `…ResponseT` → `OriginFriendT` | | | `IsProgressiveInstallationAvailableT`, `AreChunksInstalledT`, `QueryChunkStatusT`, `SetDownloaderUtilizationT`, `QueryOffersT`, `GetWalletBalanceT`, `CheckoutT`, `ConsumeEntitlementT`, `GetPresenceT`, `SetPresenceT`, `QueryFriendsT`, `GetBlockListT`, `SendInviteT`, `ShowIGOT`, `GrantAchievementT`, `ExtendTrialT`, `SelectStoreT` | | not on the login path | ### 1.2 `GetProfile` is what defines "who is logged in" — **proven** `OriginGetDefaultUser()` @ `0x1470da6d0` and `OriginGetDefaultPersona()` @ `0x1470da680` are bare field reads: ``` 1470da6d0 call 0x1470e2840 ; SDK-ready predicate 1470da6f4 call 0x1470e3560 ; get impl singleton 1470da6f9 mov rax,[rax+0x3a0] ; <<< default USER id 1470da680 ... mov rax,[rax+0x3a8] ; <<< default PERSONA id ``` Both fields are zeroed in the SDK constructor (`0x1470deb2f` / `0x1470deb36`, `rsi = 0`) and are written in exactly **one** place — `Origin::OriginSDK::Initialize` @ ~`0x1470e5a30`: ``` 1470e5ac7 call 0x147118d80 ; sync GetProfile(index=0), 15000 ms timeout (0x3a98) 1470e5acc test eax,eax 1470e5ace jne ... ; on failure, leave 0 1470e5ad0 mov rax,[rsp+0x70] 1470e5ad5 mov [rdi+0x3a0],rax ; <<< userId from GetProfileResponse 1470e5adc mov rax,[rsp+0x78] 1470e5ae1 mov [rdi+0x3a8],rax ; <<< personaId from GetProfileResponse ``` `0x147118d80` builds the request through `0x147117fe0` with `index = 0` and waits via `0x1471186f0`. The service name used is **`"EbisuSDK"`** (`0x143937d58`), which is why our responder must answer `GetProfile` with `sender="EbisuSDK"` — it already does. > **Consequence:** our `GetProfileResponse` with `UserId="33068179" PersonaId="33068179"` *is* the > mechanism that populates the SDK's identity. That part is working; the client asked `GetProfile` > three times (ids 3, 10, 17) and we answered correctly each time. ### 1.3 The auth-code request path — fully mapped `FifaOnline::FirstPartyAuthTokenRetriever::DoTick` @ **`0x146f199c0`**: ``` rbx = this+8 ; rbp = 2 ; two request slots: this+0x08, this+0x10 loop: rsi = [rbx]; if (!rsi) goto next ; nothing pending -> nothing happens, silently [rsp+0x60] = 0 ; out: auth-code buffer [rsp+0x58] = 0 ; out: auth-code length rax = call 0x1470da6d0 ; OriginGetDefaultUser() -> sdk[+0x3a0] r9 = &len ; r8 = &buf ; rdx = rsi+0x18 ; rdx = ClientId string from the request object rcx = rax ; user handle eax = call 0x1470db3c0 ; Origin::OriginSDK::RequestAuthCodeSync if (eax != 0) -> 0x146f19aab ; log "[%s] Origin Error(%d)" (0x1438f5e18) if (buf == 0 || len == 0) -> 0x146f19a7e ; log "[%s] Invalid authcode" (0x1438f5e00) ; success: store the code at rsi+0xd8, set rsi+0xe8 = 1 ``` `Origin::OriginSDK::RequestAuthCodeSync` @ **`0x1470db3c0`**: ``` 1470db3da lea rdx,[0x143936158] ; trace "OriginRequestAuthCodeSync entered" 1470db3f2 call 0x1470dbf30 ; trace 1470db3f7 call 0x1470e2840 ; <<< SDK-ready predicate 1470db3fe je 0x1470db424 ; NOT ready -> error 0xa0010000, NO LSX TRAFFIC AT ALL 1470db400 call 0x1470e3560 ; impl 1470db41d call 0x1470e67f0 ; impl->RequestAuthCodeSync(user, clientId, &buf, &len, 0) ``` **Signature (recovered):** `OriginRequestAuthCodeSync(OriginUserT user, const char* clientId, char** outBuf, size_t* outLen, ...)`. `clientId` comes from the request object at `+0x18` and is what lands in the LSX `` attribute (attribute name `ClientId` @ `0x14394e098`). **Note the wire-silent failure path:** if `0x1470e2840` returns false, `RequestAuthCodeSync` logs and returns `0xa0010000` **without ever touching the socket**. That is a failure mode that looks exactly like our symptom (nothing in the LSX log). It is, however, unlikely here, because the same predicate guards `OriginGetDefaultUser`, `OriginCheckOnline`, `OriginGetProfile` etc., all of which demonstrably worked. Ranked below in §5. ### 1.4 Pushed LSX **Events** — the SDK does support them, including `` Our responder is request-driven only and never pushes. The client's OriginSDK *does* register event handlers. Complete inventory of `Origin::EventHandler::HandleMessage`: | Element (name table `0x14393cfd8`–`0x14393d208`) | Payload type | |---|---| | **`Login`** @ `0x14393d0ac` | `unsigned int` | | `OnlineStatusEvent` @ `0x14393d120` | `bool` | | `ProfileEvent` @ `0x14393d0b8` | `OriginProfileChangeT` | | `CurrentUserPresenceEvent`, `PresenceVisibilityEvent`, `BroadcastEvent`, `IGOEvent`, `IGOUnavailable`, `MinimizeRequest`, `RestoreRequest`, `MultiplayerInvite`, `MultiplayerInvitePending`, `UserInvitedEvent`, `PurchaseEvent`, `ChatMessageEvent`, `GameMessageEvent`, `CoreContentUpdated`, `BlockListUpdated`, `AchievementSets`, `ChunkStatus`, `GroupEvent`, `GroupEnterEvent`, `GroupLeaveEvent`, `GroupInviteEvent`, `VoipStatusEvent` | various | `Origin::LSXEvent<>` (the *handshake* event path) is instantiated for `ChallengeT` **only** — that is the `` we already send. Everything in the table above goes through the `EventHandler`/`EventEnumerator` dispatch, whose element-name match is at `0x1471028ee` (it first compares the `sender` attribute — `sender` @ `0x143938028` — then the element name). **Relevant attribute names in the pool** (`0x14394de00`+) that belong to this family: `SessionInformation` `0x14394e0d8`, `IsLoggedIn` `0x14394e0f0`, `Changed` `0x14394e100`, `userid` `0x14394e108`, `isOnline` `0x14394e180`, `initial` `0x14394e0c8`, `from` `0x14394e0d0`. So hypothesis (a) from the brief is **structurally possible** — a `` event exists and the client can consume it. It is *not* proven to be required (see §5). --- ## 2. Channel 2 — Blaze (Fire2 on `:42130`) — **this is where account info actually lives** Schemas already reflected out in `auth_schema_reflection.md`; the load-bearing parts: ``` Authentication::LoginRequest (0x14487ca10, 3) AUTH authCode:string EXTB externalBlob:blob EXTI externalId:uint64 Authentication::LoginResponse (0x14487d170, 5) ANON NTOS SESS SPAM UNDR SESS = UserLoginInfo (0x14487cb00, 8) KEY_ sessionKey BUID blazeUserId UID_ userId MAIL email PDTL personaDetails LLOG FRST 1CON Authentication::AccountInfo (0x14487c810, 16) <- reply of getAccount (1/0x1E), request is EMPTY AMU anonymousUser:bool ASRC authenticationSource:string CO country:string DOB dOB:string DTCR dateCreated:string GOPT globalOptin:int8 LATH lastAuth:string LN language:string MAIL email:string PML parentalEmail:string RC reasonCode:enum STAS status:enum STAT emailStatus:enum TPOT thirdPartyOptin:int8 UDU underageUser:bool UID userId:int64 Authentication::Entitlements (0x14487d4e0, 1) NLST list <- listUserEntitlements2 (1/0x1D) Authentication::Entitlement (0x14487d490, 16) TAG entitlementTag PRID productId STAT status PID personaId GDAY grantDate UCNT useCount … ``` Command ids (recovered by calling the client's own `getCommandName`, cross-checked against the static REST binding at `0x143896a80` → `trustedLogin = 0x0B`): `login 0x0A · trustedLogin 0x0B · listUserEntitlements2 0x1D · getAccount 0x1E · getAuthToken 0x24 · listPersonaEntitlements2 0x30 · expressLogin 0x3C · logout 0x46 · getPersona 0x5A · listPersonas 0x64 · getOriginPersona 0x104`. **Observed wire behaviour (14:46 session):** `Util::preAuth (9/0x07)` ✓ → `Util::ping` ✓ → 6× `Util::fetchClientConfig` (`OSDK_CORE`, `OSDK_CLIENT`, `OSDK_NUCLEUS`, `OSDK_WEBOFFER`, `OSDK_ABUSE_REPORTING`, `OSDK_XMS_ABUSE_REPORTING`) ✓ → **`Authentication::logout (1/0x46)`, empty payload** → disconnect → 3-second transport-ping reconnect loop forever. `logout` has no request and no response TDF (confirmed by absence of `LogoutRequest`/`LogoutResponse` anywhere in the client's type index). It is the OSDK `LoginStateLogout` state — i.e. **the login state machine aborted between `LoadConfig` and `Login`.** ### 2.1 The OSDK login state machine (recovered state list) `LoginStateMachineImpl` states, from the client's `GetStateName` thunks at `0x14719b360`+: ``` LoginStateShowMaintenance LoginStateIsp LoginStateLoadIspAccountInfo LoginStateConnect LoginStateLoadConfig LoginStateVersionCheck LoginStateLogin LoginStatePCLogin LoginStateVerifyAccount LoginStateUpgradeAccount LoginStateLoginComplete LoginStateUnsuspend LoginStateCheckUser LoginStateRecheckUser LoginStateWebOffer LoginStateLogout ``` Mapped to observed traffic: `Connect` = `preAuth`; `LoadConfig` = the 6 `fetchClientConfig` calls; then it should proceed `VersionCheck → PCLogin → Login → VerifyAccount → LoadIspAccountInfo → LoginComplete`. It went to `Logout` instead. Associated OSDK events (`0x143984000`+): `EVENT_LOGIN_FETCH_ACCOUNT_INFO_FAILURE` / `…_SUCCESS`, `EVENT_LOGIN_FAILURE`, `EVENT_LOGIN_ABORTED`, `EVENT_LOGIN_QUEUED`, `EVENT_LOGIN_TOS_NOT_ACCEPTED`, `EVENT_LOGIN_TOLLBOOTH`, … Associated operation names (`0x1439623c0`+): **`FetchAccountInfo`**, `UpdateAccountInfo`, `LookupOriginPersona`, `FetchOriginPersona`, `CreateOriginPersona`. `LoginStateVersionCheck` reads three keys out of the Blaze client config — `SV_ENABLE_SERVER_VERSIONING` `0x14395d148`, `SV_CLIENT_CHANGELIST` `0x14395d168`, `SV_SERVER_VERSION` `0x14395d180` — and on mismatch emits `"Client/server version mismatch! Client is at version (%08d). Server is at version (%08d).%s"` (`0x14395d1d0`). **We currently return none of these keys.** Absent ⇒ almost certainly treated as disabled, but see §6 for the cheap belt-and-braces fix. --- ## 3. Channel 3 — the FUT web API (UTAS) — real, but strictly post-login This is a *fourth* surface the brief did not list, and it is the one that literally serves a thing called "account info". It is **not** the current blocker (it is unreachable without a Blaze session), but it *will* be the next wall, so it is documented here. ``` 0x1438dbe88 FUT_RS4_BASE_URL <- config key; NO hardcoded host anywhere in the image 0x1438dbe58 ut/game/%s/ <- path prefix, %s = sku ("fifa17", 0x1438dac20) 0x1438db6c0 user/accountinfo <- THE FUT account-info endpoint 0x1438db810 users/club?sku= <- POST body {"idList":[%I64d,…]} 0x1438db8f8 utStats?sku= 0x1438dbe48 FutServerCall / FutServerCall::RequestBuffer 0x1438dc378 FutGetUserAccountInfoServerCallConfig 0x1438dc3a0 FutGetUtStatsServerCallConfig 0x1438dc3c0 FutGetUsersClubInfoServerCallConfig ``` Request headers for `user/accountinfo` (contiguous at `0x1438db6e0`–`0x1438db731`): ``` Accept: application/json Content-Type: application/json Accept-Encoding: gzip Easw-Session-Data-Nucleus-Id: %lld <- the nucleus/user id, i.e. 33068179 ``` Response JSON keys the client parses (contiguous at `0x1438db760`–`0x1438db8b0`): `userAccountInfo` · `personas` · `userClubList` · `clubName` · `established` · `assetId` · `returningUser` · `userPersonaInfos` · `divisionOnline`. A separate OSDK "SportsWorld"/EASFC HTTP module uses its own header set (`0x14396f8b0`+): `EASW-Version: 2.0.5.0`, `EASW-Token:`, `EASW-Session:`, `EASW-Nucleus-Persona:`, `EASW-Userid:`, `EASW-Request-Signature:`, `EASW-Content-Signature:`. Because `FUT_RS4_BASE_URL` has no hardcoded default, **we can point the entire FUT web API at our own HTTP server purely by serving that config key** — no DNS/DNAT needed. Worth banking now. --- ## 4. Why the `nucleusConnect` stub on `127.0.0.1:42131` is unused — answered Two independent reasons, both proven. ### 4.1 `nucleusConnect` / `nucleusConnectTrusted` are **server-supplied config keys**, and we never send them `0x14389fef8 = "nucleusConnect"` has exactly one reader, `0x147237862`: ``` 147237830 sub rsp,0x28 147237834 call 0x1471995b0 ; get service locator 14723783f call [rdx+0x188] ; -> Blaze connection/config object 147237845 mov rcx,[rax+0x750] 147237855 test rcx,rcx ; je -> return 0 147237862 lea rdx,[0x14389fef8] ; "nucleusConnect" 147237869 call [rax+0x48] ; getConfigString(key, &out) 14723786c mov rax,[rsp+0x38] ; return the string ``` `[vtbl+0x48]` is the Blaze config-string getter. The value therefore comes from the **server** — the `CONF` map in our `PreAuthResponse`, or a `fetchClientConfig` section. Our `PreAuthResponse` `CONF` map contains only timing values (`pingPeriod` etc.) and our six `fetchClientConfig` replies are fabricated key sets that contain no `nucleusConnect*`. **The client therefore gets an empty string and never dials anything.** Zero requests on `:42131` is the expected, correct outcome of what we are currently serving. ### 4.2 The Nucleus path that *does* exist is S2S/client-cert-only, and belongs to `trustedLogin` `nucleusConnectTrusted` (`0x14389fdf8`) is read at `0x146e1658b` and immediately feeds a URL builder: ``` 146e1658b lea rdx,[0x14389fdf8] ; "nucleusConnectTrusted" 146e16595 call [rax+0x48] ; getConfigString 14e1659d lea r8,[0x14389fe10] ; "%s/connect/token" 146e165ae call 0x146dc0950 ; snprintf(buf, 0x400, "%s/connect/token", url) 146e165db movups xmm0,[0x14389fe28] ; "grant_type=client_credentials" ``` Surrounding literals, contiguous, all in the BlazeSDK `LoginStateMachine` block: ``` 0x14389fd90 Content-Type: application/x-www-form-urlencoded 0x14389fdc1 enable-client-cert-auth: true 0x14389fde0 LoginStateMachine 0x14389fdf8 nucleusConnectTrusted 0x14389fe10 %s/connect/token 0x14389fe28 grant_type=client_credentials 0x14389fe50 NEXUS_S2S 0x14389fe60 "access_token" : " 0x14389fef8 nucleusConnect ``` So the shape, if it were ever used, is: ``` POST {nucleusConnectTrusted}/connect/token Content-Type: application/x-www-form-urlencoded enable-client-cert-auth: true Authorization: NEXUS_S2S grant_type=client_credentials → 200 {"access_token" : "…"} ``` …and the token then goes into `TrustedLoginRequest {ID_ id, ITYP idType, TOKN accessToken}` = **`Authentication::trustedLogin (1/0x0B)`**, whose REST binding at `0x143896a80` confirms the shape (`GET`, headers `Authorization: accessToken`, `X-Forwarded-UserType: idType`, `X-Forwarded-UserId: id`). **This is the console/dedicated-server trusted path. It requires a client certificate (`enable-client-cert-auth: true`) and is not what the PC/Origin build uses.** The PC build uses `login (1/0x0A)` with `AUTH = `. ### 4.3 The hardcoded EA account hosts are for the web UI, not for login ``` 0x143b8b528 https://accounts.int.ea.com/ 0x143b8b588 https://accounts.ea.com/ 0x143b8b548 https://gateway.int.ea.com/ 0x143b8b5a8 https://gateway.ea.com/ 0x143b8b568 https://signin.int.ea.com/ 0x143b8b5c0 https://signin.ea.com/ 0x143b8b700 Nucleus::gNucleusLocale 0x143b8b718 Nucleus::gNucleusBaseUrl 0x143b8b738 Nucleus::gNucleusBaseProxyUrl 0x143b8b758 Nucleus::gNucleusBasePortalUrl 0x143b8b778 Nucleus::gNucleusClientSideRedirectUri ``` These back the EAWebKit account-management pages, reached through the OSDK WebOffer config keys `NUCLEUS_CREATE_URL` / `NUCLEUS_ADDED_URL` / `NUCLEUS_INCOMPLETE_URL` / `NUCLEUS_CREATE_INFO_URL` / `NUCLEUS_DUPACCT_INFO_URL` / `NUCLEUS_DEACTIVATED_INFO_URL` (`0x14395eb60`–`0x14395ec18`, sitting in the middle of the `WEB_OFFER_URL` / `NEWS_URL` / `FAQ_URL` / `TOSA_URL` block) — i.e. the `OSDK_NUCLEUS` `fetchClientConfig` section is **a set of account-web-page URLs**, not login plumbing. That the game does not dial `accounts.ea.com` is therefore *correct behaviour*, not a symptom. > **Bottom line on channel 3:** do not build a Nucleus HTTP server. If you want the `:42131` stub to > ever receive traffic you would have to (a) advertise `nucleusConnectTrusted` in the Blaze config > and (b) satisfy `enable-client-cert-auth` over TLS — and that would put you on the `trustedLogin` > path, which is *not* the path this build's login state machine takes. Serve the auth code instead. --- ## 5. The dependency chain, and where it actually breaks ``` LSX GetInternetConnectedState connected="1" ✅ done -> g_originOnline @0x1443337f8 (writer 0x146f1e6b0) + FE::FIFA::OriginOnlineEvent -> origin.nav emits OriginIsOnlineTrue -> startFutBlazeLogin LSX GetGameInfo UPTODATE="true" ✅ done LSX GetConfig / GetProfile ✅ done -> sdk[+0x3a0]=UserId, [+0x3a8]=PersonaId -------------------------------------------------------------------------------- OSDK LoginStateConnect -> Blaze Util::preAuth ✅ answered OSDK LoginStateLoadConfig-> Blaze Util::fetchClientConfig × 6 ✅ answered (fabricated data) OSDK LoginStateVersionCheck -> SV_* keys from config ⚠️ keys absent OSDK LoginStatePCLogin -> FirstPartyAuthTokenRetriever::DoTick -> OriginGetDefaultUser() -> LSX GetAuthCode(ClientId) ❌ NEVER SENT <<< BREAK -> Blaze Authentication::login AUTH= ❌ never sent OSDK LoginStateVerifyAccount / LoadIspAccountInfo -> Blaze getAccount 1/0x1E -> AccountInfo ❌ unreachable -> QueryEntitlements / listUserEntitlements2 ❌ unreachable OSDK LoginStateLoginComplete ❌ unreachable -------------------------------------------------------------------------------- FUT: CheckFUTRosters -> {FUT_RS4_BASE_URL}ut/game/fifa17/user/accountinfo ❌ unreachable ``` Observed instead: `LoadConfig` → **`LoginStateLogout`** → `Authentication::logout (1/0x46)` → disconnect → reconnect ping loop. In the most recent run the client did not even re-reach `preAuth`; it sat in a bare PING/close loop every 3 s, i.e. the connection manager gave up. ### Ranked hypotheses for "no `GetAuthCode`", with the discriminating test for each | # | Hypothesis | Evidence for | Evidence against | Discriminating probe | |---|---|---|---|---| | **H1** | The login state machine aborts *before* `LoginStatePCLogin` — i.e. nothing is ever enqueued into `DoTick`'s two slots, so `RequestAuthCodeSync` is never called. | `logout` arrives immediately after the last `fetchClientConfig`, with **no** intervening RPC and no LSX traffic. `QueryEntitlements` is *also* never issued — consistent with "the whole PC-login step never ran", not with "the auth-code call failed". | — | Breakpoint `0x146f199c0` (`DoTick`) and `0x1470db3c0`. If `DoTick` runs but both slots are null → H1 confirmed. **Do this first.** | | **H2** | Our fabricated `fetchClientConfig` payloads are wrong/insufficient, so `LoadConfig` "succeeds" but a required key is missing and the next state fails. | All six replies contain keys we invented. `SV_ENABLE_SERVER_VERSIONING` / `SV_CLIENT_CHANGELIST` / `SV_SERVER_VERSION` are read by `LoginStateVersionCheck` and we send none. `OSDK_NUCLEUS` should be six `NUCLEUS_*_URL` keys; we send four invented `OSDK_NUCLEUS_*` keys. | Missing keys usually degrade to `""`/0 in OSDK. | Serve the corrected config (§6.1) and re-run; watch whether the client advances past `LoadConfig`. Cheap, no RE needed. | | **H3** | The OriginSDK needs a *pushed* `` / `` event before it considers a user authenticated. | `Origin::EventHandler` exists (`0x14393f900`), element `Login` @ `0x14393d0ac`; attributes `IsLoggedIn` / `SessionInformation` / `userid` exist in the pool. Our responder never pushes anything. | The Steampunks emu never pushed events either, yet the game still got as far as `preAuth`. `OriginGetDefaultUser` is fed by `GetProfile`, *not* by the Login event (§1.2) — so the SDK's notion of "who" does not depend on it. | Push `` (encrypted, right after `ChallengeAccepted`) and see whether behaviour changes. Cheap; try alongside H2. | | **H4** | `RequestAuthCodeSync` early-outs at the SDK-ready predicate `0x1470e2840` and returns `0xa0010000` without wire traffic. | It is the only *silent* failure path inside the auth-code call. | The same predicate guards `OriginGetDefaultUser` / `OriginGetProfile` / `OriginCheckOnline`, all of which demonstrably worked in this session. | Breakpoint `0x1470db3f7`; check `al` after `0x1470e2840`. Only worth doing if H1's `DoTick` probe shows the call *is* being made. | H1 is by far the most likely. H2 is the cheapest thing that could plausibly cause H1. --- ## 6. What we must serve ### 6.1 Blaze `Util::fetchClientConfig` — replace the invented keys Serve authentic key *names* (values can be conservative). Recovered key names, by section: * **`OSDK_CORE`** — include the version-check trio explicitly so nothing is ambiguous: `SV_ENABLE_SERVER_VERSIONING = 0`, `SV_CLIENT_CHANGELIST = 0`, `SV_SERVER_VERSION = 0`. Keep `OSDK_PRESENCE_DELAY = 5`, `OSDK_PRESENCE_POLL = 60` (real key names, `0x143962848`/`0x143962860`). * **`OSDK_NUCLEUS`** — the *real* keys are web-page URLs, not the `OSDK_NUCLEUS_*` we invented: `NUCLEUS_CREATE_URL`, `NUCLEUS_ADDED_URL`, `NUCLEUS_INCOMPLETE_URL`, `NUCLEUS_CREATE_INFO_URL`, `NUCLEUS_DUPACCT_INFO_URL`, `NUCLEUS_DEACTIVATED_INFO_URL` (`0x14395eb60`–`0x14395ec18`). Empty strings are fine and honest; invented `OSDK_NUCLEUS_ENABLED=1` is not. * **`OSDK_WEBOFFER`** — real keys: `WEB_OFFER_URL`, `NEWS_URL`, `NEWS_TIME_STAMP_URL`, `FAQ_URL`, `TOSA_URL`, `TOSAC_URL`, `MENU_ESPN_URL`, `MENU_WEBGM0_URL`…`MENU_WEBGM2_URL`. * **`OSDK_ABUSE_REPORTING` / `OSDK_XMS_ABUSE_REPORTING`** — `OSDK_ABUSE_REPORTING_ENABLED = 0`, `OSDK_ABUSE_NUM_TYPES = 0` (already correct). * **`OSDK_CLIENT`** — the `OSDK_CLUBS_*` limits we already send are real key names. * Consider adding **`FUT_RS4_BASE_URL = http://127.0.0.1:/`** — see §6.4. ### 6.2 LSX `GetAuthCode` — be ready the instant it is asked ```xml ``` Emit **both** `Code` and `Return` until the log shows which one the client consumes (the attribute pool position between `ClientId` and `connected` is suffix-shared, so `Code` is the strong candidate). The code is opaque — we author both ends — but it **must be echoed verbatim into `LoginRequest.AUTH`** and accepted there. Log the incoming `ClientId` value; that tells us which EA client id FIFA 17 presents and is worth recording. ### 6.3 LSX `QueryEntitlements` — pre-stage the answer ```xml ``` ### 6.4 Blaze `Authentication` — the account-info replies to have ready | Cmd | Reply | Must contain | |---|---|---| | `login 0x0A` | `LoginResponse` | `ANON=0 NTOS=0 UNDR=0 SPAM=1`; `SESS.KEY_` non-empty; `SESS.BUID = SESS.UID_ = 33068179`; `SESS.PDTL.PID_ = 33068179`; `SESS.PDTL.DSNM = "CAGE"`; `SESS.PDTL.STAS = 0`; `SESS.PDTL.PLAT` = the `PLAT` from `PreAuthResponse` | | `getAccount 0x1E` | `AccountInfo` (empty request) | `UID = 33068179`, `CO = "US"`, `LN = "en_US"`, `MAIL` = any well-formed address, `STAS` = active, `STAT` = verified, `UDU = false`, `AMU = false`, `ASRC = "cem_ea_id"` (matches the `NASP` we already return in `PreAuthResponse`), `DTCR` / `LATH` ISO-8601 | | `listPersonas 0x64` | `ListPersonasResponse` | one persona: id `33068179`, name `CAGE`, status active | | `getPersona 0x5A` | `GetPersonaResponse` | same single persona | | `listUserEntitlements2 0x1D` | `Entitlements` | one `Entitlement`: `TAG = "ONLINE_ACCESS"`, `PRID` tied to offer `1027460`, `PID = 33068179`, `STAT` active | | `logout 0x46` | empty REPLY | already correct — but treat its arrival as the failure signal it is | Also serve `UserSessions` notification `UserAuthenticated (0x08)` after login (notification ids already decoded: `0x141b03f70`). ### 6.5 FUT web (`{FUT_RS4_BASE_URL}ut/game/fifa17/user/accountinfo`) — the next wall, pre-built ``` GET {FUT_RS4_BASE_URL}ut/game/fifa17/user/accountinfo Accept: application/json Content-Type: application/json Accept-Encoding: gzip Easw-Session-Data-Nucleus-Id: 33068179 200 {"userAccountInfo": { "personas": [{ "personaId": 33068179, "personaName": "CAGE", "returningUser": 0, "userClubList": [{"clubName":"…","established":,"assetId":}], "userPersonaInfos": [], "divisionOnline": 0 }] }} ``` Because `FUT_RS4_BASE_URL` has no hardcoded default, serving it as a config key is enough to point the whole FUT web API at us — plain HTTP on `127.0.0.1` is fine, no TLS or DNAT needed. --- ## 7. Immediate next actions (ordered) 1. **Probe H1.** Relaunch, breakpoint `0x146f199c0` (`FirstPartyAuthTokenRetriever::DoTick`) and `0x1470db3c0` (`RequestAuthCodeSync`). Whether `DoTick` runs at all, and whether its two slots are null, decides H1 vs H4 in one shot. 2. **Ship the corrected `fetchClientConfig` (§6.1)** — real key names, explicit `SV_*` trio. Cheapest possible fix for H2 and it removes a class of ambiguity permanently. 3. **Add `GetAuthCode` + `QueryEntitlements` responses (§6.2/6.3)** to `lsx_responder.py` so the moment the client asks, it is answered — and log the `ClientId` it presents. 4. **Try the pushed `` event (§H3)** — one extra encrypted frame after `ChallengeAccepted`. Low cost, and it is the only untested structural difference between us and a real Origin client. 5. Keep the Nucleus HTTP stub on `:42131` parked. It is on the `trustedLogin`/client-cert path and is not what this build uses. --- ## Appendix — addresses | What | VA | |---|---| | `FifaOnline::FirstPartyAuthTokenRetriever::DoTick` | `0x146f199c0` | | `Origin::OriginSDK::RequestAuthCodeSync` | `0x1470db3c0` | | … its SDK-ready predicate (wire-silent bail) | `call 0x1470e2840` @ `0x1470db3f7` | | … its impl call | `call 0x1470e67f0` @ `0x1470db41d` | | `OriginGetDefaultUser()` → `sdk[+0x3a0]` | `0x1470da6d0` | | `OriginGetDefaultPersona()` → `sdk[+0x3a8]` | `0x1470da680` | | `Origin::OriginSDK::Initialize` — writes `+0x3a0`/`+0x3a8` from `GetProfile` | `0x1470e5ad5` / `0x1470e5ae1` | | sync `GetProfile(index=0)` helper (timeout `0x3a98` = 15 s) | `0x147118d80` | | OriginSDK ctor zeroing `+0x3a0`/`+0x3a8` | `0x1470deb2f` / `0x1470deb36` | | `"[%s] Invalid authcode"` / `"[%s] Origin Error(%d)"` | `0x1438f5e00` / `0x1438f5e18` | | `"OriginRequestAuthCodeSync entered"` | `0x143936158` | | `nucleusConnect` (config key) / its only reader | `0x14389fef8` / `0x147237862` | | `nucleusConnectTrusted` / its reader | `0x14389fdf8` / `0x146e1658b` | | `%s/connect/token` · `grant_type=client_credentials` · `enable-client-cert-auth: true` · `NEXUS_S2S ` | `0x14389fe10` · `0x14389fe28` · `0x14389fdc1` · `0x14389fe50` | | `trustedLogin` REST binding (`GET`, `Authorization`/`X-Forwarded-*`) | `0x143896a80` | | LSX **event** element-name table (incl. `Login` @ `0x14393d0ac`) | `0x14393cfd8`–`0x14393d208` | | `Origin::EventHandler::HandleMessage` | `0x14393f900` | | `Origin::EventHandler::HandleMessage` | `0x143940050` | | LSX event dispatch / element-name compare | `0x1471028ee` | | LSX request element table / attribute pool | `0x14394dc00` / `0x14394de00` | | `"EbisuSDK"` / `"EALS"` service names | `0x143937d58` / `0x143937c60` | | OSDK login-state name thunks | `0x14719b360`+ | | `SV_ENABLE_SERVER_VERSIONING` / `SV_CLIENT_CHANGELIST` / `SV_SERVER_VERSION` | `0x14395d148` / `0x14395d168` / `0x14395d180` | | version-mismatch message | `0x14395d1d0` | | OSDK config section names (`OSDK_CORE`…`OSDK_TICKER`) | `0x143962be8`–`0x143962c40` | | OSDK operations `FetchAccountInfo` / `UpdateAccountInfo` | `0x1439623c0` / `0x1439623d8` | | `EVENT_LOGIN_FETCH_ACCOUNT_INFO_FAILURE` / `_SUCCESS` | `0x1439840b8` / `0x1439840e0` | | `NUCLEUS_*_URL` config keys (OSDK_NUCLEUS section) | `0x14395eb60`–`0x14395ec18` | | FUT `FUT_RS4_BASE_URL` / `ut/game/%s/` / `user/accountinfo` | `0x1438dbe88` / `0x1438dbe58` / `0x1438db6c0` | | FUT `Easw-Session-Data-Nucleus-Id: %lld` | `0x1438db731` | | FUT response JSON keys (`userAccountInfo`…`divisionOnline`) | `0x1438db760`–`0x1438db8b0` | | EASW/SportsWorld header block | `0x14396f8b0`–`0x14396fa50` | | hardcoded `accounts/gateway/signin.ea.com` (web UI only) | `0x143b8b528`–`0x143b8b5c0` | | `Nucleus::gNucleusBaseUrl` & friends | `0x143b8b700`–`0x143b8b778` | | Blaze `AccountInfo` / `LoginRequest` / `LoginResponse` descriptors | `0x14487c810` / `0x14487ca10` / `0x14487d170` | Tools written this pass (all in `…/scratchpad/`): `acct_probe1.py` (CommandInfo neighbourhood dump), `strdump.py` (string run at a VA), `wsearch.py` (ASCII+UTF-16 phrase search), `navdump.py` (nav-flow JSON extractor). Reused: `memtool.py`, `strsearch.py`, `xref.py`, `dis.sh`, `cmdinfo.py`.