docs: Blaze handshake reference, connection-gate findings, closure write-up
- 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>
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
# 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*.
|
||||
Reference in New Issue
Block a user