1use anyhow::{Result, anyhow, bail};
2
3#[inline]
8fn parse_header(src: &[u8]) -> Result<(usize, usize)> {
9 if src.len() < 8 {
10 bail!("lzss: input too short");
11 }
12 let arc_size = u32::from_le_bytes([src[0], src[1], src[2], src[3]]) as usize;
13 let org_size = u32::from_le_bytes([src[4], src[5], src[6], src[7]]) as usize;
14 Ok((arc_size, org_size))
15}
16
17pub fn lzss_decompressed_size(src: &[u8]) -> Result<usize> {
19 let (_, org_size) = parse_header(src)?;
20 Ok(org_size)
21}
22
23pub fn lzss_unpack(src: &[u8]) -> Result<Vec<u8>> {
26 let (arc_size, org_size) = parse_header(src)?;
27 if org_size == 0 {
28 bail!("lzss: org_size=0");
29 }
30
31 let payload_start = 8usize;
32 let payload_end = arc_size;
33 if payload_end < payload_start || payload_end > src.len() {
34 bail!(
35 "lzss: arc_size out of bounds (end={}, len={})",
36 payload_end,
37 src.len()
38 );
39 }
40
41 let mut pos = payload_start;
42 let mut out: Vec<u8> = Vec::with_capacity(org_size.min(src.len().saturating_mul(18)));
44
45 while out.len() < org_size {
46 if pos >= payload_end {
47 break;
48 }
49 let mut flags = src[pos];
50 pos += 1;
51
52 for _ in 0..8 {
53 if out.len() >= org_size {
54 break;
55 }
56 if pos >= payload_end {
57 break;
58 }
59
60 if (flags & 1) != 0 {
61 out.push(src[pos]);
63 pos += 1;
64 } else {
65 if pos + 2 > payload_end {
66 bail!("lzss: truncated backref token");
67 }
68 let token = u16::from_le_bytes([src[pos], src[pos + 1]]);
69 pos += 2;
70
71 let offset = (token >> 4) as usize;
72 let len = ((token & 0x0F) as usize) + 2;
74
75 if offset == 0 {
76 bail!("lzss: invalid backref offset=0");
77 }
78 if offset > out.len() {
79 bail!(
80 "lzss: backref offset out of range (offset={}, out_len={})",
81 offset,
82 out.len()
83 );
84 }
85
86 let mut src_idx = out.len() - offset;
87 for _ in 0..len {
88 if out.len() >= org_size {
89 break;
90 }
91 let b = out[src_idx];
92 out.push(b);
93 src_idx += 1;
94 }
95 }
96
97 flags >>= 1;
98 }
99 }
100
101 if out.len() != org_size {
102 bail!(
103 "lzss: size mismatch (got={}, expected={})",
104 out.len(),
105 org_size
106 );
107 }
108
109 Ok(out)
110}
111
112pub fn lzss_unpack_lenient(src: &[u8]) -> Result<Vec<u8>> {
117 let (arc_size, org_size) = parse_header(src)?;
118 if org_size == 0 {
119 bail!("lzss(lenient): org_size=0");
120 }
121
122 let payload_start = 8usize;
123 let payload_end = arc_size.min(src.len());
124
125 let mut pos = payload_start;
126 let mut out: Vec<u8> = Vec::with_capacity(org_size.min(src.len().saturating_mul(18)));
128
129 while out.len() < org_size {
130 if pos >= payload_end {
131 break;
132 }
133 let mut flags = src[pos];
134 pos += 1;
135
136 for _ in 0..8 {
137 if out.len() >= org_size {
138 break;
139 }
140 if pos >= payload_end {
141 break;
142 }
143
144 if (flags & 1) != 0 {
145 out.push(src[pos]);
146 pos += 1;
147 } else {
148 if pos + 2 > payload_end {
149 break;
150 }
151 let token = u16::from_le_bytes([src[pos], src[pos + 1]]);
152 pos += 2;
153
154 let offset = (token >> 4) as usize;
155 let len = ((token & 0x0F) as usize) + 2;
156
157 if offset == 0 || offset > out.len() {
158 break;
160 }
161
162 let mut src_idx = out.len() - offset;
163 for _ in 0..len {
164 if out.len() >= org_size {
165 break;
166 }
167 let b = out[src_idx];
168 out.push(b);
169 src_idx += 1;
170 }
171 }
172
173 flags >>= 1;
174 }
175 }
176
177 if out.len() != org_size {
178 bail!(
179 "lzss(lenient): size mismatch (got={}, expected={})",
180 out.len(),
181 org_size
182 );
183 }
184
185 Ok(out)
186}
187
188pub fn lzss_unpack32(src: &[u8]) -> Result<Vec<u8>> {
192 let (arc_size, org_size) = parse_header(src)?;
193 if org_size == 0 {
194 bail!("lzss32: org_size=0");
195 }
196 if org_size % 4 != 0 {
197 bail!("lzss32: org_size not multiple of 4 (org_size={})", org_size);
198 }
199
200 let payload_start = 8usize;
201 let payload_end = arc_size;
202 if payload_end < payload_start || payload_end > src.len() {
203 bail!(
204 "lzss32: arc_size out of bounds (end={}, len={})",
205 payload_end,
206 src.len()
207 );
208 }
209
210 let mut pos = payload_start;
211 let mut out: Vec<u8> = Vec::with_capacity(org_size.min(src.len().saturating_mul(18)));
213
214 while out.len() < org_size {
215 if pos >= payload_end {
216 break;
217 }
218 let mut flags = src[pos];
219 pos += 1;
220
221 for _ in 0..8 {
222 if out.len() >= org_size {
223 break;
224 }
225
226 if (flags & 1) != 0 {
227 if pos + 3 > payload_end {
229 bail!("lzss32: truncated literal");
230 }
231 out.push(src[pos]);
232 out.push(src[pos + 1]);
233 out.push(src[pos + 2]);
234 out.push(255);
235 pos += 3;
236 } else {
237 if pos + 2 > payload_end {
239 bail!("lzss32: truncated backref token");
240 }
241 let token = u16::from_le_bytes([src[pos], src[pos + 1]]);
242 pos += 2;
243
244 let offset_dwords = (token >> 4) as usize;
245 let len_dwords = ((token & 0x0F) as usize) + 1;
246
247 if offset_dwords == 0 {
248 bail!("lzss32: invalid backref offset=0");
249 }
250
251 let offset_bytes = offset_dwords
252 .checked_mul(4)
253 .ok_or_else(|| anyhow!("lzss32: offset overflow"))?;
254
255 if offset_bytes > out.len() {
256 bail!(
257 "lzss32: backref offset out of range (offset_bytes={}, out_len={})",
258 offset_bytes,
259 out.len()
260 );
261 }
262
263 let mut src_idx = out.len() - offset_bytes;
264 for _ in 0..len_dwords {
265 if out.len() + 4 > org_size {
266 break;
267 }
268 let d0 = out[src_idx];
270 let d1 = out[src_idx + 1];
271 let d2 = out[src_idx + 2];
272 let d3 = out[src_idx + 3];
273 out.push(d0);
274 out.push(d1);
275 out.push(d2);
276 out.push(d3);
277 src_idx += 4;
278 }
279 }
280
281 flags >>= 1;
282 if pos > payload_end {
283 break;
284 }
285 }
286 }
287
288 if out.len() != org_size {
289 bail!(
290 "lzss32: size mismatch (got={}, expected={})",
291 out.len(),
292 org_size
293 );
294 }
295
296 Ok(out)
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302
303 fn archive(payload: &[u8], org_size: u32) -> Vec<u8> {
304 let mut src = Vec::new();
305 src.extend_from_slice(&(8 + payload.len() as u32).to_le_bytes());
306 src.extend_from_slice(&org_size.to_le_bytes());
307 src.extend_from_slice(payload);
308 src
309 }
310
311 #[test]
312 fn archive_size_includes_header() {
313 let src = archive(&[0x03, b'a', b'b', 0x22, 0x00], 6);
315 assert_eq!(lzss_unpack(&src).unwrap(), b"ababab");
316 assert_eq!(lzss_unpack_lenient(&src).unwrap(), b"ababab");
317
318 let src = archive(&[0x01, 10, 20, 30, 0x11, 0x00], 12);
319 assert_eq!(lzss_unpack32(&src).unwrap(), [10, 20, 30, 255].repeat(3));
320 }
321
322 #[test]
323 fn archive_boundary_excludes_trailing_bytes() {
324 let mut src = archive(&[0x01], 1);
325 src.push(b'a');
326 assert!(lzss_unpack(&src).is_err());
327 assert!(lzss_unpack_lenient(&src).is_err());
328
329 let mut src = archive(&[0x01, 10, 20], 4);
330 src.push(30);
331 assert!(lzss_unpack32(&src).is_err());
332 }
333
334 #[test]
335 fn strict_decoders_reject_invalid_archive_sizes() {
336 for arc_size in [0u32, 7, 100] {
337 let mut src = archive(&[0x01, 10, 20, 30], 4);
338 src[..4].copy_from_slice(&arc_size.to_le_bytes());
339 assert!(lzss_unpack(&src).is_err());
340 assert!(lzss_unpack32(&src).is_err());
341 }
342 }
343}