# FIFA 17 Blaze — Authentication component (0x0001) reflection-reversed schema **Date:** 2026-07-30 · **Target:** live `FIFA17.exe` PID 19517 (alive throughout, still at the main menu afterwards) · **Method:** `/proc//mem` reflection walk + targeted disassembly + in-process function calls via gdb. Companion doc: `fifa17-recon/tools/preauth_schema_reflection.md` (Util component / PreAuthResponse). --- ## 0. Clean-room provenance Every fact below came from one of three allowed sources: 1. **The client's own runtime reflection metadata**, read out of the process we own. FIFA 17's BlazeSDK ships full TDF type descriptors (class names, member names, wire tags, struct offsets) in `.data`. This is the bulk of §3–§6. 2. **Disassembly of code in the binary we own** (`getCommandName` / `getErrorName` / `getNotificationName` switches, TDF descriptor initialisers, notification dispatcher). 3. **Calling the client's own name-lookup functions in-process** (§2). This is reading the binary's answer to its own question — no external artefact involved. **No EA/FIFA leaked source was consulted at any point.** No third-party BlazeSDK reimplementation was consulted for this document either (unlike the Fire2 header note in the preAuth doc, the findings here are all first-party). --- ## 1. Headline result: the client sent `logout`, not `login` The observed live frame ``` RX #9 Authentication::cmd:0x0046 msgType=MESSAGE msgNum=16 userIdx=0 payload=0B ``` **`Authentication` command `0x0046` (70) = `logout`.** (§2, verified twice.) `logout` has **no request TDF and no response TDF** — there is no `Blaze::Authentication::LogoutRequest` or `LogoutResponse` anywhere in the client's type index, which is exactly why the payload was 0 bytes. Our empty REPLY was therefore *correct on the wire*. This reframes the whole gate: - The client **never sent `Authentication::login` (cmd 0x000A)**, never sent `getAuthToken` (0x0024), never sent `listUserEntitlements2` (0x001D), never sent `listPersonas` (0x0064). - It went `preAuth` → `ping` → 6× `fetchClientConfig` → **`logout`** → transport-ping loop. - A client that logs out without ever logging in has decided *before touching Blaze auth* that it has no credential to present. That is the Origin/LSX layer (layer 1 in the repack recon), not Blaze. **Implication for the workflow:** implementing `Authentication::login` server-side is necessary but **not sufficient and not the current blocker**. The client must first obtain an auth code from the in-process Origin/LSX stub on `127.0.0.1:4216` (`GetAuthCode` → `AuthCodeResponse`, and `OriginIsOnline` / `GetInternetConnectedState` must report ONLINE). Until that succeeds the client will keep skipping straight to `logout`. See §7 for the ordering this implies. --- ## 2. Authentication component RPC table — **complete** ### How it was recovered Each Blaze component has a 7-pointer `ComponentDescription`-style table in `.rdata`. Layout established by diffing the Util table (whose command map we already knew) against Authentication's: | Offset | Meaning | Util | Authentication | |---|---|---|---| | `+0x00` | shared helper | `0x146f022d0` | `0x146f022d0` | | `+0x08` | `getComponentName()` | `0x146df7370` → `"UtilComponent"` | `0x146e0ec00` → `"AuthenticationComponent"` | | `+0x10` | **`getCommandName(u16)`** | `0x146df6dc0` | **`0x146e0d2a0`** | | `+0x18` | shared helper | `0x145bf3580` | `0x145bf3580` | | `+0x20` | `getErrorName(u32)` | `0x146df7380` | `0x146e0ec20` | | `+0x28` | `getRestResourceInfo(u16)` | `0x146f82070` (null stub) | `0x146e0f2e0` | | `+0x30` | shared helper | `0x1465734f0` | `0x1465734f0` | Table addresses: Util `0x143895820`, Authentication `0x14389d628`, plus one more at `0x143891940` (Redirector) and six in `0x1438f5xxx` (FIFA-custom components). Util's `getCommandName` is clean code — `movzx eax,dx; dec eax; cmp eax,0x1b; ja default;` jump table of 28 image-relative RVAs at `0x141b17af4`. Decoding it statically **reproduces the previously-known Util table exactly** (`fetchClientConfig=1, ping=2, preAuth=7, postAuth=8`, …), which validates the whole approach. **Authentication's `getCommandName` at `0x146e0d2a0` is Denuvo-mutated** — it begins `push rcx; lea rcx,[rip+…]; jmp 0x14e183f34` into the protector arena. Consequently: - the 33-name alphabetical string pool at `0x14389d690`–`0x14389d928` has **zero** references anywhere in the address space (no `lea`, no qword pointer, no image-relative u32 — checked across every readable region including the heap); - so the command map cannot be recovered statically. It was recovered instead by **calling the function in the live process**: ``` gdb --batch -p -ex 'p ((char*(*)(long,long,int))0x146e0d2a0)(0,0,)' ``` Note the calling-convention detail: the target is **Windows x64** code (`this`→`rcx`, arg→`rdx`) but gdb marshals with the **SysV** ABI (`rdi, rsi, rdx, …`), so the command id must be passed as the **third** gdb argument to land in `rdx`. Validated against Util first (returned `preAuth` for 7, `ping` for 2, `fetchClientConfig` for 1, `postAuth` for 8), then applied to Authentication. Two independent cross-checks on the Authentication result: - id `0x000B` → `"trustedLogin"`, which matches the static REST-binding struct at `0x143896a80` (`{u16 component=0x0001, u16 command=0x000B, …, const char* name="trustedLogin", …, "GET"}`). - every one of the 33 names in the static string pool is accounted for, with no leftovers. ### The table Ids 1–320 were swept; everything not listed returns the empty default string. | Cmd | RPC | Cmd | RPC | |---|---|---|---| | `0x000A` (10) | **`login`** | `0x003C` (60) | `expressLogin` | | `0x000B` (11) | `trustedLogin` | **`0x0046` (70)** | **`logout`** ← *observed on the wire* | | `0x0014` (20) | `updateAccount` | `0x005A` (90) | `getPersona` | | `0x0015` (21) | `upgradeAccount` | `0x0064` (100) | `listPersonas` | | `0x001D` (29) | `listUserEntitlements2` | `0x0065` (101) | `expressCreateAccount` | | `0x001E` (30) | `getAccount` | `0x00E6` (230) | `createWalUserSession` | | `0x001F` (31) | `grantEntitlement` | `0x00F1` (241) | `acceptLegalDocs` | | `0x0020` (32) | `listEntitlements` | `0x00F2` (242) | `getEmailOptInSettings` | | `0x0022` (34) | `getUseCount` | `0x00F6` (246) | `getTermsOfServiceContent` | | `0x0023` (35) | `decrementUseCount` | `0x0104` (260) | `getOriginPersona` | | `0x0024` (36) | `getAuthToken` | `0x010E` (270) | `checkEmail` | | `0x0026` (38) | `getPasswordRules` | `0x0118` (280) | `getPersonaNameSuggestions` | | `0x0027` (39) | `grantEntitlement2` | `0x0122` (290) | `guestLogin` | | `0x002B` (43) | `modifyEntitlement2` | | | | `0x002C` (44) | `consumecode` | | | | `0x002D` (45) | `passwordForgot` | | | | `0x002F` (47) | `getPrivacyPolicyContent` | | | | `0x0030` (48) | `listPersonaEntitlements2` | | | | `0x0033` (51) | `checkAgeReq` | | | | `0x0034` (52) | `getOptIn` | | | | `0x0035` (53) | `enableOptIn` | | | | `0x0036` (54) | `disableOptIn` | | | Confidence: **certain** for every row (the client itself produced these strings). ### Request / response type binding From the reflection type index (`Blaze::Authentication::*`): | Cmd | RPC | Request TDF | Response TDF | |---|---|---|---| | `0x000A` | `login` | `LoginRequest` | **`LoginResponse`** | | `0x000B` | `trustedLogin` | `TrustedLoginRequest` | `LoginResponse` | | `0x003C` | `expressLogin` | `ExpressLoginRequest` | `LoginResponse` | | `0x0122` | `guestLogin` | *(none)* | `LoginResponse` | | **`0x0046`** | **`logout`** | ***(none — empty)*** | ***(none — empty)*** | | `0x0024` | `getAuthToken` | *(none)* | `GetAuthTokenResponse` | | `0x001D` | `listUserEntitlements2` | `ListUserEntitlements2Request` | `Entitlements` | | `0x0030` | `listPersonaEntitlements2` | `ListPersonaEntitlements2Request` | `Entitlements` | | `0x0064` | `listPersonas` | *(none)* | `ListPersonasResponse` | | `0x005A` | `getPersona` | *(none)* | `GetPersonaResponse` | | `0x001E` | `getAccount` | *(none)* | `AccountInfo` | | `0x0104` | `getOriginPersona` | `GetOriginPersonaRequest` | `PersonaInfo` | `logout` having no TDFs on either side is confirmed by absence: there is no `LogoutRequest` / `LogoutResponse` type anywhere in the client's index. --- ## 3. `Blaze::Authentication::LoginResponse` — the login reply (cmd `0x000A`) Descriptor `0x14487d170`, member table `0x1448789c0`, 5 members. Serialise in **ascending packed-tag order** (which is the order the table itself is sorted in). | Tag | Member | TDF type | Wire type | Offset | |---|---|---|---|---| | `ANON` | `isAnonymous` | bool | `0x00` | `+0x12` | | `NTOS` | `needsLegalDoc` | bool | `0x00` | `+0x13` | | `SESS` | `userLoginInfo` | `Blaze::Authentication::UserLoginInfo` | `0x03` | `+0x18` | | `SPAM` | `isOfLegalContactAge` | bool | `0x00` | `+0x10` | | `UNDR` | `isUnderage` | bool | `0x00` | `+0x11` | ### `Blaze::Authentication::UserLoginInfo` (`SESS`) — 8 members, descriptor `0x14487cb00` | Tag | Member | Type | Offset | |---|---|---|---| | `1CON` | `isFirstConsoleLogin` | bool | `+0x51` | | `BUID` | `blazeUserId` | int64 | `+0x28` | | `FRST` | `isFirstLogin` | bool | `+0x50` | | `KEY ` | `sessionKey` | string | `+0x10` | | `LLOG` | `lastLoginDateTime` | int64 | `+0x58` | | `MAIL` | `email` | string | `+0x38` | | `PDTL` | `personaDetails` | `PersonaDetails` (struct) | `+0x60` | | `UID ` | `userId` | int64 | `+0x30` | > Note the trailing-space tags: `KEY` and `UID` are 3-char tags. `heat2.py`'s `encode_tag` > pads to 4 with the 6-bit "space" code, which is what the wire expects. ### `Blaze::Authentication::PersonaDetails` (`PDTL`) — 6 members, descriptor `0x14487cab0` | Tag | Member | Type | Offset | |---|---|---|---| | `DSNM` | `displayName` | string | `+0x18` | | `LAST` | `lastAuthenticated` | uint32 | `+0x3c` | | `PID ` | `personaId` | int64 | `+0x10` | | `PLAT` | `clientPlatform` | `Blaze::ClientPlatformType` (enum → int) | `+0x38` | | `STAS` | `status` | `PersonaStatus::Code` (enum → int) | `+0x40` | | `XREF` | `extId` | uint64 | `+0x30` | ### Request side (for completeness) `Blaze::Authentication::LoginRequest` (`0x14487ca10`, 3 members): | Tag | Member | Type | |---|---|---| | `AUTH` | `authCode` | string ← *the Origin/LSX auth code* | | `EXTB` | `externalBlob` | blob | | `EXTI` | `externalId` | uint64 | `TrustedLoginRequest` (`0x14487ca60`): `ID ` id:string, `ITYP` idType:string, `TOKN` accessToken:string. `ExpressLoginRequest` (`0x14487d0d0`): `MAIL` email:string, `PASS` password:string, `PNAM` personaName:string. `StressLoginRequest` (`0x14487d120`): `MAIL` email:string, `NUID` nucleusId:uint64, `PNAM` personaName:string. The `AUTH` field being a plain string is the whole hinge: **the client cannot fill it without a working Origin `GetAuthCode`.** --- ## 4. What the server must return for the client to consider itself authenticated **Caveat on method.** For `PreAuthResponse` we could read `ConnectionManager::onPreAuthResponse` directly and prove field-by-field what is and isn't validated. The equivalent login-response handler lives in the FIFA/OSDK layer, and that code is **Denuvo-mutated** — the same protection that hid `getCommandName`. So the list below is derived from *structure* (what the types carry, what the UserSessions notification duplicates, what the client must have in order to name itself), not from reading the handler. Flagged accordingly. | Field | Why it matters | Confidence it is required | |---|---|---| | `SESS.KEY ` `sessionKey` | The Blaze session credential. Every later component echoes it; a session with an empty key is not a session. Must be non-empty. | **high** | | `SESS.BUID` `blazeUserId` | The client's own BlazeId. Used as the identity key in UserManager and in every later `lookupUser*`. Must be non-zero. | **high** | | `SESS.UID ` `userId` | Nucleus user id. Should equal `blazeUserId` for a single-account offline emu unless you have a reason to split them. | **high** | | `SESS.PDTL.PID ` `personaId` | **Must equal `33068179`** (the `stp-origin_emu.ini` `PersonaId`). A mismatch is exactly what trips `AUTH_ERR_INVALID_PERSONA` / `AUTH_ERR_USER_DOES_NOT_MATCH_PERSONA` / `AUTH_ERR_PERSONA_NOT_FOUND`. | **high** | | `SESS.PDTL.DSNM` `displayName` | **Must equal `CAGE`.** This is the name the UI renders and the value later persona lookups are matched against. | **high** | | `SESS.PDTL.STAS` `status` | Persona status enum. Must be the "active" value; `AUTH_ERR_PERSONA_INACTIVE` (19) and `AUTH_ERR_PERSONA_BANNED` (32) exist for the other cases. Send `0`. | medium | | `SESS.PDTL.PLAT` `clientPlatform` | Should match the `PLAT` you returned in `PreAuthResponse` (`pc`). | medium | | `SESS.PDTL.XREF` `extId` | External (Origin) id. Safe to mirror `personaId`. | low | | `SESS.MAIL` `email` | Cosmetic; any well-formed address. | low | | `ANON` `isAnonymous` | **Must be `false`/0.** An anonymous session is precisely a not-really-logged-in session and will not enable online features. | **high** | | `NTOS` `needsLegalDoc` | **Must be `false`/0**, otherwise the client will branch into the legal-doc flow (`acceptLegalDocs` = 241, `getTermsOfServiceContent` = 246) instead of proceeding. | **high** | | `UNDR` `isUnderage` | **Must be `false`/0** — underage gates online play. | **high** | | `SPAM` `isOfLegalContactAge` | Set `true`/1 for symmetry with `UNDR=false`. | medium | | `FRST` / `1CON` / `LLOG` | First-login flags and timestamp. Cosmetic; `false/false/` is fine. | low | Consistency rule that spans both layers: **`personaId` and `personaName` returned over LSX, returned in `LoginResponse.PDTL`, and pushed in the `UserAuthenticated` notification must be the same triple** (`33068179` / `CAGE` / `en_US`). Every `AUTH_ERR_*_PERSONA*` code in §6 is a symptom of these three disagreeing. --- ## 5. The login-success notification the server must push Authentication (component `0x0001`) has **exactly one** notification. Its `getNotificationName`-slot function `0x146e0f2e0` is a one-case switch: ``` cmp dx,0xb ; jne default ; lea rax,[0x143896a80] ; ret ``` and `0x143896a80` is **not** a notification name — it is the REST-binding struct for `trustedLogin` (cmd `0x000B`). So Authentication publishes **no async notifications at all**; the slot is `getRestResourceInfo`, not `getNotificationName`. **The login-success notification lives on `UserSessions`, component `0x7802` (30722).** Component id confirmed independently: that component's error table compares against `(errorNumber<<16)|0x7802` (`0x17802`, `0x27802`, … `0x177802`). `UserSessions::getNotificationName` at `0x146de19a0` is **clean, unmutated** code — `movzx eax,dx; dec eax; cmp eax,0xb; ja default;` with a 12-entry image-relative jump table at `0x141b03f70`. Decoded statically: | Notification id | Name | Payload TDF | |---|---|---| | `0x0001` | `UserSessionExtendedDataUpdate` | `Blaze::UserSessionExtendedDataUpdate` | | `0x0002` | `UserAdded` | `Blaze::UserData` | | `0x0003` | `UserRemoved` | `Blaze::UserIdentification` *(or `UserStatus`)* | | `0x0004` | *(unused)* | — | | `0x0005` | `UserUpdated` | `Blaze::UserStatus` | | `0x0006`–`0x0007` | *(unused)* | — | | **`0x0008`** | **`UserAuthenticated`** | **`Blaze::UserSessionLoginInfo`** | | `0x0009` | `UserUnauthenticated` | `Blaze::UserSessionLogoutInfo` | | `0x000A`–`0x000B` | *(unused)* | — | | `0x000C` | `ServerDraining` | — | The **id → name** column is certain (decoded from the client's own jump table). The **payload TDF** column is *inferred*: the notification dispatcher at `0x146de2803` (12-entry jump table at `0x141b05258`, branches at `0x146de2bc3 / 289c / 2ae9 / 2dd8 / 29e0 / 2cf1 / 2825`) constructs its TDFs on the stack rather than through the type-descriptor getters, so I could not bind each branch to a descriptor by xref. The mapping above is a 1:1 name match against the only session types the client links in, and `UserSessionLoginInfo` is the only type in the entire index that carries a session key plus a persona — but treat the payload column as **high confidence, not verified**. ### `Blaze::UserSessionLoginInfo` — 16 members, descriptor `0x14486f920` This is the notification body to push after a successful `login`. Note it is a **superset** of `UserLoginInfo` with the persona fields flattened in rather than nested. | Tag | Member | Type | Offset | |---|---|---|---| | `1CON` | `isFirstConsoleLogin` | bool | `+0x51` | | `ALOC` | `accountLocale` | uint32 | `+0x60` | | `BUID` | `blazeUserId` | int64 | `+0x28` | | `CGID` | `connectionGroupObjectId` | ObjectId | `+0xb0` | | `DSNM` | `displayName` | string | `+0x70` | | `FRST` | `isFirstLogin` | bool | `+0x50` | | `KEY ` | `sessionKey` | string | `+0x10` | | `LAST` | `lastAuthenticated` | uint32 | `+0xac` | | `LLOG` | `lastLoginDateTime` | int64 | `+0x58` | | `MAIL` | `email` | string | `+0x38` | | `NASP` | `personaNamespace` | string | `+0x88` | | `PID ` | `personaId` | int64 | `+0x68` | | `PLAT` | `clientPlatform` | enum | `+0xa8` | | `UID ` | `userId` | int64 | `+0x30` | | `USTP` | `userSessionType` | `Blaze::UserSessionType` (enum) | `+0xc0` | | `XREF` | `extId` | uint64 | `+0xa0` | `ALOC` (`accountLocale`) is a packed uint32 locale — use the same encoding the client sent in `ClientData.LANG` in the PreAuth request, i.e. echo it back rather than inventing one. `NASP` (`personaNamespace`) should match the `NASP` you returned in `PreAuthResponse`. ### Related UserSessions types (for the rest of the session bring-up) ``` Blaze::UserSessionLogoutInfo (0x14486f970, 2) BID blazeId int64 USTP userSessionType enum Blaze::SessionInfo (0x14486ffa0, 5) BUID blazeUserId int64 DSNM displayName string KEY sessionKey string MAIL email string UID userId int64 Blaze::UserIdentification (0x14486ebc0, 9) AID accountId int64 ALOC accountLocale uint32 EXBB externalBlob blob EXID externalId uint64 ID blazeId int64 NAME name string NASP personaNamespace string ORIG originPersonaId uint64 PIDI pidId int64 Blaze::UserData (0x1448706b0, 3) EDAT extendedData Blaze::UserSessionExtendedData FLGS statusFlags Blaze::UserDataFlags (bitfield) USER userInfo Blaze::UserIdentification Blaze::UserStatus (0x14486ed10, 2) FLGS statusFlags Blaze::UserDataFlags ID blazeId int64 Blaze::UserSessionExtendedData (0x144870390, 12) ADDR address Blaze::NetworkAddress (union, 5 cases) BPS bestPingSiteAlias string CTY country string CVAR clientData variable DMAP dataMap map HWFG hardwareFlags Blaze::HardwareFlags (bitfield) ISP iSP string PSLM latencyList list QDAT qosData Blaze::Util::NetworkQosData TZ timeZone string UATT userInfoAttribute uint64 ULST blazeObjectIdList list Blaze::UserSessionExtendedDataUpdate (0x1448703e0, 3) DATA extendedData Blaze::UserSessionExtendedData SUBS subscribed bool USID userId int64 ``` --- ## 6. Authentication error codes — **complete** Blaze error codes for this component are packed `(errorNumber << 16) | 0x0001`. Recovered by calling `getErrorName` (`0x146e0ec20`, clean code) in-process across the range; the static `cmp edx,0xNN0001` chain at `0x146e0ec20`+ corroborates. | # | Name | # | Name | |---|---|---|---| | 1 | `AUTH_ERR_INVALID_TOKEN` | 72 | `AUTH_ERR_TOO_MANY_ENTITLEMENTS` | | 2 | `AUTH_ERR_TOS_REQUIRED` | 73 | `AUTH_ERR_PAGESIZE_ZERO` | | 6 | `AUTH_ERR_INVALID_SANDBOX_ID` | 74 | `AUTH_ERR_ENTITLEMENT_TAG_REQUIRED` | | 10 | `AUTH_ERR_INVALID_COUNTRY` | 75 | `AUTH_ERR_PAGENO_ZERO` | | 11 | `AUTH_ERR_INVALID_USER` | 76 | `AUTH_ERR_MODIFIED_STATUS_INVALID` | | 12 | `AUTH_ERR_INVALID_PASSWORD` | 77 | `AUTH_ERR_USECOUNT_INCREMENT` | | 14 | `AUTH_ERR_EXPIRED_TOKEN` | 78 | `AUTH_ERR_TERMINATION_INVALID` | | 16 | `AUTH_ERR_TOO_YOUNG` | 79 | `AUTH_ERR_UNKNOWN_ENTITLEMENT` | | 17 | `AUTH_ERR_NO_ACCOUNT` | 80 | `AUTH_ERR_EXCEEDS_PSU_LIMIT` | | 19 | `AUTH_ERR_PERSONA_INACTIVE` | 81 | `AUTH_ERR_OPTIN_NAME_REQUIRED` | | 20 | `AUTH_ERR_INVALID_PMAIL` | 82 | `AUTH_ERR_INVALID_OPTIN` | | 21 | `AUTH_ERR_INVALID_FIELD` | 83 | `AUTH_ERR_OPTIN_MISMATCH` | | 22 | `AUTH_ERR_INVALID_EMAIL` | 84 | `AUTH_ERR_NO_SUCH_OPTIN` | | 23 | `AUTH_ERR_INVALID_STATUS` | 85 | `AUTH_ERR_AUTHID_REQUIRED` | | 32 | `AUTH_ERR_PERSONA_BANNED` | 86 | `AUTH_ERR_PERSONA_EXTREFID_REQUIRED` | | 33 | `AUTH_ERR_INVALID_PERSONA` | 87 | `AUTH_ERR_SOURCE_REQUIRED` | | 34 | `AUTH_ERR_CURRENT_PASSWORD_REQUIRED` | 88 | `AUTH_ERR_APPLICATION_REQUIRED` | | 41 | `AUTH_ERR_DEACTIVATED` | 89 | `AUTH_ERR_TOKEN_REQUIRED` | | 43 | `AUTH_ERR_BANNED` | 90 | `AUTH_ERR_PARAMETER_TOO_LENGTH` | | 44 | `AUTH_ERR_DISABLED` | 91 | `AUTH_ERR_NO_SUCH_PERSONA_REFERENCE` | | 50 | `AUTH_ERR_NEED_PCCDKEY` | 93 | `AUTH_ERR_INVALID_SOURCE` | | 51 | `AUTH_ERR_CODE_ALREADY_USED` | 94 | `AUTH_ERR_NO_SUCH_AUTH_DATA` | | 52 | `AUTH_ERR_INVALID_CODE` | 101 | `AUTH_ERR_USER_INACTIVE` | | 53 | `AUTH_ERR_CODE_ALREADY_DISABLED` | 102 | `AUTH_ERR_UNEXPECTED_ACTIVATION` | | 54 | `AUTH_ERR_NO_ASSOCIATED_PRODUCT` | 103 | `AUTH_ERR_NAME_MISMATCH` | | 55 | `AUTH_ERR_INVALID_MAPPING_ERROR` | 105 | `AUTH_ERR_INVALID_NAMESPACE` | | 56 | `AUTH_ERR_NO_SUCH_GROUP_NAME` | 198 | `AUTH_ERR_FIELD_MIN_LOWER_CHARS` | | 57 | `AUTH_ERR_MISSING_PERSONAID` | 199 | `AUTH_ERR_FIELD_MIN_UPPER_CHARS` | | 58 | `AUTH_ERR_USER_DOES_NOT_MATCH_PERSONA` | 200 | `AUTH_ERR_FIELD_MIN_DIGITS` | | 59 | `AUTH_ERR_WHITELIST` | 201 | `AUTH_ERR_FIELD_INVALID_CHARS` | | 60 | `AUTH_ERR_LINK_PERSONA` | 202 | `AUTH_ERR_FIELD_TOO_SHORT` | | 61 | `AUTH_ERR_NO_SUCH_GROUP` | 203 | `AUTH_ERR_FIELD_TOO_LONG` | | 63 | `AUTH_ERR_NO_SUCH_ENTITLEMENT` | 204 | `AUTH_ERR_FIELD_MUST_BEGIN_WITH_LETTER` | | 64 | `AUTH_ERR_GROUP_NAME_DOES_NOT_MATCH` | 205 | `AUTH_ERR_FIELD_MISSING` | | 66 | `AUTH_ERR_USECOUNT_ZERO` | 206 | `AUTH_ERR_FIELD_INVALID` | | 67 | `AUTH_ERR_ENTITLEMETNTAG_EMPTY` *(sic)* | 207 | `AUTH_ERR_FIELD_NOT_ALLOWED` | | 70 | `AUTH_ERR_GROUPNAME_REQUIRED` | 208 | `AUTH_ERR_FIELD_NEEDS_SPECIAL_CHARS` | | 71 | `AUTH_ERR_GROUPNAME_INVALID` | 209 | `AUTH_ERR_FIELD_ALREADY_EXISTS` | | | | 210 | `AUTH_ERR_FIELD_NEEDS_CONSENT` | | | | 211 | `AUTH_ERR_FIELD_TOO_YOUNG` | | | | 212 | `AUTH_ERR_ASSOCIATION_TOO_YOUNG` | --- ## 7. Supporting types for the post-login flow ``` Blaze::Authentication::GetAuthTokenResponse (0x14487d080, 1) AUTH authToken string Blaze::Authentication::GetUserAccessTokenResponse (0x14487db80, 2) ATOK accessToken string CLID clientId string Blaze::Authentication::ListPersonasResponse (0x14487d210, 1) PINF list list Blaze::Authentication::GetPersonaResponse (0x14487d1c0, 2) PINF personaInfo Blaze::Authentication::PersonaInfo UID userId int64 Blaze::Authentication::PersonaInfo (0x14487c7c0, 7) DSNM displayName string DTCR dateCreated string LADT lastAuthenticated uint32 NSNM nameSpaceName string PID personaId int64 STAS status enum STRC statusReasonCode enum Blaze::Authentication::AccountInfo (0x14487c810, 16) AMU anonymousUser:bool ASRC authenticationSource:string CO country:string DOB dOB:string DTCR dateCreated:string GOPT globalOptin:int8 LATH lastAuth:string LN language:string MAIL email:string PML parentalEmail:string RC reasonCode:enum STAS status:enum STAT emailStatus:enum TPOT thirdPartyOptin:int8 UDU underageUser:bool UID userId:int64 Blaze::Authentication::Entitlements (0x14487d4e0, 1) NLST entitlements list Blaze::Authentication::Entitlement (0x14487d490, 16) DEVI deviceUri:string GDAY grantDate:string GNAM groupName:string ID id:uint64 ISCO isConsumable:bool PID personaId:int64 PJID projectId:string PRCA productCatalog:enum PRID productId:string STAT status:enum STRC statusReasonCode:enum TAG entitlementTag:string TDAY terminationDate:string TYPE entitlementType:enum UCNT useCount:uint32 VER version:uint32 Blaze::Authentication::UserProfileInfo (0x14487c860, 8) CITY city:string CTRY country:string STAT state:string STRT street:string ZIP zipCode:string GNDR gender:enum UID userId:int64 ELEM profileInfoElementsByCategory: map> (the binary's own map<> name string is written value-first — read it backwards, same caveat as documented for QoS maps in the preAuth doc) ``` When `listUserEntitlements2` (`0x001D`) does eventually get called, the entitlement the retail exe requires is `TAG = "ONLINE_ACCESS"` with `PRID` tied to offer id `1027460`, `STAT` = active, and `PID` = `33068179`. `AUTH_ERR_NO_SUCH_ENTITLEMENT` (63) and `AUTH_ERR_ENTITLEMENT_TAG_REQUIRED` (74) are the failure modes. --- ## 8. Recommended server behaviour, in order 1. **Fix layer 1 first.** Nothing in this document is reachable until the in-process Origin/LSX stub on `127.0.0.1:4216` answers `OriginIsOnline` / `GetInternetConnectedState` with *online* and `GetAuthCode` with a non-empty code. Right now the client's `logout` at cmd `0x0046` is the observable proof that it never got one. This is the single highest-value next step. 2. Keep replying to `Util::fetchClientConfig` (9/1) — an empty `FetchConfigResponse { CONF: {} }` is structurally valid, but see the preAuth doc for the tunables the client reads out of `CONF`. 3. When `Authentication::login` (`0x0001`/`0x000A`) finally arrives, reply `LoginResponse` per §3 with the §4 field values, keyed to persona `33068179` / `CAGE`. 4. Immediately after the login REPLY, push a **NOTIFICATION** frame (msgType=2) on component `0x7802`, notification id `0x0008` (`UserAuthenticated`), payload `Blaze::UserSessionLoginInfo` per §5 — same session key, same BlazeId, same persona triple. 5. Expect `Util::postAuth` (9/8) next; `PostAuthResponse` is documented in the preAuth doc. 6. Answer `logout` (`0x0046`) with an empty REPLY — which is already correct. --- ## 9. Tooling produced (all in the session scratchpad) | Script | Purpose | |---|---| | `stubscan.py` | list every `lea rax,[rip+str]; ret` name stub in a VA window, in address order | | `authcmd.py` | locate RPC-name strings and every reference to them | | `refscan.py` | find all references (rip-rel lea / qword / image-relative u32) into a VA window | | `heapref.py` | same, but across *every* readable region including the heap | | `cmdinfo.py` | scan the metadata arena for `{u16 component, u16 command, …, name}` REST structs | | `comptabs.py` | enumerate Blaze `ComponentDescription` 7-pointer tables | | `qdump.py` / `pooldump.py` / `dumpregion.py` | annotated qword dump / string-pool dump / raw region dump | | `jt.py` | decode an MSVC image-relative jump table of `lea/ret` name stubs | | `rpcmap.py` / `factref.py` / `vt2.py` | descriptor-getter and factory xref mapping (all three came up empty for Authentication — recorded as negative results) | | `authcmds*.gdb`, `autherr.gdb` | in-process `getCommandName` / `getErrorName` sweeps | Reused from the previous pass: `memtool.py`, `reflect2.py`, `strsearch.py`, `all_types.txt`. ### Negative results worth recording - The 33-name Authentication command-name pool at `0x14389d690` is **completely unreferenced** — no lea, no pointer, no RVA, anywhere in the process. Do not waste time looking for its xrefs. - `Blaze::Authentication::LoginRequest`'s vtable (`0x14389ac68`) is referenced only by its own factory/ctor/dtor. No game code constructs one statically — the login call site is inside Denuvo-mutated code. - Every `Blaze::Authentication::*` type-descriptor getter has exactly one caller: the TDF factory registration run at `0x1451cd0xx`. There is no per-command descriptor table to mine.