Crates.io | faf-replay-parser |
lib.rs | faf-replay-parser |
version | |
source | src |
created_at | 2019-08-24 23:16:10.155389 |
updated_at | 2025-01-20 20:18:48.825797 |
description | Supreme Commander Forged Alliance replay parser |
homepage | |
repository | https://gitlab.com/Askaholic/faf-replay-parser |
max_upload_size | |
id | 159457 |
Cargo.toml error: | TOML parse error at line 18, column 1 | 18 | autolib = false | ^^^^^^^ unknown field `autolib`, expected one of `name`, `version`, `edition`, `authors`, `description`, `readme`, `license`, `repository`, `homepage`, `documentation`, `build`, `resolver`, `links`, `default-run`, `default_dash_run`, `rust-version`, `rust_dash_version`, `rust_version`, `license-file`, `license_dash_file`, `license_file`, `licenseFile`, `license_capital_file`, `forced-target`, `forced_dash_target`, `autobins`, `autotests`, `autoexamples`, `autobenches`, `publish`, `metadata`, `keywords`, `categories`, `exclude`, `include` |
size | 0 |
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.
People sometimes find their way to this project because they're trying to build an advanced replay analysis tool to show detailed statistics like units built over time, or mass/energy income over time, etc. If this is you, be warned that what you're trying to do is probably impossible!
The replay file only contains a stream of commands that need to be re-simulated in order to get back the state of the simulation. You can think of it as the command stream enabling you to play the game again with each player performing exactly the same clicks that they did the first time. Literally re-playing the game from scratch... Since the simulation is deterministic, you will see the same unit movements, income levels, and ultimately, game outcome as the first time.
However, doing that necessarily requires you to be running exactly the same game code as the original players, which means exactly the same version of the game engine with exactly the same lua files and exactly the same mods, etc. This project won't help you with that.
That being said, the replay format does provide a mechanism for storing
auxiliary data into the command stream through the LuaSimCallback
command.
This is how chat messages are saved into the replay, and so, if you're lucky,
maybe some of the information you're looking for has been saved into the replay
in this way too.
This project will help you understand what information is available to you and provide it to you in an easy to work with way. It wont be the silver bullet you're looking for, but you might be able to get close enough.
The recommended way to parse replays if you need to do any custom command processing is with the iterator interface. This allows you to have complete control over the processing code while also maintaining pretty good performance.
use faf_replay_parser::iter::prelude::*;
use faf_replay_parser::parser::parse_header;
use faf_replay_parser::replay::SimData;
use faf_replay_parser::scfa::ReplayCommand as SCFACommand;
use faf_replay_parser::version::Command;
use faf_replay_parser::SCFA;
use std::fs::File;
use std::io::{BufReader, Cursor, Read};
use std::time::Duration;
// Read replay file
let mut f = BufReader::new(File::open("12345.scfareplay").expect("File should open"));
let mut data = Vec::new();
f.read_to_end(&mut data).expect("File should be readable");
let mut cur = Cursor::new(&data);
let header = parse_header(&mut cur).expect("Replay header should be valid");
let body_start = cur.position() as usize;
let body_data = &data[body_start..];
let mut sim = SimData::new();
let _ = body_data
.iter_commands::<SCFA>()
.map(|cmd| cmd.expect("Command data should be valid"))
.inspect(|cmd| {
// Ignore errors here to fully process desynced replays
SCFACommand::process_command(&mut sim, &cmd).unwrap_or(())
})
.count();
println!(
"Game time: {:?}",
Duration::from_millis(sim.tick as u64 * 100)
);
if !sim.desync_ticks.is_none() {
println!("Replay desynced!");
}
Or use the high performance functions for special cases.
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)
.expect("body_data should be a valid replay");
println!("Game time: {:?}", Duration::from_millis(ticks as u64 * 100));
There is also the Parser
legacy class which generally has the worst
performance, but may have a more convenient interface. Additionally it will work
with any generic Read
type so it may be useful in some rare cases where the
replay data can't be loaded into memory all at once.
use faf_replay_parser::scfa::{replay_command, ReplayCommandId};
use faf_replay_parser::{Parser, ParserBuilder, SCFA};
use std::convert::TryFrom;
use std::fs::File;
use std::io::{BufReader, Read};
use std::time::Duration;
let parser = ParserBuilder::<SCFA>::new()
// Skip all commands except the ones defined here
.commands(&[
ReplayCommandId::try_from(replay_command::ADVANCE).unwrap(),
ReplayCommandId::try_from(replay_command::VERIFY_CHECKSUM).unwrap(),
])
// 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::<SCFA>::new();
// Read replay file
let mut f = BufReader::new(File::open("12345.scfareplay").expect("File should open"));
let mut data = Vec::new();
f.read_to_end(&mut data).expect("File should be readable");
// Parse the replay
let replay = parser
.parse(&mut data.as_slice())
.expect("Replay should be valid");
println!(
"Game time: {:?}",
Duration::from_millis(replay.body.sim.tick as u64 * 100)
);
if !replay.body.sim.desync_ticks.is_none() {
println!("Replay desynced!");
}
The CLI aims to provide some basic replay inspection and manipulation functionality for general use cases. It is meant as a tool for humans to look inside of a replay file and gain some knowledge about what information is actually sitting on disk.
You can install the latest published version of the CLI with cargo
:
cargo install faf-replay-parser --features="cli"
To install from source, you can clone this repository and build 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/
on Linux:
cp target/release/fafreplay ~/.local/bin/
Alternatively, a Linux binary is available in the GitLab CI build artifacts of every tagged release.
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.
Use the --help
flag with any subcommand for all available options.
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
6176549.fafreplay
Supreme Commander v1.50.3698 Replay v1.9
Title: 1200+
Quality: 92.75%
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
Displays commands as they appear in the replay file. By default this only shows the most common commands.
$ fafreplay commands 9000556.scfareplay --offset --limit 10
processing replay: 9000556.scfareplay
Supreme Commander v1.50.3701 Replay v1.9
0x000042a2 ├── SetCommandSource { id: 0 }
0x000042a6 ├── VerifyChecksum { digest: a8377a57463c1191e0ae3447028f6d02, tick: 0 }
0x000042de ├── Advance { ticks: 1 }
0x000042e5 ├── Advance { ticks: 1 }
0x000042ec ├── Advance { ticks: 1 }
0x000042f3 ├── Advance { ticks: 1 }
0x000042fa ├── Advance { ticks: 1 }
0x00004301 ├── Advance { ticks: 1 }
0x00004308 ├── Advance { ticks: 1 }
0x0000430f ├── Advance { ticks: 1 }
Total commands parsed: 10
Converts a .fafreplay file into a .scfareplay file.
$ fafreplay unpack 9000556.fafreplay
Extracting...
Done
Writing 9000556.scfareplay
Wrote 12314246 bytes
Looking for a parser in a different language? Check out these other replay parser implementations: