# 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`
