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
32 KiB
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 inLoginRequest.AUTHand returnsLoginResponse.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
GetProfileResponsewithUserId="33068179" PersonaId="33068179"is the mechanism that populates the SDK's identity. That part is working; the client askedGetProfilethree 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 <GetAuthCode ClientId="…"/> 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 <Login>
Our responder is request-driven only and never pushes. The client's OriginSDK does register
event handlers. Complete inventory of Origin::EventHandler<lsx::…T, X>::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 <Event sender="EALS"><Challenge …/></Event> 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 <Login> 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<Entitlement> <- 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 <credential>
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 = <Origin auth code>.
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
:42131stub to ever receive traffic you would have to (a) advertisenucleusConnectTrustedin the Blaze config and (b) satisfyenable-client-cert-authover TLS — and that would put you on thetrustedLoginpath, 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=<code> ❌ 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 <Login> / <OnlineStatusEvent> event before it considers a user authenticated. |
Origin::EventHandler<lsx::LoginT, unsigned int> 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 <LSX><Event sender="EbisuSDK"><Login userid="33068179" IsLoggedIn="1"/></Event></LSX> (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. KeepOSDK_PRESENCE_DELAY = 5,OSDK_PRESENCE_POLL = 60(real key names,0x143962848/0x143962860).OSDK_NUCLEUS— the real keys are web-page URLs, not theOSDK_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; inventedOSDK_NUCLEUS_ENABLED=1is 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— theOSDK_CLUBS_*limits we already send are real key names.- Consider adding
FUT_RS4_BASE_URL = http://127.0.0.1:<port>/— see §6.4.
6.2 LSX GetAuthCode — be ready the instant it is asked
<LSX><Response id="N" sender="EbisuSDK">
<AuthCode Code="QUXbLm3…opaque…" Return="QUXbLm3…opaque…"/>
</Response></LSX>
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
<LSX><Response id="N" sender="EbisuSDK"><QueryEntitlementsResponse>
<OriginItem ItemId="ONLINE_ACCESS" EntitlementId="1" ResourceId="1027460" OfferId="1027460"
GrantDate="2016-09-01T00:00:00Z" bIsOwned="true" Uses="0"/>
</QueryEntitlementsResponse></Response></LSX>
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":<epoch>,"assetId":<id>}],
"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)
- Probe H1. Relaunch, breakpoint
0x146f199c0(FirstPartyAuthTokenRetriever::DoTick) and0x1470db3c0(RequestAuthCodeSync). WhetherDoTickruns at all, and whether its two slots are null, decides H1 vs H4 in one shot. - Ship the corrected
fetchClientConfig(§6.1) — real key names, explicitSV_*trio. Cheapest possible fix for H2 and it removes a class of ambiguity permanently. - Add
GetAuthCode+QueryEntitlementsresponses (§6.2/6.3) tolsx_responder.pyso the moment the client asks, it is answered — and log theClientIdit presents. - Try the pushed
<Login>event (§H3) — one extra encrypted frame afterChallengeAccepted. Low cost, and it is the only untested structural difference between us and a real Origin client. - Keep the Nucleus HTTP stub on
:42131parked. It is on thetrustedLogin/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<lsx::LoginT,unsigned int>::HandleMessage |
0x14393f900 |
Origin::EventHandler<lsx::OnlineStatusEventT,bool>::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.