use crate::error::SteamError;
const VZSTD_HEADER: u32 = 0x615A_5356;
const HEADER_LENGTH: usize = 8;
const FOOTER_LENGTH: usize = 15;
#[derive(Debug)]
pub struct VZstdUtil;
impl VZstdUtil {
pub fn decompress(
buffer: &[u8],
destination: &mut [u8],
verify_checksum: bool,
) -> Result<i32, SteamError> {
if buffer.len() < HEADER_LENGTH + FOOTER_LENGTH {
return Err(SteamError::Decompression(format!(
"VZstd data is too short (was {} bytes).",
buffer.len()
)));
}
if u32::from_ne_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]) != VZSTD_HEADER {
return Err(SteamError::Decompression(
"Expecting VZstdHeader at start of stream".to_string(),
));
}
let crc32 = i32::from_ne_bytes([buffer[4], buffer[5], buffer[6], buffer[7]]);
let footer = buffer.len() - FOOTER_LENGTH;
let crc32_footer =
i32::from_ne_bytes([buffer[footer], buffer[footer + 1], buffer[footer + 2], buffer[footer + 3]]);
let size_decompressed = i32::from_ne_bytes([
buffer[footer + 4],
buffer[footer + 5],
buffer[footer + 6],
buffer[footer + 7],
]);
debug_assert_eq!(crc32, crc32_footer);
if buffer[buffer.len() - 3] != b'z'
|| buffer[buffer.len() - 2] != b's'
|| buffer[buffer.len() - 1] != b'v'
{
return Err(SteamError::Decompression(
"Expecting VZstdFooter at end of stream".to_string(),
));
}
if destination.len() < size_decompressed as usize {
return Err(SteamError::InvalidArgument(
"The destination buffer is smaller than the decompressed data size.".to_string(),
));
}
let input = &buffer[HEADER_LENGTH..footer];
let decompressed = zstd::stream::decode_all(input)
.map_err(|_| SteamError::Decompression("Failed to decompress Zstd.".to_string()))?;
if decompressed.len() != size_decompressed as usize {
return Err(SteamError::Decompression(format!(
"Failed to decompress Zstd (expected {size_decompressed} bytes, got {}).",
decompressed.len()
)));
}
destination[..decompressed.len()].copy_from_slice(&decompressed);
if verify_checksum
&& crc32fast::hash(&destination[..decompressed.len()]) != crc32_footer as u32
{
return Err(SteamError::Decompression(
"CRC does not match decompressed data. VZstd data may be corrupted.".to_string(),
));
}
Ok(size_decompressed)
}
}