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
27 KiB
FIFA 17 Blaze — Authentication (component 0x0001) login flow
Date: 2026-07-30 · Scope: what the client needs so it believes it is logged in.
0. Provenance legend
| Flag | Meaning |
|---|---|
| (A) binary | Reflected/disassembled out of our own FIFA17.exe (live /proc/<pid>/mem, PID 19517). Authoritative for FIFA 17. |
| (A) wire | Our own captured bytes (fifa17-recon/captures/blaze/session/). |
| (A) clean-room 3P | Third-party clean-room reimplementations cloned in the scratchpad. grid-blaze states explicitly it is "a clean-room implementation based entirely on network analysis"; pamplona-future/catalyst-mitm are the same Beat-Revival lineage (packet captures + zeroKilo/jacobtread public TDF work). Mirror's Edge Catalyst = Blaze 15.1.1.0.5, FIFA 17 = Blaze 15.1.1.3.0 — same SDK generation. |
| (B?) unverified 3P | refs/z7_*.txt in the scratchpad — pre-existing reference captures of unknown origin, not produced by us. Treated as a hint only; every z7 claim below was independently re-derived from (A). Where z7 disagrees with our binary, our binary wins (see §6). |
No EA/FIFA leaked source was consulted. Nothing below is derived from the 2021 leak.
1. THE HEADLINE: the client is not stalling — it is logging out
Observed frame #9 (captures/blaze/session/auth_cmd0x46.bin):
00 00 00 00 | 00 00 | 00 01 | 00 46 | 00 00 10 | 00 | 00 00
payload=0 meta=0 comp=1 cmd=0x46 msgNum=16 MESSAGE
component 0x0001 (Authentication), command 0x0046 = 70 decimal, EMPTY payload.
Command 70 = logout. Evidence:
- (A) clean-room 3P —
pamplona-future/src/blaze/components/authentication.ts:10-46is a full BlazeSDK-15.1.1Authenticationcommand enum, and it listslogout = 70. That enum is independently corroborated on three other entries by our own data:login = 10↔ z7 capturecomp=0x0001 cmd=0x000A [LoginRequest]getAuthToken = 36↔ z7 capturecomp=0x0001 cmd=0x0024 [GetAuthTokenResponse]listUserEntitlements2 = 29↔grid-blaze/src/main.rs:35routes1, 29 => list_entitlments
- (A) binary — FIFA 17's own Authentication RPC-name literal pool at
0x14389d6xx–0x14389d928contains exactly the same 33 RPC names as the pamplona enum (acceptLegalDocs … logout … upgradeAccount), i.e. FIFA 17 and MEC ship the same Authentication component definition."logout"is at0x14389d8d8. logoutis the only Authentication RPC in that set that takes no request parameters, which matches the observed 0-byte payload. (getAuthTokenis also parameterless but is0x24, not0x46.)
Confidence: high, cross-validated three ways, but not 100% binary-pinned — FIFA 17's
getCommandNamefor Authentication is not emitted as thelea rax,[rip+str]; retstub pattern that yielded the Util table, and the name pool has zero code xrefs (searched everylea/mov rip-reland every aligned+unaligned 8-byte pointer, module-wide). So the id↔name mapping itself is inherited from (A) clean-room 3P, not re-derived from FIFA 17.
What this changes
The client never attempted login (cmd 10). It went:
preAuth → ping → 6× fetchClientConfig (all answered EMPTY) → logout → give up
So it is not waiting on an Authentication response we failed to send. It decided, before issuing any login, that it had nothing to log in with, and tore the Blaze session down. That is consistent with the two-layer gate: it had no Origin auth code to put in LoginRequest.AUTH, and/or the OSDK layer failed to initialise from the empty configs.
(A) binary corroboration of layer 1: FIFA17.exe contains the localisation key TXT_NOT_LOGIN_TO_EBISU at 0x1439633e8 (Ebisu = EA's internal codename for Origin), sitting immediately beside TXT_ORIGIN_GAME_VERSION_OUT_OF_DATE, OSDK_PRESENCE_OFFLINE, OSDK_OL_STATE_NONE. The UI flow events checkOriginConnected / OriginIsOnline / OriginIsOffline live at 0x143b4cb58. This is almost certainly the on-screen message, and it is produced by the Origin/LSX layer, not by Blaze.
2. Authentication component — command ids
Component id 0x0001. Ids (A) clean-room 3P (pamplona enum); every name (A) binary-confirmed present in FIFA 17's RPC name pool unless noted.
| Cmd | RPC | Cmd | RPC |
|---|---|---|---|
| 10 (0x0A) | login |
54 | disableOptIn |
| 11 (0x0B) | trustedLogin |
60 (0x3C) | expressLogin |
| 20 | updateAccount |
70 (0x46) | logout ← observed |
| 21 | upgradeAccount |
90 (0x5A) | getPersona |
| 29 (0x1D) | listUserEntitlements2 |
100 | listPersonas |
| 30 | getAccount |
101 | expressCreateAccount |
| 31 | grantEntitlement |
230 | createWalUserSession |
| 32 | listEntitlements |
241 | acceptLegalDocs |
| 34 | getUseCount |
242 | getEmailOptInSettings |
| 35 | decrementUseCount |
246 | getTermsOfServiceContent |
| 36 (0x24) | getAuthToken |
260 | getOriginPersona |
| 38 | getPasswordRules |
270 | checkEmail |
| 39 | grantEntitlement2 |
280 | getPersonaNameSuggestions |
| 43 | modifyEntitlement2 |
290 | guestLogin |
| 44 | consumecode |
||
| 45 | passwordForgot |
||
| 47 | getPrivacyPolicyContent |
||
| 48 | listPersonaEntitlements2 |
||
| 51 | checkAgeReq |
||
| 52 | getOptIn |
||
| 53 | enableOptIn |
FIFA 17 additionally ships types with no id in the MEC enum — GetUserAccessTokenRequest/Response, GetUserXblTokenRequest/Response, StressLoginRequest, CheckLegalDocRequest/Response, GetSuggestionsRequest — so FIFA's component is a superset. Ids for those are unknown.
Error-code encoding (A) binary: Blaze error codes are (index << 16) | componentId. Confirmed by the 0x000N7802 constant block at 0x146de08xx sitting alongside the USER_ERR_* name stubs (component 0x7802 = UserSessions). So an Authentication error is 0xNNNN0001.
3. login (1/10) request + response TDF — (A) binary, authoritative
Reflected from FIFA 17's own TDF type descriptors. Fields MUST be emitted in ascending packed-tag order, which for A–Z tags is plain alphabetical with ' ' (pad) sorting first — the member tables below are already in that order.
Request — Blaze::Authentication::LoginRequest @ 0x14487ca10 (3 members)
| Tag | Member | Type |
|---|---|---|
AUTH |
authCode |
string ← the Origin/Nucleus auth code |
EXTB |
externalBlob |
blob |
EXTI |
externalId |
uint64 |
z7's
LoginRequestalso carriedACHT{SHID,SKID}— FIFA 17 has noACHTmember. z7 is a different title/version. Ignore it.
Response — Blaze::Authentication::LoginResponse @ 0x14487d170 (5 members)
| Tag | Member | Type |
|---|---|---|
ANON |
isAnonymous |
bool |
NTOS |
needsLegalDoc |
bool |
SESS |
userLoginInfo |
struct UserLoginInfo |
SPAM |
isOfLegalContactAge |
bool |
UNDR |
isUnderage |
bool |
Important divergence from the MEC emulators.
grid-blaze/src/models/authentication.rs:23-50andpamplona-future/.../authentication.ts:78-128both emitCNTX,ERRCand a top-levelSKEYin the login payload. FIFA 17'sLoginResponsehas none of those.CNTX/ERRCare the Blaze error metadata block (seegrid-blaze/src/packet.rs:62-71ErrorBody, and its own// TODO: move ErrorBody to metadata; pamplona setsmetadataSize: 75on the login reply). Emit exactly the 5 members above in the payload; leave metadata empty on success. The session key lives atSESS.KEY, not at top level.
SESS — Blaze::Authentication::UserLoginInfo @ 0x14487cb00 (8 members)
| Tag | Member | Type |
|---|---|---|
1CON |
isFirstConsoleLogin |
bool |
BUID |
blazeUserId |
int64 |
FRST |
isFirstLogin |
bool |
KEY |
sessionKey |
string ← the forged session key |
LLOG |
lastLoginDateTime |
int64 |
MAIL |
email |
string |
PDTL |
personaDetails |
struct PersonaDetails |
UID |
userId |
int64 |
(1 = 0x31 → packed 0x11, which is below A = 0x21, so 1CON correctly sorts first.)
PDTL — Blaze::Authentication::PersonaDetails @ 0x14487cab0 (6 members)
| Tag | Member | Type |
|---|---|---|
DSNM |
displayName |
string |
LAST |
lastAuthenticated |
uint32 |
PID |
personaId |
int64 |
PLAT |
clientPlatform |
enum ClientPlatformType |
STAS |
status |
enum PersonaStatus::Code |
XREF |
extId |
uint64 |
getAuthToken (1/36) response — GetAuthTokenResponse @ 0x14487d080
Single member: AUTH authToken : string. (Matches z7 exactly.)
listUserEntitlements2 (1/29) response — Blaze::Authentication::Entitlements @ 0x14487d4e0
Single member NLST : list<Entitlement>.
Blaze::Authentication::Entitlement @ 0x14487d490 (16 members, tag order):
DEVI deviceUri(str), GDAY grantDate(str), GNAM groupName(str), ID id(u64), ISCO isConsumable(bool), PID personaId(i64), PJID projectId(str), PRCA productCatalog(enum), PRID productId(str), STAT status(enum EntitlementStatus::Code), STRC statusReasonCode(enum), TAG entitlementTag(str), TDAY terminationDate(str), TYPE entitlementType(enum EntitlementType::Code), UCNT useCount(u32), VER version(u32).
(A) binary enum literals: EntitlementType::Code = ONLINE_ACCESS, TRIAL_ONLINE_ACCESS, SUBSCRIPTIONS, PARENTAL_APPROVAL (@0x1438991e8). EntitlementStatus::Code includes ACTIVE/USED/UNUSED/BANNED/DISABLED.
4. Forging a session with no Nucleus — the recipe
(A) clean-room 3P for the shape of the forgery, (A) binary for every tag.
4.1 What a fake session consists of
grid-blaze/src/routes/authentication.rs:17-133 is the canonical minimal pattern:
- Ignore whatever is in
LoginRequest.AUTH(it never validates it against Nucleus — it swaps in Discord OAuth; an offline emulator just skips validation entirely). - Mint a
User { user_id, persona_id, username }from local storage / config. - Store it on the session (
session.data.set_user). - Push the
UserAuthenticatednotification (see §4.3). - Reply with
LoginResponse.
4.2 Session key
Both 3P emulators use a canned literal; the client does not verify it:
grid-blazeuses simply"0"for bothSKEYandSESS.KEY.pamplona-futureuses a realistic-looking"0540000031e5dde8_wT9NlhYTUidv3EMiZo7kaRMYV0x3$x72YrtOC*QU1v".
Real Blaze session keys look like <16 hex>_<44 random base64-ish chars>. Recommend generating that shape once per session and reusing the same string in LoginResponse.SESS.KEY and in the UserAuthenticated notification's KEY — they must match.
4.3 The UserAuthenticated notification — (A) binary, exact
Component 0x7802 (30722, UserSessions), command 0x0008, msgType = NOTIFICATION (2), msgNum = 0.
Payload type is Blaze::UserSessionLoginInfo @ 0x14486f920, 16 members:
| Tag | Member | Type |
|---|---|---|
1CON |
isFirstConsoleLogin |
bool |
ALOC |
accountLocale |
uint32 |
BUID |
blazeUserId |
int64 |
CGID |
connectionGroupObjectId |
ObjectId (triple) |
DSNM |
displayName |
string |
FRST |
isFirstLogin |
bool |
KEY |
sessionKey |
string |
LAST |
lastAuthenticated |
uint32 |
LLOG |
lastLoginDateTime |
int64 |
MAIL |
email |
string |
NASP |
personaNamespace |
string |
PID |
personaId |
int64 |
PLAT |
clientPlatform |
enum |
UID |
userId |
int64 |
USTP |
userSessionType |
enum UserSessionType |
XREF |
extId |
uint64 |
This is a hard confirmation of the z7 reference:
z7_userauth_notif.txt(comp=0x7802 cmd=0x0008 [UserSessions::UserAuthenticated]) carries exactly these 16 tags. It also corrects both MEC emulators, which call notification 30722/8updateHardwareFlags— that is the name of command 8, not notification 8. The payload they build is right; the name is wrong.
4.4 UserSessionExtendedDataUpdate — (A) binary
Blaze::UserSessionExtendedDataUpdate @ 0x1448703e0, 3 members: DATA (struct UserSessionExtendedData), SUBS (bool), USID (int64).
This is what pamplona/grid-blaze mislabel validateSessionKey and send as notification 30722/1.
Blaze::UserSessionExtendedData @ 0x144870390 — 12 members (tag order):
ADDR address(NetworkAddress, union), BPS bestPingSiteAlias(str), CTY country(str), CVAR clientData(variable), DMAP dataMap(map<int64,uint32>), HWFG hardwareFlags, ISP iSP(str), PSLM latencyList(list<int32>), QDAT qosData, TZ timeZone(str), UATT userInfoAttribute(u64), ULST blazeObjectIdList(list<ObjectId>).
Two FIFA-17-specific deltas vs the MEC emulators: FIFA has
PSLM(they don't), and FIFA hasBPSas a top-level string member (they wrap it inside theADDRunion asBPS). Follow the FIFA layout.
Blaze::Util::NetworkQosData (QDAT) @ 0x14486e680: BWHR u32, DBPS u32, NAHR u32, NATT enum NatType, UBPS u32.
4.5 Persona identity must match the Origin emu
From stp-origin_emu.ini [Globals]: PersonaId=33068179, PersonaName=CAGE, Language=en_US.
Use BUID = PID = 33068179, DSNM = "CAGE", NASP = "cem_ea_id" (namespace confirmed by our own PreAuthResponse being accepted), PLAT = 4 (pc), USTP = 0.
Mismatch trips AUTH_ERR_INVALID_PERSONA / AUTH_ERR_USER_DOES_NOT_MATCH_PERSONA / AUTH_ERR_PERSONA_NOT_FOUND — all present in the binary at 0x146e0ed91, 0x146e0eebf, 0x146e0ecf8.
5. Util::fetchClientConfig (9/1) — what to return
Request: Blaze::Util::FetchClientConfigRequest = { CFID: string }.
Response: Blaze::Util::FetchConfigResponse @ 0x1448752e0 = single member CONF : map<string,string>. (Note: not wrapped in an extra struct — the extra nesting only exists inside PreAuthResponse, where CONF is a FetchConfigResponse struct whose own single member is also called CONF. Easy to get wrong.)
5.1 The BlazeSDK section (returned inside PreAuthResponse.CONF.CONF)
Our capture shows the client asks for this inside the preAuth request: FCCR { CFID = 'BlazeSDK' }. (A) binary — these are the config keys FIFA 17 actually parses (string literals present in the exe; the "absent" ones are MEC-only and are silently ignored by FIFA 17):
| Key | Present in FIFA17.exe | Owner (from adjacent literals) |
|---|---|---|
connIdleTimeout |
✅ 0x1438a0a58 |
ConnectionManager |
defaultRequestTimeout |
✅ 0x1438a0a40 |
ConnectionManager |
pingPeriod |
✅ 0x1438a0a30 |
ConnectionManager |
autoReconnectEnabled |
✅ 0x1438a0a68 |
ConnectionManager |
maxReconnectAttempts |
✅ 0x1438a0a80 |
ConnectionManager |
enableQosFirewallTest |
✅ 0x1438a09f0 |
ConnectionManager/QosManager |
enableQosBandwidthTest |
✅ 0x1438a0a08 |
ConnectionManager/QosManager |
nucleusConnect |
✅ 0x14389fef8 |
LoginStateMachineImpl |
nucleusConnectTrusted |
✅ 0x14389fdf8 |
LoginStateMachineImpl |
associationListSkipInitialSet |
✅ 0x143b6eb88 |
AssociationListAPI |
userManagerMaxCachedUsers |
✅ | UserManager |
voipHeadsetUpdateRate |
✅ | VoIP |
nucleusPortal |
❌ absent | MEC-only |
nucleusProxy |
❌ absent | MEC-only |
bytevaultHostname / bytevaultPort / bytevaultSecure |
❌ absent | MEC-only |
xblTokenUrn, xboxOneStringValidationUri, xlspConnectionIdleTimeout |
❌ absent | MEC/Xbox-only |
How nucleusConnect is used — (A) binary. The LoginManagerImpl / LoginStateMachineImpl string cluster at 0x14389fd50–0x14389fef8 reads, in order:
LoginManagerImpl · LoginData · LoginStateMachineImpl · LoginStateMachine
nucleusConnectTrusted · "%s/connect/token" · "grant_type=client_credentials"
recvBuf · "NEXUS_S2S " · "\"access_token\" : \"" · headers
LoginStateBase::buffer
LoginStateAuthenticated::mTermsOfServiceBuffer
LoginStateAuthenticated::mPrivacypolicyBuffer
nucleusConnect
So the client builds <nucleusConnect>/connect/token, POSTs grant_type=client_credentials, and scrapes "access_token" : " out of the JSON reply. Point nucleusConnect / nucleusConnectTrusted at our own HTTPS listener and serve a canned OAuth token JSON — that is the Blaze-side half of defeating auth. There is a NEXUS_S2S header value involved. LoginStateAuthenticated also buffers ToS + privacy-policy text, which is why NTOS (needsLegalDoc) in LoginResponse should be 0.
Recommended BlazeSDK map (keep it minimal — FIFA 17 ignores unknown keys):
associationListSkipInitialSet = 1
autoReconnectEnabled = 0
connIdleTimeout = 90000000
defaultRequestTimeout = 30000000
enableQosBandwidthTest = false
enableQosFirewallTest = false
maxReconnectAttempts = 0
nucleusConnect = https://accounts.ea.com <- repoint to us
nucleusConnectTrusted = https://accounts2s.ea.com <- repoint to us
pingPeriod = 20000000
userManagerMaxCachedUsers = 0
5.2 The OSDK_* sections — (A) binary
OSDK_CORE, OSDK_CLIENT, OSDK_NUCLEUS, OSDK_WEBOFFER, OSDK_ABUSE_REPORTING, OSDK_TICKER are literals at 0x143962be8..0x143962c40, and they sit inside the ResourceLoader / NETRESOURCE / LoadResourceFromMultiUrl / netres string cluster. Adjacent source path: .../extern/OSDK/8.01.03.00-fifa.01/source/common/presencedownloadmanagerabstract.cpp → the game embeds OSDK 8.01.03.00-fifa.01.
Meaning: these sections are game-tuning key/value maps consumed by FIFA's OSDK layer (a ResourceLoader with states LOADING/LOADED/NOT_FOUND), not Blaze plumbing. The key namespace is OSDK_*; confirmed examples of real config keys (as opposed to the many OSDK_* localisation ids):
OSDK_PRESENCE_DELAY, OSDK_PRESENCE_POLL, OSDK_ABUSE_NUM_TYPES, OSDK_ANTIGRIEFING_MAX_COUNT, OSDK_ARENA_ENABLED, OSDK_ARENA_CHALLENGE_SCHEDULE_URL, OSDK_ARENA_REGISTER_EMAIL_URL, OSDK_CLUBS_MAX_SEARCH_RESULT, OSDK_CLUBS_LOAD_MEMBER_PAGE_SIZE, OSDK_CLUBS_MAX_USERS_FOR_GAME, OSDK_CLUBS_LEADERBOARD_CLUB_MAX, OSDK_CLUBS_INCOME_SEARCH_MAX.
There is no nucleusConnect-equivalent in OSDK_NUCLEUS that we could find; the Nucleus URLs are BlazeSDK-level (§5.1). OSDK_NUCLEUS most likely holds Nucleus tuning (poll intervals, retry counts).
Assessment: returning an empty
CONFmap for theOSDK_*sections is probably not what killed us — these are tuning values that fall back to defaults. Answering them non-empty is cheap insurance, but the real blocker is layer 1 (Origin/LSXGetAuthCode/OriginIsOnline). Also noteOSDK_XMS_ABUSE_REPORTING(which the client requested) is not in the literal block — so the section list is built dynamically.
5.3 IdentityParams
Not requested by FIFA 17 in our capture, but both MEC emulators and z7 answer it identically:
display = console2/welcome, redirect_uri = http://127.0.0.1/success.
6. Post-login RPC order
(A) wire (ours) for everything up to logout. Beyond that, (A) clean-room 3P + z7, since we have never got past it.
Observed by us (FIFA 17):
1 9/7 Util::preAuth (req carries FCCR{CFID='BlazeSDK'}, CINF{CLNT='FIFA17', BSDK='15.1.1.3.0'})
2 9/2 Util::ping
-- reconnect --
3 9/1 Util::fetchClientConfig CFID=OSDK_CORE
4 9/1 CFID=OSDK_CLIENT
5 9/1 CFID=OSDK_NUCLEUS
6 9/1 CFID=OSDK_WEBOFFER
7 9/1 CFID=OSDK_ABUSE_REPORTING
8 9/1 CFID=OSDK_XMS_ABUSE_REPORTING
9 1/70 Authentication::logout <-- gave up here
Expected happy path (compiled from grid-blaze/src/main.rs:32-44, pamplona-future/.../util.ts:41-74, and the z7 msgNum ordering):
9/7 Util::preAuth -> PreAuthResponse
9/1 Util::fetchClientConfig -> FetchConfigResponse (xN)
1/10 Authentication::login -> LoginResponse
<< NOTIFY 30722/8 UserAuthenticated (UserSessionLoginInfo)
9/8 Util::postAuth -> PostAuthResponse
<< NOTIFY 30722/1 UserSessionExtendedDataUpdate
<< NOTIFY 30722/2 (UserAdded — DATA + USER)
1/29 Authentication::listUserEntitlements2 -> Entitlements{NLST}
9/28 Util::setClientState -> empty reply (req: MODE=1, STAT=0)
1/36 Authentication::getAuthToken -> {AUTH: "<token>"}
9/10 Util::userSettingsLoad -> UserSettingsResponse
25/6 AssociationLists::getLists -> GetListsResponse{LMAP}
30722/20 UserSessions::updateNetworkInfo -> empty reply
9/22 Util::setClientMetrics -> empty reply
9/2 Util::ping (every pingPeriod)
Ordering caveat: in grid-blaze the UserAuthenticated notification is pushed from inside the login handler, before the login reply is written (routes/authentication.rs:128-132); pamplona writes the reply first, then the notification (authentication.ts:54-57). Both apparently work. postAuth pushes its two notifications around its reply (util.ts:60-64: extendedDataAttribute → reply → extendedData).
Util::postAuth (9/8) — (A) binary
Request Blaze::Util::PostAuthRequest @ 0x1448757c0: DSUI dirtySockUserIndex(int32), UDID uniqueDeviceId(string) — only 2 members. (z7 shows a third MAC field; FIFA 17 does not have it. Another z7 mismatch.)
Response Blaze::Util::PostAuthResponse @ 0x144875810: TELE, TICK, UROP.
TELE=GetTelemetryServerResponse@0x144875470, 15 members:ADRS(str)ANON(bool)DISA(str)EDCT(bool)FILT(str)LOC(u32)MINR(bool)NOOK(str)PORT(u32)SDLY(u32)SESS(str)SKEY(str)SPCT(u32)STIM(str)SVNM(str).TICK=GetTickerServerResponse@0x1448754c0, 3 members:ADRS(str)PORT(u32)SKEY(str).UROP=UserOptions@0x144875770, 2 members:TMOP(enumTelemetryOpt)UID(int64).
Bug in our current responder
Blaze::Util::PingResponse @ 0x144875560 has exactly one member: STIM (serverTime, uint32). blaze_responder_v2.py sends STIM and TIME. TIME is not a member of FIFA 17's PingResponse (it is MEC's). Harmless-ish, but drop it.
7. Association lists (25/6) — for completeness
(A) clean-room 3P only (grid-blaze/src/models/association_lists.rs). Response GetListsResponse{ LMAP: list<AssociationList> }; each entry is INFO{ BOID(ObjectId) FLGS(u8) LID{LNM(str) TYPE(u8)} LMS(u32) PNAM(str) PRID(u8) PRMS(u32) }, OFRC, TOCT. MEC ships friendList(type 1), followList(type 5), communicationBlockList(type 4). FIFA's list names are not verified — do not assume.
8. Recommended next actions
- Fix layer 1 first. The client logs out before trying to log in, and the on-screen string is
TXT_NOT_LOGIN_TO_EBISU. Make the in-process LSX server on127.0.0.1:4216answerOriginIsOnline/GetInternetConnectedStateas online, andGetAuthCodewith any non-empty code. Without that there is nothing to put inLoginRequest.AUTHand Blaze work is unreachable. - Implement
Util::fetchClientConfigreturning a non-emptyCONFmap ({CONF: map<string,string>}) for everyCFID, even if only a couple of keys — removes it as a variable. - Put
nucleusConnect/nucleusConnectTrustedin the preAuthCONFmap pointing at our own listener, and servePOST <base>/connect/tokenreturning{"access_token" : "<anything>"}. - Implement
Authentication::login(1/10) → the 5-memberLoginResponseabove, then pushUserAuthenticated(30722/8, 16 members) with the same session-key string, using PersonaId 33068179 / "CAGE". - Implement
Util::postAuth(9/8),Authentication::listUserEntitlements2(1/29) returning oneONLINE_ACCESSentitlement (PJID/offer id1027460,TYPE=ONLINE_ACCESS,STAT=ACTIVE),Util::setClientState(9/28) empty,Authentication::getAuthToken(1/36) →{AUTH}. - Keep handling
Authentication::logout(1/70) with an empty reply — but treat receiving it as a failure signal in the responder log, not a normal step.
9. File index
Cloned reference repos (scratchpad):
/tmp/.../scratchpad/grid-blaze/src/routes/authentication.rs— login handler + entitlements/tmp/.../scratchpad/grid-blaze/src/models/authentication.rs— AuthResponse/Entitlement serialisers/tmp/.../scratchpad/grid-blaze/src/routes/util.rs,src/models/util.rs— preAuth/postAuth/fetchClientConfig/tmp/.../scratchpad/grid-blaze/src/models/user_sessions.rs— the 4 session notifications/tmp/.../scratchpad/grid-blaze/src/main.rs:32-44— full route table/tmp/.../scratchpad/grid-blaze/src/packet.rs— Fire2 framing (matches our corrected layout)/tmp/.../scratchpad/pamplona-future/src/blaze/components/authentication.ts:10-46— the command-id enum/tmp/.../scratchpad/pamplona-future/src/blaze/components/util.ts:17-39— Util command enum/tmp/.../scratchpad/pamplona-future/src/blaze/components/user-sessions.ts:21-50— UserSessions command enum/tmp/.../scratchpad/catalyst-mitm/blaze/interceptor.ts— redirector request XML shape/tmp/.../scratchpad/tdf/src/{writer,reader,tag,types}.rs— reference TDF codec
Our own artefacts:
/home/alex/Documents/OpenFUT/fifa17-recon/captures/blaze/session/session_full.log— the live session/home/alex/Documents/OpenFUT/fifa17-recon/captures/blaze/session/auth_cmd0x46.bin— the logout frame/home/alex/Documents/OpenFUT/fifa17-recon/tools/preauth_schema_reflection.md— prior reflection write-up + Util command table
Tooling written/used this pass (scratchpad):
reflect2.py— TDF type-descriptor walker (raw <va>/index <pat>/byname)authscan.py,stubrange.py,allstubs.txt—lea/retcommand-name stub recoveryfindstr2.py,nameblk.py,allstr.py,xref.py,rvatab.py,notifid.py— string/xref/constant huntingclusters.json— all 124 name-stub clusters (includes the UserSessions notification-name clusterServerDraining, UserAdded, UserAuthenticated, UserRemoved, UserSessionExtendedDataUpdate, UserUnauthenticated@0x146de19c0)
Descriptor VAs (FIFA17.exe, base 0x140000000):
LoginRequest 0x14487ca10 · LoginResponse 0x14487d170 · UserLoginInfo 0x14487cb00 · PersonaDetails 0x14487cab0 · GetAuthTokenResponse 0x14487d080 · ExpressLoginRequest 0x14487d0d0 · Entitlement 0x14487d490 · Entitlements 0x14487d4e0 · AccountInfo 0x14487c810 · PersonaInfo 0x14487c7c0 · UserSessionLoginInfo 0x14486f920 · UserSessionLogoutInfo 0x14486f970 · UserSessionExtendedData 0x144870390 · UserSessionExtendedDataUpdate 0x1448703e0 · Util::PostAuthRequest 0x1448757c0 · Util::PostAuthResponse 0x144875810 · Util::FetchConfigResponse 0x1448752e0 · Util::PingResponse 0x144875560 · Util::PreAuthResponse 0x144875600