banished_formats/
stream.rs1use crate::error::{format_err, Result};
11use crate::lzf;
12
13pub const BLOCK_SIZE: usize = 0x8000;
15
16pub fn decode(buf: &[u8]) -> Result<Vec<u8>> {
18 let rd = |at: usize, n: usize| -> Result<&[u8]> {
19 buf.get(at..at + n)
20 .ok_or(crate::Error::Truncated { at, wanted: n })
21 };
22 let count = u32::from_le_bytes(rd(0, 4)?.try_into().unwrap()) as usize;
23 let table_end = 4usize
24 .checked_add(
25 count
26 .checked_mul(8)
27 .ok_or(crate::Error::Format("block count overflow".into()))?,
28 )
29 .unwrap_or(usize::MAX);
30 if table_end > buf.len() {
31 return format_err("block table exceeds stream");
32 }
33 let mut blocks = Vec::with_capacity(count);
34 let mut total = table_end;
35 for k in 0..count {
36 let e = rd(4 + 8 * k, 8)?;
37 let offset = u32::from_le_bytes(e[0..4].try_into().unwrap()) as usize;
38 let comp = u16::from_le_bytes(e[4..6].try_into().unwrap()) as usize;
39 let raw = u16::from_le_bytes(e[6..8].try_into().unwrap()) as usize;
40 total += if comp != 0 { comp } else { raw };
41 blocks.push((offset, comp, raw));
42 }
43 if total != buf.len() {
44 return format_err(format!(
45 "stream size mismatch: table says {total}, have {}",
46 buf.len()
47 ));
48 }
49 let mut out = Vec::with_capacity(blocks.iter().map(|b| b.2).sum());
50 for (offset, comp, raw) in blocks {
51 if comp != 0 {
52 lzf::decompress_into(rd(offset, comp)?, &mut out, BLOCK_SIZE)?;
53 } else {
54 out.extend_from_slice(rd(offset, raw)?);
55 }
56 }
57 Ok(out)
58}
59
60pub fn encode(raw: &[u8]) -> Vec<u8> {
63 let blocks: Vec<(Vec<u8>, usize)> = raw
64 .chunks(BLOCK_SIZE)
65 .map(|b| {
66 let packed = lzf::compress(b);
67 if packed.len() < b.len() {
68 (packed, b.len())
69 } else {
70 (Vec::new(), b.len())
71 }
72 })
73 .collect();
74 let mut out = Vec::new();
75 out.extend_from_slice(&(blocks.len() as u32).to_le_bytes());
76 let mut offset = 4 + 8 * blocks.len();
77 for (packed, len) in &blocks {
78 out.extend_from_slice(&(offset as u32).to_le_bytes());
79 out.extend_from_slice(&(packed.len() as u16).to_le_bytes());
80 out.extend_from_slice(&(*len as u16).to_le_bytes());
81 offset += if packed.is_empty() {
82 *len
83 } else {
84 packed.len()
85 };
86 }
87 for (chunk, (packed, _)) in raw.chunks(BLOCK_SIZE).zip(&blocks) {
88 out.extend_from_slice(if packed.is_empty() { chunk } else { packed });
89 }
90 out
91}