use crate::Result;
use crate::compression::error_helpers::{compression_error, decompression_error};
use std::io::{BufReader, Cursor};
const MPQ_LZMA_HEADER_SIZE: usize = 14;
pub(crate) fn decompress(data: &[u8], expected_size: usize) -> Result<Vec<u8>> {
if data.len() > MPQ_LZMA_HEADER_SIZE && data[0] == 0x00 && is_valid_lzma_props(data[1]) {
log::debug!(
"Detected MPQ LZMA format: filter=0x{:02X}, props=0x{:02X}, header size={}",
data[0],
data[1],
MPQ_LZMA_HEADER_SIZE
);
let standard_lzma = &data[1..];
let cursor = Cursor::new(standard_lzma);
let mut input = BufReader::new(cursor);
let mut output = Vec::with_capacity(expected_size);
match lzma_rs::lzma_decompress(&mut input, &mut output) {
Ok(()) => {
if expected_size > 0 && output.len() != expected_size {
log::warn!(
"LZMA decompressed size mismatch: expected {}, got {}",
expected_size,
output.len()
);
}
return Ok(output);
}
Err(e) => {
log::debug!("MPQ LZMA decompression failed, trying raw: {e:?}");
}
}
}
let cursor = Cursor::new(data);
let mut input = BufReader::new(cursor);
let mut output = Vec::with_capacity(expected_size);
match lzma_rs::lzma_decompress(&mut input, &mut output) {
Ok(()) => {
if expected_size > 0 && output.len() != expected_size {
log::warn!(
"LZMA decompressed size mismatch: expected {}, got {}",
expected_size,
output.len()
);
}
Ok(output)
}
Err(e) => {
let cursor = Cursor::new(data);
let mut input = BufReader::new(cursor);
let mut output = Vec::with_capacity(expected_size);
match lzma_rs::xz_decompress(&mut input, &mut output) {
Ok(()) => Ok(output),
Err(xz_err) => {
log::error!("LZMA decompression failed: {e:?}");
log::error!("XZ decompression also failed: {xz_err:?}");
log::debug!(
"First 16 bytes of data: {:02X?}",
&data[..16.min(data.len())]
);
Err(decompression_error(
"LZMA/XZ",
format!("LZMA: {e:?}, XZ: {xz_err:?}"),
))
}
}
}
}
}
fn is_valid_lzma_props(byte: u8) -> bool {
byte <= 224
}
pub(crate) fn compress(data: &[u8]) -> Result<Vec<u8>> {
let cursor = Cursor::new(data);
let mut input = BufReader::new(cursor);
let mut lzma_data = Vec::new();
match lzma_rs::lzma_compress(&mut input, &mut lzma_data) {
Ok(()) => {
let mut output = Vec::with_capacity(1 + lzma_data.len());
output.push(0x00);
output.extend_from_slice(&lzma_data);
Ok(output)
}
Err(e) => Err(compression_error("LZMA", format!("{e:?}"))),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_round_trip() {
let original = b"Hello, World! This is a test of LZMA compression in MPQ archives. \
LZMA should provide good compression ratios.";
let compressed = compress(original).expect("Compression failed");
#[cfg(debug_assertions)]
println!(
"LZMA - Original size: {}, Compressed size: {}",
original.len(),
compressed.len()
);
let decompressed = decompress(&compressed, original.len()).expect("Decompression failed");
assert_eq!(decompressed, original);
}
#[test]
fn test_mpq_lzma_header_format() {
let original = b"Test data for MPQ LZMA header verification";
let compressed = compress(original).expect("Compression failed");
assert_eq!(compressed[0], 0x00, "First byte should be filter=0x00");
assert!(
is_valid_lzma_props(compressed[1]),
"Second byte should be valid LZMA props"
);
assert!(
compressed.len() > MPQ_LZMA_HEADER_SIZE,
"Compressed data should be larger than header"
);
let decompressed = decompress(&compressed, original.len()).expect("Decompression failed");
assert_eq!(decompressed, original);
}
#[test]
fn test_xz_format() {
let original = b"Test data for XZ format";
let cursor = Cursor::new(original);
let mut input = BufReader::new(cursor);
let mut compressed = Vec::new();
lzma_rs::xz_compress(&mut input, &mut compressed).expect("XZ compression failed");
let decompressed = decompress(&compressed, original.len()).expect("Decompression failed");
assert_eq!(decompressed, original);
}
}