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
402 lines
27 KiB
Markdown
402 lines
27 KiB
Markdown
# 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:
|
||
|
||
1. **(A) clean-room 3P** — `pamplona-future/src/blaze/components/authentication.ts:10-46` is a full BlazeSDK-15.1.1 `Authentication` command enum, and it lists `logout = 70`. That enum is independently corroborated on three other entries by our own data:
|
||
- `login = 10` ↔ z7 capture `comp=0x0001 cmd=0x000A [LoginRequest]`
|
||
- `getAuthToken = 36` ↔ z7 capture `comp=0x0001 cmd=0x0024 [GetAuthTokenResponse]`
|
||
- `listUserEntitlements2 = 29` ↔ `grid-blaze/src/main.rs:35` routes `1, 29 => list_entitlments`
|
||
2. **(A) binary** — FIFA 17's own Authentication RPC-name literal pool at `0x14389d6xx–0x14389d928` contains **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 at `0x14389d8d8`.
|
||
3. `logout` is the only Authentication RPC in that set that takes **no request parameters**, which matches the observed 0-byte payload. (`getAuthToken` is also parameterless but is `0x24`, not `0x46`.)
|
||
|
||
> Confidence: **high**, cross-validated three ways, but *not* 100% binary-pinned — FIFA 17's `getCommandName` for Authentication is not emitted as the `lea rax,[rip+str]; ret` stub pattern that yielded the Util table, and the name pool has **zero** code xrefs (searched every `lea/mov rip-rel` and 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 `LoginRequest` also carried `ACHT{SHID,SKID}` — **FIFA 17 has no `ACHT` member**. 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-50` and `pamplona-future/.../authentication.ts:78-128` both emit `CNTX`, `ERRC` and a top-level `SKEY` in the login *payload*. **FIFA 17's `LoginResponse` has none of those.** `CNTX`/`ERRC` are the Blaze **error metadata** block (see `grid-blaze/src/packet.rs:62-71` `ErrorBody`, and its own `// TODO: move ErrorBody to metadata`; pamplona sets `metadataSize: 75` on the login reply). Emit **exactly the 5 members above** in the payload; leave metadata empty on success. The session key lives at `SESS.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:
|
||
|
||
1. Ignore whatever is in `LoginRequest.AUTH` (it never validates it against Nucleus — it swaps in Discord OAuth; an offline emulator just skips validation entirely).
|
||
2. Mint a `User { user_id, persona_id, username }` from local storage / config.
|
||
3. Store it on the session (`session.data.set_user`).
|
||
4. **Push the `UserAuthenticated` notification** (see §4.3).
|
||
5. Reply with `LoginResponse`.
|
||
|
||
### 4.2 Session key
|
||
|
||
Both 3P emulators use a **canned literal**; the client does not verify it:
|
||
- `grid-blaze` uses simply `"0"` for both `SKEY` and `SESS.KEY`.
|
||
- `pamplona-future` uses 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/8 `updateHardwareFlags` — 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 has **`BPS` as a top-level string member** (they wrap it inside the `ADDR` union as `BPS `). 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 `CONF` map for the `OSDK_*` 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/LSX `GetAuthCode` / `OriginIsOnline`). Also note `OSDK_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`(enum `TelemetryOpt`) `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
|
||
|
||
1. **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 on `127.0.0.1:4216` answer `OriginIsOnline` / `GetInternetConnectedState` as **online**, and `GetAuthCode` with any non-empty code. Without that there is nothing to put in `LoginRequest.AUTH` and Blaze work is unreachable.
|
||
2. Implement `Util::fetchClientConfig` returning a non-empty `CONF` map (`{CONF: map<string,string>}`) for every `CFID`, even if only a couple of keys — removes it as a variable.
|
||
3. Put `nucleusConnect` / `nucleusConnectTrusted` in the **preAuth** `CONF` map pointing at our own listener, and serve `POST <base>/connect/token` returning `{"access_token" : "<anything>"}`.
|
||
4. Implement `Authentication::login` (1/10) → the 5-member `LoginResponse` above, then push `UserAuthenticated` (30722/8, 16 members) with the **same** session-key string, using PersonaId 33068179 / "CAGE".
|
||
5. Implement `Util::postAuth` (9/8), `Authentication::listUserEntitlements2` (1/29) returning one `ONLINE_ACCESS` entitlement (`PJID`/offer id `1027460`, `TYPE`=ONLINE_ACCESS, `STAT`=ACTIVE), `Util::setClientState` (9/28) empty, `Authentication::getAuthToken` (1/36) → `{AUTH}`.
|
||
6. 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/ret` command-name stub recovery
|
||
- `findstr2.py`, `nameblk.py`, `allstr.py`, `xref.py`, `rvatab.py`, `notifid.py` — string/xref/constant hunting
|
||
- `clusters.json` — all 124 name-stub clusters (includes the UserSessions notification-name cluster
|
||
`ServerDraining, 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`
|