use std::io::{Cursor, Read};
use flate2::read::{DeflateDecoder, GzDecoder};
pub type DecompressResult<T> = std::result::Result<T, DecompressError>;
#[derive(Debug)]
pub enum DecompressError {
InvalidLzmaHeader,
LzmaError(String),
DeflateError(String),
BufferTooSmall,
}
impl std::fmt::Display for DecompressError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidLzmaHeader => write!(f, "Invalid LZMA header"),
Self::LzmaError(msg) => write!(f, "LZMA decompression error: {msg}"),
Self::DeflateError(msg) => write!(f, "Deflate decompression error: {msg}"),
Self::BufferTooSmall => write!(f, "Input buffer too small"),
}
}
}
impl std::error::Error for DecompressError {}
#[must_use]
pub fn is_confuserex_lzma(data: &[u8]) -> bool {
if data.len() < 13 {
return false;
}
let props_byte = data[0];
if props_byte > 224 {
return false;
}
let dict_size = u32::from_le_bytes([data[1], data[2], data[3], data[4]]);
if !(1024..=16 * 1024 * 1024).contains(&dict_size) {
return false;
}
let uncompressed_size = i32::from_le_bytes([data[5], data[6], data[7], data[8]]);
if uncompressed_size <= 0 || uncompressed_size > 10 * 1024 * 1024 {
return false;
}
let compressed_data_len = data.len() - 9; if compressed_data_len > uncompressed_size.cast_unsigned() as usize {
return false;
}
true
}
pub fn decompress_confuserex_lzma(data: &[u8]) -> DecompressResult<Vec<u8>> {
if data.len() < 9 {
return Err(DecompressError::BufferTooSmall);
}
let props = &data[0..5];
let uncompressed_size = i32::from_le_bytes([data[5], data[6], data[7], data[8]]);
let compressed = &data[9..];
let mut lzma_stream = Vec::with_capacity(13 + compressed.len());
lzma_stream.extend_from_slice(props);
let size_u64 = if uncompressed_size < 0 {
u64::MAX } else {
u64::from(uncompressed_size.cast_unsigned())
};
lzma_stream.extend_from_slice(&size_u64.to_le_bytes());
lzma_stream.extend_from_slice(compressed);
let mut cursor = Cursor::new(&lzma_stream);
let mut decompressed = Vec::new();
lzma_rs::lzma_decompress(&mut cursor, &mut decompressed)
.map_err(|e| DecompressError::LzmaError(e.to_string()))?;
Ok(decompressed)
}
pub fn decompress_deflate(data: &[u8]) -> DecompressResult<Vec<u8>> {
let mut decoder = DeflateDecoder::new(data);
let mut decompressed = Vec::new();
decoder
.read_to_end(&mut decompressed)
.map_err(|e| DecompressError::DeflateError(e.to_string()))?;
Ok(decompressed)
}
pub fn decompress_gzip(data: &[u8]) -> DecompressResult<Vec<u8>> {
let mut decoder = GzDecoder::new(data);
let mut decompressed = Vec::new();
decoder
.read_to_end(&mut decompressed)
.map_err(|e| DecompressError::DeflateError(e.to_string()))?;
Ok(decompressed)
}
#[cfg(test)]
mod tests {
use std::io::Write;
use flate2::{write::DeflateEncoder, write::GzEncoder, Compression};
use super::*;
#[test]
fn test_is_confuserex_lzma_valid() {
let valid_header = [
0x5D, 0x00, 0x00, 0x10, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
assert!(is_confuserex_lzma(&valid_header));
}
#[test]
fn test_is_confuserex_lzma_invalid_props() {
let invalid_props = [
0xFF, 0x00, 0x00, 0x10, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
assert!(!is_confuserex_lzma(&invalid_props));
}
#[test]
fn test_is_confuserex_lzma_too_small() {
let too_small = [0x5D, 0x00, 0x00, 0x10, 0x00, 0x64, 0x00, 0x00, 0x00];
assert!(!is_confuserex_lzma(&too_small));
}
#[test]
fn test_decompress_deflate() {
let original = b"Hello, World! This is a test of deflate compression.";
let mut encoder = DeflateEncoder::new(Vec::new(), Compression::default());
encoder.write_all(original).unwrap();
let compressed = encoder.finish().unwrap();
let decompressed = decompress_deflate(&compressed).unwrap();
assert_eq!(&decompressed, original);
}
#[test]
fn test_decompress_gzip() {
let original = b"Hello, World! This is a test of gzip compression.";
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder.write_all(original).unwrap();
let compressed = encoder.finish().unwrap();
let decompressed = decompress_gzip(&compressed).unwrap();
assert_eq!(&decompressed, original);
}
}