Files
OpenFUT/fifa17-recon/tools/auth_schema_reflection.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

28 KiB
Raw Blame History

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/<pid>/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 preAuthping → 6× fetchClientConfiglogout → 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 (GetAuthCodeAuthCodeResponse, 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 0x14389d6900x14389d928 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 <pid> -ex 'p ((char*(*)(long,long,int))0x146e0d2a0)(0,0,<id>)'

Note the calling-convention detail: the target is Windows x64 code (thisrcx, 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 1320 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) logoutobserved 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/<now> 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
0x00060x0007 (unused)
0x0008 UserAuthenticated Blaze::UserSessionLoginInfo
0x0009 UserUnauthenticated Blaze::UserSessionLogoutInfo
0x000A0x000B (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.

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<int64,uint32>
  HWFG  hardwareFlags          Blaze::HardwareFlags (bitfield)
  ISP   iSP                    string
  PSLM  latencyList            list<int32>
  QDAT  qosData                Blaze::Util::NetworkQosData
  TZ    timeZone               string
  UATT  userInfoAttribute      uint64
  ULST  blazeObjectIdList      list<ObjectId>

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::PersonaDetails>

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>

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<string, map<string,string>>
       (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.


  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.