use std::{
fmt,
io::{Cursor, Read},
};
use super::{WadChunkCompression, WadError};
use flate2::read::GzDecoder;
use memchr::memmem;
const ZSTD_MAGIC: [u8; 4] = [0x28, 0xB5, 0x2F, 0xFD];
#[cfg(all(feature = "zstd", feature = "ruzstd"))]
compile_error!("feature \"zstd\" and feature \"ruzstd\" cannot be enabled at the same time");
pub fn decompress_raw(
raw_data: &[u8],
compression: WadChunkCompression,
uncompressed_size: usize,
) -> Result<Box<[u8]>, WadError> {
match compression {
WadChunkCompression::None => Ok(raw_data.into()),
WadChunkCompression::GZip => decompress_gzip(raw_data, uncompressed_size),
WadChunkCompression::Satellite => Err(WadError::Other(String::from(
"satellite chunks are not supported",
))),
WadChunkCompression::Zstd => decompress_zstd(raw_data, uncompressed_size),
WadChunkCompression::ZstdMulti => decompress_zstd_multi(raw_data, uncompressed_size),
}
}
fn decompress_gzip(raw_data: &[u8], uncompressed_size: usize) -> Result<Box<[u8]>, WadError> {
let mut data = vec![0; uncompressed_size];
GzDecoder::new(Cursor::new(raw_data)).read_exact(&mut data)?;
Ok(data.into_boxed_slice())
}
fn decompress_zstd(raw_data: &[u8], uncompressed_size: usize) -> Result<Box<[u8]>, WadError> {
let mut data = vec![0; uncompressed_size];
#[cfg(feature = "zstd")]
{
zstd::Decoder::new(Cursor::new(raw_data))
.expect("failed to create zstd decoder")
.read_exact(&mut data)?;
}
#[cfg(feature = "ruzstd")]
{
ruzstd::decoding::StreamingDecoder::new(Cursor::new(raw_data))
.expect("failed to create ruzstd decoder")
.read_exact(&mut data)?;
}
Ok(data.into_boxed_slice())
}
fn decompress_zstd_multi(raw_data: &[u8], uncompressed_size: usize) -> Result<Box<[u8]>, WadError> {
let mut data = vec![0; uncompressed_size];
let zstd_magic_offset =
memmem::find(raw_data, &ZSTD_MAGIC).ok_or(WadError::DecompressionFailure {
reason: String::from("failed to find zstd magic"),
})?;
data[..zstd_magic_offset].copy_from_slice(&raw_data[..zstd_magic_offset]);
let zstd_data = &raw_data[zstd_magic_offset..];
#[cfg(feature = "zstd")]
{
zstd::Decoder::new(Cursor::new(zstd_data))
.expect("failed to create zstd decoder")
.read_exact(&mut data[zstd_magic_offset..])?;
}
#[cfg(feature = "ruzstd")]
{
ruzstd::decoding::StreamingDecoder::new(Cursor::new(zstd_data))
.expect("failed to create ruzstd decoder")
.read(&mut data[zstd_magic_offset..])?;
}
Ok(data.into_boxed_slice())
}
pub fn decompress_prefix(
raw_data: &[u8],
compression: WadChunkCompression,
max_len: usize,
) -> Result<Vec<u8>, WadError> {
match compression {
WadChunkCompression::None => Ok(raw_data[..raw_data.len().min(max_len)].to_vec()),
WadChunkCompression::GZip => read_prefix(GzDecoder::new(Cursor::new(raw_data)), max_len),
WadChunkCompression::Satellite => Err(WadError::Other(String::from(
"satellite chunks are not supported",
))),
WadChunkCompression::Zstd => read_prefix(zstd_reader(raw_data)?, max_len),
WadChunkCompression::ZstdMulti => {
let frame_at = memmem::find(raw_data, &ZSTD_MAGIC).unwrap_or(raw_data.len());
let mut data = raw_data[..frame_at.min(max_len)].to_vec();
if data.len() < max_len && frame_at < raw_data.len() {
let rest = read_prefix(zstd_reader(&raw_data[frame_at..])?, max_len - data.len())?;
data.extend(rest);
}
Ok(data)
}
}
}
fn read_prefix(reader: impl Read, max_len: usize) -> Result<Vec<u8>, WadError> {
let mut data = Vec::with_capacity(max_len);
reader.take(max_len as u64).read_to_end(&mut data)?;
Ok(data)
}
#[cfg(feature = "zstd")]
fn zstd_reader(raw_data: &[u8]) -> Result<impl Read + '_, WadError> {
Ok(zstd::Decoder::new(Cursor::new(raw_data))?)
}
#[cfg(all(feature = "ruzstd", not(feature = "zstd")))]
fn zstd_reader(raw_data: &[u8]) -> Result<impl Read + '_, WadError> {
ruzstd::decoding::StreamingDecoder::new(Cursor::new(raw_data)).map_err(|error| {
WadError::DecompressionFailure {
reason: error.to_string(),
}
})
}
#[cfg(not(any(feature = "zstd", feature = "ruzstd")))]
fn zstd_reader(_raw_data: &[u8]) -> Result<std::io::Empty, WadError> {
Err(WadError::Other(String::from("zstd support is not enabled")))
}
#[derive(Default)]
pub struct ChunkDecoder {
#[cfg(feature = "zstd")]
zstd: Option<zstd::zstd_safe::DCtx<'static>>,
}
impl fmt::Debug for ChunkDecoder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ChunkDecoder").finish_non_exhaustive()
}
}
impl ChunkDecoder {
pub fn new() -> Self {
Self::default()
}
pub fn decompress(
&mut self,
raw_data: &[u8],
compression: WadChunkCompression,
uncompressed_size: usize,
) -> Result<Box<[u8]>, WadError> {
#[cfg(feature = "zstd")]
match compression {
WadChunkCompression::Zstd => {
return self.decompress_zstd(raw_data, 0, uncompressed_size)
}
WadChunkCompression::ZstdMulti => {
let frame_at = memmem::find(raw_data, &ZSTD_MAGIC).ok_or_else(|| {
WadError::DecompressionFailure {
reason: String::from("failed to find zstd magic"),
}
})?;
return self.decompress_zstd(raw_data, frame_at, uncompressed_size);
}
_ => {}
}
decompress_raw(raw_data, compression, uncompressed_size)
}
pub fn decompress_prefix(
&mut self,
raw_data: &[u8],
compression: WadChunkCompression,
max_len: usize,
) -> Result<Vec<u8>, WadError> {
#[cfg(feature = "zstd")]
match compression {
WadChunkCompression::Zstd => return self.decompress_zstd_prefix(raw_data, 0, max_len),
WadChunkCompression::ZstdMulti => {
let frame_at = memmem::find(raw_data, &ZSTD_MAGIC).unwrap_or(raw_data.len());
return self.decompress_zstd_prefix(raw_data, frame_at, max_len);
}
_ => {}
}
decompress_prefix(raw_data, compression, max_len)
}
#[cfg(feature = "zstd")]
fn decompress_zstd(
&mut self,
raw_data: &[u8],
frame_at: usize,
uncompressed_size: usize,
) -> Result<Box<[u8]>, WadError> {
let mut data = vec![0; uncompressed_size];
let copied = frame_at.min(uncompressed_size);
data[..copied].copy_from_slice(&raw_data[..copied]);
let written = self.stream_into(&raw_data[frame_at..], &mut data[copied..])?;
if copied + written != uncompressed_size {
return Err(WadError::DecompressionFailure {
reason: format!(
"decompressed {} bytes, expected {uncompressed_size}",
copied + written
),
});
}
Ok(data.into_boxed_slice())
}
#[cfg(feature = "zstd")]
fn decompress_zstd_prefix(
&mut self,
raw_data: &[u8],
frame_at: usize,
max_len: usize,
) -> Result<Vec<u8>, WadError> {
let mut data = vec![0; max_len];
let copied = frame_at.min(max_len);
data[..copied].copy_from_slice(&raw_data[..copied]);
let written = if frame_at < raw_data.len() {
self.stream_into(&raw_data[frame_at..], &mut data[copied..])?
} else {
0
};
data.truncate(copied + written);
Ok(data)
}
#[cfg(feature = "zstd")]
fn stream_into(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize, WadError> {
use zstd::zstd_safe::{InBuffer, OutBuffer, ResetDirective};
let context = self.zstd.get_or_insert_with(zstd::zstd_safe::DCtx::create);
context
.reset(ResetDirective::SessionOnly)
.map_err(zstd_error)?;
let mut input = InBuffer::around(input);
let mut output = OutBuffer::around(output);
while output.pos() < output.capacity() && input.pos() < input.src.len() {
let before = (input.pos(), output.pos());
context
.decompress_stream(&mut output, &mut input)
.map_err(zstd_error)?;
if (input.pos(), output.pos()) == before {
break;
}
}
Ok(output.pos())
}
}
#[cfg(feature = "zstd")]
fn zstd_error(code: zstd::zstd_safe::ErrorCode) -> WadError {
WadError::DecompressionFailure {
reason: zstd::zstd_safe::get_error_name(code).to_owned(),
}
}