weakauras_codec_lib_compress/
lib.rs

1// Code extraction algorithm is based on code from LibCompress
2// https://www.curseforge.com/wow/addons/libcompress
3// Copyright 2008-2018 jjsheets, Galmok
4// Copyright 2020-2025 Velithris
5// SPDX-License-Identifier: GPL-2.0-only
6
7//! This library provides a routine for decompressing data
8//! compressed by a Lua library called LibCompress.
9//!
10//! # Example
11//!
12//! ```
13//! use weakauras_codec_lib_compress::{DecompressionError, decompress};
14//!
15//! fn main() -> Result<(), DecompressionError> {
16//!     let expected = b"aaaaaaaa bbbbbbbb cccccccc";
17//!
18//!     // Huffman code
19//!     assert_eq!(
20//!         &*decompress(
21//!             &[
22//!                 0x03, 0x03, 0x1a, 0x00, 0x00, 0x62, 0x0c, 0x52, 0x8f,
23//!                 0xe9, 0xb0, 0x5c, 0x55, 0x35, 0x00, 0xc0, 0xaa, 0xaa
24//!             ],
25//!             1024
26//!         )?,
27//!         expected
28//!     );
29//!
30//!     // Uncompressed
31//!     assert_eq!(
32//!         &*decompress(b"\x01aaaaaaaa bbbbbbbb cccccccc", 1024)?,
33//!         expected
34//!     );
35//!
36//!     Ok(())
37//! }
38//! ```
39
40#![forbid(unsafe_code)]
41#![deny(missing_docs)]
42
43mod bitfield;
44/// Error types.
45pub mod error;
46mod lookup_table;
47mod utils;
48
49pub use error::*;
50
51use self::bitfield::Bitfield;
52use lookup_table::{TableData, build_lookup_table};
53use std::borrow::Cow;
54use utils::{get_code, unescape_code};
55
56/// Decompress `input` compressed by LibCompress.
57///
58/// # Example
59///
60/// ```
61/// use weakauras_codec_lib_compress::{DecompressionError, decompress};
62///
63/// fn main() -> Result<(), DecompressionError> {
64///     let expected = b"aaaaaaaa bbbbbbbb cccccccc";
65///
66///     // Huffman code
67///     assert_eq!(
68///         &*decompress(
69///             &[
70///                 0x03, 0x03, 0x1a, 0x00, 0x00, 0x62, 0x0c, 0x52, 0x8f,
71///                 0xe9, 0xb0, 0x5c, 0x55, 0x35, 0x00, 0xc0, 0xaa, 0xaa
72///             ],
73///             1024
74///         )?,
75///         expected
76///     );
77///
78///     // Uncompressed
79///     assert_eq!(
80///         &*decompress(b"\x01aaaaaaaa bbbbbbbb cccccccc", 1024)?,
81///         expected
82///     );
83///
84///     Ok(())
85/// }
86/// ```
87pub fn decompress(input: &[u8], max_size: usize) -> Result<Cow<'_, [u8]>, DecompressionError> {
88    let mut iter = input.iter();
89    match iter.next() {
90        Some(1) => return Ok(Cow::from(&input[1..])),
91        Some(3) => {}
92        _ => return Err(DecompressionError::InvalidPrefix),
93    }
94
95    let len = input.len();
96    if len < 5 {
97        return Err(DecompressionError::InputIsTooSmall);
98    }
99
100    let num_symbols = iter
101        .next()
102        .unwrap()
103        .checked_add(1)
104        .ok_or(DecompressionError::InvalidData)?;
105
106    let original_size = iter
107        .by_ref()
108        .take(3)
109        .map(|&byte| usize::from(byte))
110        .enumerate()
111        .fold(0, |acc, (i, byte)| acc + (byte << (i * 8)));
112
113    if original_size == 0 {
114        return Err(DecompressionError::InputIsTooSmall);
115    }
116
117    if original_size > max_size {
118        return Err(DecompressionError::DataExceedsMaxSize);
119    }
120
121    let mut codes = Vec::with_capacity(num_symbols as usize);
122    let mut result = Vec::with_capacity(original_size);
123
124    let mut bitfield = Bitfield::new();
125
126    let mut min_code_len = u8::MAX;
127    let mut max_code_len = u8::MIN;
128
129    // Code extraction:
130    for _ in 0..num_symbols {
131        let symbol = bitfield
132            .insert_and_extract_byte(*iter.next().ok_or(DecompressionError::UnexpectedEof)?);
133
134        loop {
135            bitfield
136                .insert(*iter.next().ok_or(DecompressionError::UnexpectedEof)?)
137                .map_err(|_| DecompressionError::InvalidData)?;
138
139            if let Some(v) = get_code(&mut bitfield)? {
140                let (code, code_len) = unescape_code(v.0, v.1);
141                min_code_len = core::cmp::min(min_code_len, code_len);
142                max_code_len = core::cmp::max(max_code_len, code_len);
143
144                codes.push((code, code_len, symbol));
145
146                break;
147            }
148        }
149    }
150    codes.sort_unstable_by(|a, b| a.1.cmp(&b.1).then_with(|| a.0.cmp(&b.0)));
151
152    // Decompression:
153    let lut = build_lookup_table(&codes)?;
154
155    loop {
156        bitfield.fill_from_iterator(&mut iter);
157        let original_len = bitfield.get_len();
158
159        if bitfield.get_len() >= min_code_len {
160            let mut cursor = &lut[(bitfield.peek_byte()) as usize];
161
162            if bitfield.get_len() < cursor.code_length {
163                break;
164            }
165
166            let mut new_bitfield = bitfield;
167            while new_bitfield.get_len() >= cursor.code_length {
168                if cursor.code_length == 0 {
169                    return Err(DecompressionError::InvalidData);
170                }
171
172                match cursor.data {
173                    TableData::Reference(ref v) => {
174                        new_bitfield.discard_bits(cursor.code_length);
175                        cursor = &v[(new_bitfield.peek_byte()) as usize];
176                    }
177                    TableData::Symbol(s) => {
178                        result.push(s);
179                        if result.len() == original_size {
180                            return Ok(Cow::from(result));
181                        }
182
183                        bitfield = new_bitfield;
184                        bitfield.discard_bits(cursor.code_length);
185                        break;
186                    }
187                }
188            }
189        } else {
190            break;
191        }
192
193        if bitfield.get_len() == original_len {
194            return Err(DecompressionError::InvalidData);
195        }
196    }
197
198    if result.len() == original_size {
199        Ok(Cow::from(result))
200    } else {
201        Err(DecompressionError::InvalidData)
202    }
203}