# FAF Replay Parser
*A Supreme Commander: Forged Alliance replay parser based on https://github.com/FAForever/faf-scfa-replay-parser.*

This project aims to provide a fast parser for the Supreme Commander Forged
Alliance (SCFA) replay data format. These replay files are relatively small by
today's standards, however, performance still matters when you need to parse a
large number of replays.

This repository includes the parser implementation as a Rust crate, as well as a
command line utility.

## Usage
Parse replays through the `Parser` class:
```rust
extern crate faf_replay_parser;

use faf_replay_parser::{Parser, ParserBuilder};
use std::fs::File;
use std::io::{BufReader, Read};
use std::time::Duration;

let parser = ParserBuilder::new()
    // Skip all commands except the ones defined here
    .commands(&[
       replay_command::ADVANCE,
       replay_command::VERIFY_CHECKSUM,
    ])
    // Throw away commands right after we parse them. Setting this to `True` will
    // increase the parse time.
    .save_commands(false)
    // Parse the whole replay even if it desynced
    .stop_on_desync(false)
    .build();

// Or create a parser with default arguments (turn off save_commands though)
// let parser = Parser::new();

// Read replay file
let mut f = BufReader::new(File::open("12345.scfareplay").expect("Couldn't open file"));
let mut data = Vec::new();
f.read_to_end(&mut data).expect("Couldn't read from file");

// Parse the replay
let replay = parser
    .parse(&mut data.as_slice())
    .expect("Failed to parse replay");

println!("Game time: {:?}", Duration::from_millis(replay.body.sim.tick as u64 * 100));
if !replay.body.sim.desync_ticks.is_empty() {
    println!("Replay desynced!");
}
```

Or use the high performance functions for special cases:

```rust
use faf_replay_parser::{body_offset, body_ticks};
use std::time::Duration;

// Split replay data into header and body
let offset = body_offset(&data);
let (header_data, body_data) = data.split_at(offset);

// Get replay length in ticks
let ticks = body_ticks(&body_data);
println!("Game time: {:?}", Duration::from_millis(ticks as u64 * 100));
```

## The Command Line Utility
*Disclaimer: The cli is a work in progress, and some parts of it may currently
print out excess amounts of debug information.*

### Installing
You can get the cli by cloning this repository and building it with:
```
$ cargo build --release --features cli
```
This will produce an executable in `target/release/`. Copy this to some location
in your `$PATH`, for instance `~/.local/bin/`.

Alternatively, a Linux binary is available in the GitLab CI build artifacts of
every tagged release.

### Functionality

The cli currently performs 3 main functions. Printing a summary of the replay
(mostly header information), inspecting the contents of the command stream, and
converting `fafreplay` files into `scfareplay` files.

#### Info
Shows an overview of the replay contents. Additional information about the map,
players, game options and more can be enabled as well.

```
$ fafreplay info 6176549.fafreplay
processing replay: 6176549.fafreplay
Supreme Commander v1.50.3698 Replay v1.9

Seton's Clutch (00:48:11)
    Dozens of battles have been fought over the years across Seton's Clutch. A patient searcher could find the remains of thousands of units resting beneath the earth and under the waves.

Team 1
    civilian (AI) 5

Team 2
    [PLD] EricaPwnz (1200) Aeon
    Vmcsnekke (1600) UEF
    [AoS] Strogo (2100) UEF
    [JEW] Licious (1600) Seraphim

Team 3
    [SNF] PlodoNoob (1800) Cybran
    Mizer (1400) Seraphim
    [SC] HerzogGorky (1400) Cybran
    [JEW] Robogear (2100) UEF
```

#### Commands
Displays commands as they appear in the replay file. By default this only shows
the most common commands.

```
$ fafreplay commands 9000556.scfareplay --limit 10
processing replay: 9000556.fafreplay
Supreme Commander v1.50.3701 Replay v1.9

0 ├── SetCommandSource { id: 0 }
1 ├── VerifyChecksum { digest: [168, 55, 122, 87, 70, 60, 17, 145, 224, 174, 52, 71, 2, 143, 109, 2], tick: 0 }
2 ├── Advance { ticks: 1 }
3 ├── Advance { ticks: 1 }
4 ├── Advance { ticks: 1 }
5 ├── Advance { ticks: 1 }
6 ├── Advance { ticks: 1 }
7 ├── Advance { ticks: 1 }
8 ├── Advance { ticks: 1 }
9 ├── Advance { ticks: 1 }

Total commands parsed: 10
```

#### Unpack
Converts a .fafreplay file into a .scfareplay file.

```
$ fafreplay unpack 9000556.fafreplay
Extracting...
Done
Writing 9000556.scfareplay
Wrote 12314246 bytes
```

## Building
To build the command line utility:
```
$ cargo build --release --features cli
```

## Related Projects
Looking for a parser in a different language? Check out these other replay
parser implementations:

- [faf-scfa-replay-parser](https://github.com/FAForever/faf-scfa-replay-parser/)
a library implementation in pure Python
- [ReplayParser](https://github.com/Crotalus/ReplayParser) a library
implementation in C#
- [fafafaf.bitbucket.org](https://bitbucket.org/fafafaf/fafafaf.bitbucket.org)
a JavaScript application for viewing replay stats
- [Replay Sync Tool](https://bitbucket.org/fafafaf/livereplayserver) a PyQT
application for synchronized replay playback with a rudimentary Python 2 parser
implementation.
- (Bonus) [https://pastebin.com/H4hazWFp](https://pastebin.com/H4hazWFp) the
beginnings of a Python implementation by ZePilot
