Skip to content
forked from hohav/peppi

Rust parser for Slippi SSBM replay files

License

Notifications You must be signed in to change notification settings

NickCondron/peppi

 
 

Repository files navigation

Peppi

test

Peppi is a Rust parser for .slp game replay files for Super Smash Brothers Melee for the Nintendo GameCube. These replays are generated by Jas Laferriere's Slippi recording code, which runs on a Wii or the Dolphin emulator.

⚠️ The slp tool has moved to the peppi-slp crate.

Installation

In your Cargo.toml:

[dependencies]
peppi = "2.0"

Usage

One-shot .slp parsing with slippi::read (use peppi::read instead for .slpp):

use std::{fs, io};
use peppi::io::slippi::read;

fn main() {
    let mut r = io::BufReader::new(fs::File::open("tests/data/game.slp").unwrap());
    let game = read(&mut r, None).unwrap();
    println!("{:#?}", game);
}
A more involved example
use std::{fs, io};
use peppi::io::slippi::read;
use peppi::frame::Rollbacks;

// `ssbm-data` provides enums for characters, stages, action states, etc.
// You can just hard-code constants instead, if you prefer.
use ssbm_data::action_state::Common::{self, *};

/// Print the frames on which each player died.
fn main() {
    let mut r = io::BufReader::new(fs::File::open("tests/data/game.slp").unwrap());
    let game = read(&mut r, None).unwrap();

    let mut is_dead = vec![false; game.frames.ports.len()];
    let rollbacks = game.frames.rollbacks(Rollbacks::ExceptLast);
    for frame_idx in 0..game.frames.len() {
        if rollbacks[frame_idx] {
            continue;
        }
        for (port_idx, port_data) in game.frames.ports.iter().enumerate() {
            match port_data
                .leader
                .post
                .state
                .get(frame_idx)
                .and_then(|s| Common::try_from(s).ok())
            {
                Some(DeadDown)
                | Some(DeadLeft)
                | Some(DeadRight)
                | Some(DeadUp)
                | Some(DeadUpStar)
                | Some(DeadUpStarIce)
                | Some(DeadUpFall)
                | Some(DeadUpFallHitCamera)
                | Some(DeadUpFallHitCameraFlat)
                | Some(DeadUpFallIce)
                | Some(DeadUpFallHitCameraIce) => {
                    if !is_dead[port_idx] {
                        is_dead[port_idx] = true;
                        println!(
                            "{} died on frame {}",
                            game.start.players[port_idx].port,
                            game.frames.id.get(frame_idx).unwrap(),
                        )
                    }
                }
                _ => is_dead[port_idx] = false,
            }
        }
    }
}
Live parsing
use std::fs;
use std::io::BufReader;
use byteorder::ReadBytesExt;
use peppi::io::slippi::de;

fn main() {
    let mut r = BufReader::new(fs::File::open("tests/data/game.slp").unwrap());

    // UBJSON wrapper (skip if using spectator protocol)
    let size = de::parse_header(&mut r, None).unwrap() as usize;

    // payload sizes & game start
    let mut state = de::parse_start(&mut r, None).unwrap();

    // loop until we hit GameEnd or run out of bytes
    while de::parse_event(&mut r, &mut state, None).unwrap() != de::Event::GameEnd as u8
        && state.bytes_read() < size
    {
        println!(
            "current frame number: {:?}",
            state.frames().id.iter().last()
        );
    }

    // `U` (0x55) means metadata next (skip if using spectator protocol)
    if r.read_u8().unwrap() == 0x55 {
        de::parse_metadata(&mut r, &mut state, None).unwrap();
    }
}

Development

The Rust source files in src/frame are generated using Clojure from frames.json, which describes all the per-frame fields present in each version of the spec. If you modify frames.json or the generator code in gen/src, run gen/scripts/frames to regenerate those Rust files.

If you're adding support for a new version of the spec, you'll also need to bump peppi::io::slippi::MAX_SUPPORTED_VERSION.

Goals

  • Performance: Peppi aims to be the fastest parser for .slp files.
  • Ergonomics: It should be easy and natural to work with parsed data.
  • Lenience: accept-and-warn on malformed data, when feasible.
  • Cross-language support: other languages should be able to interact with Peppi easily and efficiently.
  • Round-tripping: Peppi can parse a replay and then write it back, bit-for-bit identically.
  • Alternative format: Peppi provides an alternative format that is more compressible and easier to work with than .slp.

Peppi Format

The Peppi format (.slpp) is a GNU tar archive containing the following files, in order:

  • peppi.json: Peppi-specific info.
  • metadata.json: Slippi's metadata block.
  • start.json: JSON representation of the Game Start event.
  • start.raw: Raw binary Game Start event.
  • end.json: JSON representation of the Game End event.
  • end.raw: Raw binary Game End event.
  • frames.arrow: Frame data in Arrow format (see below).

The bulk of this data is in frames.arrow, an Arrow IPC file containing all of the game's frame data. This is a columnar format, which makes .slpp about twice as compressible as .slp.

To convert between formats, use the slp CLI tool.

About

Rust parser for Slippi SSBM replay files

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Rust 88.3%
  • Clojure 11.3%
  • Shell 0.4%