use std::io::{self, BufReader, Read};
use crate::{Error, Result};
pub const METHOD_ID: &[u8] = &[0x03, 0x03, 0x01, 0x1B];
mod range {
pub const NUM_MOVE_BITS: u32 = 5;
pub const NUM_BIT_MODEL_TOTAL_BITS: u32 = 11;
pub const BIT_MODEL_TOTAL: u32 = 1 << NUM_BIT_MODEL_TOTAL_BITS;
pub const NUM_TOP_BITS: u32 = 24;
pub const TOP_VALUE: u32 = 1 << NUM_TOP_BITS;
pub const INITIAL_PROB: u32 = BIT_MODEL_TOTAL / 2;
}
pub struct RangeDecoder<R> {
reader: R,
range: u32,
code: u32,
}
impl<R: Read> RangeDecoder<R> {
pub fn new(mut reader: R) -> Result<Self> {
let mut code: u32 = 0;
for _ in 0..5 {
let mut byte = [0u8; 1];
reader.read_exact(&mut byte).map_err(Error::Io)?;
code = (code << 8) | byte[0] as u32;
}
Ok(Self {
reader,
range: 0xFFFFFFFF,
code,
})
}
pub fn decode_bit(&mut self, prob: u32) -> Result<(u32, u32)> {
let bound = (self.range >> range::NUM_BIT_MODEL_TOTAL_BITS) * prob;
let (bit, new_prob) = if self.code < bound {
self.range = bound;
let new_prob = prob + ((range::BIT_MODEL_TOTAL - prob) >> range::NUM_MOVE_BITS);
(0, new_prob)
} else {
self.range -= bound;
self.code -= bound;
let new_prob = prob - (prob >> range::NUM_MOVE_BITS);
(1, new_prob)
};
if self.range < range::TOP_VALUE {
let mut byte = [0u8; 1];
match self.reader.read(&mut byte) {
Ok(0) => {} Ok(_) => {} Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {} Err(e) => return Err(Error::Io(e)), }
self.code = (self.code << 8) | byte[0] as u32;
self.range <<= 8;
}
Ok((bit, new_prob))
}
}
#[derive(Clone, Copy)]
struct StatusDecoder {
prob: u32,
}
impl StatusDecoder {
fn new() -> Self {
Self {
prob: range::INITIAL_PROB,
}
}
fn decode<R: Read>(&mut self, rd: &mut RangeDecoder<R>) -> Result<bool> {
let (bit, new_prob) = rd.decode_bit(self.prob)?;
self.prob = new_prob;
Ok(bit == 1)
}
}
pub struct Bcj2Decoder<R> {
main: BufReader<R>,
call: R,
jump: R,
range_decoder: RangeDecoder<R>,
status_decoders: [StatusDecoder; 258],
prev_byte: u8,
written: u32,
buffer: Vec<u8>,
buffer_pos: usize,
}
impl<R: Read> Bcj2Decoder<R> {
pub fn new(main: R, call: R, jump: R, range: R) -> Result<Self> {
let range_decoder = RangeDecoder::new(range)?;
Ok(Self {
main: BufReader::new(main),
call,
jump,
range_decoder,
status_decoders: [StatusDecoder::new(); 258],
prev_byte: 0,
written: 0,
buffer: Vec::with_capacity(65536),
buffer_pos: 0,
})
}
#[inline]
fn is_jump(prev: u8, curr: u8) -> bool {
(curr & 0xFE) == 0xE8 || Self::is_jcc(prev, curr)
}
#[inline]
fn is_jcc(prev: u8, curr: u8) -> bool {
prev == 0x0F && (curr & 0xF0) == 0x80
}
#[inline]
fn status_index(prev: u8, curr: u8) -> usize {
match curr {
0xE8 => prev as usize, 0xE9 => 256, _ => 257, }
}
fn fill_buffer(&mut self) -> io::Result<()> {
self.buffer.clear();
self.buffer_pos = 0;
loop {
let mut byte = [0u8; 1];
match self.main.read(&mut byte) {
Ok(0) => return Ok(()), Ok(_) => {}
Err(e) => return Err(e),
}
let b = byte[0];
self.written += 1;
self.buffer.push(b);
if Self::is_jump(self.prev_byte, b) {
let idx = Self::status_index(self.prev_byte, b);
let is_converted = self.status_decoders[idx]
.decode(&mut self.range_decoder)
.map_err(|e| io::Error::other(e.to_string()))?;
if is_converted {
let reader: &mut dyn Read = if b == 0xE8 {
&mut self.call
} else {
&mut self.jump
};
let mut dest_bytes = [0u8; 4];
reader.read_exact(&mut dest_bytes)?;
let dest = u32::from_be_bytes(dest_bytes);
let relative = dest.wrapping_sub(self.written + 4);
self.buffer.extend_from_slice(&relative.to_le_bytes());
self.prev_byte = (relative >> 24) as u8;
self.written += 4;
} else {
self.prev_byte = b;
}
} else {
self.prev_byte = b;
}
if self.buffer.len() >= self.buffer.capacity() / 2 {
break;
}
}
Ok(())
}
}
impl<R: Read> Read for Bcj2Decoder<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.buffer_pos >= self.buffer.len() {
self.fill_buffer()?;
if self.buffer.is_empty() {
return Ok(0); }
}
let available = self.buffer.len() - self.buffer_pos;
let to_copy = available.min(buf.len());
buf[..to_copy].copy_from_slice(&self.buffer[self.buffer_pos..self.buffer_pos + to_copy]);
self.buffer_pos += to_copy;
Ok(to_copy)
}
}
pub struct Bcj2DecoderWrapper<R> {
inner: Bcj2Decoder<R>,
}
impl<R: Read> Bcj2DecoderWrapper<R> {
pub fn new(inner: Bcj2Decoder<R>) -> Self {
Self { inner }
}
}
impl<R: Read + Send> Read for Bcj2DecoderWrapper<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.read(buf)
}
}
impl<R: Read + Send> super::Decoder for Bcj2DecoderWrapper<R> {
fn method_id(&self) -> &'static [u8] {
METHOD_ID
}
}
pub struct RangeEncoder {
range: u32,
low: u64,
cache: u8,
cache_size: u32,
output: Vec<u8>,
}
impl RangeEncoder {
pub fn new() -> Self {
Self {
range: 0xFFFFFFFF,
low: 0,
cache: 0,
cache_size: 1,
output: Vec::with_capacity(4096),
}
}
pub fn encode_bit(&mut self, bit: bool, prob: u32) -> u32 {
let bound = (self.range >> range::NUM_BIT_MODEL_TOTAL_BITS) * prob;
let new_prob = if bit {
self.low += bound as u64;
self.range -= bound;
prob - (prob >> range::NUM_MOVE_BITS)
} else {
self.range = bound;
prob + ((range::BIT_MODEL_TOTAL - prob) >> range::NUM_MOVE_BITS)
};
while self.range < range::TOP_VALUE {
self.shift_low();
self.range <<= 8;
}
new_prob
}
fn shift_low(&mut self) {
let low32 = self.low as u32;
let high = (self.low >> 32) as u8;
self.low = (low32 << 8) as u64;
if low32 < 0xFF000000 || high != 0 {
let temp = self.cache.wrapping_add(high);
self.cache = (low32 >> 24) as u8;
if self.cache_size > 0 {
self.output.push(temp);
for _ in 1..self.cache_size {
self.output.push(0xFF_u8.wrapping_add(high));
}
self.cache_size = 0;
}
self.cache_size += 1;
} else {
self.cache_size += 1;
}
}
pub fn finish(mut self) -> Vec<u8> {
for _ in 0..5 {
self.shift_low();
}
self.output
}
}
impl Default for RangeEncoder {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy)]
struct StatusEncoder {
prob: u32,
}
impl StatusEncoder {
fn new() -> Self {
Self {
prob: range::INITIAL_PROB,
}
}
fn encode(&mut self, re: &mut RangeEncoder, bit: bool) {
self.prob = re.encode_bit(bit, self.prob);
}
}
#[derive(Debug, Clone)]
pub struct Bcj2EncodedStreams {
pub main: Vec<u8>,
pub call: Vec<u8>,
pub jump: Vec<u8>,
pub range: Vec<u8>,
}
impl Bcj2EncodedStreams {
pub fn total_size(&self) -> usize {
self.main.len() + self.call.len() + self.jump.len() + self.range.len()
}
}
pub struct Bcj2Encoder {
main: Vec<u8>,
call: Vec<u8>,
jump: Vec<u8>,
range_encoder: RangeEncoder,
status_encoders: [StatusEncoder; 258],
prev_byte: u8,
position: u32,
}
impl Bcj2Encoder {
pub fn new() -> Self {
Self {
main: Vec::with_capacity(65536),
call: Vec::with_capacity(4096),
jump: Vec::with_capacity(1024),
range_encoder: RangeEncoder::new(),
status_encoders: [StatusEncoder::new(); 258],
prev_byte: 0,
position: 0,
}
}
pub fn encode(&mut self, data: &[u8]) {
let mut i = 0;
while i < data.len() {
let b = data[i];
let is_jump = Self::is_jump(self.prev_byte, b);
if is_jump && i + 4 < data.len() {
let rel_addr =
u32::from_le_bytes([data[i + 1], data[i + 2], data[i + 3], data[i + 4]]);
let abs_addr = rel_addr.wrapping_add(self.position + 5);
let should_convert = Self::should_convert(abs_addr);
let idx = Self::status_index(self.prev_byte, b);
self.status_encoders[idx].encode(&mut self.range_encoder, should_convert);
if should_convert {
self.main.push(b);
self.position += 1;
let dest_bytes = abs_addr.to_be_bytes();
if b == 0xE8 {
self.call.extend_from_slice(&dest_bytes);
} else {
self.jump.extend_from_slice(&dest_bytes);
}
self.prev_byte = data[i + 4];
self.position += 4;
i += 5;
} else {
self.main.push(b);
self.prev_byte = b;
self.position += 1;
i += 1;
}
} else {
self.main.push(b);
self.prev_byte = b;
self.position += 1;
i += 1;
}
}
}
#[inline]
fn is_jump(prev: u8, curr: u8) -> bool {
(curr & 0xFE) == 0xE8 || Self::is_jcc(prev, curr)
}
#[inline]
fn is_jcc(prev: u8, curr: u8) -> bool {
prev == 0x0F && (curr & 0xF0) == 0x80
}
#[inline]
fn status_index(prev: u8, curr: u8) -> usize {
match curr {
0xE8 => prev as usize, 0xE9 => 256, _ => 257, }
}
#[inline]
fn should_convert(_abs_addr: u32) -> bool {
true
}
pub fn finish(self) -> Bcj2EncodedStreams {
Bcj2EncodedStreams {
main: self.main,
call: self.call,
jump: self.jump,
range: self.range_encoder.finish(),
}
}
}
impl Default for Bcj2Encoder {
fn default() -> Self {
Self::new()
}
}
pub fn bcj2_encode(data: &[u8]) -> Bcj2EncodedStreams {
let mut encoder = Bcj2Encoder::new();
encoder.encode(data);
encoder.finish()
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
mod range_decoder {
use super::*;
#[test]
fn test_new_reads_5_bytes() {
let data = vec![0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06];
let cursor = Cursor::new(data);
let rd = RangeDecoder::new(cursor).unwrap();
assert_eq!(rd.code, 0x0000_0102_0304);
assert_eq!(rd.range, 0xFFFFFFFF);
}
#[test]
fn test_new_fails_on_short_input() {
let data = vec![0x00, 0x01, 0x02]; let cursor = Cursor::new(data);
let result = RangeDecoder::new(cursor);
assert!(result.is_err());
}
#[test]
fn test_decode_bit_zero() {
let data = vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
let cursor = Cursor::new(data);
let mut rd = RangeDecoder::new(cursor).unwrap();
let initial_prob = range::INITIAL_PROB;
let (bit, new_prob) = rd.decode_bit(initial_prob).unwrap();
assert_eq!(bit, 0);
assert!(new_prob > initial_prob);
}
#[test]
fn test_decode_bit_one() {
let data = vec![0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00];
let cursor = Cursor::new(data);
let mut rd = RangeDecoder::new(cursor).unwrap();
let initial_prob = range::INITIAL_PROB;
let (bit, new_prob) = rd.decode_bit(initial_prob).unwrap();
assert_eq!(bit, 1);
assert!(new_prob < initial_prob);
}
#[test]
fn test_probability_adaptation() {
let data = vec![0x00; 100];
let cursor = Cursor::new(data);
let mut rd = RangeDecoder::new(cursor).unwrap();
let mut prob = range::INITIAL_PROB;
for _ in 0..10 {
let (bit, new_prob) = rd.decode_bit(prob).unwrap();
if bit == 0 {
assert!(new_prob >= prob);
}
prob = new_prob;
}
}
}
mod algorithm {
use super::*;
#[test]
fn test_is_jump_e8_call() {
assert!(Bcj2Decoder::<Cursor<Vec<u8>>>::is_jump(0x00, 0xE8));
assert!(Bcj2Decoder::<Cursor<Vec<u8>>>::is_jump(0xFF, 0xE8));
}
#[test]
fn test_is_jump_e9_jmp() {
assert!(Bcj2Decoder::<Cursor<Vec<u8>>>::is_jump(0x00, 0xE9));
assert!(Bcj2Decoder::<Cursor<Vec<u8>>>::is_jump(0xFF, 0xE9));
}
#[test]
fn test_is_jump_jcc() {
assert!(Bcj2Decoder::<Cursor<Vec<u8>>>::is_jump(0x0F, 0x80));
assert!(Bcj2Decoder::<Cursor<Vec<u8>>>::is_jump(0x0F, 0x8F));
assert!(!Bcj2Decoder::<Cursor<Vec<u8>>>::is_jump(0x00, 0x80));
}
#[test]
fn test_is_jump_not_jump() {
assert!(!Bcj2Decoder::<Cursor<Vec<u8>>>::is_jump(0x00, 0x00));
assert!(!Bcj2Decoder::<Cursor<Vec<u8>>>::is_jump(0x90, 0x90)); assert!(!Bcj2Decoder::<Cursor<Vec<u8>>>::is_jump(0xE8, 0x00)); }
#[test]
fn test_status_index_call() {
assert_eq!(Bcj2Decoder::<Cursor<Vec<u8>>>::status_index(0x00, 0xE8), 0);
assert_eq!(
Bcj2Decoder::<Cursor<Vec<u8>>>::status_index(0xFF, 0xE8),
255
);
assert_eq!(
Bcj2Decoder::<Cursor<Vec<u8>>>::status_index(0x90, 0xE8),
0x90
);
}
#[test]
fn test_status_index_jmp() {
assert_eq!(
Bcj2Decoder::<Cursor<Vec<u8>>>::status_index(0x00, 0xE9),
256
);
assert_eq!(
Bcj2Decoder::<Cursor<Vec<u8>>>::status_index(0xFF, 0xE9),
256
);
}
#[test]
fn test_status_index_jcc() {
assert_eq!(
Bcj2Decoder::<Cursor<Vec<u8>>>::status_index(0x0F, 0x80),
257
);
assert_eq!(
Bcj2Decoder::<Cursor<Vec<u8>>>::status_index(0x0F, 0x8F),
257
);
}
#[test]
fn test_address_conversion() {
let written: u32 = 100;
let absolute: u32 = 200;
let relative = absolute.wrapping_sub(written + 4);
assert_eq!(relative, 96); }
#[test]
fn test_address_conversion_negative() {
let written: u32 = 200;
let absolute: u32 = 100;
let relative = absolute.wrapping_sub(written + 4);
assert_eq!(relative, 0xFFFFFF98);
}
}
mod decoder {
use super::*;
fn create_test_streams() -> (Vec<u8>, Vec<u8>, Vec<u8>, Vec<u8>) {
let main = vec![0x90, 0x90, 0x90, 0x90];
let call = vec![];
let jump = vec![];
let range = vec![0x00, 0x00, 0x00, 0x00, 0x00];
(main, call, jump, range)
}
#[test]
fn test_decoder_passthrough_no_jumps() {
let (main, call, jump, range) = create_test_streams();
let mut decoder = Bcj2Decoder::new(
Cursor::new(main.clone()),
Cursor::new(call),
Cursor::new(jump),
Cursor::new(range),
)
.unwrap();
let mut output = Vec::new();
decoder.read_to_end(&mut output).unwrap();
assert_eq!(output, main);
}
#[test]
fn test_decoder_empty_streams() {
let main = vec![];
let call = vec![];
let jump = vec![];
let range = vec![0x00, 0x00, 0x00, 0x00, 0x00];
let mut decoder = Bcj2Decoder::new(
Cursor::new(main),
Cursor::new(call),
Cursor::new(jump),
Cursor::new(range),
)
.unwrap();
let mut output = Vec::new();
decoder.read_to_end(&mut output).unwrap();
assert!(output.is_empty());
}
#[test]
fn test_decoder_initialization() {
let (main, call, jump, range) = create_test_streams();
let decoder = Bcj2Decoder::new(
Cursor::new(main),
Cursor::new(call),
Cursor::new(jump),
Cursor::new(range),
)
.unwrap();
assert_eq!(decoder.prev_byte, 0);
assert_eq!(decoder.written, 0);
assert_eq!(decoder.status_decoders.len(), 258);
}
#[test]
fn test_decoder_requires_5_byte_range_init() {
let main = vec![0x90];
let call = vec![];
let jump = vec![];
let range = vec![0x00, 0x00];
let result = Bcj2Decoder::new(
Cursor::new(main),
Cursor::new(call),
Cursor::new(jump),
Cursor::new(range),
);
assert!(result.is_err());
}
}
mod known_vectors {
use super::*;
#[test]
fn test_vector_no_conversion() {
let main = vec![0x55, 0x48, 0x89, 0xE5, 0x5D]; let call = vec![];
let jump = vec![];
let range = vec![0x00, 0x00, 0x00, 0x00, 0x00];
let mut decoder = Bcj2Decoder::new(
Cursor::new(main.clone()),
Cursor::new(call),
Cursor::new(jump),
Cursor::new(range),
)
.unwrap();
let mut output = Vec::new();
decoder.read_to_end(&mut output).unwrap();
assert_eq!(output, main);
}
#[test]
fn test_vector_e8_not_converted() {
let main = vec![0x90, 0xE8, 0x90, 0x90, 0x90, 0x90];
let call = vec![];
let jump = vec![];
let range = vec![0x00, 0x00, 0x00, 0x00, 0x00];
let mut decoder = Bcj2Decoder::new(
Cursor::new(main.clone()),
Cursor::new(call),
Cursor::new(jump),
Cursor::new(range),
)
.unwrap();
let mut output = Vec::new();
decoder.read_to_end(&mut output).unwrap();
assert_eq!(output, main);
}
}
mod encoder {
use super::*;
#[test]
fn test_encoder_no_jumps() {
let data = vec![0x55, 0x89, 0xE5, 0x5D, 0xC3];
let streams = bcj2_encode(&data);
assert_eq!(streams.main, data);
assert!(streams.call.is_empty());
assert!(streams.jump.is_empty());
assert!(!streams.range.is_empty());
}
#[test]
fn test_encoder_with_call() {
let data = vec![
0x55, 0xE8, 0x01, 0x00, 0x00, 0x00, 0xC3, ];
let streams = bcj2_encode(&data);
assert!(streams.main.contains(&0x55));
assert!(streams.main.contains(&0xE8));
assert!(streams.main.contains(&0xC3));
assert_eq!(streams.call.len(), 4);
}
#[test]
fn test_encoder_with_jmp() {
let data = vec![
0x90, 0xE9, 0x05, 0x00, 0x00, 0x00, 0xC3, ];
let streams = bcj2_encode(&data);
assert_eq!(streams.jump.len(), 4);
assert!(streams.call.is_empty());
}
#[test]
fn test_encoder_defaults() {
let encoder = Bcj2Encoder::new();
assert!(encoder.main.is_empty());
assert!(encoder.call.is_empty());
assert!(encoder.jump.is_empty());
assert_eq!(encoder.position, 0);
}
#[test]
fn test_range_encoder_initialization() {
let encoder = RangeEncoder::new();
assert_eq!(encoder.range, 0xFFFFFFFF);
assert_eq!(encoder.low, 0);
}
#[test]
fn test_streams_total_size() {
let streams = Bcj2EncodedStreams {
main: vec![1, 2, 3],
call: vec![4, 5, 6, 7],
jump: vec![],
range: vec![8, 9],
};
assert_eq!(streams.total_size(), 9);
}
#[test]
fn test_roundtrip_no_jumps() {
let original = vec![0x55, 0x89, 0xE5, 0x5D, 0xC3];
let streams = bcj2_encode(&original);
let mut decoder = Bcj2Decoder::new(
Cursor::new(streams.main),
Cursor::new(streams.call),
Cursor::new(streams.jump),
Cursor::new(streams.range),
)
.unwrap();
let mut decoded = Vec::new();
decoder.read_to_end(&mut decoded).unwrap();
assert_eq!(decoded, original);
}
#[test]
fn test_roundtrip_with_calls() {
let mut original = Vec::new();
for i in 0..100 {
original.push(0xE8); let offset = (i * 0x1000) as u32;
original.extend_from_slice(&offset.to_le_bytes());
original.extend_from_slice(&[0x90, 0x90, 0x90]); }
let streams = bcj2_encode(&original);
let mut decoder = Bcj2Decoder::new(
Cursor::new(streams.main),
Cursor::new(streams.call),
Cursor::new(streams.jump),
Cursor::new(streams.range),
)
.unwrap();
let mut decoded = Vec::new();
decoder.read_to_end(&mut decoded).unwrap();
assert_eq!(decoded.len(), original.len());
assert_eq!(decoded, original);
}
#[test]
fn test_status_encoder_adaptation() {
let mut encoder = StatusEncoder::new();
let mut re = RangeEncoder::new();
let initial = encoder.prob;
assert_eq!(initial, range::INITIAL_PROB);
encoder.encode(&mut re, false);
assert!(encoder.prob > initial);
let after_zero = encoder.prob;
encoder.encode(&mut re, true);
assert!(encoder.prob < after_zero);
}
#[test]
fn test_range_coder_single_bit() {
let mut re = RangeEncoder::new();
let _new_prob = re.encode_bit(true, range::INITIAL_PROB);
let bytes = re.finish();
let mut rd = RangeDecoder::new(Cursor::new(bytes)).unwrap();
let (bit, _) = rd.decode_bit(range::INITIAL_PROB).unwrap();
assert_eq!(bit, 1, "Decoded bit should be 1");
}
#[test]
fn test_range_coder_multiple_ones() {
let mut re = RangeEncoder::new();
let mut prob = range::INITIAL_PROB;
for _ in 0..10 {
prob = re.encode_bit(true, prob);
}
let bytes = re.finish();
let mut rd = RangeDecoder::new(Cursor::new(bytes)).unwrap();
let mut prob = range::INITIAL_PROB;
for i in 0..10 {
let (bit, new_prob) = rd.decode_bit(prob).unwrap();
assert_eq!(bit, 1, "Bit {} should be 1", i);
prob = new_prob;
}
}
}
}
#[cfg(test)]
mod integration_tests {
use super::*;
use std::io::Cursor;
#[cfg(feature = "lzma")]
use std::path::Path;
#[test]
fn test_method_id_constant() {
assert_eq!(METHOD_ID, &[0x03, 0x03, 0x01, 0x1B]);
let id_u64 = METHOD_ID.iter().fold(0u64, |acc, &b| (acc << 8) | b as u64);
assert_eq!(id_u64, 0x0303011B);
}
#[cfg(feature = "lzma")]
#[test]
fn test_bcj2_archive_structure() {
let archive_path = Path::new("tests/data/bcj2/7za433_7zip_lzma2_bcj2.7z");
if !archive_path.exists() {
eprintln!("Test archive not found: {:?}", archive_path);
return;
}
let archive = crate::Archive::open_path(archive_path).unwrap();
let entries = archive.entries();
assert!(!entries.is_empty(), "Archive should have entries");
let info = archive.info();
println!("Archive info:");
println!(" Entry count: {}", info.entry_count);
println!(" Total size: {}", info.total_size);
println!(" Packed size: {}", info.packed_size);
println!(" Is solid: {}", info.is_solid);
println!(" Compression methods: {:?}", info.compression_methods);
for entry in entries {
println!(
" Entry: {} ({} bytes, encrypted={})",
entry.path.as_str(),
entry.size,
entry.is_encrypted
);
}
}
#[cfg(feature = "lzma")]
#[test]
fn test_bcj2_archive_opens() {
let archive_path = Path::new("tests/data/bcj2/7za433_7zip_lzma2_bcj2.7z");
if !archive_path.exists() {
eprintln!("Skipping: test archive not found");
return;
}
let archive = crate::Archive::open_path(archive_path).unwrap();
assert!(!archive.is_empty(), "Archive should have entries");
println!("Archive contains {} entries:", archive.len());
for entry in archive.entries() {
println!(
" {} ({} bytes, dir={})",
entry.path.as_str(),
entry.size,
entry.is_directory
);
}
let info = archive.info();
println!("Folder count: {}", info.folder_count);
println!("Compression methods: {:?}", info.compression_methods);
}
#[cfg(feature = "lzma")]
#[test]
fn test_sevenzip_bcj2_archive_opens() {
let archive_path = Path::new("tests/data/bcj2/bcj2.7z");
if !archive_path.exists() {
eprintln!("Skipping: test archive not found");
return;
}
let archive = crate::Archive::open_path(archive_path).unwrap();
assert!(!archive.is_empty(), "Archive should have entries");
println!("Archive contains {} entries:", archive.len());
for entry in archive.entries() {
println!(" {} ({} bytes)", entry.path.as_str(), entry.size);
}
}
#[cfg(feature = "lzma")]
#[test]
fn test_bcj2_archive_extraction() {
let archive_path = Path::new("tests/data/bcj2/7za433_7zip_lzma2_bcj2.7z");
if !archive_path.exists() {
eprintln!("Test archive not found: {:?}", archive_path);
return;
}
let mut archive = crate::Archive::open_path(archive_path).unwrap();
let entries: Vec<_> = archive.entries().to_vec();
for entry in entries {
if !entry.is_directory {
println!("Extracting: {}", entry.path.as_str());
let data = archive.extract_to_vec(entry.path.as_str()).unwrap();
assert!(!data.is_empty(), "Extracted data should not be empty");
println!(" Size: {} bytes", data.len());
}
}
}
#[cfg(feature = "lzma")]
#[test]
fn test_sevenzip_bcj2_archive() {
let archive_path = Path::new("tests/data/bcj2/bcj2.7z");
if !archive_path.exists() {
eprintln!("Test archive not found: {:?}", archive_path);
return;
}
let mut archive = crate::Archive::open_path(archive_path).unwrap();
let entries: Vec<_> = archive.entries().to_vec();
for entry in entries {
if !entry.is_directory {
println!("Extracting: {}", entry.path.as_str());
let data = archive.extract_to_vec(entry.path.as_str()).unwrap();
assert!(!data.is_empty(), "Extracted data should not be empty");
}
}
}
#[test]
fn test_bcj2_x86_reconstruction() {
let main = vec![0x55, 0x89, 0xE5, 0xE8, 0x5D, 0xC3]; let call = vec![]; let jump = vec![];
let range = vec![0x00, 0x00, 0x00, 0x00, 0x00];
let mut decoder = Bcj2Decoder::new(
Cursor::new(main.clone()),
Cursor::new(call),
Cursor::new(jump),
Cursor::new(range),
)
.unwrap();
let mut output = Vec::new();
decoder.read_to_end(&mut output).unwrap();
assert_eq!(output, main);
}
#[cfg(feature = "lzma")]
#[test]
fn test_simple_bcj2_archive() {
let archive_path = Path::new("tests/data/bcj2/simple_bcj2.7z");
if !archive_path.exists() {
eprintln!("Skipping: test archive not found at {:?}", archive_path);
return;
}
let mut archive = crate::Archive::open_path(archive_path).expect("Failed to open archive");
assert!(!archive.is_empty(), "Archive should have entries");
let entries: Vec<_> = archive.entries().to_vec();
for entry in entries {
if !entry.is_directory {
let data = archive
.extract_to_vec(entry.path.as_str())
.expect("Failed to extract file");
if entry.path.as_str().ends_with("test.txt") {
let content = String::from_utf8_lossy(&data);
assert!(
content.contains("Hello World"),
"Expected 'Hello World' in content, got: {}",
content
);
}
}
}
}
}