1use binrw::BinReaderExt;
2use std::io;
3
4#[derive(Debug, thiserror::Error)]
5pub enum Error {
6 #[error(transparent)]
7 Io(#[from] io::Error),
8
9 #[error(transparent)]
10 BinRw(#[from] binrw::Error),
11
12 #[error("Input data did not produce enough data and is likely cut-off or incomplete")]
13 NotEnoughInstructions,
14
15 #[error("Input would have produced more data than expected and was aborted")]
16 TooMuchOutput,
17
18 #[error("Invalid reference")]
19 BookmarkHasNotBeenSet,
20
21 #[error("Referenced dictionary entry does not exist")]
22 DictionaryOutOfBounds,
23
24 #[error("Unknown tree encoding")]
25 TreeEncodingUnknown,
26
27 #[error("Embedded tree is not properly encoded")]
28 InvalidTree,
29}
30
31struct CheckpointMap {
32 storage: [Option<usize>; 0x1000],
33 next: u8,
34}
35
36impl CheckpointMap {
37 fn new() -> CheckpointMap {
38 Self {
39 storage: [None; 0x1000],
40 next: 0,
41 }
42 }
43
44 fn add(&mut self, key: u16, value: usize) {
45 let index = self.next as usize + key as usize;
46 self.next = self.next.wrapping_add(1);
47 self.storage[index] = Some(value);
48 }
49
50 fn get(&mut self, key: u16) -> Option<usize> {
51 self.storage[key as usize]
52 }
53}
54
55enum Command {
56 Literal,
57 Reference,
58}
59
60struct CommandBuffer {
61 value: u16,
62 bits: u8,
63}
64
65impl CommandBuffer {
66 #[inline]
67 pub fn new() -> Self {
68 Self { value: 0, bits: 0 }
69 }
70
71 #[inline]
72 pub fn next(&mut self, mut reader: impl io::Read + io::Seek) -> Result<Command, Error> {
73 if self.is_empty() {
74 match reader.read_le() {
75 Ok(value) => {
76 self.value = value;
77 self.bits = 16
78 }
79 Err(e) if e.is_eof() => return Err(Error::NotEnoughInstructions),
80 Err(e) => return Err(Error::BinRw(e)),
81 }
82 }
83
84 let bitset = (self.value & 1) != 0;
85 self.bits -= 1;
86 self.value >>= 1;
87 Ok(if bitset {
88 Command::Reference
89 } else {
90 Command::Literal
91 })
92 }
93
94 #[inline]
95 fn is_empty(&self) -> bool {
96 self.bits == 0
97 }
98}
99
100pub fn decompress(
101 uncompressed_len: usize,
102 mut input: impl io::Read + io::Seek,
103) -> Result<Vec<u8>, Error> {
104 let mut output = vec![0u8; uncompressed_len];
105 let mut output_ptr: usize = 0;
106 let mut bookmark = CheckpointMap::new();
107 let mut cmd = CommandBuffer::new();
108
109 let mut streak = 0;
110
111 fn hash(output: &[u8], i: usize) -> u16 {
112 (output[i]
113 .wrapping_add(output[i - 1])
114 .wrapping_add(output[i - 2])
115 & 0x1e)
116 .cast_signed() as u16
117 * 0x80
118 }
119
120 loop {
121 if output_ptr == output.len() {
122 return Ok(output);
123 }
124
125 match cmd.next(&mut input)? {
126 Command::Literal => {
127 output[output_ptr] = input.read_be()?;
128 output_ptr += 1;
129 streak += 1;
130
131 if streak == 3 {
132 let key = hash(&output, output_ptr - 1);
133 bookmark.add(key, output_ptr - 3);
134 streak = 2;
135 }
136 }
137 Command::Reference => {
138 let [b1, b2]: [u8; 2] = input.read_le()?;
139 let [b1, b2] = [b1 as u16, b2 as u16];
140 let count = (b1 & 0x0F) as usize + 3;
141 if output_ptr + count >= output.len() {
142 return Err(Error::TooMuchOutput);
143 }
144
145 let key = ((b1 & 0xF0) << 4) | b2;
146 if let Some(offset) = bookmark.get(key) {
147 for i in 0..(count) {
148 output[output_ptr + i] = output[offset + i];
149 }
150 } else {
151 output[output_ptr..(output_ptr + count)].fill(0x20);
152 }
153
154 if streak > 0 {
155 let key = hash(&output, output_ptr - streak + 2);
156 bookmark.add(key, output_ptr - streak);
157 }
158
159 if streak == 2 {
160 let key = hash(&output, output_ptr + 1);
161 bookmark.add(key, output_ptr - 1);
162 }
163
164 bookmark.add((b1 & 0xF0) << 4, output_ptr);
165 output_ptr += count;
166 streak = 0;
167 }
168 }
169 }
170}