rar_stream/decompress/mod.rs
1//! RAR decompression algorithms.
2//!
3//! This module provides decompression support for RAR archives, implementing
4//! the LZSS + Huffman and PPMd algorithms used by RAR 2.9-5.x.
5//!
6//! ## Decoders
7//!
8//! | Decoder | Format | Algorithms |
9//! |---------|--------|------------|
10//! | [`Rar29Decoder`] | RAR 2.9-4.x | LZSS + Huffman, PPMd, VM filters |
11//! | [`Rar5Decoder`] | RAR 5.0+ | LZSS + Huffman, byte filters |
12//!
13//! ## Compression Methods
14//!
15//! RAR uses a single byte to identify the compression method:
16//!
17//! | Value | Name | Description |
18//! |-------|------|-------------|
19//! | `0x30` | Store | No compression (data is stored as-is) |
20//! | `0x31` | Fastest | LZSS with minimal dictionary |
21//! | `0x32` | Fast | LZSS with small dictionary |
22//! | `0x33` | Normal | LZSS with medium dictionary (default) |
23//! | `0x34` | Good | LZSS with large dictionary |
24//! | `0x35` | Best | LZSS with maximum dictionary |
25//!
26//! ## Filter Support
27//!
28//! RAR applies preprocessing filters before compression to improve ratios:
29//!
30//! | Filter | RAR4 | RAR5 | Description |
31//! |--------|------|------|-------------|
32//! | Delta | ✅ | ✅ | Byte delta encoding (audio, images) |
33//! | E8/E8E9 | ✅ | ✅ | x86 CALL/JMP instruction preprocessing |
34//! | ARM | — | ✅ | ARM branch instruction preprocessing |
35//! | Audio | ✅ | — | Multi-channel audio predictor |
36//! | RGB | ✅ | — | Predictive color filter (images) |
37//!
38//! ## Example
39//!
40//! ```rust
41//! use rar_stream::Rar29Decoder;
42//!
43//! // Create a new decoder (reusable for multiple files)
44//! let mut decoder = Rar29Decoder::new();
45//!
46//! // Decompress data (compressed_data from file header's data area)
47//! // let decompressed = decoder.decompress(&compressed_data, expected_size)?;
48//! ```
49//!
50//! ## Architecture
51//!
52//! The decompression pipeline:
53//!
54//! ```text
55//! Compressed Data
56//! ↓
57//! ┌─────────────┐
58//! │ BitReader │ ← Bit-level access to compressed stream
59//! └─────────────┘
60//! ↓
61//! ┌─────────────┐
62//! │ Huffman │ ← Decode variable-length symbols
63//! └─────────────┘
64//! ↓
65//! ┌─────────────┐
66//! │ LZSS/PPMd │ ← Expand literals and back-references
67//! └─────────────┘
68//! ↓
69//! ┌─────────────┐
70//! │ Filters │ ← Apply inverse preprocessing (E8, Delta, etc.)
71//! └─────────────┘
72//! ↓
73//! Decompressed Data
74//! ```
75//!
76//! ## Performance Notes
77//!
78//! - Decoders are reusable and maintain internal state
79//! - Window size is 4MB for RAR4, up to 4GB for RAR5
80//! - PPMd uses significant memory (~100MB for order-8 model)
81
82mod bit_reader;
83pub(crate) mod byte_search;
84mod huffman;
85mod lzss;
86mod ppm;
87mod rar29;
88pub mod rar5;
89mod vm;
90
91#[cfg(test)]
92mod tests;
93
94pub use bit_reader::BitReader;
95pub use huffman::{HuffmanDecoder, HuffmanTable};
96pub use lzss::LzssDecoder;
97pub use ppm::PpmModel;
98pub use rar29::Rar29Decoder;
99pub use rar5::Rar5Decoder;
100pub use vm::RarVM;
101
102use std::fmt;
103use std::io;
104
105/// Decompression errors.
106#[derive(Debug)]
107pub enum DecompressError {
108 UnexpectedEof,
109 InvalidHuffmanCode,
110 InvalidBackReference { offset: u32, position: u32 },
111 BufferOverflow,
112 UnsupportedMethod(u8),
113 IncompleteData,
114 Io(io::Error),
115}
116
117impl fmt::Display for DecompressError {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 match self {
120 Self::UnexpectedEof => write!(f, "Unexpected end of data"),
121 Self::InvalidHuffmanCode => write!(f, "Invalid Huffman code"),
122 Self::InvalidBackReference { offset, position } => {
123 write!(
124 f,
125 "Invalid back reference: offset {} exceeds window position {}",
126 offset, position
127 )
128 }
129 Self::BufferOverflow => write!(f, "Decompression buffer overflow"),
130 Self::UnsupportedMethod(m) => write!(f, "Unsupported compression method: {}", m),
131 Self::IncompleteData => write!(f, "Incomplete compressed data"),
132 Self::Io(e) => write!(f, "I/O error: {}", e),
133 }
134 }
135}
136
137impl std::error::Error for DecompressError {
138 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
139 match self {
140 Self::Io(e) => Some(e),
141 _ => None,
142 }
143 }
144}
145
146impl From<io::Error> for DecompressError {
147 fn from(e: io::Error) -> Self {
148 Self::Io(e)
149 }
150}
151
152pub type Result<T> = std::result::Result<T, DecompressError>;
153
154/// Compression methods used in RAR.
155///
156/// ```
157/// use rar_stream::CompressionMethod;
158///
159/// let method = CompressionMethod::from_u8(0x33);
160/// assert_eq!(method, Some(CompressionMethod::Normal));
161///
162/// assert_eq!(CompressionMethod::from_u8(0xFF), None);
163/// ```
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
165#[repr(u8)]
166pub enum CompressionMethod {
167 /// Store (no compression)
168 Store = 0x30,
169 /// Fastest compression
170 Fastest = 0x31,
171 /// Fast compression
172 Fast = 0x32,
173 /// Normal compression
174 Normal = 0x33,
175 /// Good compression
176 Good = 0x34,
177 /// Best compression
178 Best = 0x35,
179}
180
181impl CompressionMethod {
182 /// Convert a raw byte to a compression method, if valid.
183 pub fn from_u8(v: u8) -> Option<Self> {
184 match v {
185 0x30 => Some(Self::Store),
186 0x31 => Some(Self::Fastest),
187 0x32 => Some(Self::Fast),
188 0x33 => Some(Self::Normal),
189 0x34 => Some(Self::Good),
190 0x35 => Some(Self::Best),
191 _ => None,
192 }
193 }
194
195 /// Whether this method requires decompression.
196 pub fn needs_decompression(&self) -> bool {
197 *self != Self::Store
198 }
199}