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:
funman300
2026-07-03 15:58:37 -07:00
parent 61b138db89
commit 3649c3d6f0
3 changed files with 639 additions and 0 deletions
+177
View File
@@ -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 16 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*.