Files
OpenFUT/openfut-protocol-blaze/README.md
T
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

104 lines
4.6 KiB
Markdown

# 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
```bash
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:
```bash
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.