use std::{
cmp::min,
collections::HashMap,
convert::{TryFrom, TryInto},
io::{self, Read, Result},
};
use byteorder::ReadBytesExt;
use encoding_rs::SHIFT_JIS;
use log::{debug, trace};
type BE = byteorder::BigEndian;
use super::{
action_state::{self, Common, State},
attack::Attack,
buttons,
character::{self, Internal},
frame::{self, Pre, Post},
game::{self, NUM_PORTS, Netplay, Player, PlayerType},
item,
primitives::{Direction, Port, Position, Velocity},
stage,
triggers,
ubjson,
};
const ZELDA_TRANSFORM_FRAME: u32 = 43;
const SHEIK_TRANSFORM_FRAME: u32 = 36;
const DEFAULT_CHAR_STATE: CharState = CharState {
character: Internal(255),
state: State::Common(Common::WAIT),
age: 0
};
#[derive(Clone, Copy, Debug, PartialEq)]
struct CharState {
character: Internal,
state: State,
age: u32,
}
const PAYLOADS_EVENT_CODE: u8 = 0x35;
#[derive(Clone, Copy, Debug, PartialEq, num_enum::TryFromPrimitive)]
#[repr(u8)]
enum Event {
GameStart = 0x36,
FramePre = 0x37,
FramePost = 0x38,
GameEnd = 0x39,
FrameStart = 0x3A,
Item = 0x3B,
FrameEnd = 0x3C,
}
pub trait Indexed {
fn index(&self) -> i32;
fn array_index(&self) -> usize;
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FrameId {
pub index: i32,
}
impl Indexed for FrameId {
fn index(&self) -> i32 {
self.index
}
fn array_index(&self) -> usize {
(self.index - game::FIRST_FRAME_INDEX).try_into().unwrap()
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct PortId {
pub index: i32,
pub port: Port,
pub is_follower: bool,
}
impl Indexed for PortId {
fn index(&self) -> i32 {
self.index
}
fn array_index(&self) -> usize {
(self.index - game::FIRST_FRAME_INDEX).try_into().unwrap()
}
}
#[derive(Debug)]
pub struct FrameEvent<Id, Event> {
pub id: Id,
pub event: Event,
}
fn if_more<F, T>(r: &mut &[u8], f: F) -> Result<Option<T>>
where F: FnOnce(&mut &[u8]) -> Result<T> {
Ok(match r.is_empty() {
true => None,
_ => Some(f(r)?),
})
}
fn payload_sizes<R: Read>(r: &mut R) -> Result<(usize, HashMap<u8, u16>)> {
let code = r.read_u8()?;
if code != PAYLOADS_EVENT_CODE {
Err(err!("expected event payloads, but got: {}", code))?;
}
let size = r.read_u8()?;
if size % 3 != 1 {
Err(err!("invalid payload size: {}", size))?;
}
let mut sizes = HashMap::new();
for _ in (0 .. size - 1).step_by(3) {
sizes.insert(r.read_u8()?, r.read_u16::<BE>()?);
}
trace!("Event payload sizes: {:?}", sizes);
Ok((1 + size as usize, sizes)) }
fn player(port: Port, v0: &[u8; 36], is_teams: bool, v1_0: Option<[u8; 8]>, v1_3: Option<[u8; 16]>, v3_9: Option<[u8; 41]>) -> Result<Option<Player>> {
let mut r = &v0[..];
let character = character::External(r.read_u8()?);
let r#type = game::PlayerType(r.read_u8()?);
let stocks = r.read_u8()?;
let costume = r.read_u8()?;
r.read_exact(&mut [0; 3])?; let team_shade = r.read_u8()?;
let handicap = r.read_u8()?;
let team_color = r.read_u8()?;
let team = {
match is_teams {
true => Some(game::Team {
color: game::TeamColor(team_color),
shade: game::TeamShade(team_shade),
}),
false => None,
}
};
r.read_u16::<BE>()?; let bitfield = r.read_u8()?;
r.read_u16::<BE>()?; let cpu_level = {
let cpu_level = r.read_u8()?;
match r#type {
PlayerType::CPU => Some(cpu_level),
_ => None,
}
};
r.read_u32::<BE>()?; let offense_ratio = r.read_f32::<BE>()?;
let defense_ratio = r.read_f32::<BE>()?;
let model_scale = r.read_f32::<BE>()?;
r.read_u32::<BE>()?;
let ucf = match v1_0 {
Some(v1_0) => {
let mut r = &v1_0[..];
Some(game::Ucf {
dash_back: match r.read_u32::<BE>()? {
0 => None,
db => Some(game::DashBack(db)),
},
shield_drop: match r.read_u32::<BE>()? {
0 => None,
sd => Some(game::ShieldDrop(sd)),
},
})
},
_ => None,
};
let name_tag = v1_3.map(|v1_3| {
let first_null = v1_3.iter().position(|&x| x == 0).unwrap_or(16);
SHIFT_JIS.decode_without_bom_handling(&v1_3[0..first_null]).0.to_string()
});
let netplay = v3_9.map(|v3_9| {
let r = &v3_9[..];
Netplay {
name: {
let first_null = r.iter().position(|&x| x == 0).unwrap_or(31);
SHIFT_JIS.decode_without_bom_handling(&r[0..first_null]).0.to_string()
},
code: {
let first_null = r.iter().position(|&x| x == 0).unwrap_or(10);
SHIFT_JIS.decode_without_bom_handling(&r[0..first_null]).0.to_string()
},
}
});
Ok(match r#type {
PlayerType::HUMAN | PlayerType::CPU | PlayerType::DEMO => Some(Player {
port: port,
character: character,
r#type: r#type,
stocks: stocks,
costume: costume,
team: team,
handicap: handicap,
bitfield: bitfield,
cpu_level: cpu_level,
offense_ratio: offense_ratio,
defense_ratio: defense_ratio,
model_scale: model_scale,
ucf: ucf,
name_tag: name_tag,
netplay: netplay,
}),
_ => None,
})
}
fn player_bytes_v3_9(r: &mut &[u8]) -> Result<[u8; 41]> {
let mut buf = [0; 41];
r.read_exact(&mut buf)?;
Ok(buf)
}
fn player_bytes_v1_3(r: &mut &[u8]) -> Result<[u8; 16]> {
let mut buf = [0; 16];
r.read_exact(&mut buf)?;
Ok(buf)
}
fn player_bytes_v1_0(r: &mut &[u8]) -> Result<[u8; 8]> {
let mut buf = [0; 8];
r.read_exact(&mut buf)?;
Ok(buf)
}
fn game_start(mut r: &mut &[u8]) -> Result<game::Start> {
let slippi = game::Slippi {
version: game::SlippiVersion(r.read_u8()?, r.read_u8()?, r.read_u8()?),
};
r.read_u8()?; let bitfield = {
let mut buf = [0; 3];
buf[0] = r.read_u8()?; buf[1] = r.read_u8()?; r.read_u8()?; buf[2] = r.read_u8()?; buf
};
r.read_u32::<BE>()?; let is_teams = r.read_u8()? != 0;
r.read_u16::<BE>()?; let item_spawn_frequency = r.read_i8()?;
let self_destruct_score = r.read_i8()?;
r.read_u8()?; let stage = stage::Stage(r.read_u16::<BE>()?);
let timer = r.read_u32::<BE>()?;
r.read_exact(&mut [0; 15])?; let item_spawn_bitfield = {
let mut buf = [0; 5];
r.read_exact(&mut buf)?;
buf
};
r.read_u64::<BE>()?; let damage_ratio = r.read_f32::<BE>()?;
r.read_exact(&mut [0; 44])?; let mut players_v0 = [[0; 36]; 4];
for p in &mut players_v0 {
r.read_exact(p)?;
}
r.read_exact(&mut [0; 72])?; let random_seed = r.read_u32::<BE>()?;
let players_v1_0 = match r.is_empty() {
true => [None, None, None, None],
_ => [
Some(player_bytes_v1_0(&mut r)?),
Some(player_bytes_v1_0(&mut r)?),
Some(player_bytes_v1_0(&mut r)?),
Some(player_bytes_v1_0(&mut r)?),
],
};
let players_v1_3 = match r.is_empty() {
true => [None, None, None, None],
_ => [
Some(player_bytes_v1_3(&mut r)?),
Some(player_bytes_v1_3(&mut r)?),
Some(player_bytes_v1_3(&mut r)?),
Some(player_bytes_v1_3(&mut r)?),
],
};
let is_pal = if_more(r, |r| Ok(r.read_u8()? != 0))?;
let is_frozen_ps = if_more(r, |r| Ok(r.read_u8()? != 0))?;
let scene = if_more(r, |r| Ok(game::Scene {
minor: r.read_u8()?,
major: r.read_u8()?,
}))?;
let players_v3_9 = match r.is_empty() {
true => [None, None, None, None],
_ => [
Some(player_bytes_v3_9(&mut r)?),
Some(player_bytes_v3_9(&mut r)?),
Some(player_bytes_v3_9(&mut r)?),
Some(player_bytes_v3_9(&mut r)?),
],
};
let mut players = Vec::<Player>::new();
for n in 0 .. NUM_PORTS {
if let Some(p) = player(Port::try_from(n as u8).unwrap(), &players_v0[n], is_teams, players_v1_0[n], players_v1_3[n], players_v3_9[n])? {
players.push(p);
}
}
Ok(game::Start {
slippi: slippi,
bitfield: bitfield,
is_teams: is_teams,
item_spawn_frequency: item_spawn_frequency,
self_destruct_score: self_destruct_score,
stage: stage,
timer: timer,
item_spawn_bitfield: item_spawn_bitfield,
damage_ratio: damage_ratio,
players: players,
random_seed: random_seed,
is_pal: is_pal,
is_frozen_ps: is_frozen_ps,
scene: scene,
})
}
fn game_end(r: &mut &[u8]) -> Result<game::End> {
Ok(game::End {
method: game::EndMethod(r.read_u8()?),
lras_initiator: if_more(r, |r| Ok(Port::try_from(r.read_u8()?).ok()))?,
})
}
fn frame_start(r: &mut &[u8]) -> Result<FrameEvent<FrameId, frame::Start>> {
let id = FrameId { index: r.read_i32::<BE>()? };
trace!("Frame Start: {:?}", id);
Ok(FrameEvent {
id: id,
event: frame::Start {
random_seed: r.read_u32::<BE>()?,
},
})
}
fn frame_end(r: &mut &[u8]) -> Result<FrameEvent<FrameId, frame::End>> {
let id = FrameId { index: r.read_i32::<BE>()? };
trace!("Frame End: {:?}", id);
Ok(FrameEvent {
id: id,
event: frame::End {
latest_finalized_frame: if_more(r, |r| r.read_i32::<BE>())?,
},
})
}
fn item(r: &mut &[u8]) -> Result<FrameEvent<FrameId, frame::Item>> {
let id = FrameId { index: r.read_i32::<BE>()? };
trace!("Item Update: {:?}", id);
let item_type = item::Item(r.read_u16::<BE>()?);
Ok(FrameEvent {
id: id,
event: frame::Item {
r#type: item_type,
state: r.read_u8()?,
direction: direction(r.read_f32::<BE>()?)?,
velocity: Velocity {
x: r.read_f32::<BE>()?,
y: r.read_f32::<BE>()?,
},
position: Position {
x: r.read_f32::<BE>()?,
y: r.read_f32::<BE>()?,
},
damage: r.read_u16::<BE>()?,
timer: r.read_f32::<BE>()?,
id: r.read_u32::<BE>()?,
misc: if_more(r, |r| Ok([r.read_u8()?, r.read_u8()?, r.read_u8()?, r.read_u8()?]))?,
owner: if_more(r, |r| Ok(Port::try_from(r.read_u8()?).ok()))?
},
})
}
fn direction(value: f32) -> Result<Direction> {
match value {
v if v < 0.0 => Ok(Direction::Left),
v if v > 0.0 => Ok(Direction::Right),
_ => Err(err!("direction == 0")),
}
}
fn predict_character(id: PortId, last_char_states: &[CharState; NUM_PORTS]) -> Internal {
let prev = last_char_states[id.port as usize];
match prev.state {
State::Zelda(action_state::Zelda::TRANSFORM_GROUND) |
State::Zelda(action_state::Zelda::TRANSFORM_AIR)
if prev.age >= ZELDA_TRANSFORM_FRAME => Internal::SHEIK,
State::Sheik(action_state::Sheik::TRANSFORM_GROUND) |
State::Sheik(action_state::Sheik::TRANSFORM_AIR)
if prev.age >= SHEIK_TRANSFORM_FRAME => Internal::ZELDA,
_ => prev.character,
}
}
fn frame_pre(r: &mut &[u8], last_char_states: &[CharState; NUM_PORTS]) -> Result<FrameEvent<PortId, Pre>> {
let id = PortId {
index: r.read_i32::<BE>()?,
port: Port::try_from(r.read_u8()?).map_err(|e| err!("invalid port: {:?}", e))?,
is_follower: r.read_u8()? != 0,
};
trace!("Pre-Frame Update: {:?}", id);
let character = predict_character(id, last_char_states);
let random_seed = r.read_u32::<BE>()?;
let state = State::from(r.read_u16::<BE>()?, character);
let position = Position {
x: r.read_f32::<BE>()?,
y: r.read_f32::<BE>()?,
};
let direction = direction(r.read_f32::<BE>()?)?;
let joystick = Position {
x: r.read_f32::<BE>()?,
y: r.read_f32::<BE>()?,
};
let cstick = Position {
x: r.read_f32::<BE>()?,
y: r.read_f32::<BE>()?,
};
let trigger_logical = r.read_f32::<BE>()?;
let buttons = frame::Buttons {
logical: buttons::Logical(r.read_u32::<BE>()?),
physical: buttons::Physical(r.read_u16::<BE>()?),
};
let triggers = frame::Triggers {
logical: trigger_logical,
physical: triggers::Physical {
l: r.read_f32::<BE>()?,
r: r.read_f32::<BE>()?,
},
};
Ok(FrameEvent {
id: id,
event: Pre {
random_seed: random_seed,
state: state,
position: position,
direction: direction,
joystick: joystick,
cstick: cstick,
triggers: triggers,
buttons: buttons,
raw_analog_x: if_more(r, |r| r.read_u8())?,
damage: if_more(r, |r| r.read_f32::<BE>())?,
}
})
}
fn flags(buf: &[u8; 5]) -> frame::StateFlags {
frame::StateFlags(
((buf[0] as u64) << 00) +
((buf[1] as u64) << 08) +
((buf[2] as u64) << 16) +
((buf[3] as u64) << 24) +
((buf[4] as u64) << 32)
)
}
fn update_last_char_state(id: PortId, character: Internal, state: State, last_char_states: &mut [CharState; NUM_PORTS]) {
const Z_AIR: State = State::Zelda(action_state::Zelda::TRANSFORM_AIR);
const Z_GROUND: State = State::Zelda(action_state::Zelda::TRANSFORM_GROUND);
const S_AIR: State = State::Sheik(action_state::Sheik::TRANSFORM_AIR);
const S_GROUND: State = State::Sheik(action_state::Sheik::TRANSFORM_GROUND);
let prev = last_char_states[id.port as usize];
last_char_states[id.port as usize] = CharState {
character: character,
state: state,
age: match (prev.state, state) {
(s0, s1) if s0 == s1 => prev.age + 1,
(Z_AIR, Z_GROUND) | (Z_GROUND, Z_AIR) =>
min(ZELDA_TRANSFORM_FRAME - 1, prev.age + 1),
(S_AIR, S_GROUND) | (S_GROUND, S_AIR) =>
min(SHEIK_TRANSFORM_FRAME - 1, prev.age + 1),
_ => 0,
},
};
}
fn frame_post(r: &mut &[u8], last_char_states: &mut [CharState; NUM_PORTS]) -> Result<FrameEvent<PortId, Post>> {
let id = PortId {
index: r.read_i32::<BE>()?,
port: Port::try_from(r.read_u8()?).map_err(|e| err!("invalid port: {:?}", e))?,
is_follower: r.read_u8()? != 0,
};
trace!("Post-Frame Update: {:?}", id);
let character = Internal(r.read_u8()?);
let state = State::from(r.read_u16::<BE>()?, character);
let position = Position {
x: r.read_f32::<BE>()?,
y: r.read_f32::<BE>()?,
};
let direction = direction(r.read_f32::<BE>()?)?;
let damage = r.read_f32::<BE>()?;
let shield = r.read_f32::<BE>()?;
let last_attack_landed = {
let attack = r.read_u8()?;
match attack {
0 => None,
attack => Some(Attack(attack)),
}
};
let combo_count = r.read_u8()?;
let last_hit_by = Port::try_from(r.read_u8()?).ok();
let stocks = r.read_u8()?;
let state_age = if_more(r, |r| r.read_f32::<BE>())?;
let flags = if_more(r, |r| {
let mut buf = [0; 5];
r.read_exact(&mut buf)?;
Ok(flags(&buf))
})?;
let misc_as = if_more(r, |r| r.read_f32::<BE>())?;
let airborne = if_more(r, |r| Ok(r.read_u8()? != 0))?;
let ground = if_more(r, |r| r.read_u16::<BE>())?;
let jumps = if_more(r, |r| r.read_u8())?;
let l_cancel = if_more(r, |r| Ok(
match r.read_u8()? {
0 => None,
1 => Some(true),
2 => Some(false),
i => Err(err!("invalid L-Cancel value: {}", i))?,
}
))?;
let hurtbox_state = if_more(r, |r| Ok(frame::HurtboxState(r.read_u8()?)))?;
let velocities = if_more(r, |r| Ok({
let autogenous_air_x = r.read_f32::<BE>()?;
let autogenous_y = r.read_f32::<BE>()?;
let knockback_x = r.read_f32::<BE>()?;
let knockback_y = r.read_f32::<BE>()?;
let autogenous_ground_x = r.read_f32::<BE>()?;
frame::Velocities {
autogenous: Velocity {
x: match airborne.unwrap() {
true => autogenous_air_x,
_ => autogenous_ground_x,
},
y: autogenous_y,
},
knockback: Velocity {
x: knockback_x,
y: knockback_y,
},
}
}))?;
let hitlag = if_more(r, |r| r.read_f32::<BE>())?;
update_last_char_state(id, character, state, last_char_states);
Ok(FrameEvent {
id: id,
event: Post {
character: character,
state: state,
position: position,
direction: direction,
damage: damage,
shield: shield,
last_attack_landed: last_attack_landed,
combo_count: combo_count,
last_hit_by: last_hit_by,
stocks: stocks,
state_age: state_age,
flags: flags,
misc_as: misc_as,
airborne: airborne,
ground: ground,
jumps: jumps,
l_cancel: l_cancel,
hurtbox_state: hurtbox_state,
velocities: velocities,
hitlag: hitlag,
},
})
}
pub trait Handlers {
fn game_start(&mut self, _: game::Start) -> Result<()> { Ok(()) }
fn game_end(&mut self, _: game::End) -> Result<()> { Ok(()) }
fn metadata(&mut self, _: serde_json::Map<String, serde_json::Value>) -> Result<()> { Ok(()) }
fn frame_start(&mut self, _: FrameEvent<FrameId, frame::Start>) -> Result<()> { Ok(()) }
fn frame_pre(&mut self, _: FrameEvent<PortId, Pre>) -> Result<()> { Ok(()) }
fn frame_post(&mut self, _: FrameEvent<PortId, Post>) -> Result<()> { Ok(()) }
fn frame_end(&mut self, _: FrameEvent<FrameId, frame::End>) -> Result<()> { Ok(()) }
fn item(&mut self, _: FrameEvent<FrameId, frame::Item>) -> Result<()> { Ok(()) }
fn finalize(&mut self) -> Result<()> { Ok(()) }
}
fn expect_bytes<R: Read>(r: &mut R, expected: &[u8]) -> Result<()> {
let mut actual = vec![0; expected.len()];
r.read_exact(&mut actual)?;
if expected == actual.as_slice() {
Ok(())
} else {
Err(err!("expected: {:?}, got: {:?}", expected, actual))
}
}
fn event<R: Read, H: Handlers>(mut r: R, payload_sizes: &HashMap<u8, u16>, last_char_states: &mut [CharState; NUM_PORTS], handlers: &mut H) -> Result<(usize, Option<Event>)> {
let code = r.read_u8()?;
debug!("Event: {:#x}", code);
let size = *payload_sizes.get(&code).ok_or_else(|| err!("unknown event: {}", code))? as usize;
let mut buf = vec![0; size];
r.read_exact(&mut *buf)?;
let event = Event::try_from(code).ok();
if let Some(event) = event {
use Event::*;
match event {
GameStart => handlers.game_start(game_start(&mut &*buf)?)?,
GameEnd => handlers.game_end(game_end(&mut &*buf)?)?,
FrameStart => handlers.frame_start(frame_start(&mut &*buf)?)?,
FramePre => handlers.frame_pre(frame_pre(&mut &*buf, last_char_states)?)?,
FramePost => handlers.frame_post(frame_post(&mut &*buf, last_char_states)?)?,
FrameEnd => handlers.frame_end(frame_end(&mut &*buf)?)?,
Item => handlers.item(item(&mut &*buf)?)?,
};
}
Ok((1 + size as usize, event)) }
#[derive(Clone, Copy, Debug)]
pub struct Opts {
pub skip_frames: bool,
}
pub fn parse<R: Read, H: Handlers>(mut r: &mut R, handlers: &mut H, opts: Option<Opts>) -> Result<()> {
expect_bytes(&mut r,
&[0x7b, 0x55, 0x03, 0x72, 0x61, 0x77, 0x5b, 0x24, 0x55, 0x23, 0x6c])?;
let raw_len = r.read_u32::<BE>()? as usize;
let (mut bytes_read, payload_sizes) = payload_sizes(&mut r)?;
let mut last_char_states = [DEFAULT_CHAR_STATE; NUM_PORTS];
let mut last_event: Option<Event> = None;
let skip_frames = opts.map(|o| o.skip_frames).unwrap_or(false);
while (raw_len == 0 || bytes_read < raw_len) && last_event != Some(Event::GameEnd) {
if skip_frames && last_event == Some(Event::GameStart) {
let skip = raw_len - bytes_read - payload_sizes[&(Event::GameEnd as u8)] as usize - 1;
io::copy(&mut r.by_ref().take(skip as u64), &mut io::sink())?;
bytes_read += skip;
}
let (bytes, event) = event(r.by_ref(), &payload_sizes, &mut last_char_states, handlers)?;
bytes_read += bytes;
last_event = event;
}
if raw_len != 0 && bytes_read != raw_len {
Err(err!("failed to consume expected number of bytes: {}, {}", raw_len, bytes_read))?;
}
expect_bytes(&mut r,
&[0x55, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x7b])?;
let metadata = ubjson::parse_map(&mut r)?;
debug!("Raw metadata: {:?}", metadata);
handlers.metadata(metadata)?;
expect_bytes(&mut r, &[0x7d])?;
handlers.finalize()?;
Ok(())
}