Skip to main content

banished_formats/
crs.rs

1//! Compiled resources (`.crs`): serialized engine objects.
2//!
3//! ```text
4//! block    = u32 0xaaaaaaaa, u32 size, u32 version, payload, u32 0xbbbbbbbb   (size covers payload + terminator)
5//! resource = block { u32 0x5412a91f, u32 typeId, u32 fileVersion,
6//!                    u32 depCount, depCount x { String, u64 FILETIME }, object... }
7//! String   = u32 n|0x80000000 then n bytes (8-bit, NUL included) | u32 n then n UTF-16 units
8//! ```
9//! Original: the constructor of `IO::BinaryWriter` (Runtime `0x1800a9980`),
10//! `Object::External::Save` (Runtime `0x1800be4a0`), `System::String::Serialize` (Runtime
11//! `0x1800cc1a0`).
12
13use crate::error::{format_err, Error, Result};
14
15/// The marker at the start of every block.
16pub const BLOCK_START: u32 = 0xAAAA_AAAA;
17/// The marker at the end of every block.
18pub const BLOCK_END: u32 = 0xBBBB_BBBB;
19/// The magic number at the start of a resource's payload.
20pub const MAGIC: u32 = 0x5412_A91F;
21
22/// A reader of a resource's fields, little-endian, that knows the block structure.
23pub struct Reader<'a> {
24    data: &'a [u8],
25    pos: usize,
26    end: usize,
27    /// Set in reference mode (saves): external pointers are `u32` ids into this table.
28    reference_ids: Option<std::sync::Arc<std::collections::BTreeMap<u32, String>>>,
29    /// A UTF-16 `System::String` was read (older builds write every string that way).
30    pub wide_seen: bool,
31}
32
33impl<'a> Reader<'a> {
34    /// A reader at the start of `data`.
35    pub fn new(data: &'a [u8]) -> Self {
36        Reader {
37            data,
38            pos: 0,
39            end: data.len(),
40            reference_ids: None,
41            wide_seen: false,
42        }
43    }
44
45    /// Read external pointers as reference ids (`ExternalFactory` reference mode, used by saves).
46    pub fn set_reference_ids(
47        &mut self,
48        ids: std::sync::Arc<std::collections::BTreeMap<u32, String>>,
49    ) {
50        self.reference_ids = Some(ids);
51    }
52
53    /// The current position, in bytes.
54    pub fn pos(&self) -> usize {
55        self.pos
56    }
57
58    /// The number of bytes left before the end of the readable range.
59    pub fn remaining(&self) -> usize {
60        self.end - self.pos
61    }
62
63    /// Bytes already read or ahead, by absolute position.
64    pub fn slice(&self, start: usize, end: usize) -> Result<&'a [u8]> {
65        self.data.get(start..end).ok_or(Error::Truncated {
66            at: start,
67            wanted: end.saturating_sub(start),
68        })
69    }
70
71    /// The next `n` bytes.
72    pub fn bytes(&mut self, n: usize) -> Result<&'a [u8]> {
73        if n > self.end - self.pos {
74            return Err(Error::Truncated {
75                at: self.pos,
76                wanted: n,
77            });
78        }
79        let s = &self.data[self.pos..self.pos + n];
80        self.pos += n;
81        Ok(s)
82    }
83
84    fn arr<const N: usize>(&mut self) -> Result<[u8; N]> {
85        Ok(self.bytes(N)?.try_into().unwrap())
86    }
87
88    /// Read a `u8`.
89    pub fn u8(&mut self) -> Result<u8> {
90        Ok(self.arr::<1>()?[0])
91    }
92    /// Read a `bool` (one byte, not zero is true).
93    pub fn bool(&mut self) -> Result<bool> {
94        Ok(self.u8()? != 0)
95    }
96    /// Read a `u32`.
97    pub fn u32(&mut self) -> Result<u32> {
98        Ok(u32::from_le_bytes(self.arr()?))
99    }
100    /// Read an `i32`.
101    pub fn i32(&mut self) -> Result<i32> {
102        Ok(i32::from_le_bytes(self.arr()?))
103    }
104    /// Read a `u64`.
105    pub fn u64(&mut self) -> Result<u64> {
106        Ok(u64::from_le_bytes(self.arr()?))
107    }
108    /// Read an `i64`.
109    pub fn i64(&mut self) -> Result<i64> {
110        Ok(i64::from_le_bytes(self.arr()?))
111    }
112    /// Read an `i16`.
113    pub fn i16(&mut self) -> Result<i16> {
114        Ok(i16::from_le_bytes(self.bytes(2)?.try_into().unwrap()))
115    }
116
117    /// Read a `u16`.
118    pub fn u16(&mut self) -> Result<u16> {
119        Ok(u16::from_le_bytes(self.bytes(2)?.try_into().unwrap()))
120    }
121
122    /// Narrow the readable range to end at `end` (inside the current one); returns the old
123    /// end for [`Reader::unlimit`].
124    pub fn limit(&mut self, end: usize) -> Result<usize> {
125        if end > self.end || end < self.pos {
126            return format_err(format!(
127                "limit {end:#x} outside {:#x}..{:#x}",
128                self.pos, self.end
129            ));
130        }
131        Ok(std::mem::replace(&mut self.end, end))
132    }
133
134    /// Restore the readable range that [`limit`](Self::limit) returned.
135    pub fn unlimit(&mut self, end: usize) {
136        self.end = end;
137    }
138
139    /// Move to a position.
140    pub fn set_pos(&mut self, pos: usize) {
141        self.pos = pos;
142    }
143
144    /// The `u32` at an absolute offset.
145    pub fn u32_at(&self, at: usize) -> Option<u32> {
146        self.data
147            .get(at..at + 4)
148            .map(|b| u32::from_le_bytes(b.try_into().unwrap()))
149    }
150
151    /// The next `u32` without consuming it (`None` at the end).
152    pub fn peek_u32(&self) -> Option<u32> {
153        self.data
154            .get(self.pos..self.pos + 4)
155            .filter(|_| self.pos + 4 <= self.end)
156            .map(|b| u32::from_le_bytes(b.try_into().unwrap()))
157    }
158
159    /// A `u32` count of UTF-16 units, the terminating NUL included (0 for an empty string), then
160    /// the units.
161    ///
162    /// Original: `System::String::Serialize`, Runtime `0x1800cc1a0`.
163    pub fn wstring(&mut self) -> Result<String> {
164        let n = self.u32()? as usize;
165        let b = self.bytes(n * 2)?;
166        let units: Vec<u16> = b
167            .chunks_exact(2)
168            .map(|c| u16::from_le_bytes([c[0], c[1]]))
169            .collect();
170        let end = units.iter().position(|&c| c == 0).unwrap_or(units.len());
171        Ok(String::from_utf16_lossy(&units[..end]))
172    }
173
174    /// Read an `f32`.
175    pub fn f32(&mut self) -> Result<f32> {
176        Ok(f32::from_le_bytes(self.arr()?))
177    }
178    /// Read `N` `f32` values.
179    pub fn f32s<const N: usize>(&mut self) -> Result<[f32; N]> {
180        let mut out = [0.0; N];
181        for v in &mut out {
182            *v = self.f32()?;
183        }
184        Ok(out)
185    }
186
187    /// Element count; rejects negative values.
188    pub fn count(&mut self) -> Result<usize> {
189        let n = self.i32()?;
190        usize::try_from(n).map_err(|_| Error::Format(format!("negative count {n}")))
191    }
192
193    /// Read a string: a length with bit 31 set for 8-bit text (else UTF-16), then the characters
194    /// with the terminating zero.
195    pub fn string(&mut self) -> Result<String> {
196        let n = self.u32()?;
197        if n & 0x8000_0000 != 0 {
198            let b = self.bytes((n & 0x7FFF_FFFF) as usize)?;
199            let end = b.iter().position(|&c| c == 0).unwrap_or(b.len());
200            // 8-bit strings hold UTF-8 (e.g. the build notice's `©`); fall back to Latin-1.
201            Ok(match std::str::from_utf8(&b[..end]) {
202                Ok(s) => s.to_owned(),
203                Err(_) => b[..end].iter().map(|&c| c as char).collect(),
204            })
205        } else {
206            self.wide_seen = true;
207            let b = self.bytes(n as usize * 2)?;
208            let units: Vec<u16> = b
209                .chunks_exact(2)
210                .map(|c| u16::from_le_bytes([c[0], c[1]]))
211                .collect();
212            let end = units.iter().position(|&c| c == 0).unwrap_or(units.len());
213            Ok(String::from_utf16_lossy(&units[..end]))
214        }
215    }
216
217    /// `ExternalPtr`: bool present, then a `Path\File.rsc:object` reference.
218    pub fn external_ref(&mut self) -> Result<Option<String>> {
219        if let Some(ids) = self.reference_ids.clone() {
220            let at = self.pos;
221            let id = self.u32()?;
222            if id == u32::MAX {
223                return Ok(None);
224            }
225            return match ids.get(&id) {
226                Some(path) => Ok(Some(path.clone())),
227                None => format_err(format!("unknown reference id {id} at {at:#x}")),
228            };
229        }
230        if self.bool()? {
231            Ok(Some(self.string()?))
232        } else {
233            Ok(None)
234        }
235    }
236
237    /// Read one block, running `f` over its payload. Fails unless `f` consumes it exactly.
238    pub fn block<T>(&mut self, f: impl FnOnce(&mut Self, u32) -> Result<T>) -> Result<T> {
239        let start = self.pos;
240        if self.u32()? != BLOCK_START {
241            return format_err(format!("expected block marker at {start:#x}"));
242        }
243        let size = self.u32()? as usize;
244        let version = self.u32()?;
245        let end = self.pos + size;
246        if size < 4 || end > self.end {
247            return format_err(format!("block at {start:#x} (size {size}) overruns parent"));
248        }
249        if u32::from_le_bytes(self.data[end - 4..end].try_into().unwrap()) != BLOCK_END {
250            return format_err(format!("block at {start:#x} lacks terminator"));
251        }
252        let outer_end = std::mem::replace(&mut self.end, end - 4);
253        let result = f(self, version);
254        let consumed = self.pos;
255        self.end = outer_end;
256        let value = result?;
257        if consumed != end - 4 {
258            return format_err(format!(
259                "block at {start:#x} has {} unread bytes",
260                end - 4 - consumed
261            ));
262        }
263        self.pos = end;
264        Ok(value)
265    }
266
267    /// Skip the rest of the current block (for partially decoded types).
268    pub fn skip_rest(&mut self) {
269        self.pos = self.end;
270    }
271
272    /// Skip a block without interpreting it.
273    pub fn skip_block(&mut self) -> Result<()> {
274        self.block(|r, _| {
275            r.pos = r.end;
276            Ok(())
277        })
278    }
279}
280
281/// The header of a resource.
282#[derive(Debug, Clone)]
283pub struct Header {
284    /// The id of the object's type.
285    pub type_id: u32,
286    /// The file version.
287    pub file_version: u32,
288    /// The resources it depends on.
289    pub dependencies: Vec<String>,
290}
291
292/// Parse a resource, handing the object payload to `f` (called inside the outer block).
293pub fn parse<T>(
294    data: &[u8],
295    f: impl FnOnce(&mut Reader, &Header) -> Result<T>,
296) -> Result<(Header, T)> {
297    let mut r = Reader::new(data);
298    let out = r.block(|r, _| {
299        if r.u32()? != MAGIC {
300            return format_err("missing resource magic");
301        }
302        let type_id = r.u32()?;
303        let file_version = r.u32()?;
304        let n = r.count()?;
305        let mut dependencies = Vec::with_capacity(n);
306        for _ in 0..n {
307            dependencies.push(r.string()?);
308            r.u64()?; // source FILETIME
309        }
310        let header = Header {
311            type_id,
312            file_version,
313            dependencies,
314        };
315        let value = f(r, &header)?;
316        Ok((header, value))
317    })?;
318    if r.pos != data.len() {
319        return format_err(format!(
320            "{} trailing bytes after resource",
321            data.len() - r.pos
322        ));
323    }
324    Ok(out)
325}
326
327/// Header only; skips the object payload.
328pub fn header(data: &[u8]) -> Result<Header> {
329    parse(data, |r, _| {
330        r.pos = r.end;
331        Ok(())
332    })
333    .map(|(h, ())| h)
334}
335
336/// The writing side of [`Reader`] (`IO::BinaryWriter`): little-endian values, 8-bit strings and
337/// blocks whose size is filled in when they close.
338#[derive(Default)]
339pub struct Writer {
340    /// The bytes written.
341    pub data: Vec<u8>,
342    /// Set in reference mode (saves): external pointers are written as ids from this table.
343    reference_ids: Option<std::collections::BTreeMap<String, u32>>,
344    /// Write `System::String`s as UTF-16 (as older builds did) instead of 8-bit.
345    pub wide_strings: bool,
346}
347
348impl Writer {
349    /// An empty writer.
350    pub fn new() -> Self {
351        Self::default()
352    }
353
354    /// Write external pointers as reference ids (the inverse of the reference table).
355    pub fn set_reference_ids(&mut self, ids: &std::collections::BTreeMap<u32, String>) {
356        self.reference_ids = Some(ids.iter().map(|(&id, p)| (p.clone(), id)).collect());
357    }
358
359    /// Write bytes as they are.
360    pub fn bytes(&mut self, b: &[u8]) {
361        self.data.extend_from_slice(b);
362    }
363    /// Write a `u8`.
364    pub fn u8(&mut self, v: u8) {
365        self.data.push(v);
366    }
367    /// Write a `bool` as one byte.
368    pub fn bool(&mut self, v: bool) {
369        self.data.push(v as u8);
370    }
371    /// Write an `i16`.
372    pub fn i16(&mut self, v: i16) {
373        self.bytes(&v.to_le_bytes());
374    }
375    /// Write a `u16`.
376    pub fn u16(&mut self, v: u16) {
377        self.bytes(&v.to_le_bytes());
378    }
379    /// Write a `u32`.
380    pub fn u32(&mut self, v: u32) {
381        self.bytes(&v.to_le_bytes());
382    }
383    /// Write an `i32`.
384    pub fn i32(&mut self, v: i32) {
385        self.bytes(&v.to_le_bytes());
386    }
387    /// Write an `i64`.
388    pub fn i64(&mut self, v: i64) {
389        self.bytes(&v.to_le_bytes());
390    }
391    /// Write an `f32`.
392    pub fn f32(&mut self, v: f32) {
393        self.bytes(&v.to_le_bytes());
394    }
395
396    /// A count as the reader's `count` takes it.
397    pub fn count(&mut self, n: usize) {
398        self.i32(n as i32);
399    }
400
401    /// `System::String` with the 8-bit flag (or UTF-16 with [`Writer::wide_strings`]); the
402    /// length counts the NUL.
403    pub fn string(&mut self, s: &str) {
404        if self.wide_strings {
405            self.wstring(s);
406            return;
407        }
408        self.u32((s.len() as u32 + 1) | 0x8000_0000);
409        self.bytes(s.as_bytes());
410        self.u8(0);
411    }
412
413    /// A UTF-16 string: the unit count with its NUL, then the units.
414    pub fn wstring(&mut self, s: &str) {
415        let units: Vec<u16> = s.encode_utf16().chain([0]).collect();
416        self.u32(units.len() as u32);
417        for u in units {
418            self.u16(u);
419        }
420    }
421
422    /// `ExternalPtr`, in reference mode an id (`u32::MAX` for none), else a present flag and
423    /// the path.
424    pub fn external_ref(&mut self, path: Option<&str>) -> Result<()> {
425        if let Some(ids) = &self.reference_ids {
426            let id = match path {
427                None => u32::MAX,
428                Some(p) => *ids
429                    .get(p)
430                    .ok_or_else(|| Error::Format(format!("no reference id for {p}")))?,
431            };
432            self.u32(id);
433            return Ok(());
434        }
435        self.bool(path.is_some());
436        if let Some(p) = path {
437            self.string(p);
438        }
439        Ok(())
440    }
441
442    /// Open a block at `version`; returns where its size goes, for [`Writer::end_block`].
443    pub fn begin_block(&mut self, version: u32) -> usize {
444        self.u32(BLOCK_START);
445        let size_at = self.data.len();
446        self.u32(0);
447        self.u32(version);
448        size_at
449    }
450
451    /// Close the block opened at `size_at`. The size covers the payload and terminator, not
452    /// the version.
453    pub fn end_block(&mut self, size_at: usize) {
454        self.u32(BLOCK_END);
455        let size = (self.data.len() - size_at - 8) as u32;
456        self.data[size_at..size_at + 4].copy_from_slice(&size.to_le_bytes());
457    }
458
459    /// One block at `version`, its payload written by `f`.
460    pub fn block<T>(&mut self, version: u32, f: impl FnOnce(&mut Self) -> Result<T>) -> Result<T> {
461        let at = self.begin_block(version);
462        let out = f(self)?;
463        self.end_block(at);
464        Ok(out)
465    }
466}