mod model;
mod render;
mod texture;
pub use model::{Material, Model, Node, Nsbmd};
pub use texture::{Palette, Texture, TextureFormat, Textures};
use crate::Result;
use crate::io::{Reader, ascii};
fn blocks(file: &Reader, magic: &[u8; 4]) -> Result<Vec<([u8; 4], usize)>> {
file.clone().expect(magic)?;
let count = file.u16_at(14)? as usize;
(0..count)
.map(|index| {
let offset = file.u32_at(16 + 4 * index)? as usize;
Ok((file.array_at(offset)?, offset))
})
.collect()
}
fn block<'a>(file: &Reader<'a>, format: &'static str, offset: usize) -> Result<Reader<'a>> {
let len = file.u32_at(offset + 4)? as usize;
Ok(Reader::new(format, file.slice_at(offset, len)?))
}
fn dictionary<'a>(data: &Reader<'a>, at: usize) -> Result<Vec<(String, &'a [u8])>> {
let count = data.u8_at(at + 1)? as usize;
let entries = at + data.u16_at(at + 6)? as usize;
let entry_len = data.u16_at(entries)? as usize;
let names = entries + data.u16_at(entries + 2)? as usize;
(0..count)
.map(|index| {
let name = ascii(data.slice_at(names + 16 * index, 16)?);
Ok((
name,
data.slice_at(entries + 4 + entry_len * index, entry_len)?,
))
})
.collect()
}
fn fx32(raw: u32) -> f32 {
raw as i32 as f32 / 4096.0
}
fn fx16(raw: u16) -> f32 {
raw as i16 as f32 / 4096.0
}
#[cfg(test)]
pub(crate) mod testing {
pub fn file(magic: &[u8; 4], blocks: &[Vec<u8>]) -> Vec<u8> {
let header = 16 + 4 * blocks.len();
let mut bytes = magic.to_vec();
bytes.extend(b"\xFF\xFE\x02\x00");
let size = header + blocks.iter().map(Vec::len).sum::<usize>();
bytes.extend((size as u32).to_le_bytes());
bytes.extend(16u16.to_le_bytes());
bytes.extend((blocks.len() as u16).to_le_bytes());
let mut offset = header;
for block in blocks {
bytes.extend((offset as u32).to_le_bytes());
offset += block.len();
}
bytes.extend(blocks.concat());
bytes
}
pub fn block(magic: &[u8; 4], body: &[u8]) -> Vec<u8> {
let mut bytes = magic.to_vec();
bytes.extend((8 + body.len() as u32).to_le_bytes());
bytes.extend(body);
bytes
}
pub fn dictionary(entries: &[(&str, &[u8])]) -> Vec<u8> {
let count = entries.len();
let entry_len = entries.first().map_or(4, |(_, data)| data.len());
let tree = 12 + 4 * count;
let mut bytes = vec![0, count as u8];
let size = tree + 4 + entry_len * count + 16 * count;
bytes.extend((size as u16).to_le_bytes());
bytes.extend(8u16.to_le_bytes());
bytes.extend((tree as u16).to_le_bytes());
bytes.extend(0x17Fu32.to_le_bytes());
bytes.resize(tree, 0);
bytes.extend((entry_len as u16).to_le_bytes());
bytes.extend(((4 + entry_len * count) as u16).to_le_bytes());
for (_, data) in entries {
bytes.extend(*data);
}
for (name, _) in entries {
let mut field = [0; 16];
field[..name.len()].copy_from_slice(name.as_bytes());
bytes.extend(field);
}
bytes
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reads_dictionaries() {
let bytes = testing::dictionary(&[("first", &[1, 0, 0, 0]), ("second", &[2, 0, 0, 0])]);
let entries = dictionary(&Reader::new("test", &bytes), 0).unwrap();
assert_eq!(entries[1], ("second".to_owned(), [2, 0, 0, 0].as_slice()));
assert!(dictionary(&Reader::new("test", &bytes[..20]), 0).is_err());
}
#[test]
fn finds_blocks() {
let file = testing::file(b"BTX0", &[testing::block(b"TEX0", &[0; 8])]);
let reader = Reader::new("test", &file);
assert_eq!(blocks(&reader, b"BTX0").unwrap(), [(*b"TEX0", 20)]);
assert_eq!(block(&reader, "test", 20).unwrap().len(), 16);
assert!(blocks(&reader, b"BMD0").is_err());
}
}