use binrw::{BinRead, BinResult, Endian};
use std::io::Cursor;
use super::SqpackFile;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IndexCommand {
Add,
Delete,
}
fn read_index_command<R: std::io::Read + std::io::Seek>(
reader: &mut R,
_: Endian,
(): (),
) -> BinResult<IndexCommand> {
let byte = <u8 as BinRead>::read_options(reader, Endian::Big, ())?;
match byte {
b'A' => Ok(IndexCommand::Add),
b'D' => Ok(IndexCommand::Delete),
_ => Err(binrw::Error::Custom {
pos: 0,
err: Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"unknown IndexCommand",
)),
}),
}
}
#[derive(BinRead, Debug, Clone, PartialEq, Eq)]
#[br(big)]
pub struct SqpkIndex {
#[br(parse_with = read_index_command)]
pub command: IndexCommand,
#[br(map = |x: u8| x != 0)]
pub is_synonym: bool,
#[br(pad_before = 1)]
pub target_file: SqpackFile,
pub file_hash: u64,
pub block_offset: u32,
pub block_number: u32,
}
#[derive(BinRead, Debug, Clone, PartialEq, Eq)]
#[br(big)]
pub struct SqpkPatchInfo {
pub status: u8,
pub version: u8,
#[br(pad_before = 1)]
pub install_size: u64,
}
pub(crate) fn parse_index(body: &[u8]) -> crate::Result<SqpkIndex> {
Ok(SqpkIndex::read_be(&mut Cursor::new(body))?)
}
pub(crate) fn parse_patch_info(body: &[u8]) -> crate::Result<SqpkPatchInfo> {
Ok(SqpkPatchInfo::read_be(&mut Cursor::new(body))?)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_sqpk_index_add() {
let mut body = Vec::new();
body.push(b'A'); body.push(1u8); body.push(0u8); body.extend_from_slice(&0x0102u16.to_be_bytes()); body.extend_from_slice(&0x0304u16.to_be_bytes()); body.extend_from_slice(&0u32.to_be_bytes()); body.extend_from_slice(&0x0807060504030201u64.to_be_bytes()); body.extend_from_slice(&5u32.to_be_bytes()); body.extend_from_slice(&10u32.to_be_bytes());
let idx = parse_index(&body).unwrap();
assert!(matches!(idx.command, IndexCommand::Add));
assert!(idx.is_synonym);
assert_eq!(idx.target_file.main_id, 0x0102);
assert_eq!(idx.file_hash, 0x0807060504030201);
assert_eq!(idx.block_offset, 5);
assert_eq!(idx.block_number, 10);
}
#[test]
fn rejects_unknown_index_command() {
let mut body = Vec::new();
body.push(b'Z'); body.extend_from_slice(&[0u8; 20]);
assert!(parse_index(&body).is_err());
}
#[test]
fn parses_sqpk_patch_info() {
let mut body = Vec::new();
body.push(3u8); body.push(1u8); body.push(0u8); body.extend_from_slice(&0x0102030405060708u64.to_be_bytes());
let info = parse_patch_info(&body).unwrap();
assert_eq!(info.status, 3);
assert_eq!(info.version, 1);
assert_eq!(info.install_size, 0x0102030405060708);
}
}