a9a816e0ed
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>
104 lines
4.6 KiB
Markdown
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.
|