This repository has been archived on 2024-01-28. You can view files and clone it, but cannot push or open issues or pull requests.
brd/src/xact3/xwb.rs
Simon Bruder 5a77d86fe7
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.
2020-06-26 13:25:21 +02:00

272 lines
7.7 KiB
Rust

use std::collections::HashMap;
use std::convert::TryInto;
use std::io;
use std::io::Cursor;
use byteorder::{ReadBytesExt, LE};
use log::{debug, info, trace, warn};
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use thiserror::Error;
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)]
pub enum FormatTag {
PCM = 0,
XMA = 1,
ADPCM = 2,
WMA = 3,
}
#[derive(Clone, Debug)]
struct Format {
tag: FormatTag,
channels: u16,
sample_rate: u32,
alignment: u8,
}
impl From<u32> for Format {
fn from(format: u32) -> Self {
Self {
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,
}
}
}
impl TryInto<adpcm::WaveFormat> for Format {
type Error = Error;
fn try_into(self) -> Result<adpcm::WaveFormat, Error> {
if self.tag != FormatTag::ADPCM {
return Err(Error::UnsupportedFormat(self.tag));
}
let n_block_align = (self.alignment as u16 + 22) * self.channels;
let n_samples_per_block =
(((n_block_align - (7 * self.channels)) * 8) / (4 * self.channels)) + 2;
let n_avg_bytes_per_sec =
(self.sample_rate / n_samples_per_block as u32) * n_block_align as u32;
Ok(adpcm::WaveFormat {
n_channels: self.channels,
n_samples_per_sec: self.sample_rate,
n_avg_bytes_per_sec,
n_block_align,
n_samples_per_block,
})
}
}
#[derive(Debug)]
struct SegmentPosition {
offset: usize,
length: usize,
}
impl SegmentPosition {
fn get_from<'a>(&self, data: &'a [u8]) -> Result<&'a [u8], MiniParserError> {
mini_parser::get_slice_range(data, self.offset..self.offset + self.length)
}
}
struct Header {
segment_positions: Vec<SegmentPosition>,
}
impl Header {
fn parse(data: &[u8]) -> Result<Self, Error> {
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::<LE>()?;
debug!("Recognised file (version {})", version);
if version != 43 {
warn!("The provided file has an unsupported version ({})", version);
}
let _header_version = cursor.read_u32::<LE>()?;
let mut segment_positions = Vec::new();
for _ in 0..5 {
let offset = cursor.read_u32::<LE>()?;
let length = cursor.read_u32::<LE>()?;
segment_positions.push(SegmentPosition {
offset: offset as usize,
length: length as usize,
})
}
Ok(Header { segment_positions })
}
}
#[derive(Debug)]
struct Info {
entry_count: usize,
name: String,
entry_name_element_size: usize,
}
impl Info {
fn parse(data: &[u8]) -> Result<Self, Error> {
let mut cursor = Cursor::new(data);
let _flags = cursor.read_u32::<LE>()?;
let entry_count = cursor.read_u32::<LE>()?;
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,
name,
entry_name_element_size: entry_name_element_size as usize,
})
}
}
#[derive(Debug)]
struct Entry {
name: String,
format: Format,
data_offset: usize,
data_length: usize,
}
impl Entry {
fn parse(data: &[u8]) -> Result<Self, Error> {
let mut cursor = Cursor::new(data);
let _flags_and_duration = cursor.read_u32::<LE>()?;
let format = cursor.read_u32::<LE>()?;
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(),
format: format.into(),
data_offset: data_offset as usize,
data_length: data_length as usize,
})
}
}
#[derive(Debug, Clone)]
pub struct WaveBank<'a> {
pub name: String,
pub sounds: HashMap<String, Sound<'a>>,
}
impl WaveBank<'_> {
pub fn parse(data: &'_ [u8]) -> Result<WaveBank, Error> {
debug!("Parsing header");
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(|segment| segment.get_from(data))
.collect::<Result<_, _>>()?;
debug!("Parsing info (length {})", segments[0].len());
let info = Info::parse(segments[0])?;
debug!("Parsing entries (length {})", segments[1].len());
let mut entries: Vec<Entry> = segments[1]
.chunks_exact(24)
.map(Entry::parse)
.collect::<Result<_, _>>()?;
debug!("Parsing entry names (length {})", segments[3].len());
let entry_names: Vec<String> = 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 in entries.iter() {
let end = entry.data_offset + entry.data_length;
wave_bank.sounds.insert(
entry.name.replace("\0", "").to_string(),
Sound {
format: entry.format.clone(),
data: mini_parser::get_slice_range(segments[4], entry.data_offset..end)?,
size: entry.data_length,
},
);
}
info!("Parsed WaveBank with {} sounds", wave_bank.sounds.len());
Ok(wave_bank)
}
}
#[derive(Clone, Debug)]
pub struct Sound<'a> {
format: Format,
data: &'a [u8],
pub size: usize,
}
impl Sound<'_> {
pub fn to_wav(&self) -> Result<Vec<u8>, 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())),
}
}
}