mod ncer;
mod ncgr;
mod nclr;
mod nscr;
pub use ncer::{Cell, Cells, Object};
pub use ncgr::{Depth, Tiles};
pub use nclr::Palette;
pub use nscr::Screen;
use crate::io::Reader;
use crate::{Error, Result};
fn chunk<'a>(format: &'static str, bytes: &'a [u8], magic: &[u8; 4]) -> Result<Reader<'a>> {
find_chunk(format, bytes, magic)?
.ok_or_else(|| Error::malformed(format, 0, "a required chunk is missing"))
}
fn find_chunk<'a>(
format: &'static str,
bytes: &'a [u8],
magic: &[u8; 4],
) -> Result<Option<Reader<'a>>> {
let file = Reader::new(format, bytes);
let mut at = file.u16_at(12)? as usize;
for _ in 0..file.u16_at(14)? {
let len = file.u32_at(at + 4)? as usize;
if len < 8 {
return Err(file.error_at(at + 4, "chunk is shorter than its header"));
}
if &file.array_at::<4>(at)? == magic {
let end = at.saturating_add(len).min(bytes.len());
return Ok(Some(Reader::new(
format,
file.slice_at(at + 8, end - at - 8)?,
)));
}
at = at.saturating_add(len);
}
Ok(None)
}
#[cfg(test)]
pub(crate) mod testing {
pub fn nitro(magic: &[u8; 4], chunks: &[(&[u8; 4], &[u8])]) -> Vec<u8> {
let body: Vec<u8> = chunks
.iter()
.flat_map(|(magic, body)| {
let len = (body.len() as u32 + 8).to_le_bytes();
[&magic[..], &len, body].concat()
})
.collect();
let mut bytes = magic.to_vec();
bytes.extend(b"\xFF\xFE\x00\x01");
bytes.extend((16 + body.len() as u32).to_le_bytes());
bytes.extend(16u16.to_le_bytes());
bytes.extend((chunks.len() as u16).to_le_bytes());
bytes.extend(body);
bytes
}
pub fn ncgr(size: (u16, u16), depth: u32, format: u32, data: &[u8]) -> Vec<u8> {
let mut body = words(&[size.1, size.0]);
for value in [depth, 0x10, format, data.len() as u32, 24] {
body.extend(value.to_le_bytes());
}
body.extend(data);
nitro(b"RGCN", &[(b"RAHC", &body)])
}
pub fn words(values: &[u16]) -> Vec<u8> {
values
.iter()
.flat_map(|value| value.to_le_bytes())
.collect()
}
}