fifa-blaze

EA Blaze protocol server emulator — FIFA 17 target.

Why FIFA 17 and not FIFA 23

FIFA 23 was the original target. It does not work, for a structural reason documented in ../openfut-bridge/docs/closure-and-preservation.md: the FIFA 23 client never dials. The functions that register the redirector dial are Blaze message handlers, dispatched only once Blaze messages are already flowing — so the dial requires the connection it is supposed to create. Offline that handler container is empty and nothing can break the circle. Two further walls sit behind it (ProtoSSL cert pinning, and an anti-tamper VM that faults on forced state).

FIFA 17 differs categorically, not by degree:

FIFA 17 (2016) FIFA 23 (2022)
Online entry Client dials gosredirector directly Gated behind an EA-App handshake with no counterpart
Anti-tamper None VM faults deterministically on forced state
Transport ProtoSSL — SSLv3 + RC4 Pinned modern TLS (CertificateUnknown)
Feedback loop You get a wire on day one Never dials → nothing to iterate against

That last row is the one that matters. Redirect the hostname, and the client connects and starts talking — which restores the oracle the whole method depends on: send a frame, watch the client react, correct, repeat.

openfut-core is unaffected by any of this. It is game-independent and already complete; only the bridge layer is title-specific.

Status

Milestone 1 — capture stub. Two SSLv3 listeners start and log every byte. No FIFA 17 component/command IDs are known yet — the capture log is how they are discovered.

Milestone 1 goal: one real frame in captures/. The FIFA 23 effort produced zero in a month.

Architecture

FIFA 17 (hosts file, or the openfut-hook DLL under Proton)
    │
    ▼ gosredirector.ea.com → 127.0.0.1:42127
blaze-server redirector listener   (SSLv3, Fire2 framing)
    │  replies: "connect to 127.0.0.1:10041"
    ▼
blaze-server Blaze listener        (SSLv3, Fire2 framing)
    │
    ├─ raw byte tee  → captures/<label>-<port>.raw     ← always, before framing
    └─ framed packet → captures/<timestamp>.jsonl      ← only if framing is right

Transport: SSLv3, not TLS

EA's DirtySDK speaks ProtoSSL, a homegrown SSLv3 restricted to RC4-SHA / RC4-MD5. A modern TLS stack cannot negotiate with it at all — no shared version, let alone a shared cipher. This server uses blaze-ssl-async, which implements that dialect and ships a certificate built to satisfy old ProtoSSL verification.

There are no certificates to generate. The certs/ directory and the old openssl step are obsolete; the [tls] config section is gone.

The raw tee

Every byte is mirrored to captures/<label>-<port>.raw before framing is attempted:

IN  12 0000000900000001...
OUT 30 0000001e00090001...

This exists because the FIFA 23 run ended with six capture files containing zero bytes — captures were only written after a packet decoded, so a wrong framing guess destroyed the very evidence needed to fix the guess. With the tee, a bad guess still yields bytes.

Disable with raw_tee = false in [capture]. Don't.

Framing

Fire and Fire2 both exist. FIFA 17 is post-2012 so Fire2 is the likely variant, but this is UNCONFIRMED. It is a config value, not a constant — flip it without rebuilding:

[blaze]
framing = "raw"   # bypass framing entirely, capture byte streams

Quick start

cp config.example.toml config.toml     # edit if needed
cargo run --release --bin blaze-server -- config.toml

Then redirect the hostname:

127.0.0.1  gosredirector.ea.com

…or deploy openfut-hook to redirect inside Proton, and launch FIFA 17 and go online.

Verifying without FIFA

The SSLv3 path is covered by a test that completes a real handshake and round-trips application bytes through the tee — so the server can be validated before touching the game:

cargo test

Reading captures

# Raw bytes (always present)
cat captures/blaze-*.raw

# Framed packets (only if the framing guess is right)
jq '.' captures/capture-*.jsonl | less
jq -r '[.component, .command] | @tsv' captures/*.jsonl | sort -u

Replaying a capture offline

blaze-replay feeds a recorded .raw back through the codec, so a framing hypothesis is tested against real bytes in a second — no game, no rebuild:

blaze-replay captures/blaze-54321.raw                # test fire2
blaze-replay captures/blaze-54321.raw --framing raw  # dump unparsed, read the header by hand
blaze-replay captures/blaze-54321.raw --dir out      # decode our replies instead

It concatenates records before decoding (TCP does not preserve message boundaries) and ends with a verdict:

#1  component=0x0009 command=0x0007 type=REQUEST seq=1 error=0 body=0B
2 packet(s) decoded, 0 byte(s) undecoded
VERDICT: fire2 is a clean fit — every byte accounted for.

What the server answers today

src/components.rs carries the Blaze component/command tables and turns capture lines from raw hex into names like Util.preAuth, so an unknown ID stands out as unknown.

src/routes.rs holds the response bodies. Three commands are answered; everything else falls through to an empty response so the client keeps talking and the log keeps growing.

Command Status
Redirector.getServerInstance ADDR union + VALU{HOST,IP,PORT}, SECU, XDNS
Util.ping STIM server clock
Util.preAuth minimal scaffold — the gate that must be right before auth is attempted

All of it is TODO/CONFIRM. The tags and structure come from open-source emulators for other EA titles (chiefly PocketRelay, ME3, MIT); no EA source is involved. Component IDs are framework-level and stable across titles; command IDs are per-title and may well differ for FIFA 17. When a capture disagrees with anything in these files, the capture wins.

The point of answering at all is that an empty body is guaranteed wrong and teaches nothing, whereas a structurally plausible one either advances the client — a result — or is rejected with an error code that is itself information.

A note on TDF tags

Tags are not ASCII on the wire. TDF packs each character into 6 bits across 3 bytes, so ADDR never appears literally in a capture. Grepping bytes for tag names will always fail; parse instead (blaze-replay, or TdfStringifier as the tests in routes.rs do).

Next steps (Milestone 2)

Once captures reveal component/command IDs:

  1. Confirm the framing variant from the raw bytes
  2. Identify the redirector request/response tag layout
  3. Identify Util preAuth / postAuth / ping
  4. Identify Authentication login
  5. Implement handlers via Dispatcher::register and test against the client

Prior art worth reading before guessing: PocketRelay/Server (ME3, Rust), openBlase, and Tratos/New-Blaze-Emulator (BF3). For the FUT REST layer that follows, futapi/fut documents real fut.ea.com endpoint shapes.

Development

cargo build
cargo test
RUST_LOG=debug cargo run --bin blaze-server -- config.toml
S
Description
FIFA 23 Blaze protocol server emulator
Readme 99 KiB
Languages
Rust 100%