use std::{
io::{Cursor, Read, Seek},
path::Path,
};
use binrw::{BinRead, BinReaderExt, BinWrite, NullString};
use flate2::{Compression, bufread::ZlibEncoder};
use zune_inflate::{DeflateDecoder, DeflateOptions};
use xc3_write::{WriteFull, Xc3Write};
use crate::{
error::{CreateXbc1Error, DecompressStreamError},
hash::hash_crc,
};
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, BinRead, BinWrite, PartialEq, Clone)]
#[brw(magic(b"xbc1"))]
pub struct Xbc1 {
pub compression_type: CompressionType,
pub decompressed_size: u32,
pub compressed_size: u32,
pub decompressed_hash: u32,
#[br(map = |x: NullString| x.to_string())]
#[bw(map = |x: &String| NullString::from(x.as_str()))]
#[brw(pad_size_to = 28)]
pub name: String,
#[br(count = compressed_size)]
pub compressed_stream: Vec<u8>,
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, BinRead, BinWrite, Clone, Copy, PartialEq, Eq, Hash)]
#[brw(repr(u32))]
pub enum CompressionType {
Uncompressed = 0,
Zlib = 1,
Zstd = 3,
}
impl Xbc1 {
pub fn new<T>(
name: String,
data: &T,
compression_type: CompressionType,
) -> Result<Self, CreateXbc1Error>
where
T: WriteFull<Args = ()>,
{
let mut writer = Cursor::new(Vec::new());
data.write_full(&mut writer, 0, &mut 0, xc3_write::Endian::Little, ())?;
let decompressed = writer.into_inner();
Self::from_decompressed(name, &decompressed, compression_type)
}
pub fn from_decompressed(
name: String,
decompressed: &[u8],
compression_type: CompressionType,
) -> Result<Self, CreateXbc1Error> {
let compressed_stream = match compression_type {
CompressionType::Uncompressed => decompressed.to_vec(),
CompressionType::Zlib => {
let mut encoder = ZlibEncoder::new(decompressed, Compression::best());
let mut compressed_stream = Vec::new();
encoder.read_to_end(&mut compressed_stream)?;
compressed_stream
}
CompressionType::Zstd => zstd::stream::encode_all(Cursor::new(decompressed), 0)?,
};
Ok(Self {
compression_type,
decompressed_size: decompressed.len() as u32,
compressed_size: compressed_stream.len() as u32,
decompressed_hash: hash_crc(decompressed),
name,
compressed_stream,
})
}
pub fn decompress(&self) -> Result<Vec<u8>, DecompressStreamError> {
let decompressed = match self.compression_type {
CompressionType::Uncompressed => Ok(self.compressed_stream.clone()),
CompressionType::Zlib => {
let mut decoder = DeflateDecoder::new_with_options(
&self.compressed_stream,
DeflateOptions::default().set_size_hint(self.decompressed_size as usize),
);
decoder.decode_zlib().map_err(DecompressStreamError::from)
}
CompressionType::Zstd => zstd::stream::decode_all(Cursor::new(&self.compressed_stream))
.map_err(DecompressStreamError::from),
}?;
Ok(decompressed)
}
pub fn decompress_check_hash(&self) -> Result<Vec<u8>, DecompressStreamError> {
let decompressed = self.decompress()?;
let decompressed_hash = hash_crc(&decompressed);
if decompressed_hash != self.decompressed_hash {
return Err(DecompressStreamError::Checksum(decompressed));
}
Ok(decompressed)
}
pub fn extract<T>(&self) -> Result<T, DecompressStreamError>
where
for<'a> T: BinRead<Args<'a> = ()>,
{
let bytes = self.decompress()?;
T::read_le(&mut Cursor::new(bytes)).map_err(Into::into)
}
}
impl Xc3Write for Xbc1 {
type Offsets<'a> = ();
fn xc3_write<W: std::io::Write + std::io::Seek>(
&self,
writer: &mut W,
endian: xc3_write::Endian,
) -> xc3_write::Xc3Result<Self::Offsets<'_>> {
let endian = match endian {
xc3_write::Endian::Little => binrw::Endian::Little,
xc3_write::Endian::Big => binrw::Endian::Big,
};
self.write_options(writer, endian, ())
.map_err(std::io::Error::other)?;
Ok(())
}
const ALIGNMENT: u64 = 16;
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, BinRead, Clone)]
pub enum MaybeXbc1<T>
where
for<'a> T: BinRead<Args<'a> = ()>,
{
Uncompressed(T),
Xbc1(Xbc1),
}
impl<T> MaybeXbc1<T>
where
for<'a> T: BinRead<Args<'a> = ()>,
{
pub fn read<R: Read + Seek>(reader: &mut R) -> binrw::BinResult<Self> {
reader.read_le()
}
pub fn from_file<P: AsRef<Path>>(path: P) -> binrw::BinResult<Self> {
let mut reader = Cursor::new(std::fs::read(path)?);
reader.read_le()
}
pub fn from_bytes<B: AsRef<[u8]>>(bytes: B) -> binrw::BinResult<Self> {
Self::read(&mut Cursor::new(bytes))
}
}