3649c3d6f0
- blaze-handshake.md: three-layer LSX/Blaze-core/UTAS model + six-step Blaze session ordering, tagged CONFIRMED/SHAPE/UNKNOWN. - connection-gate-findings.md: accumulated RE of the online-connect gate. - closure-and-preservation.md: the capstone. Records what was achieved (clean-room LSX/EbisuSDK emulator booting FIFA to an authenticated offline menu), the wall reached from three directions, the dial-initiation gate RE as the final chapter (Outcome B, circular), and the boundary. Clean-room throughout. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
178 lines
9.6 KiB
Markdown
178 lines
9.6 KiB
Markdown
# Blaze Handshake — Reference & Milestone Map
|
||
|
||
This is the reference for the **blaze_brain** arc: emulating the EA Blaze backend
|
||
well enough for FIFA 23 FUT to get past *"FUT is connecting to the EA Servers…"*.
|
||
It replaces the earlier in-process *forcing* arc, which concluded that the game's
|
||
online dispatch scaffold cannot be filled by forcing local state — it fills only when
|
||
a real Blaze exchange happens.
|
||
|
||
Every claim below is tagged so that on a re-read you can see instantly what is
|
||
grounded in our own reverse engineering versus what is general public knowledge of
|
||
Blaze's protocol shape versus what still needs a capture:
|
||
|
||
- **CONFIRMED** — established by our own RE in this project (live memory reads,
|
||
objdump, hook logs).
|
||
- **SHAPE** — the general shape of EA's Blaze protocol from public community
|
||
knowledge. The *ordering* and *purpose* are reliable; exact numeric IDs and field
|
||
tags for FIFA 23's specific Blaze version are **not** implied by a SHAPE tag.
|
||
- **UNKNOWN** — must be captured from the live client before it can be implemented.
|
||
|
||
> Clean-room discipline: nothing here derives from leaked EA source. The Blaze
|
||
> protocol shape is reconstructed from public community reverse-engineering of EA
|
||
> titles and our own observations of this client.
|
||
|
||
---
|
||
|
||
## The three layers that all say "connecting to EA"
|
||
|
||
"Connecting to EA" is not one thing. Three distinct systems wear that label, stacked
|
||
on top of each other, and it is easy to confuse a stall in one for a stall in
|
||
another. Understanding which layer the FUT spinner belongs to is the whole point of
|
||
this document.
|
||
|
||
**Layer 0 — LSX (Local Socket eXchange).** `CONFIRMED working.` This is the
|
||
Origin/EA-App desktop-client local API: entitlements, the local login handshake, and
|
||
`GetAuthCode`. It is *not* Blaze — it is a localhost protocol the game uses to talk to
|
||
whatever is standing in for the EA App. Our native bridge answers it on ports
|
||
3216/3217, and answering it is what got the client to the main menu with a working
|
||
first-party login. Layer 0's output that matters to Blaze is a **Nucleus auth code /
|
||
access token**: the credential the client will later present to the Blaze
|
||
authentication component.
|
||
|
||
**Layer 1 — the Blaze core session.** *This is the wall.* Blaze is EA's online backend
|
||
framework. Reaching it is a multi-step handshake (detailed below) that starts by
|
||
asking a *redirector* where the real server is, connecting there over TLS, then
|
||
negotiating configuration, authenticating with the Layer-0 auth code, and finally
|
||
bringing the session fully online. Until this completes, nothing above it can work.
|
||
Everything we have observed says the client currently never even *starts* this — see
|
||
the transport finding below.
|
||
|
||
**Layer 2 — FUT / UTAS.** `SHAPE.` FIFA Ultimate Team specifically is served by the
|
||
EASFC Blaze component *and* by UTAS, a separate HTTPS REST service
|
||
(`utas.*.fut.ea.com`). Once a Blaze session exists, the FUT client authenticates to
|
||
UTAS (`POST /ut/auth`) with the session credential to obtain a FUT session id, and
|
||
only then does it load squad/club data. The FUT spinner in the screenshot lives at the
|
||
*top* of this stack, but it cannot clear until Layer 1 is answered — so "minimal Blaze
|
||
handshake" means **Layer 1**, and Layer 2 is a second, HTTP-shaped project that comes
|
||
after.
|
||
|
||
---
|
||
|
||
## Where the client currently stalls — the transport finding
|
||
|
||
`CONFIRMED.` Across a full menu-plus-FUT-attempt session we observed **zero
|
||
`getaddrinfo` calls and zero outbound sockets**, and — via the `ELEM_WATCH` probe — the
|
||
game's per-connection message-handler dispatch container at `element[0]+0x40` stayed a
|
||
clean, empty, default-constructed vector the entire time, including while the FUT
|
||
spinner was on screen. `connectState.ctor` fired (the client builds connect-state
|
||
objects) but registered nothing into that container.
|
||
|
||
The mechanical reading: **that container is populated by an actual Blaze message
|
||
exchange** — handlers register as component notifications arrive during session
|
||
bring-up (Layer-1 step 6 below). No Blaze reply → no handler registration → empty
|
||
container → spinner spins forever. The spinner *is* the Layer-1 wall.
|
||
|
||
This leaves two possible transport situations, and **Milestone 0 exists solely to
|
||
decide which one we are in** (see the Milestones section):
|
||
|
||
- **(a)** the client expects Blaze on a hardcoded local endpoint and would dial it if a
|
||
listener were there, or
|
||
- **(b)** the client only dials the Blaze redirector after an in-process bootstrap (the
|
||
dial handler `0x144f4d360` `CONFIRMED`) fires — in which case the transport wall and
|
||
the bootstrap wall are the same wall.
|
||
|
||
---
|
||
|
||
## Layer 1 — the minimal Blaze handshake ordering
|
||
|
||
Blaze is a framed binary protocol. Each message is a header — length, component id,
|
||
command id, message type (request / response / notification / error), error code, and
|
||
a message/sequence id — followed by a **TDF**-encoded body (EA's tag/type/value binary
|
||
serialization). `SHAPE.` The exact numeric component/command ids and TDF field tags for
|
||
FIFA 23's Blaze version are **UNKNOWN** until captured.
|
||
|
||
These are the six steps a responder must satisfy, **in order**, to bring a session
|
||
online:
|
||
|
||
1. **`Redirector::getServerInstance`** `SHAPE` — the client asks the redirector for the
|
||
address of the real Blaze server, naming the FIFA 23 service/SKU. The responder
|
||
returns a `ServerInstanceInfo` TDF containing a **host:port** (point it at
|
||
ourselves) plus an SSL flag. Without a valid address the client has nowhere to go.
|
||
2. **connect + TLS** `CONFIRMED (bypass)` — the client opens a TLS connection to the
|
||
returned address. Our ProtoSSL/cert-verify bypass is already in place, so our
|
||
listener can terminate TLS.
|
||
3. **`Util::preAuth`** `SHAPE` — the client sends its config/version/locale; the
|
||
responder returns server config: the **component list**, ping-site list, telemetry
|
||
settings (disable), and a misc config bundle. The client uses the component list to
|
||
know what exists; an empty or malformed response here stalls it.
|
||
4. **`Authentication::login`** `CONFIRMED (GetAuthCode feeds here)` + `SHAPE` — the
|
||
client authenticates, presenting the **Nucleus auth code from Layer 0** (SSO). The
|
||
responder returns the session: **BlazeId, session key, persona, entitlements**. A
|
||
failure here is what surfaces as *"EA Servers are down."*
|
||
5. **`Util::postAuth`** `SHAPE` — after auth, the client finalizes the session; the
|
||
responder returns post-auth config (UserManager, association lists, telemetry,
|
||
ticker/PIN).
|
||
6. **`UserSessions::updateNetworkInfo` / `updateHardwareFlags`, then a
|
||
`UserSessionExtendedDataUpdate` notification** `CONFIRMED (container is
|
||
Blaze-downstream)` + `SHAPE (which command)` — the client publishes its network/
|
||
hardware info and the server pushes extended session data. **The client flips to
|
||
"online" on receipt of the extended-data notification** — and this is the exact
|
||
moment the `element[0]+0x40` container we watched would begin filling, as handlers
|
||
register for the arriving component notifications.
|
||
|
||
At step 6 the top-level "connected to EA" clears, and Layer 2 (UTAS) can begin.
|
||
|
||
### Why step 6 is the Milestone-1 success signal
|
||
|
||
The container-fill and the "online" flip are the *same event*: handlers register
|
||
because Blaze notifications arrived. That gives blaze_brain a precise, already-built
|
||
success detector. The first real win is **not** "full FUT works" — it is a single line
|
||
in the hook log:
|
||
|
||
```
|
||
ELEM_WATCH: [elem+0x40] CHANGED! …
|
||
```
|
||
|
||
That line means the client accepted our Blaze replies and started registering
|
||
handlers — i.e. steps 1–6 were convincing enough to bring the session online. The
|
||
`ELEM_WATCH` probe that emits it is already written and deployed; it is our
|
||
instrumentation for blaze_brain regardless of anything else.
|
||
|
||
---
|
||
|
||
## Milestones
|
||
|
||
- **M0 — transport reachability (this task).** Read-only. Confirm whether the client
|
||
attempts *any* Blaze-flavored transport (situation a) or none at all (situation b).
|
||
Gates every decision below. See the M0 section in the hook docs / launch notes.
|
||
- **M1 — minimal session bring-up.** Stand up a listener at the M0-observed endpoint;
|
||
answer `getServerInstance` (redirect to ourselves), then `preAuth` / `login` /
|
||
`postAuth` with minimal valid TDFs and a single hardcoded persona. **Success signal:
|
||
`ELEM_WATCH: CHANGED` fires and the spinner advances.**
|
||
- **M2 — session online.** Handle the step-6 session notifications until the top-level
|
||
"connected to EA" state is reached and stays.
|
||
- **M3 — FUT / UTAS.** Implement the UTAS REST layer (`POST /ut/auth` and the
|
||
`/ut/game/...` endpoints already sketched in `endpoint-map.md`) so FUT itself loads.
|
||
|
||
M1 is only reachable if M0 says the client actually dials a listener. If M0 comes back
|
||
"no traffic" (situation b), M1 in its "answer over a wire" shape is blocked until the
|
||
dial trigger is solved, and the strategic options are re-opened rather than a next task
|
||
being obvious.
|
||
|
||
---
|
||
|
||
## Honest UNKNOWNs (capture before implementing)
|
||
|
||
- **Numeric component and command ids** for FIFA 23's Blaze version. The *names* and
|
||
*ordering* above are `SHAPE`-reliable; the wire ids are `UNKNOWN`.
|
||
- **TDF field tags and body layouts** for each request/response. `UNKNOWN` — every
|
||
response body must be shaped from a real capture (or careful trial against the
|
||
client's parser).
|
||
- **The redirector/Blaze transport itself** — host, port, and whether it is remote,
|
||
loopback, or a pipe. This is exactly M0's question and is `UNKNOWN` until M0 runs.
|
||
- **Whether the client dials at all** without the in-process bootstrap firing — the
|
||
situation (a) vs (b) question. `UNKNOWN` until M0.
|
||
|
||
Until M0 answers the transport question, the ordering above is designable but
|
||
untestable: there is nothing to answer *to*.
|