Switch from nom to custom parser
The reason for the switch is mostly related to error handling (and me being unable to unterstand how nom does it). Using byteorder and a few handwritten helper functions for parsing makes error handling much easier. It also allows me to return more helpful error messages.
This commit is contained in:
parent
2b0b66ddca
commit
5a77d86fe7
43
Cargo.lock
generated
43
Cargo.lock
generated
|
@ -21,12 +21,6 @@ version = "1.0.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f"
|
checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "arrayvec"
|
|
||||||
version = "0.5.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atty"
|
name = "atty"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
|
@ -58,7 +52,6 @@ dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"clap",
|
"clap",
|
||||||
"log",
|
"log",
|
||||||
"nom",
|
|
||||||
"num-derive",
|
"num-derive",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
|
@ -186,19 +179,6 @@ version = "1.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
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]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.71"
|
version = "0.2.71"
|
||||||
|
@ -229,17 +209,6 @@ dependencies = [
|
||||||
"adler32",
|
"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]]
|
[[package]]
|
||||||
name = "num-derive"
|
name = "num-derive"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
@ -350,18 +319,6 @@ version = "0.6.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
|
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]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
|
|
|
@ -9,7 +9,6 @@ anyhow = "^1.0.31"
|
||||||
byteorder = "^1.3.4"
|
byteorder = "^1.3.4"
|
||||||
clap = "3.0.0-beta.1"
|
clap = "3.0.0-beta.1"
|
||||||
log = "^0.4.8"
|
log = "^0.4.8"
|
||||||
nom = "^5.1.1"
|
|
||||||
num-derive = "^0.3"
|
num-derive = "^0.3"
|
||||||
num-traits = "^0.2"
|
num-traits = "^0.2"
|
||||||
pretty_env_logger = "^0.4"
|
pretty_env_logger = "^0.4"
|
||||||
|
|
|
@ -385,7 +385,7 @@ impl ssq::SSQ {
|
||||||
|
|
||||||
let mut shock_step_generator =
|
let mut shock_step_generator =
|
||||||
ShockStepGenerator::new(chart.difficulty.players * 4, config.shock_action.clone());
|
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);
|
trace!("Converting {:?} to hit object", step);
|
||||||
if let Some(mut step_hit_objects) = step.to_hit_objects(
|
if let Some(mut step_hit_objects) = step.to_hit_objects(
|
||||||
chart.difficulty.players * 4,
|
chart.difficulty.players * 4,
|
||||||
|
|
610
src/ddr/ssq.rs
610
src/ddr/ssq.rs
|
@ -1,331 +1,38 @@
|
||||||
use std::convert::From;
|
use std::convert::From;
|
||||||
|
use std::convert::TryInto;
|
||||||
use std::fmt;
|
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 log::{debug, info, trace, warn};
|
||||||
use nom::bytes::complete::take;
|
use thiserror::Error;
|
||||||
use nom::multi::many0;
|
|
||||||
use nom::number::complete::{le_i16, le_i32, le_u16};
|
|
||||||
use nom::IResult;
|
|
||||||
|
|
||||||
|
use crate::mini_parser::{MiniParser, MiniParserError};
|
||||||
use crate::utils;
|
use crate::utils;
|
||||||
use crate::utils::exec_nom_parser;
|
|
||||||
|
|
||||||
const MEASURE_LENGTH: i32 = 4096;
|
const MEASURE_LENGTH: i32 = 4096;
|
||||||
const FREEZE: bool = false;
|
const FREEZE: bool = false;
|
||||||
|
|
||||||
// Convert time offset to beats
|
#[derive(Error, Debug)]
|
||||||
// time offset is the measure times MEASURE_LENGTH
|
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 {
|
fn measure_to_beats(metric: i32) -> f32 {
|
||||||
4.0 * metric as f32 / MEASURE_LENGTH as f32
|
4.0 * metric as f32 / MEASURE_LENGTH as f32
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_n_i32(n: usize, input: &[u8]) -> IResult<&[u8], Vec<i32>> {
|
|
||||||
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<TempoChange>);
|
|
||||||
|
|
||||||
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<Step>);
|
|
||||||
|
|
||||||
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<usize> {
|
|
||||||
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<u16> for Difficulty {
|
|
||||||
fn from(parameter: u16) -> Self {
|
|
||||||
Self {
|
|
||||||
difficulty: ((parameter & 0xFF00) >> 8) as u8,
|
|
||||||
players: (parameter & 0xF) as u8 / 4,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<f32> 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<Chart>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Chunks> 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<Self> {
|
|
||||||
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<u8>),
|
|
||||||
}
|
|
||||||
|
|
||||||
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<Chunk>);
|
|
||||||
|
|
||||||
impl Chunks {
|
|
||||||
fn parse(input: &[u8]) -> IResult<&[u8], Self> {
|
|
||||||
let (input, chunks) = many0(Chunk::parse)(input)?;
|
|
||||||
Ok((input, Self(chunks)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct PlayerRow {
|
pub struct PlayerRow {
|
||||||
pub left: bool,
|
pub left: bool,
|
||||||
|
@ -420,3 +127,278 @@ impl Row {
|
||||||
false
|
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<TempoChange>);
|
||||||
|
|
||||||
|
impl TempoChanges {
|
||||||
|
fn parse(ticks_per_second: i32, data: &[u8]) -> Result<Self, Error> {
|
||||||
|
let mut cursor = Cursor::new(data);
|
||||||
|
|
||||||
|
let count = cursor.read_u32::<LE>()?.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<Step>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Chart {
|
||||||
|
fn parse(data: &[u8], parameter: u16) -> Result<Self, Error> {
|
||||||
|
let difficulty: Difficulty = parameter.into();
|
||||||
|
|
||||||
|
let mut cursor = Cursor::new(data);
|
||||||
|
|
||||||
|
let count = cursor.read_u32::<LE>()?.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<Step>, row: &Row) -> Option<usize> {
|
||||||
|
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<u16> for Difficulty {
|
||||||
|
fn from(parameter: u16) -> Self {
|
||||||
|
Self {
|
||||||
|
difficulty: ((parameter & 0xFF00) >> 8) as u8,
|
||||||
|
players: (parameter & 0xF) as u8 / 4,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<f32> 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<Chart>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SSQ {
|
||||||
|
pub fn parse(data: &[u8]) -> Result<Self, Error> {
|
||||||
|
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::<LE>()? as usize;
|
||||||
|
trace!("Found chunk (length {})", length);
|
||||||
|
if length == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let chunk_type = cursor.read_u16::<LE>()?;
|
||||||
|
let parameter = cursor.read_u16::<LE>()?;
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
pub mod converter;
|
pub mod converter;
|
||||||
pub mod ddr;
|
pub mod ddr;
|
||||||
|
mod mini_parser;
|
||||||
pub mod osu;
|
pub mod osu;
|
||||||
mod utils;
|
mod utils;
|
||||||
pub mod xact3;
|
pub mod xact3;
|
||||||
|
|
53
src/mini_parser.rs
Normal file
53
src/mini_parser.rs
Normal file
|
@ -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<String, MiniParserError> {
|
||||||
|
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<Vec<i32>, 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::<Vec<i32>>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implement MiniParser for all io::Read implementors.
|
||||||
|
impl<R: io::Read + ?Sized> 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<usize>) -> 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()
|
||||||
|
})
|
||||||
|
}
|
28
src/utils.rs
28
src/utils.rs
|
@ -1,6 +1,3 @@
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use log::debug;
|
|
||||||
|
|
||||||
pub fn get_nth_bit(byte: u8, n: u8) -> bool {
|
pub fn get_nth_bit(byte: u8, n: u8) -> bool {
|
||||||
((byte & (0b1 << n)) >> n) != 0
|
((byte & (0b1 << n)) >> n) != 0
|
||||||
}
|
}
|
||||||
|
@ -12,28 +9,3 @@ pub fn byte_to_bitarray(byte: u8) -> [bool; 8] {
|
||||||
}
|
}
|
||||||
bitarray
|
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<R>
|
|
||||||
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)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
194
src/xact3/xwb.rs
194
src/xact3/xwb.rs
|
@ -1,25 +1,28 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
use std::io;
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
use anyhow::Result;
|
use byteorder::{ReadBytesExt, LE};
|
||||||
use log::{debug, info, warn};
|
use log::{debug, info, trace, 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 num_derive::FromPrimitive;
|
use num_derive::FromPrimitive;
|
||||||
use num_traits::FromPrimitive;
|
use num_traits::FromPrimitive;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::utils;
|
use crate::mini_parser;
|
||||||
use crate::utils::exec_nom_parser;
|
use crate::mini_parser::{MiniParser, MiniParserError};
|
||||||
use crate::xact3::adpcm;
|
use crate::xact3::adpcm;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("{0:?} is not a supported format")]
|
#[error("{0:?} is not a supported format")]
|
||||||
UnsupportedFormat(FormatTag),
|
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)]
|
#[derive(Clone, FromPrimitive, Debug, PartialEq)]
|
||||||
|
@ -41,7 +44,7 @@ struct Format {
|
||||||
impl From<u32> for Format {
|
impl From<u32> for Format {
|
||||||
fn from(format: u32) -> Self {
|
fn from(format: u32) -> Self {
|
||||||
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,
|
channels: ((format >> 2) & ((1 << 3) - 1)) as u16,
|
||||||
sample_rate: (format >> 5) & ((1 << 18) - 1),
|
sample_rate: (format >> 5) & ((1 << 18) - 1),
|
||||||
alignment: ((format >> 23) & ((1 << 8) - 1)) as u8,
|
alignment: ((format >> 23) & ((1 << 8) - 1)) as u8,
|
||||||
|
@ -75,21 +78,13 @@ impl TryInto<adpcm::WaveFormat> for Format {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct SegmentPosition {
|
struct SegmentPosition {
|
||||||
start: usize,
|
offset: usize,
|
||||||
end: usize,
|
length: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SegmentPosition {
|
impl SegmentPosition {
|
||||||
fn get_from<'a>(&self, data: &'a [u8]) -> &'a [u8] {
|
fn get_from<'a>(&self, data: &'a [u8]) -> Result<&'a [u8], MiniParserError> {
|
||||||
&data[self.start..self.end]
|
mini_parser::get_slice_range(data, self.offset..self.offset + self.length)
|
||||||
}
|
|
||||||
|
|
||||||
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 }))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,20 +93,31 @@ struct Header {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Header {
|
impl Header {
|
||||||
fn parse(input: &[u8]) -> IResult<&[u8], Self> {
|
fn parse(data: &[u8]) -> Result<Self, Error> {
|
||||||
let (input, _magic) = tag("WBND")(input)?;
|
let mut cursor = Cursor::new(data);
|
||||||
let (input, version) = le_u32(input)?;
|
|
||||||
|
let magic = cursor.read_string(4)?;
|
||||||
|
if magic != "WBND" {
|
||||||
|
return Err(Error::InvalidMagic(magic));
|
||||||
|
}
|
||||||
|
|
||||||
|
let version = cursor.read_u32::<LE>()?;
|
||||||
debug!("Recognised file (version {})", version);
|
debug!("Recognised file (version {})", version);
|
||||||
if version != 43 {
|
if version != 43 {
|
||||||
warn!("The provided file has an unsupported version ({})", version);
|
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)?;
|
let _header_version = cursor.read_u32::<LE>()?;
|
||||||
Ok((
|
let mut segment_positions = Vec::new();
|
||||||
// difference between first segment and parsed bytes of header
|
for _ in 0..5 {
|
||||||
&input[8 * 5 + 12..segment_positions[0].start],
|
let offset = cursor.read_u32::<LE>()?;
|
||||||
Self { segment_positions },
|
let length = cursor.read_u32::<LE>()?;
|
||||||
))
|
segment_positions.push(SegmentPosition {
|
||||||
|
offset: offset as usize,
|
||||||
|
length: length as usize,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Ok(Header { segment_positions })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,26 +125,30 @@ impl Header {
|
||||||
struct Info {
|
struct Info {
|
||||||
entry_count: usize,
|
entry_count: usize,
|
||||||
name: String,
|
name: String,
|
||||||
|
entry_name_element_size: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Info {
|
impl Info {
|
||||||
fn parse(input: &[u8]) -> IResult<&[u8], Self> {
|
fn parse(data: &[u8]) -> Result<Self, Error> {
|
||||||
let (input, _flags) = le_u32(input)?;
|
let mut cursor = Cursor::new(data);
|
||||||
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)?;
|
|
||||||
|
|
||||||
Ok((
|
let _flags = cursor.read_u32::<LE>()?;
|
||||||
input,
|
let entry_count = cursor.read_u32::<LE>()?;
|
||||||
Self {
|
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::<LE>()?;
|
||||||
|
let entry_name_element_size = cursor.read_u32::<LE>()?;
|
||||||
|
debug!("Size of entry names: {}", entry_name_element_size);
|
||||||
|
let _alignment = cursor.read_u32::<LE>()?;
|
||||||
|
let _compact_format = cursor.read_u32::<LE>()?;
|
||||||
|
let _build_time = cursor.read_u32::<LE>()?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
entry_count: entry_count as usize,
|
entry_count: entry_count as usize,
|
||||||
name,
|
name,
|
||||||
},
|
entry_name_element_size: entry_name_element_size as usize,
|
||||||
))
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,35 +161,32 @@ struct Entry {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entry {
|
impl Entry {
|
||||||
fn parse(input: &[u8]) -> IResult<&[u8], Self> {
|
fn parse(data: &[u8]) -> Result<Self, Error> {
|
||||||
let (input, _flags_and_duration) = le_u32(input)?;
|
let mut cursor = Cursor::new(data);
|
||||||
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)?;
|
|
||||||
|
|
||||||
Ok((
|
let _flags_and_duration = cursor.read_u32::<LE>()?;
|
||||||
input,
|
let format = cursor.read_u32::<LE>()?;
|
||||||
Self {
|
let data_offset = cursor.read_u32::<LE>()?;
|
||||||
|
let data_length = cursor.read_u32::<LE>()?;
|
||||||
|
let _loop_start = cursor.read_u32::<LE>()?;
|
||||||
|
let _loop_length = cursor.read_u32::<LE>()?;
|
||||||
|
|
||||||
|
trace!(
|
||||||
|
"Parsed Entry with Format {:?} at offset {} (length {})",
|
||||||
|
Format::from(format),
|
||||||
|
data_offset,
|
||||||
|
data_length
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
name: "".to_string(),
|
name: "".to_string(),
|
||||||
format: format.into(),
|
format: format.into(),
|
||||||
data_offset: data_offset as usize,
|
data_offset: data_offset as usize,
|
||||||
data_length: data_length 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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct WaveBank<'a> {
|
pub struct WaveBank<'a> {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -187,43 +194,56 @@ pub struct WaveBank<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WaveBank<'_> {
|
impl WaveBank<'_> {
|
||||||
pub fn parse(data: &'_ [u8]) -> Result<WaveBank> {
|
pub fn parse(data: &'_ [u8]) -> Result<WaveBank, Error> {
|
||||||
debug!("Parsing header");
|
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");
|
debug!("Getting segments from file");
|
||||||
let segments: Vec<&'_ [u8]> = header
|
let segments: Vec<&'_ [u8]> = header
|
||||||
.segment_positions
|
.segment_positions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| x.get_from(data))
|
.map(|segment| segment.get_from(data))
|
||||||
.collect();
|
.collect::<Result<_, _>>()?;
|
||||||
|
|
||||||
debug!("Parsing info (length {})", segments[0].len());
|
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());
|
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<Entry> = segments[1]
|
||||||
|
.chunks_exact(24)
|
||||||
|
.map(Entry::parse)
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
|
|
||||||
debug!("Parsing entry names (length {})", segments[3].len());
|
debug!("Parsing entry names (length {})", segments[3].len());
|
||||||
let entry_names = if segments[3].len() == info.entry_count as usize * 64 {
|
let entry_names: Vec<String> = segments[3]
|
||||||
exec_nom_parser(count(take_str64, info.entry_count as usize), segments[3])?
|
.chunks_exact(info.entry_name_element_size)
|
||||||
} else {
|
.map(String::from_utf8_lossy)
|
||||||
warn!("Wave bank does not have name entries");
|
.map(|name| name.into_owned())
|
||||||
(0..info.entry_count).map(|x| x.to_string()).collect()
|
.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 {
|
let mut wave_bank = WaveBank {
|
||||||
name: info.name,
|
name: info.name,
|
||||||
sounds: HashMap::new(),
|
sounds: HashMap::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
for (entry, name) in entries.iter().zip(entry_names.iter()) {
|
for entry in entries.iter() {
|
||||||
let (start, end) =
|
let end = entry.data_offset + entry.data_length;
|
||||||
utils::offset_length_to_start_end(entry.data_offset, entry.data_length);
|
|
||||||
wave_bank.sounds.insert(
|
wave_bank.sounds.insert(
|
||||||
name.replace("\0", "").to_string(),
|
entry.name.replace("\0", "").to_string(),
|
||||||
Sound {
|
Sound {
|
||||||
format: entry.format.clone(),
|
format: entry.format.clone(),
|
||||||
data: &segments[4][start..end],
|
data: mini_parser::get_slice_range(segments[4], entry.data_offset..end)?,
|
||||||
size: end - start,
|
size: entry.data_length,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -242,10 +262,10 @@ pub struct Sound<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sound<'_> {
|
impl Sound<'_> {
|
||||||
pub fn to_wav(&self) -> Result<Vec<u8>> {
|
pub fn to_wav(&self) -> Result<Vec<u8>, Error> {
|
||||||
match &self.format.tag {
|
match &self.format.tag {
|
||||||
FormatTag::ADPCM => Ok(adpcm::build_wav(self.format.clone().try_into()?, self.data)),
|
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())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue