use std::io::{BufReader, Read};
use std::{fmt, io};
use byteorder::{ReadBytesExt as _, WriteBytesExt as _, LE};
use ltk_hash::WadHash;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use super::WadError;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
#[repr(u8)]
pub enum WadChunkCompression {
None = 0,
GZip = 1,
Satellite = 2,
Zstd = 3,
ZstdMulti = 4,
}
impl fmt::Display for WadChunkCompression {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
WadChunkCompression::None => write!(f, "None"),
WadChunkCompression::GZip => write!(f, "GZip"),
WadChunkCompression::Satellite => write!(f, "Satellite"),
WadChunkCompression::Zstd => write!(f, "Zstd"),
WadChunkCompression::ZstdMulti => write!(f, "ZstdMulti"),
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WadChunk {
pub path_hash: WadHash,
pub data_offset: usize,
pub compressed_size: usize,
pub uncompressed_size: usize,
pub compression_type: WadChunkCompression,
pub is_duplicated: bool,
pub frame_count: u8,
pub start_frame: u32,
pub checksum: u64,
}
impl WadChunk {
pub fn read_v3_1<R: Read>(reader: &mut BufReader<R>) -> Result<WadChunk, WadError> {
let path_hash = WadHash(reader.read_u64::<LE>()?);
let data_offset = reader.read_u32::<LE>()? as usize;
let compressed_size = reader.read_i32::<LE>()? as usize;
let uncompressed_size = reader.read_i32::<LE>()? as usize;
let type_frame_count = reader.read_u8()?;
let frame_count = type_frame_count >> 4;
let compression_type = WadChunkCompression::try_from_primitive(type_frame_count & 0xF)
.map_err(|_| WadError::InvalidChunkCompression {
compression: type_frame_count & 0xF,
})?;
let is_duplicated = reader.read_u8()? == 1;
let start_frame = reader.read_u16::<LE>()?;
let checksum = reader.read_u64::<LE>()?;
Ok(WadChunk {
path_hash,
data_offset,
compressed_size,
uncompressed_size,
compression_type,
is_duplicated,
frame_count,
start_frame: start_frame as u32,
checksum,
})
}
pub fn read_v3_4<R: Read>(reader: &mut BufReader<R>) -> Result<WadChunk, WadError> {
let path_hash = WadHash(reader.read_u64::<LE>()?);
let data_offset = reader.read_u32::<LE>()? as usize;
let compressed_size = reader.read_u32::<LE>()? as usize;
let uncompressed_size = reader.read_u32::<LE>()? as usize;
let type_frame_count = reader.read_u8()?;
let frame_count = type_frame_count >> 4;
let compression_type = WadChunkCompression::try_from_primitive(type_frame_count & 0xF)
.map_err(|_| WadError::InvalidChunkCompression {
compression: type_frame_count & 0xF,
})?;
let start_frame = read_24_bit_subchunk_start_frame(reader)?;
let checksum = reader.read_u64::<LE>()?;
Ok(WadChunk {
path_hash,
data_offset,
compressed_size,
uncompressed_size,
compression_type,
is_duplicated: false, frame_count,
start_frame: start_frame as u32,
checksum,
})
}
pub fn write_v3_4<W: io::Write>(&self, writer: &mut W) -> Result<(), WadError> {
writer.write_u64::<LE>(self.path_hash.0)?;
writer.write_u32::<LE>(self.data_offset as u32)?;
writer.write_u32::<LE>(self.compressed_size as u32)?;
writer.write_u32::<LE>(self.uncompressed_size as u32)?;
let type_frame_count = (self.frame_count << 4) | (self.compression_type as u8 & 0xF);
writer.write_u8(type_frame_count)?;
write_24_bit_subchunk_start_frame(writer, self.start_frame)?;
writer.write_u64::<LE>(self.checksum)?;
Ok(())
}
pub fn path_hash(&self) -> WadHash {
self.path_hash
}
pub fn data_offset(&self) -> usize {
self.data_offset
}
pub fn compressed_size(&self) -> usize {
self.compressed_size
}
pub fn uncompressed_size(&self) -> usize {
self.uncompressed_size
}
pub fn compression_type(&self) -> WadChunkCompression {
self.compression_type
}
pub fn checksum(&self) -> u64 {
self.checksum
}
}
pub(crate) fn read_24_bit_subchunk_start_frame<R: Read>(
reader: &mut BufReader<R>,
) -> Result<u32, WadError> {
let start_frame_hi = reader.read_u8()? as u32;
let start_frame_lo = reader.read_u8()? as u32;
let start_frame_mi = reader.read_u8()? as u32;
let start_frame = start_frame_hi << 16 | start_frame_mi << 8 | start_frame_lo;
Ok(start_frame)
}
pub(crate) fn write_24_bit_subchunk_start_frame<W: io::Write>(
writer: &mut W,
start_frame: u32,
) -> Result<(), WadError> {
writer.write_u8(((start_frame >> 16) & 0xFF) as u8)?;
writer.write_u8((start_frame & 0xFF) as u8)?;
writer.write_u8(((start_frame >> 8) & 0xFF) as u8)?;
Ok(())
}
#[cfg(test)]
mod tests {
use std::io::{BufReader, Cursor};
use crate::{read_24_bit_subchunk_start_frame, write_24_bit_subchunk_start_frame};
#[test]
fn test_read_24_bit_subchunk_start_frame() {
let mut reader = BufReader::new(Cursor::new([0x01, 0x03, 0x02]));
let start_frame = read_24_bit_subchunk_start_frame(&mut reader).unwrap();
assert_eq!(start_frame, 0x010203);
}
#[test]
fn test_write_24_bit_subchunk_start_frame() {
let mut writer = Vec::new();
write_24_bit_subchunk_start_frame(&mut writer, 0x010302).unwrap();
assert_eq!(writer, [0x01, 0x02, 0x03]);
}
}