Files
OpenFUT/openfut-protocol-blaze
funman300 a9a816e0ed openfut-protocol-blaze: generic Blaze protocol layer, oracle-tested
First Rust component of the Python -> Rust migration. Chosen first because
it is the lowest genuinely game-independent layer, it has an executable
oracle, and both existing Rust implementations of it are wrong.

Contents:
  * fire2   -- the proven 16-byte frame header, frame/stream splitting
  * heat2   -- tag packing, varints, all 11 TDF value types
  * message -- frame + decoded body, routed by NUMERIC component/command
  * diagnostics -- dumps for capture review

No FIFA 17 command tables, response schemas or notification IDs: this layer
knows 0x0009/0x0007 is component 9, command 7, not that it means
Util::preAuth. That mapping belongs to a game adapter, which is what lets a
future FIFA 18/23 adapter reuse this.

Parity is tested, not asserted. fixtures/generate.py drives the proven
Python responders (heat2.py, blaze_responder_v3b.py) and freezes 56 vectors
-- 31 of them real payloads from the responder's own builders, including
the 11.8 KB preAuth reply. tests/oracle_parity.rs replays every one
byte-for-byte. 54 tests green; clippy clean.

Supersedes two wrong framings, neither of which is removed yet:
  * fifa-blaze/crates/blaze-proto/frame.rs -- a 12-byte header with a u16
    length, nibble-packed type/options, an error field and a JUMBO flag.
    A documented guess at FIFA 23 predating the FIFA 17 recon.
  * heat2.py::build_fire2_frame -- packs >IHHHHB3s, msgId at [10:12] and
    msgType at [12]. Dead code, but its docstring still states that layout.

Confidence is carried in the types: TypeId::is_verified() reports which
layouts are capture-backed (int/string/blob/struct) and which the oracle
marks UNVERIFIED (list/map/union/varlist/objtype/objid/float), with a test
asserting the unverified ones stay flagged.

Cargo.lock is deliberately NOT included: it re-resolves ~240 lines against
the current registry even without this crate, so that churn is pre-existing
and does not belong in a foundation commit.

The Python backend remains the live runtime and is untouched. Nothing
consumes this crate yet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 00:53:59 +00:00
..

openfut-protocol-blaze

Game-independent EA Blaze wire protocol: Fire2 framing and the Heat2/TDF codec.

This is the first Rust component of the Python → Rust migration. It was chosen first because it is the lowest layer that is genuinely game-independent, it has an executable oracle, and both existing Rust implementations of it are wrong.

Scope

In Out
Fire2 16-byte frame header, frame/stream splitting Component and command name tables
Heat2 tag packing, varints, the 11 value types Notification IDs, response schemas
A message = frame + decoded body, routed by number Login sequencing, session identity
Diagnostics dumps Anything FIFA-17-specific

The boundary test: could FIFA 18 or FIFA 23 use this without importing FIFA 17's command tables? This crate knows 0x0009/0x0007 is component 9, command 7. That it means Util::preAuth is a FIFA 17 fact, and belongs in a FIFA 17 adapter.

Provenance

Ported from fifa17-recon/tools/heat2.py (TDF codec) and fifa17-recon/tools/blaze_responder_v3b.py (Fire2 framing) — the Python implementation that drove a retail FIFA 17 client from Origin login to an opened FUT pack. That implementation is the project's behavioural oracle, and this crate does not claim parity with it, it tests parity against it.

Two superseded framing implementations

Both other Fire2 implementations in this repository are wrong for FIFA 17, and this crate exists partly to replace them:

  • fifa-blaze/crates/blaze-proto/src/frame.rs — a 12-byte header with a u16 length, nibble-packed type/options, an error field, and a JUMBO-frame escape flag. Its own doc comment says FIFA 23's variant "is UNKNOWN" and that Fire2 was picked because it "is the most likely candidate". It predates the FIFA 17 recon and nothing has since validated it.
  • fifa17-recon/tools/heat2.py::build_fire2_frame — packs >IHHHHB3s, putting a u16 msgId at [10:12] and msgType at [12]. Dead code: the responder carries a comment telling callers not to use it, and it is not on any live path. Its module docstring still describes the old layout.

The proven layout is 16 bytes, with a u24 msgNum at [10:13] and (msgType << 5) | userIndex in byte 13. There is no error field (that is Fire v1) and no jumbo flag (the length is already a u32). The payload_over_64kib_needs_no_jumbo_flag test is the direct refutation.

Confidence is not uniform

int, string, blob and struct are validated byte-exact against captured FIFA 17 traffic. list, map, union, varlist, objtype, objid and float are marked UNVERIFIED in the oracle — they are absent from every capture we hold. TypeId::is_verified() reports which is which, and a test asserts the unverified ones stay flagged, so "Rust and Python agree" can never be quietly read as "this is how EA does it".

Testing

cargo test -p openfut-protocol-blaze     # 43 unit + 10 differential + 1 doc
./check-parity.sh                        # regenerate fixtures, then re-verify

Differential vectors live in fixtures/, generated by fixtures/generate.py from the Python oracle. 25 of the TDF vectors and 6 of the Fire2 vectors are origin: "live" — real payloads from the responder's own builders, including the preAuth reply (~11.8 KB) that is the first RPC FIFA 17 sends.

Comparison is byte-for-byte. FIFA 17's deserialisers hard-freeze on an unexpected shape, so "semantically equivalent" is not a useful category at this layer. (It is the right call one layer up at UTAS/JSON — see fifa17-recon/tools/test_fut_contract.py.)

Regenerate fixtures after any change to the Python oracle:

python3 fixtures/generate.py           # rewrite
python3 fixtures/generate.py --check   # assert committed files are current

No runtime dependencies

Deliberate. This is a byte-level codec whose oracle is stdlib-only Python; there is nothing here a third-party crate can do that std cannot. In particular the crates.io tdf crate (already a fifa-blaze dependency) is not used: it targets a generic BlazeSDK 15.x dialect, and adopting it would substitute someone else's reading of the format for our own captured evidence. It remains useful as an independent cross-check, not as the implementation.

serde_json is a dev-dependency only, for reading fixture files.

Not yet done

  • No TLS, no sockets, no async. Framing and codec only; transport belongs to whatever hosts this.
  • The Blaze redirector (HTTPS + XML getServerInstance) is a different protocol and is not here.
  • Nothing consumes this crate yet. The Python backend remains the live runtime, untouched.