diff --git a/Cargo.lock b/Cargo.lock index 6e6d9d8..934c3b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,12 +21,6 @@ version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f" -[[package]] -name = "arrayvec" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" - [[package]] name = "atty" version = "0.2.14" @@ -58,7 +52,6 @@ dependencies = [ "byteorder", "clap", "log", - "nom", "num-derive", "num-traits", "pretty_env_logger", @@ -186,19 +179,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -[[package]] -name = "lexical-core" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616" -dependencies = [ - "arrayvec", - "bitflags", - "cfg-if", - "ryu", - "static_assertions", -] - [[package]] name = "libc" version = "0.2.71" @@ -229,17 +209,6 @@ dependencies = [ "adler32", ] -[[package]] -name = "nom" -version = "5.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" -dependencies = [ - "lexical-core", - "memchr", - "version_check", -] - [[package]] name = "num-derive" version = "0.3.0" @@ -350,18 +319,6 @@ version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" -[[package]] -name = "ryu" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "strsim" version = "0.10.0" diff --git a/Cargo.toml b/Cargo.toml index 3515ef2..0fa8018 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,6 @@ anyhow = "^1.0.31" byteorder = "^1.3.4" clap = "3.0.0-beta.1" log = "^0.4.8" -nom = "^5.1.1" num-derive = "^0.3" num-traits = "^0.2" pretty_env_logger = "^0.4" diff --git a/src/converter/ddr2osu.rs b/src/converter/ddr2osu.rs index fda9b23..56c9906 100644 --- a/src/converter/ddr2osu.rs +++ b/src/converter/ddr2osu.rs @@ -385,7 +385,7 @@ impl ssq::SSQ { let mut shock_step_generator = ShockStepGenerator::new(chart.difficulty.players * 4, config.shock_action.clone()); - for step in &chart.steps.0 { + for step in &chart.steps { trace!("Converting {:?} to hit object", step); if let Some(mut step_hit_objects) = step.to_hit_objects( chart.difficulty.players * 4, diff --git a/src/ddr/ssq.rs b/src/ddr/ssq.rs index f2b6235..a2c8a59 100644 --- a/src/ddr/ssq.rs +++ b/src/ddr/ssq.rs @@ -1,331 +1,38 @@ use std::convert::From; +use std::convert::TryInto; use std::fmt; +use std::io; +use std::io::prelude::*; +use std::io::Cursor; -use anyhow::Result; +use byteorder::{ReadBytesExt, LE}; use log::{debug, info, trace, warn}; -use nom::bytes::complete::take; -use nom::multi::many0; -use nom::number::complete::{le_i16, le_i32, le_u16}; -use nom::IResult; +use thiserror::Error; +use crate::mini_parser::{MiniParser, MiniParserError}; use crate::utils; -use crate::utils::exec_nom_parser; const MEASURE_LENGTH: i32 = 4096; const FREEZE: bool = false; -// Convert time offset to beats -// time offset is the measure times MEASURE_LENGTH +#[derive(Error, Debug)] +pub enum Error { + #[error("Not enough freeze data was found")] + NotEnoughFreezeData, + #[error(transparent)] + IOError(#[from] io::Error), + #[error(transparent)] + TryFromIntError(#[from] std::num::TryFromIntError), + #[error(transparent)] + MiniParserError(#[from] MiniParserError), +} + +/// Convert time offset to beats +/// time offset is the measure times MEASURE_LENGTH fn measure_to_beats(metric: i32) -> f32 { 4.0 * metric as f32 / MEASURE_LENGTH as f32 } -fn parse_n_i32(n: usize, input: &[u8]) -> IResult<&[u8], Vec> { - let (input, bytes) = take(n as usize * 4)(input)?; - let (unprocessed_input, values) = many0(le_i32)(bytes)?; - assert_eq!(unprocessed_input.len(), 0); - Ok((input, values)) -} - -fn parse_usize(input: &[u8]) -> IResult<&[u8], usize> { - let (input, value) = le_i32(input)?; - Ok((input, value as usize)) -} - -#[derive(Clone, Debug, PartialEq)] -pub struct TempoChange { - pub start_ms: i32, - pub start_beats: f32, - pub end_beats: f32, - pub beat_length: f32, -} - -#[derive(Clone, Debug, PartialEq)] -pub struct TempoChanges(pub Vec); - -impl TempoChanges { - fn parse(ticks_per_second: i32, input: &[u8]) -> IResult<&[u8], Self> { - let (input, count) = parse_usize(input)?; - let (input, measure) = parse_n_i32(count, input)?; - let (input, tempo_data) = parse_n_i32(count, input)?; - - let mut entries = Vec::new(); - - let mut elapsed_ms = 0; - let mut elapsed_beats = 0.0; - for i in 1..count { - let delta_measure = measure[i] - measure[i - 1]; - let delta_ticks = tempo_data[i] - tempo_data[i - 1]; - - let length_ms = 1000 * delta_ticks / ticks_per_second; - let length_beats = measure_to_beats(delta_measure); - - let beat_length = length_ms as f32 / length_beats; - - let entry = TempoChange { - start_ms: elapsed_ms, - start_beats: elapsed_beats, - end_beats: elapsed_beats + length_beats, - beat_length, - }; - - entries.push(entry); - - elapsed_ms += length_ms; - elapsed_beats += length_beats; - } - - Ok((input, Self(entries))) - } -} - -#[derive(Clone, Debug, PartialEq)] -pub enum Step { - Step { beats: f32, row: Row }, - Freeze { start: f32, end: f32, row: Row }, - Shock { beats: f32 }, -} - -#[derive(Clone, Debug, PartialEq)] -pub struct Steps(pub Vec); - -impl Steps { - fn parse(input: &[u8], players: u8) -> IResult<&[u8], Self> { - let (input, count) = parse_usize(input)?; - let (input, measure) = parse_n_i32(count, input)?; - let (input, steps) = take(count)(input)?; - - // freeze data can be padded with zeroes - let (input, freeze_data) = take(input.len())(input)?; - let mut freeze = freeze_data.iter().skip_while(|x| **x == 0).copied(); - - let mut parsed_steps = Vec::new(); - - for i in 0..count { - let beats = measure_to_beats(measure[i]); - - // check if either all eight bits are set (shock for double) or the first four (shock for - // single) - if steps[i] == 0xff || steps[i] == 0xf { - // shock - trace!("Shock arrow at {}", beats); - - parsed_steps.push(Step::Shock { beats }); - } else if steps[i] == 0x00 { - // extra data - let columns = freeze.next().unwrap(); - let extra_type = freeze.next().unwrap(); - - if extra_type == 1 { - // freeze end (start is the last normal step in that column) - trace!("Freeze arrow at {}", beats); - - let row = Row::new(columns, players); - if row.count_active() != 1 { - warn!("Found freeze with not exactly one column, which is not implemented, skipping"); - continue; - } - - if FREEZE { - match Self::find_last(Self(parsed_steps.clone()), &row) { - Some(last_step) => { - parsed_steps.push(Step::Freeze { - start: if let Step::Step { beats, .. } = parsed_steps[last_step] - { - beats - } else { - unreachable!() - }, - end: beats, - row, - }); - - parsed_steps.remove(last_step); - } - None => { - warn!( - "Could not find previous step for freeze, adding normal step" - ); - parsed_steps.push(Step::Step { beats, row }); - } - } - } else { - trace!("Freeze disabled, adding normal step"); - parsed_steps.push(Step::Step { beats, row }); - } - } else { - debug!( - "Encountered unknown extra step with type {}, ignoring", - extra_type - ); - } - } else { - // normal step - trace!("Normal step at {}", beats); - - parsed_steps.push(Step::Step { - beats, - row: Row::new(steps[i], players), - }); - } - } - - debug!("Parsed {} steps", parsed_steps.len()); - - Ok((input, Self(parsed_steps))) - } - - fn find_last(steps: Self, row: &Row) -> Option { - for i in (0..steps.0.len()).rev() { - if let Step::Step { row: step_row, .. } = &steps.0[i] { - if step_row.clone().intersects(row.clone()) { - return Some(i); - } - } - } - - None - } -} - -#[derive(Clone, Debug, PartialEq)] -pub struct Difficulty { - pub players: u8, - difficulty: u8, -} - -impl From for Difficulty { - fn from(parameter: u16) -> Self { - Self { - difficulty: ((parameter & 0xFF00) >> 8) as u8, - players: (parameter & 0xF) as u8 / 4, - } - } -} - -impl Into for Difficulty { - fn into(self) -> f32 { - match self.difficulty { - 1 => 0.25, - 2 => 0.5, - 3 => 1.0, - 4 => 0.0, - 6 => 0.75, - _ => 1.0, - } - } -} - -impl fmt::Display for Difficulty { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let players = match self.players { - 1 => "Single", - 2 => "Double", - _ => "Unknown Number of Players", - }; - let difficulty = match self.difficulty { - 1 => "Basic", - 2 => "Difficult", - 3 => "Challenge", - 4 => "Beginner", - 6 => "Expert", - _ => "Unknown Difficulty", - }; - write!(f, "{} {}", players, difficulty) - } -} - -#[derive(Clone, Debug, PartialEq)] -pub struct Chart { - pub difficulty: Difficulty, - pub steps: Steps, -} - -#[derive(Clone, Debug, PartialEq)] -pub struct SSQ { - pub tempo_changes: TempoChanges, - pub charts: Vec, -} - -impl From for SSQ { - fn from(chunks: Chunks) -> Self { - let mut ssq = Self { - tempo_changes: TempoChanges(Vec::new()), - charts: Vec::new(), - }; - for chunk in chunks.0 { - match chunk { - Chunk::TempoChanges(mut tempo_changes) => { - ssq.tempo_changes.0.append(&mut tempo_changes.0) - } - Chunk::Chart(chart) => ssq.charts.push(chart), - Chunk::Extra(..) => {} - } - } - info!("Parsed {} charts", ssq.charts.len()); - ssq - } -} - -impl SSQ { - pub fn parse(data: &[u8]) -> Result { - debug!( - "Configuration: measure length: {}, use freezes: {}", - MEASURE_LENGTH, FREEZE - ); - let chunks = exec_nom_parser(Chunks::parse, data)?; - - Ok(Self::from(chunks)) - } -} - -#[derive(Clone, Debug, PartialEq)] -enum Chunk { - Chart(Chart), - TempoChanges(TempoChanges), - Extra(Vec), -} - -impl Chunk { - fn parse(input: &[u8]) -> IResult<&[u8], Self> { - let (input, length) = le_i32(input)?; - - let (input, chunk_type) = le_i16(input)?; - let (input, parameter) = le_u16(input)?; - - // length without i32 and 2 × i16 - let (input, data) = take(length as usize - 8)(input)?; - - let chunk = match chunk_type { - 1 => { - debug!("Parsing tempo changes (ticks/s: {})", parameter); - let (_, TempoChanges(tempo_changes)) = TempoChanges::parse(parameter as i32, data)?; - Self::TempoChanges(TempoChanges(tempo_changes)) - } - 3 => { - let difficulty = Difficulty::from(parameter); - debug!("Parsing step chunk ({})", difficulty); - let (_, steps) = Steps::parse(data, difficulty.players)?; - Self::Chart(Chart { difficulty, steps }) - } - _ => { - debug!("Found extra chunk (length {})", data.len()); - Self::Extra(data.to_vec()) - } - }; - Ok((input, chunk)) - } -} - -pub struct Chunks(Vec); - -impl Chunks { - fn parse(input: &[u8]) -> IResult<&[u8], Self> { - let (input, chunks) = many0(Chunk::parse)(input)?; - Ok((input, Self(chunks))) - } -} - #[derive(Debug, Clone, PartialEq)] pub struct PlayerRow { pub left: bool, @@ -420,3 +127,278 @@ impl Row { false } } + +#[derive(Clone, Debug, PartialEq)] +pub struct TempoChange { + pub start_ms: i32, + pub start_beats: f32, + pub end_beats: f32, + pub beat_length: f32, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct TempoChanges(pub Vec); + +impl TempoChanges { + fn parse(ticks_per_second: i32, data: &[u8]) -> Result { + let mut cursor = Cursor::new(data); + + let count = cursor.read_u32::()?.try_into()?; + let measure = cursor.read_n_u32(count)?; + let tempo_data = cursor.read_n_u32(count)?; + + let mut entries = Vec::new(); + + let mut elapsed_ms = 0; + let mut elapsed_beats = 0.0; + for i in 1..count { + let delta_measure = measure[i] - measure[i - 1]; + let delta_ticks = tempo_data[i] - tempo_data[i - 1]; + + let length_ms = 1000 * delta_ticks / ticks_per_second; + let length_beats = measure_to_beats(delta_measure); + + let beat_length = length_ms as f32 / length_beats; + + let entry = TempoChange { + start_ms: elapsed_ms, + start_beats: elapsed_beats, + end_beats: elapsed_beats + length_beats, + beat_length, + }; + + entries.push(entry); + + elapsed_ms += length_ms; + elapsed_beats += length_beats; + } + + Ok(Self(entries)) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Step { + Step { beats: f32, row: Row }, + Freeze { start: f32, end: f32, row: Row }, + Shock { beats: f32 }, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Chart { + pub difficulty: Difficulty, + pub steps: Vec, +} + +impl Chart { + fn parse(data: &[u8], parameter: u16) -> Result { + let difficulty: Difficulty = parameter.into(); + + let mut cursor = Cursor::new(data); + + let count = cursor.read_u32::()?.try_into()?; + let measures = cursor.read_n_u32(count)?; + let mut steps = vec![0; count]; + cursor.read_exact(&mut steps)?; + + let mut freeze_data = Vec::new(); + cursor.read_to_end(&mut freeze_data)?; + // freeze data can be padded with zeroes + let mut freeze_data = freeze_data.iter().skip_while(|x| **x == 0).copied(); + + let mut parsed_steps = Vec::new(); + + for step in 0..count { + let beats = measure_to_beats(measures[step]); + + // check if either all eight bits are set (shock for double) or the first four (shock for + // single) + if steps[step] == 0xff || steps[step] == 0xf { + // shock + trace!("Shock arrow at {}", beats); + + parsed_steps.push(Step::Shock { beats }); + } else if steps[step] == 0x00 { + // extra data + let columns = freeze_data.next().ok_or(Error::NotEnoughFreezeData)?; + let extra_type = freeze_data.next().ok_or(Error::NotEnoughFreezeData)?; + + if extra_type == 1 { + // freeze end (start is the last normal step in that column) + trace!("Freeze arrow at {}", beats); + + let row = Row::new(columns, difficulty.players); + if row.count_active() != 1 { + warn!("Found freeze with not exactly one column, which is not implemented, skipping"); + continue; + } + + if FREEZE { + match Self::find_last(parsed_steps.clone(), &row) { + Some(last_step) => { + parsed_steps.push(Step::Freeze { + start: if let Step::Step { beats, .. } = parsed_steps[last_step] + { + beats + } else { + unreachable!() + }, + end: beats, + row, + }); + + parsed_steps.remove(last_step); + } + None => { + warn!( + "Could not find previous step for freeze, adding normal step" + ); + parsed_steps.push(Step::Step { beats, row }); + } + } + } else { + trace!("Freeze disabled, adding normal step"); + parsed_steps.push(Step::Step { beats, row }); + } + } else { + debug!( + "Encountered unknown extra step with type {}, ignoring", + extra_type + ); + } + } else { + // normal step + trace!("Normal step at {}", beats); + + parsed_steps.push(Step::Step { + beats, + row: Row::new(steps[step], difficulty.players), + }); + } + } + + debug!("Parsed {} steps", parsed_steps.len()); + + Ok(Self { + difficulty, + steps: parsed_steps, + }) + } + + fn find_last(steps: Vec, row: &Row) -> Option { + for i in (0..steps.len()).rev() { + if let Step::Step { row: step_row, .. } = &steps[i] { + if step_row.clone().intersects(row.clone()) { + return Some(i); + } + } + } + + None + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Difficulty { + pub players: u8, + difficulty: u8, +} + +impl From for Difficulty { + fn from(parameter: u16) -> Self { + Self { + difficulty: ((parameter & 0xFF00) >> 8) as u8, + players: (parameter & 0xF) as u8 / 4, + } + } +} + +impl Into for Difficulty { + fn into(self) -> f32 { + match self.difficulty { + 1 => 0.25, + 2 => 0.5, + 3 => 1.0, + 4 => 0.0, + 6 => 0.75, + _ => 1.0, + } + } +} + +impl fmt::Display for Difficulty { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let players = match self.players { + 1 => "Single", + 2 => "Double", + _ => "Unknown Number of Players", + }; + let difficulty = match self.difficulty { + 1 => "Basic", + 2 => "Difficult", + 3 => "Challenge", + 4 => "Beginner", + 6 => "Expert", + _ => "Unknown Difficulty", + }; + write!(f, "{} {}", players, difficulty) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct SSQ { + pub tempo_changes: TempoChanges, + pub charts: Vec, +} + +impl SSQ { + pub fn parse(data: &[u8]) -> Result { + debug!( + "Configuration: measure length: {}, use freezes: {}", + MEASURE_LENGTH, FREEZE + ); + let mut cursor = Cursor::new(data); + + let mut ssq = Self { + tempo_changes: TempoChanges(Vec::new()), + charts: Vec::new(), + }; + + loop { + let length = cursor.read_i32::()? as usize; + trace!("Found chunk (length {})", length); + if length == 0 { + break; + } + + let chunk_type = cursor.read_u16::()?; + let parameter = cursor.read_u16::()?; + + // length without i32 and 2 × i16 + let mut data = vec![0; length - 8]; + cursor.read_exact(&mut data)?; + + match chunk_type { + 1 => { + debug!("Parsing tempo changes (ticks/s: {})", parameter); + ssq.tempo_changes = TempoChanges::parse(parameter as i32, &data)?; + } + 3 => { + debug!("Parsing step chunk ({})", Difficulty::from(parameter)); + ssq.charts.push(Chart::parse(&data, parameter)?) + } + _ => { + debug!( + "Found extra chunk (type {}, length {})", + chunk_type, + data.len() + ); + } + }; + } + + info!("Parsed {} charts", ssq.charts.len()); + + Ok(ssq) + } +} diff --git a/src/lib.rs b/src/lib.rs index ba7f2ae..1ac793a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ pub mod converter; pub mod ddr; +mod mini_parser; pub mod osu; mod utils; pub mod xact3; diff --git a/src/mini_parser.rs b/src/mini_parser.rs new file mode 100644 index 0000000..e4891e6 --- /dev/null +++ b/src/mini_parser.rs @@ -0,0 +1,53 @@ +use std::convert::TryInto; +use std::io; +use std::io::prelude::*; +use std::num; +use std::ops::Range; + +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum MiniParserError { + #[error(transparent)] + TryFromIntError(#[from] num::TryFromIntError), + #[error(transparent)] + IOError(#[from] io::Error), +} + +/// Provides convenience methods for parsing binary formats. +pub trait MiniParser: io::Read { + /// Read a `String` of length `length` and strip NUL bytes. + #[inline] + fn read_string(&mut self, length: usize) -> Result { + let mut buf = String::new(); + self.take(length.try_into()?).read_to_string(&mut buf)?; + Ok(buf.replace("\0", "")) + } + + /// Read `n` `u32`. + #[inline] + fn read_n_u32(&mut self, n: usize) -> Result, MiniParserError> { + let mut buf = vec![0; 4 * n]; + self.read_exact(&mut buf)?; + Ok(buf + .chunks_exact(4) + .map(|x| x.try_into().unwrap()) // chunks are guarenteed to be of size 4 + .map(i32::from_le_bytes) + .collect::>()) + } +} + +/// Implement MiniParser for all io::Read implementors. +impl MiniParser for R {} + +/// Gets the requested `range` from `slice` and errors with `UnexpectedEof` when range does not fit +/// in slice. +pub fn get_slice_range(slice: &[u8], range: Range) -> Result<&[u8], MiniParserError> { + slice.get(range).ok_or_else(|| { + io::Error::new( + io::ErrorKind::UnexpectedEof, + "File ended while there was data left to process", + ) + .into() + }) +} diff --git a/src/utils.rs b/src/utils.rs index 4597ab1..4c308e2 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,3 @@ -use anyhow::{anyhow, Result}; -use log::debug; - pub fn get_nth_bit(byte: u8, n: u8) -> bool { ((byte & (0b1 << n)) >> n) != 0 } @@ -12,28 +9,3 @@ pub fn byte_to_bitarray(byte: u8) -> [bool; 8] { } bitarray } - -pub fn offset_length_to_start_end(offset: usize, length: usize) -> (usize, usize) { - (offset, offset + length) -} - -// This probably isn’t the right way to do this, but after countless attempts to implement -// error conversion (IResult to anyhow::Result) it was the only thing I could come up with. -pub fn exec_nom_parser<'a, F, R>(func: F, input: &'a [u8]) -> Result -where - F: Fn(&'a [u8]) -> nom::IResult<&[u8], R>, -{ - match func(input) { - Ok((unprocessed, result)) => { - if !unprocessed.is_empty() { - debug!( - "Parser returned {} bytes of unprocessed input: {:?}", - unprocessed.len(), - unprocessed - ); - } - Ok(result) - } - Err(error) => Err(anyhow!("Nom returned error: {}", error)), - } -} diff --git a/src/xact3/xwb.rs b/src/xact3/xwb.rs index 70d6d88..cedcebf 100644 --- a/src/xact3/xwb.rs +++ b/src/xact3/xwb.rs @@ -1,25 +1,28 @@ use std::collections::HashMap; use std::convert::TryInto; +use std::io; +use std::io::Cursor; -use anyhow::Result; -use log::{debug, info, warn}; -use nom::bytes::complete::tag; -use nom::error::ParseError; -use nom::multi::count; -use nom::number::complete::{le_i32, le_u32}; -use nom::{take_str, IResult}; +use byteorder::{ReadBytesExt, LE}; +use log::{debug, info, trace, warn}; use num_derive::FromPrimitive; use num_traits::FromPrimitive; use thiserror::Error; -use crate::utils; -use crate::utils::exec_nom_parser; +use crate::mini_parser; +use crate::mini_parser::{MiniParser, MiniParserError}; use crate::xact3::adpcm; #[derive(Error, Debug)] pub enum Error { #[error("{0:?} is not a supported format")] UnsupportedFormat(FormatTag), + #[error("Invalid magic: expected “WBND”, found “{0}”")] + InvalidMagic(String), + #[error(transparent)] + IOError(#[from] io::Error), + #[error(transparent)] + MiniParserError(#[from] MiniParserError), } #[derive(Clone, FromPrimitive, Debug, PartialEq)] @@ -41,7 +44,7 @@ struct Format { impl From for Format { fn from(format: u32) -> Self { Self { - tag: FormatTag::from_u32(format & ((1 << 2) - 1)).unwrap(), + tag: FormatTag::from_u32(format & ((1 << 2) - 1)).unwrap(), // all 2 bit ints covered channels: ((format >> 2) & ((1 << 3) - 1)) as u16, sample_rate: (format >> 5) & ((1 << 18) - 1), alignment: ((format >> 23) & ((1 << 8) - 1)) as u8, @@ -75,21 +78,13 @@ impl TryInto for Format { #[derive(Debug)] struct SegmentPosition { - start: usize, - end: usize, + offset: usize, + length: usize, } impl SegmentPosition { - fn get_from<'a>(&self, data: &'a [u8]) -> &'a [u8] { - &data[self.start..self.end] - } - - fn parse<'a, E: ParseError<&'a [u8]>>(input: &'a [u8]) -> IResult<&[u8], Self, E> { - let (input, offset) = le_u32(input)?; - let (input, length) = le_u32(input)?; - - let (start, end) = utils::offset_length_to_start_end(offset as usize, length as usize); - Ok((input, Self { start, end })) + fn get_from<'a>(&self, data: &'a [u8]) -> Result<&'a [u8], MiniParserError> { + mini_parser::get_slice_range(data, self.offset..self.offset + self.length) } } @@ -98,20 +93,31 @@ struct Header { } impl Header { - fn parse(input: &[u8]) -> IResult<&[u8], Self> { - let (input, _magic) = tag("WBND")(input)?; - let (input, version) = le_u32(input)?; + fn parse(data: &[u8]) -> Result { + let mut cursor = Cursor::new(data); + + let magic = cursor.read_string(4)?; + if magic != "WBND" { + return Err(Error::InvalidMagic(magic)); + } + + let version = cursor.read_u32::()?; debug!("Recognised file (version {})", version); if version != 43 { warn!("The provided file has an unsupported version ({})", version); } - let (input, _header_version) = le_u32(input)?; - let (_input, segment_positions) = count(SegmentPosition::parse, 5)(input)?; - Ok(( - // difference between first segment and parsed bytes of header - &input[8 * 5 + 12..segment_positions[0].start], - Self { segment_positions }, - )) + + let _header_version = cursor.read_u32::()?; + let mut segment_positions = Vec::new(); + for _ in 0..5 { + let offset = cursor.read_u32::()?; + let length = cursor.read_u32::()?; + segment_positions.push(SegmentPosition { + offset: offset as usize, + length: length as usize, + }) + } + Ok(Header { segment_positions }) } } @@ -119,26 +125,30 @@ impl Header { struct Info { entry_count: usize, name: String, + entry_name_element_size: usize, } impl Info { - fn parse(input: &[u8]) -> IResult<&[u8], Self> { - let (input, _flags) = le_u32(input)?; - let (input, entry_count) = le_u32(input)?; - let (input, name) = take_str64(input)?; - let (input, _entry_meta_data_element_size) = le_u32(input)?; - let (input, _entry_name_element_size) = le_u32(input)?; - let (input, _alignment) = le_u32(input)?; - let (input, _compact_format) = le_i32(input)?; - let (input, _build_time) = le_u32(input)?; + fn parse(data: &[u8]) -> Result { + let mut cursor = Cursor::new(data); - Ok(( - input, - Self { - entry_count: entry_count as usize, - name, - }, - )) + let _flags = cursor.read_u32::()?; + let entry_count = cursor.read_u32::()?; + debug!("Number of entries: {}", entry_count); + let name = cursor.read_string(64)?; + debug!("Name of wave bank: {}", name); + let _entry_meta_data_element_size = cursor.read_u32::()?; + let entry_name_element_size = cursor.read_u32::()?; + debug!("Size of entry names: {}", entry_name_element_size); + let _alignment = cursor.read_u32::()?; + let _compact_format = cursor.read_u32::()?; + let _build_time = cursor.read_u32::()?; + + Ok(Self { + entry_count: entry_count as usize, + name, + entry_name_element_size: entry_name_element_size as usize, + }) } } @@ -151,35 +161,32 @@ struct Entry { } impl Entry { - fn parse(input: &[u8]) -> IResult<&[u8], Self> { - let (input, _flags_and_duration) = le_u32(input)?; - let (input, format) = le_u32(input)?; - let (input, data_offset) = le_u32(input)?; - let (input, data_length) = le_u32(input)?; - let (input, _loop_start) = le_u32(input)?; - let (input, _loop_length) = le_u32(input)?; + fn parse(data: &[u8]) -> Result { + let mut cursor = Cursor::new(data); - Ok(( - input, - Self { - name: "".to_string(), - format: format.into(), - data_offset: data_offset as usize, - data_length: data_length as usize, - }, - )) + let _flags_and_duration = cursor.read_u32::()?; + let format = cursor.read_u32::()?; + let data_offset = cursor.read_u32::()?; + let data_length = cursor.read_u32::()?; + let _loop_start = cursor.read_u32::()?; + let _loop_length = cursor.read_u32::()?; + + trace!( + "Parsed Entry with Format {:?} at offset {} (length {})", + Format::from(format), + data_offset, + data_length + ); + + Ok(Self { + name: "".to_string(), + format: format.into(), + data_offset: data_offset as usize, + data_length: data_length as usize, + }) } } -fn take_str(input: &[u8], len: usize) -> IResult<&[u8], String> { - let (input, parsed) = take_str!(input, len)?; - Ok((input, parsed.replace("\0", ""))) -} - -fn take_str64(input: &[u8]) -> IResult<&[u8], String> { - take_str(input, 64) -} - #[derive(Debug, Clone)] pub struct WaveBank<'a> { pub name: String, @@ -187,43 +194,56 @@ pub struct WaveBank<'a> { } impl WaveBank<'_> { - pub fn parse(data: &'_ [u8]) -> Result { + pub fn parse(data: &'_ [u8]) -> Result { debug!("Parsing header"); - let header = exec_nom_parser(Header::parse, data)?; + let header = Header::parse(mini_parser::get_slice_range(data, 0..52)?)?; debug!("Getting segments from file"); let segments: Vec<&'_ [u8]> = header .segment_positions .iter() - .map(|x| x.get_from(data)) - .collect(); + .map(|segment| segment.get_from(data)) + .collect::>()?; debug!("Parsing info (length {})", segments[0].len()); - let info = exec_nom_parser(Info::parse, segments[0])?; + let info = Info::parse(segments[0])?; + debug!("Parsing entries (length {})", segments[1].len()); - let entries = exec_nom_parser(count(Entry::parse, info.entry_count as usize), segments[1])?; + let mut entries: Vec = segments[1] + .chunks_exact(24) + .map(Entry::parse) + .collect::>()?; + debug!("Parsing entry names (length {})", segments[3].len()); - let entry_names = if segments[3].len() == info.entry_count as usize * 64 { - exec_nom_parser(count(take_str64, info.entry_count as usize), segments[3])? - } else { - warn!("Wave bank does not have name entries"); - (0..info.entry_count).map(|x| x.to_string()).collect() - }; + let entry_names: Vec = segments[3] + .chunks_exact(info.entry_name_element_size) + .map(String::from_utf8_lossy) + .map(|name| name.into_owned()) + .collect(); + + for (i, entry) in entries.iter_mut().enumerate() { + entry.name = entry_names + .get(i) + .map(|name| name.to_string()) + .unwrap_or_else(|| { + warn!("Entry does not have name; naming after index {}", i); + i.to_string() + }); + } let mut wave_bank = WaveBank { name: info.name, sounds: HashMap::new(), }; - for (entry, name) in entries.iter().zip(entry_names.iter()) { - let (start, end) = - utils::offset_length_to_start_end(entry.data_offset, entry.data_length); + for entry in entries.iter() { + let end = entry.data_offset + entry.data_length; wave_bank.sounds.insert( - name.replace("\0", "").to_string(), + entry.name.replace("\0", "").to_string(), Sound { format: entry.format.clone(), - data: &segments[4][start..end], - size: end - start, + data: mini_parser::get_slice_range(segments[4], entry.data_offset..end)?, + size: entry.data_length, }, ); } @@ -242,10 +262,10 @@ pub struct Sound<'a> { } impl Sound<'_> { - pub fn to_wav(&self) -> Result> { + pub fn to_wav(&self) -> Result, Error> { match &self.format.tag { FormatTag::ADPCM => Ok(adpcm::build_wav(self.format.clone().try_into()?, self.data)), - _ => Err(Error::UnsupportedFormat(self.format.tag.clone()).into()), + _ => Err(Error::UnsupportedFormat(self.format.tag.clone())), } } }