- 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>
16 KiB
OpenFUT — Closure & Preservation Record
A clean-room reverse-engineering effort to restore FIFA 23 Ultimate Team offline after EA's server shutdown (October 2025). This document records what was built, what was achieved, and the precise technical wall at which the effort concludes.
Status: the online/FUT route is closed at a characterized wall. The offline-menu / EA-App-emulation layer works and is preserved.
Provenance: everything here derives from observing the running client's own behaviour — live memory reads, static disassembly of the shipped binary, and the game's responses to our synthesized inputs. No leaked EA source was used or referenced at any point. That clean-room discipline is the legal foundation of the work and is the reason this record can exist.
1. What OpenFUT set out to do
FIFA 23's online services were permanently shut down in October 2025, which removed FIFA Ultimate Team — the mode is backed by EA's online infrastructure and simply cannot start without it. OpenFUT's goal was game preservation: let a legitimately-owned copy run FUT against a local, emulated backend instead of EA's dead servers.
The intended shape was three cooperating pieces:
FIFA 23 client (offline, under Proton/Wine or native Windows)
│ EA-App / Blaze / FUT protocols
openfut_hook.dll — injected; redirects EA traffic, bypasses cert pinning, observes
│ localhost
openfut-bridge — answers the EA-App (LSX) protocol; was to answer Blaze + FUT
│
openfut-core — the game-independent FUT economy backend (cards, packs, SBCs…)
The entry sequence the client runs, in order, each gating the next:
- LSX — the EA App ↔ game local handshake (login, entitlements, config).
- Blaze redirector — "where is my game server?"
- Blaze preauth / login / postauth — establish the online session.
- FUT entry — eligibility, then the FUT hub loads over REST.
The plan was to walk these gates one at a time, synthesizing each response and verifying it against the live client (the client is the oracle — a gate is "done" when the game advances).
2. What was achieved
This is preservation documentation, so the wins are recorded first and plainly. They are real, and they stand independent of the wall reached later.
2.1 Clean-room injection and TLS neutralization
- A
version.dll/ FLE-loaded hook that injects into FIFA 23 under Proton/Wine without tripping the (neutralized) anti-cheat, writing a durable log for observation. - ProtoSSL / cert-verify bypass sufficient to let the game accept our substituted endpoints at the layers we terminate.
- Winsock interception (
getaddrinfo,connect,WSAConnect,ConnectEx) with EA-host and EA-port redirection to the local bridge — including, by the end, IPv6 (IPv4-mapped) redirection, which closed a real leak the earlier IPv4-only path had missed.
2.2 The LSX / EA-App layer works — the game reaches a fully-authenticated menu offline
This is the substantive achievement. The bridge's native LSX server (port 3217) emulates the EA App / EbisuSDK local protocol completely enough that FIFA 23, with EA's servers gone, boots to its main menu believing it is logged in and online. Confirmed working, from the bridge's own logs of a live session:
EALS ChallengeResponse— the EA login-service challenge/response handshake completes (handshake complete).EbisuSDK GetConfig / GetProfile / GetGameInfo / GetSetting— all answered; the game gets its service list, profile, and configuration.Utility GetInternetConnectedState → connected=1— the connectivity check passes.XMPP SetPresence → INGAME— presence is set.Login IsLoggedIn=trueandOnlineStatusEvent isOnline=true— pushed continuously; the game's own online-status state flips to "online."
Everything the EA-App layer is asked for, it receives. This is a genuine, reusable clean-room EA-App/EbisuSDK emulator.
2.3 Reverse-engineering infrastructure
The effort produced durable tooling and knowledge, all clean-room:
- Live memory inspection via
/proc/<pid>/mem(Wine mapsFIFA23.exeflat at ImageBase0x140000000), including a stable algorithm to resolve the live Blaze connection manager (G = *[0x14acd02c0]→M = *[G+0x360]→ctx = *[M+0x778]; then a heap scan for the objectPwith[P+0] == base+0x80200b8and[P+8] == M). - Read-only in-process probes (menu-time snapshots, a write-watchpoint on the dispatch container, transport observation) — all env-gated, none altering game state.
- Handshake-independent TLS SNI capture on the bridge (peek the ClientHello, parse SNI before the handshake, so the hostname is learned even when the client rejects our cert).
- A documented Blaze handshake reference (
docs/blaze-handshake.md) mapping the three-layer LSX / Blaze-core / UTAS model and the six-step Blaze session ordering.
3. The wall — reached from three independent directions
FUT never loaded. The reason is a single wall, and the strongest evidence for it is that three unrelated lines of investigation arrived at the same place.
3.1 The forcing arc (memory side)
We confirmed the live Blaze connection manager and the in-process "dial" handler
(0x144f4d360) that begins the Blaze connection when the network comes online. Both exist at
the menu. But:
- Forcing the dial with the connection scaffold empty crashes in normal
.text(not the VM) at0x144fd6b6c: a binary search over a per-connection message-handler table whosebeginpointer is an uninitialized sentinel (1). The table is empty because nothing has populated it. - Forcing the online state machine to "fully online" (state 2) crashes the anti-tamper VM deterministically — the game's own protected online code runs against a forced-but- absent session and faults. A forced state cannot substitute for a real session.
Conclusion of the arc: the dial and the online state are primed but unpumped — real infrastructure that only a genuine connection flow drives, and that cannot be safely forced.
3.2 The network / transport arc
With the entire EA-App layer satisfied (§2.2) and the network path fully instrumented (including IPv6 and SNI capture), we watched a complete FUT "connecting to EA Servers" attempt. The complete outbound inventory:
| Traffic | Identity | Fate |
|---|---|---|
2× IPv6 :443 → rl.data.ea.com, pin-river.data.ea.com |
EA PIN/River telemetry (fire-and-forget) | reached the bridge, cert-rejected by ProtoSSL |
6× IPv6 UPnP (:1900, :80, LAN) |
NAT traversal | — |
LSX (:3217) |
EA-App layer | fully answered |
No gosredirector / redirector.ea.com. No Blaze-port (:42127 / :10041) connect. On
any transport. Starting FUT added zero new dials. The game is satisfied enough by the
EA-App layer that it never attempts a Blaze connection at all. (This also corrected an
earlier false belief — "zero outbound sockets" — which turned out to be undecoded IPv6
traffic.)
Conclusion of the arc: the wall is not transport, not an unanswered LSX request, not the cert. The game simply never initiates Blaze.
3.3 The two arcs meet
The forcing arc found, from memory, that the connection scaffold is never populated. The network arc found, from the wire, that no connection is ever attempted. Same wall, two sides. The final chapter explains why, from the binary itself.
4. Final chapter — the dial-initiation gate (why it is circular)
This is the concluding technical result: a static reverse-engineering pass answering the one question both arcs left open — beyond "online," what does the code check before it fires the Blaze dial, and is that condition reachable offline?
4.1 Method
Read-only static analysis of the shipped FIFA23.exe (objdump), two cross-reference
techniques: an 8-byte absolute-address search across the whole file (finds function pointers
stored in data — vtables, tables) and a disassembly grep for call/jmp/lea targets
(finds code references; a lea reg,[…] # 0x… is an address being taken for registration, a
call 0x… is a direct invocation). Addresses below are FIFA23.exe virtual addresses
(ImageBase 0x140000000). The anti-tamper VM region [0x14c1cd000, 0x160eb1000) is
unreadable; everything traced here is in normal .text.
4.2 The caller graph
dial handler 0x144f4d360
▲ address-taken only (lea r9,[dial]) — NEVER called directly, NEVER in a vtable
│ inserted into the container [connMgr+0xc38] by:
F1 registrar 0x144f4b7b0 (exactly ONE caller)
▲
F2 0x144f4b960 (builds a callback, calls F1 with rcx = connMgr)
▲ TWO callers, identical guard: cmp bpl,[this+0x42] ; cmp bp,[this+0x142]
├── F3a 0x144f44770
└── F3b 0x144f4bbf0
Every reference is a normal-.text lea — the dial handler is registered as a callback,
never called by address. The registrar F1 is a "get-or-create into the message-handler
container": on a miss it allocates a handler and copies a caller-supplied callback into it.
Its sole caller F2 supplies the callback and resolves the connection manager
(rcx = [[this+8]+0xca0]).
4.3 The decisive structural fact
F3a and F3b — the two functions that actually decide to register the dial — are, from the
whole-binary sweep, in zero vtables and never directly called. They are lea'd at four
sites and registered into [connMgr+0xc38], each keyed by a .rdata message-type
descriptor (0x1483fcf98, 0x14808d7d0). In other words they are themselves message
handlers, dispatched indirectly by the connection manager when a matching Blaze message
arrives — not methods invoked by a state machine you can drive locally. (In Rust terms: not
methods on a trait object, but closures inserted into a HashMap<MessageType, Handler> and
called by a dispatcher.) The functions that register them (G_a 0x144f4a350,
G_b 0x144f45220) are ordinary helpers inside the same cluster, and the entire cluster
F1…G_b is vtable-free.
So the whole thing is one self-registering, message-driven state machine on
[connMgr+0xc38]: each handler fires when its Blaze message arrives, checks connection
state, and registers the next handler — until F3a/F3b register the dial. The state each
step checks ([this+0x42], [this+0x142], …) is connection-lifecycle state, advanced only
as messages are processed.
4.4 Reachability, and why it is circular
Offline, the container [connMgr+0xc38] is empty (measured directly: the crashing table's
begin is 0/garbage; the network arc saw zero Blaze messages). Nothing ever dispatches
F3a/F3b, so their guards never even execute — the wall sits upstream of them. The one
thing that does fire offline is the connection-state constructor (on the online-status event),
so the machine's entrance is partially reached and then stalls immediately, because no
Blaze message follows to drive the first dispatch.
Outcome: circular gate. The trigger is found — we can see precisely how the dial gets registered and fired. But its precondition is prior Blaze connection-lifecycle messages having populated and dispatched the handler container — i.e. the very network exchange the connection is meant to produce. The state that gates the dial requires the dial's own earlier connection steps to have already run. It is not a settable flag (there is none between "online" and "dial"); it is an entire message exchange that never begins.
This is the exact mechanical explanation of both arcs: the container is empty because no messages flow (network arc), and forcing the dial into that empty container crashes (memory arc).
5. What this means — the boundary, stated plainly
OpenFUT can take FIFA 23 to a fully-authenticated main menu, offline, believing it is logged in and online — but it cannot take it into FUT. FUT requires a Blaze session, and on this build the Blaze connection is never initiated. The bootstrap is circular: the client will only walk its connection state machine as real Blaze messages arrive, and no messages arrive because no connection is begun. "Online," at the EA-App level, is genuinely disconnected from "reach out to Blaze," and the gap between them is not a flag we failed to set — it is a live protocol exchange that has no counterpart to talk to.
Two secondary walls sit behind the first, and would matter only if it were solved:
- ProtoSSL cert pinning (
CertificateUnknown) rejects our self-signed cert on the DirtySDK path — so even redirected EA HTTPS cannot complete a handshake as-is. - The anti-tamper VM faults deterministically whenever forced state is used by the game's own protected online code — so "force the state and let the game run" is not viable.
6. Paths not taken, scoped honestly
Only one route could still reach FUT, and this record scopes it so the decision is clear-eyed rather than reopened casually.
In-process Blaze state-machine injection. Rather than answer Blaze over a wire (there is no wire — the game never dials), blaze_brain would drive the game's in-process connection state machine by injecting synthesized Blaze response frames into its dispatch layer. From the final-chapter RE, that requires:
- Making the connection appear initiated, so the game registers its first response handler.
- Feeding synthesized Blaze responses in order and keyed to the same
.rdatamessage-type descriptors, so each registered handler fires, advances the connection-state flags, and registers its successor — walking the machine forward until the dial is registered and fires.
This is a weeks-scale build requiring per-message protocol RE done from the receive side, against a client that is the only available oracle (the servers are dead, so every frame must be synthesized and validated by whether the client advances). It also inherits an unsolved sub-problem: the initial "connection initiated" state must itself be synthesized or forced, since the game does not produce it offline. It is possible in principle; it is not a small task, and its scope should be understood before it is begun.
The alternative — the one this document records — is to treat the fully-authenticated offline menu as the achieved preservation state and close the online/FUT route here.
7. Preservation value
Even without FUT, the effort produced things worth keeping:
- A working clean-room EA-App / EbisuSDK (LSX) emulator that boots FIFA 23 to an authenticated main menu with no live EA servers — a genuine preservation artifact for the offline single-player state.
- A documented map of exactly where and why online play is unreachable on this build, grounded in the binary: the dial caller graph, the message-driven container mechanism, and the circular bootstrap. Future work (on this title or the engine family) starts from a known wall, not a blank page.
- Reusable RE tooling and references — live-memory resolution of the connection manager, the transport/SNI instrumentation, and the Blaze handshake reference doc.
- A clean provenance record — nothing derived from leaked source, so the work remains usable and shareable.
8. Closing note
OpenFUT reached the last gate it could reach without a live server to talk to, and then proved — from memory, from the wire, and from the binary — that the next gate is not a lock we failed to pick but a door that only opens from the far side. That is a complete and honest result. The offline menu stands; the map is drawn; the wall is named.
This record is the concluding chapter of the OpenFUT reverse-engineering arc.