Skip to main content

banished_formats/
stream.rs

1//! Block streams (`IO::CompressedStream`): a table of up-to-32 KiB blocks, each LZF or stored.
2//!
3//! ```text
4//! u32 n
5//! n x { u32 offset, u16 compSize (0 = stored), u16 rawSize }
6//! blocks...
7//! ```
8//! `IO::CompressedStream::Open` requires `4 + 8n + sum(compSize or rawSize) == len`.
9
10use crate::error::{format_err, Result};
11use crate::lzf;
12
13/// The largest expanded block, 32 KiB.
14pub const BLOCK_SIZE: usize = 0x8000;
15
16/// Expand a block stream.
17pub 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
60/// The writing side of [`decode`]: `raw` in 32 KiB blocks, each LZF unless storing it is no
61/// larger. Offsets count from the start of the stream.
62pub 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}