//! The per-level overlay: a 16-byte header, a large constant block, then //! level-specific data. //! //! Spec: `docs/formats/core-data-regions.md`. //! //! `overlay.bin` was the least-characterised file in the level set -- no parser, //! no located structure. What follows is what the disc actually shows, and it is //! narrower than the name suggests. //! //! # Header //! //! Four words at `+0x00`: //! //! | field | levels | notes | //! |---|---|---| //! | [`OverlayHeader::field_00`] | `1735808` on 27/27 | the only constant word | //! | [`OverlayHeader::field_04`] | 37,540..45,308 | level-specific, always a multiple of 4 | //! | [`OverlayHeader::field_08`] | `1` on 27/27 | | //! | [`OverlayHeader::field_0c`] | 2,763,912..3,027,336 | level-specific | //! //! **The word at `+0x0c` is not a file size.** It was briefly recorded as one //! because it sat close to the file length on the first levels measured, but //! across all 27 it is smaller than the file on 5 levels and larger on 22, and //! the difference runs to −147,616 bytes on level 2. It is reported as //! [`OverlayHeader::field_0c`] and nothing depends on it. //! //! # The constant block //! //! Bytes `+0x10..+0x1220` -- **4,624 bytes** -- are byte-identical on all 27 //! levels. The common prefix is only 4 bytes, because the header's middle two //! words vary; everything from `+0x10` up to `+0x1220` does not. //! //! That block contains a fixed configuration at `+0x30` (`f32` 7.9844, the word //! 13,590,528, then `u16` 100 and 70, then 25), and the start of the game's //! debug string pool -- `"crap\n"` at `+0x18`, `"system misc"` at `+0x20`, then //! strings such as `host0:r:/rc2/shock/wavetable.bin`. //! //! # The string pool is not a table //! //! The pool was first written up as a table of records. It is not. Dumping the //! 32 bytes before each printable run shows they are overwhelmingly the tail of //! the preceding string, and the 32-byte prefixes are 327 distinct out of 332 -- //! i.e. essentially all different, because they are just the previous string's //! bytes. There is no per-record header. Counted on 16-byte alignment, level 0 //! yields 332 runs, level 1 yields 362 and level 2 yields 351, but those counts //! include continuations and overstate the number of strings. //! //! What the pool is *for* is settled by its contents: `Camera_CollPrimTest //! WARNING! - grid out of bounds! (RAR)` is the game's own string, which is //! independent support for reading the collision payload's two leading `u16`s as //! grid dimensions. use crate::buf::{Buf, Error, ErrorKind, Result}; /// Size of the header in bytes. pub const OVERLAY_HEADER_SIZE: usize = 0x10; /// Start of the block that is byte-identical on every level. pub const OVERLAY_CONSTANT_BLOCK_START: usize = 0x10; /// End of the block that is byte-identical on every level. pub const OVERLAY_CONSTANT_BLOCK_END: usize = 0x1220; /// The overlay's fixed 16-byte header. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct OverlayHeader { /// The word at `+0x00`. Reads `1735808` on all 27 levels. pub field_00: u32, /// The word at `+0x04`. Level-specific; 37,540 on level 24 through 45,308 /// on level 2, and a multiple of 4 on every level. pub field_04: u32, /// The word at `+0x08`. Reads `1` on all 27 levels. pub field_08: u32, /// The word at `+0x0c`. Level-specific. /// /// Reported, never relied on. It is **not** the file length: see the module /// docs. pub field_0c: u32, } impl OverlayHeader { /// Parse the header, bounds-checked against `region`. pub fn parse(region: &[u8]) -> Result { let mut r = Buf::new(region); Ok(Self { field_00: r.u32("overlay field_00")?, field_04: r.u32("overlay field_04")?, field_08: r.u32("overlay field_08")?, field_0c: r.u32("overlay field_0c")?, }) } } /// The byte-identical block shared by every level's overlay. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct OverlayConstantBlock<'a> { /// Bytes `+0x10..+0x1220`. Identical on all 27 levels. pub bytes: &'a [u8], } /// The level-specific data following the constant block. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct OverlayLevelData<'a> { /// Everything from `+0x1220` to the end of the file. pub bytes: &'a [u8], } /// Split an overlay into its header, constant block and level-specific data. pub fn parse_overlay( region: &[u8], ) -> Result<( OverlayHeader, OverlayConstantBlock<'_>, OverlayLevelData<'_>, )> { let header = OverlayHeader::parse(region)?; if region.len() < OVERLAY_CONSTANT_BLOCK_END { return Err(Error { offset: OVERLAY_CONSTANT_BLOCK_END, context: "overlay constant block", kind: ErrorKind::OutOfBounds { needed: OVERLAY_CONSTANT_BLOCK_END, available: region.len(), }, }); } let constant = ®ion[OVERLAY_CONSTANT_BLOCK_START..OVERLAY_CONSTANT_BLOCK_END]; let level = ®ion[OVERLAY_CONSTANT_BLOCK_END..]; Ok(( header, OverlayConstantBlock { bytes: constant }, OverlayLevelData { bytes: level }, )) }