use std::io::{Cursor, Read, Write};
use crate::crypto::Decryptor;
use crate::error::{EggError, EggResult};
pub fn extract_lzma<R: Read, W: Write>(
reader: &mut R,
writer: &mut W,
compressed_size: u64,
max_output: u64,
mut crypto: Option<&mut dyn Decryptor>,
) -> EggResult<u32> {
if compressed_size < 9 {
return Err(EggError::LzmaFailed(
"block too small for LZMA header".into(),
));
}
let mut header = [0u8; 9];
reader.read_exact(&mut header)?;
if let Some(ref mut c) = crypto {
c.decrypt(&mut header);
}
let lzma_props = &header[4..9];
let data_size = compressed_size - 9;
let mut compressed_data = Vec::new();
if (&mut *reader)
.take(data_size)
.read_to_end(&mut compressed_data)? as u64
!= data_size
{
return Err(EggError::LzmaFailed("truncated LZMA block".into()));
}
if let Some(ref mut c) = crypto {
c.decrypt(&mut compressed_data);
}
let mut full_stream = Vec::with_capacity(13 + compressed_data.len());
full_stream.extend_from_slice(lzma_props);
full_stream.extend_from_slice(&u64::MAX.to_le_bytes());
full_stream.extend_from_slice(&compressed_data);
let memlimit = (max_output.saturating_add(1 << 16)).min(usize::MAX as u64) as usize;
let options = lzma_rs::decompress::Options {
unpacked_size: lzma_rs::decompress::UnpackedSize::ReadFromHeader,
memlimit: Some(memlimit),
allow_incomplete: false,
};
let mut cursor = Cursor::new(full_stream);
let mut hasher = crc32fast::Hasher::new();
{
let mut sink = HashingWriter {
inner: writer,
hasher: &mut hasher,
};
lzma_rs::lzma_decompress_with_options(&mut cursor, &mut sink, &options)
.map_err(|e| EggError::LzmaFailed(e.to_string()))?;
}
Ok(hasher.finalize())
}
struct HashingWriter<'a, W: Write> {
inner: &'a mut W,
hasher: &'a mut crc32fast::Hasher,
}
impl<W: Write> Write for HashingWriter<'_, W> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let n = self.inner.write(buf)?;
self.hasher.update(&buf[..n]);
Ok(n)
}
fn flush(&mut self) -> std::io::Result<()> {
self.inner.flush()
}
}