Files
OpenFUT/fifa17-recon/tools/auth_refs.md
T
funman300 6ddd5e9d47 fifa17-recon: offline FUT squad-shell working + full card-system RE
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
2026-08-01 20:24:30 -07:00

402 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 `0x14389d6xx0x14389d928` 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 AZ 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 `0x14389fd500x14389fef8` 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`