use binrw::{BinRead, BinResult, Endian};
use std::io::Cursor;
use super::SqpackFile;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TargetFileKind {
Dat,
Index,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TargetHeaderKind {
Version,
Index,
Data,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SqpkHeaderTarget {
Dat(SqpackFile),
Index(SqpackFile),
}
fn read_file_kind<R: std::io::Read + std::io::Seek>(
reader: &mut R,
_: Endian,
(): (),
) -> BinResult<TargetFileKind> {
let byte = <u8 as BinRead>::read_options(reader, Endian::Big, ())?;
match byte {
b'D' => Ok(TargetFileKind::Dat),
b'I' => Ok(TargetFileKind::Index),
_ => Err(binrw::Error::Custom {
pos: 0,
err: Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"unknown SqpkHeader file kind",
)),
}),
}
}
fn read_header_kind<R: std::io::Read + std::io::Seek>(
reader: &mut R,
_: Endian,
(): (),
) -> BinResult<TargetHeaderKind> {
let byte = <u8 as BinRead>::read_options(reader, Endian::Big, ())?;
match byte {
b'V' => Ok(TargetHeaderKind::Version),
b'I' => Ok(TargetHeaderKind::Index),
b'D' => Ok(TargetHeaderKind::Data),
_ => Err(binrw::Error::Custom {
pos: 0,
err: Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"unknown SqpkHeader header kind",
)),
}),
}
}
fn read_header_target<R: std::io::Read + std::io::Seek>(
reader: &mut R,
endian: Endian,
(file_kind,): (&TargetFileKind,),
) -> BinResult<SqpkHeaderTarget> {
let f = SqpackFile::read_options(reader, endian, ())?;
match file_kind {
TargetFileKind::Dat => Ok(SqpkHeaderTarget::Dat(f)),
TargetFileKind::Index => Ok(SqpkHeaderTarget::Index(f)),
}
}
#[derive(BinRead, Debug, Clone, PartialEq, Eq)]
#[br(big)]
pub struct SqpkHeader {
#[br(parse_with = read_file_kind)]
pub file_kind: TargetFileKind,
#[br(parse_with = read_header_kind)]
pub header_kind: TargetHeaderKind,
#[br(pad_before = 1, parse_with = read_header_target, args(&file_kind))]
pub target: SqpkHeaderTarget,
#[br(count = 1024)]
pub header_data: Vec<u8>,
}
pub(crate) fn parse(body: &[u8]) -> crate::Result<SqpkHeader> {
Ok(SqpkHeader::read_be(&mut Cursor::new(body))?)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_header_dat_version() {
let mut body = Vec::new();
body.push(b'D'); body.push(b'V'); body.push(0u8); body.extend_from_slice(&10u16.to_be_bytes()); body.extend_from_slice(&20u16.to_be_bytes()); body.extend_from_slice(&0u32.to_be_bytes()); body.extend_from_slice(&[0xCCu8; 1024]);
let cmd = parse(&body).unwrap();
assert!(matches!(cmd.file_kind, TargetFileKind::Dat));
assert!(matches!(cmd.header_kind, TargetHeaderKind::Version));
match cmd.target {
SqpkHeaderTarget::Dat(f) => {
assert_eq!(f.main_id, 10);
assert_eq!(f.sub_id, 20);
}
other @ SqpkHeaderTarget::Index(_) => {
panic!("expected SqpkHeaderTarget::Dat, got {other:?}")
}
}
assert_eq!(cmd.header_data.len(), 1024);
}
#[test]
fn rejects_unknown_file_kind() {
let mut body = Vec::new();
body.push(b'Z'); body.push(b'V');
body.push(0u8);
body.extend_from_slice(&[0u8; 8 + 1024]);
assert!(parse(&body).is_err());
}
#[test]
fn rejects_unknown_header_kind() {
let mut body = Vec::new();
body.push(b'D');
body.push(b'Z'); body.push(0u8);
body.extend_from_slice(&[0u8; 8 + 1024]);
assert!(parse(&body).is_err());
}
#[test]
fn parses_header_index_file() {
let mut body = Vec::new();
body.push(b'I'); body.push(b'I'); body.push(0u8);
body.extend_from_slice(&7u16.to_be_bytes()); body.extend_from_slice(&8u16.to_be_bytes()); body.extend_from_slice(&0u32.to_be_bytes()); body.extend_from_slice(&[0xBBu8; 1024]);
let cmd = parse(&body).unwrap();
assert!(matches!(cmd.file_kind, TargetFileKind::Index));
assert!(matches!(cmd.header_kind, TargetHeaderKind::Index));
match cmd.target {
SqpkHeaderTarget::Index(f) => {
assert_eq!(f.main_id, 7);
assert_eq!(f.sub_id, 8);
}
other @ SqpkHeaderTarget::Dat(_) => {
panic!("expected SqpkHeaderTarget::Index, got {other:?}")
}
}
assert_eq!(cmd.header_data.len(), 1024);
}
#[test]
fn header_data_truncated() {
let mut body = Vec::new();
body.push(b'D');
body.push(b'V');
body.push(0u8);
body.extend_from_slice(&[0u8; 8]);
body.extend_from_slice(&[0u8; 512]); assert!(parse(&body).is_err());
}
}