use std::io::{Read, Seek};
use binrw::prelude::*;
use super::*;
use crate::{Error, Result};
impl ParameterIO {
pub fn read<R: Read + Seek>(reader: R) -> Result<ParameterIO> {
Parser::new(reader)?.parse()
}
pub fn from_binary(data: impl AsRef<[u8]>) -> Result<ParameterIO> {
#[cfg(feature = "yaz0")]
{
if data.as_ref().starts_with(b"Yaz0") {
return Parser::new(std::io::Cursor::new(crate::yaz0::decompress(
data.as_ref(),
)?))?
.parse();
}
}
Parser::new(std::io::Cursor::new(data.as_ref()))?.parse()
}
}
struct Parser<R: Read + Seek> {
reader: R,
header: ResHeader,
opts: binrw::ReadOptions,
}
impl<R: Read + Seek> Parser<R> {
fn new(mut reader: R) -> Result<Self> {
if reader.stream_len()? < 0x30 {
return Err(Error::InvalidData("Incomplete parameter archive"));
}
let header = ResHeader::read(&mut reader)?;
if header.version != 2 {
return Err(Error::InvalidData(
"Only version 2 parameter archives are supported",
));
}
if header.flags & 1 << 0 != 1 << 0 {
return Err(Error::InvalidData(
"Only little endian parameter archives are supported",
));
}
if header.flags & 1 << 1 != 1 << 1 {
return Err(Error::InvalidData(
"Only UTF-8 parameter archives are supported",
));
}
Ok(Self {
reader,
header,
opts: binrw::ReadOptions::default().with_endian(binrw::Endian::Little),
})
}
fn parse(&mut self) -> Result<ParameterIO> {
let (root_name, param_root) = self.parse_list(self.header.pio_offset + 0x30)?;
if root_name != ROOT_KEY {
Err(Error::InvalidData(
"No param root found in parameter archive",
))
} else {
Ok(ParameterIO {
version: self.header.pio_version,
data_type: {
self.seek(0x30)?;
self.read_null_string()?
},
param_root,
})
}
}
#[inline]
fn seek(&mut self, offset: u32) -> Result<()> {
self.reader.seek(std::io::SeekFrom::Start(offset as u64))?;
Ok(())
}
#[inline]
fn read<T: BinRead<Args = ()>>(&mut self) -> Result<T> {
Ok(self.reader.read_le()?)
}
#[inline]
fn read_null_string(&mut self) -> Result<String> {
let mut string_ = [0u8; 0x256];
let mut c: u8 = self.read()?;
let mut len = 0;
while c != 0 {
string_[len] = c;
len += 1;
c = self.read()?;
}
Ok(unsafe { std::str::from_utf8_unchecked(&string_[..len]) }.into())
}
#[inline]
fn read_at<T: BinRead<Args = ()>>(&mut self, offset: u32) -> Result<T> {
let old_pos = self.reader.stream_position()? as u32;
self.seek(offset)?;
let val = self.read()?;
self.seek(old_pos)?;
Ok(val)
}
fn read_buffer<T: BinRead<Args = ()> + Copy>(&mut self, offset: u32) -> Result<Vec<T>> {
let size = self.read_at::<u32>(offset - 4)?;
let buf = Vec::<T>::read_options(
&mut self.reader,
&self.opts,
binrw::VecArgs::builder().count(size as usize).finalize(),
)?;
Ok(buf)
}
#[inline]
fn read_float_buffer(&mut self, offset: u32) -> Result<Vec<f32>> {
let size = self.read_at::<u32>(offset - 4)?;
let mut buf = Vec::<f32>::with_capacity(size as usize);
for _ in 0..size {
buf.push(self.read()?);
}
Ok(buf)
}
fn parse_parameter(&mut self, offset: u32) -> Result<(Name, Parameter)> {
self.seek(offset)?;
let info: ResParameter = self.read()?;
let data_offset = info.data_rel_offset.as_u32() * 4 + offset;
self.seek(data_offset)?;
let value = match info.type_ {
Type::Bool => Parameter::Bool(self.read::<u32>()? != 0),
Type::F32 => Parameter::F32(self.read::<f32>()?),
Type::Int => Parameter::I32(self.read()?),
Type::Vec2 => Parameter::Vec2(self.read()?),
Type::Vec3 => Parameter::Vec3(self.read()?),
Type::Vec4 => Parameter::Vec4(self.read()?),
Type::Quat => Parameter::Quat(self.read()?),
Type::Color => Parameter::Color(self.read()?),
Type::U32 => Parameter::U32(self.read()?),
Type::Curve1 => Parameter::Curve1(self.read()?),
Type::Curve2 => Parameter::Curve2(self.read()?),
Type::Curve3 => Parameter::Curve3(self.read()?),
Type::Curve4 => Parameter::Curve4(self.read()?),
Type::String32 => Parameter::String32(self.read()?),
Type::String64 => Parameter::String64(self.read()?),
Type::String256 => Parameter::String256(self.read()?),
Type::StringRef => Parameter::StringRef(self.read_null_string()?),
Type::BufferInt => Parameter::BufferInt(self.read_buffer::<i32>(data_offset)?),
Type::BufferU32 => Parameter::BufferU32(self.read_buffer::<u32>(data_offset)?),
Type::BufferF32 => Parameter::BufferF32(self.read_float_buffer(offset)?),
Type::BufferBinary => Parameter::BufferBinary(self.read_buffer::<u8>(data_offset)?),
};
Ok((info.name, value))
}
fn parse_object(&mut self, offset: u32) -> Result<(Name, ParameterObject)> {
self.seek(offset)?;
let info: ResParameterObj = self.read()?;
let offset = info.params_rel_offset as u32 * 4 + offset;
let params = (0..info.param_count)
.into_iter()
.map(|i| self.parse_parameter(offset + 0x8 * i as u32))
.collect::<Result<_>>()?;
Ok((info.name, params))
}
fn parse_list(&mut self, offset: u32) -> Result<(Name, ParameterList)> {
self.seek(offset)?;
let info: ResParameterList = self.read()?;
let lists_offset = info.lists_rel_offset as u32 * 4 + offset;
let objects_offset = info.objects_rel_offset as u32 * 4 + offset;
let plist = ParameterList {
lists: (0..info.list_count)
.into_iter()
.map(|i| self.parse_list(lists_offset + 0xC * i as u32))
.collect::<Result<_>>()?,
objects: (0..info.object_count)
.into_iter()
.map(|i| self.parse_object(objects_offset + 0x8 * i as u32))
.collect::<Result<_>>()?,
};
Ok((info.name, plist))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse() {
for file in jwalk::WalkDir::new("test/aamp")
.into_iter()
.filter_map(|f| {
f.ok().and_then(|f| {
(f.file_type().is_file() && !f.file_name().to_str().unwrap().ends_with("yml"))
.then(|| f.path())
})
})
{
println!("{}", file.display());
let data = std::fs::read(&file).unwrap();
ParameterIO::from_binary(data).unwrap();
}
}
}