1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#![allow(clippy::zero_prefixed_literal)]
#![allow(clippy::redundant_field_names)]

macro_rules! err {
	($( $arg: expr ),*) => {
		std::io::Error::new(std::io::ErrorKind::InvalidData, format!($( $arg ),*))
	}
}

#[derive(Clone, Copy, Debug)]
pub struct SerializationConfig {
	/// Print enum names with numeric values (e.g. `14:WAIT`).
	pub enum_names: bool,
}

// TODO: replace with `serde_state`?
/// Global singleton, a hack to smuggle config into serializers.
/// You probably don't care about this unless you're serializing with Serde.
pub static mut SERIALIZATION_CONFIG: SerializationConfig = SerializationConfig {
	enum_names: false,
};

pub(crate) mod ubjson {
	pub(crate) mod de;
	pub(crate) mod ser;
}

pub mod model {
	#[macro_use] #[doc(hidden)] pub(crate) mod pseudo_bitmask;
	#[macro_use] #[doc(hidden)] pub(crate) mod pseudo_enum;

	pub mod buttons;
	pub mod frame;
	pub mod game;
	pub mod item;
	pub mod metadata;
	pub mod primitives;
	pub mod slippi;
	pub mod triggers;
	pub mod enums {
		pub mod action_state;
		pub mod attack;
		pub mod character;
		pub mod ground;
		pub mod item;
		pub mod stage;
	}
}

pub mod serde {
	pub mod arrow;
	pub mod collect;
	pub mod de;
	pub mod ser;
}

use std::{
	error,
	fmt,
	io::{self, Read},
};

#[derive(Debug)]
pub struct ParseError {
	pub pos: Option<u64>,
	pub error: io::Error,
}

impl fmt::Display for ParseError {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		if let Some(pos) = self.pos {
			write!(f, "parse error @{:#x}: {}", pos, self.error)
		} else {
			write!(f, "parse error: {}", self.error)
		}
	}
}

impl error::Error for ParseError {
	fn source(&self) -> Option<&(dyn error::Error + 'static)> {
		Some(&self.error)
	}
}

/// Since we support non-seekable readers, we use this wrapper to track
/// position for better error reporting.
struct TrackingReader<R> {
	reader: R,
	pos: u64,
}

impl<R: Read> Read for TrackingReader<R> {
	fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
		let result = self.reader.read(buf);
		if let Ok(read) = result {
			self.pos += read as u64;
		}
		result
	}
}

/// Parse a Slippi replay from `r`, passing events to the callbacks in `handlers` as they occur.
pub fn parse<R: Read, H: serde::de::Handlers>(r: &mut R, handlers: &mut H, opts: Option<serde::de::Opts>) -> std::result::Result<(), ParseError> {
	let mut r = TrackingReader {
		pos: 0,
		reader: r,
	};
	serde::de::deserialize(&mut r, handlers, opts)
		.map_err(|e| ParseError { error: e, pos: Some(r.pos) })
}

/// Parse a Slippi replay from `r`, returning a `game::Game` object.
pub fn game<R: Read>(r: &mut R, parse_opts: Option<serde::de::Opts>, collect_opts: Option<serde::collect::Opts>) -> Result<model::game::Game, ParseError> {
	let mut game_parser = serde::collect::Collector {
		opts: collect_opts.unwrap_or_default(),
		..Default::default()
	};
	parse(r, &mut game_parser, parse_opts)
		.and_then(|_| game_parser.into_game().map_err(|e| ParseError { error: e, pos: None }))
}