use core::str;
use std::{fs::File, path::Path};
use memmap2::Mmap;
use crate::{
node::{NxNode, NxNodeData},
NxError, NxTryGet,
};
pub struct NxFile {
pub(crate) data: Mmap,
pub(crate) header: NxHeader,
pub(crate) root: NxNodeData,
}
impl NxFile {
pub fn open(path: &Path) -> Result<Self, NxError> {
let file = File::open(path)?;
let data = unsafe { Mmap::map(&file)? };
let header = NxHeader::new(&data)?;
let root = data.try_get_node_data(header.node_offset)?;
Ok(Self { data, header, root })
}
pub fn node_count(&self) -> u32 {
self.header.node_count
}
pub fn string_count(&self) -> u32 {
self.header.string_count
}
pub fn bitmap_count(&self) -> u32 {
self.header.bitmap_count
}
pub fn audio_count(&self) -> u32 {
self.header.audio_count
}
pub fn root(&self) -> NxNode {
NxNode {
data: self.root,
file: self,
}
}
pub(crate) fn get_str(&self, index: u32) -> Result<&str, NxError> {
let offset = self
.data
.try_get_u64(self.header.string_offset + (index as u64 * size_of::<u64>() as u64))?;
let len = self.data.try_get_u16(offset)?;
Ok(self.data.try_get_str(offset + 2, len)?)
}
pub(crate) fn get_bitmap(&self, index: u32) -> Result<&[u8], NxError> {
let offset = self
.data
.try_get_u64(self.header.bitmap_offset + (index as u64 * size_of::<u64>() as u64))?;
let len = self.data.try_get_u32(offset)?;
Ok(self.data.try_get_bytes(offset + 4, len as usize)?)
}
}
pub(crate) struct NxHeader {
node_count: u32,
pub(crate) node_offset: u64,
string_count: u32,
pub(crate) string_offset: u64,
bitmap_count: u32,
pub(crate) bitmap_offset: u64,
audio_count: u32,
pub(crate) audio_offset: u64,
}
impl NxHeader {
pub fn new(data: &Mmap) -> Result<Self, NxError> {
if data.try_get_u32(0)? != 0x34474B50 {
return Err(NxError::InvalidHeader);
}
Ok(Self {
node_count: data.try_get_u32(4).map_err(|_| NxError::InvalidHeader)?,
node_offset: data.try_get_u64(8).map_err(|_| NxError::InvalidHeader)?,
string_count: data.try_get_u32(16).map_err(|_| NxError::InvalidHeader)?,
string_offset: data.try_get_u64(20).map_err(|_| NxError::InvalidHeader)?,
bitmap_count: data.try_get_u32(28).map_err(|_| NxError::InvalidHeader)?,
bitmap_offset: data.try_get_u64(32).map_err(|_| NxError::InvalidHeader)?,
audio_count: data.try_get_u32(40).map_err(|_| NxError::InvalidHeader)?,
audio_offset: data.try_get_u64(44).map_err(|_| NxError::InvalidHeader)?,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn open_file_does_not_exist() {
let result = NxFile::open(Path::new("data/file_that_does_not_exist.nx"));
assert!(result.is_err());
}
#[test]
fn open_file_with_invalid_header() {
let result = NxFile::open(Path::new("data/invalid_header.nx"));
assert!(result.is_err());
assert!(matches!(result.err().unwrap(), NxError::InvalidHeader));
}
#[test]
fn open_valid_file() {
let result = NxFile::open(Path::new("data/valid.nx"));
assert!(result.is_ok());
let file = result.unwrap();
assert_eq!(file.node_count(), 432);
assert_eq!(file.string_count(), 227);
assert_eq!(file.bitmap_count(), 0);
assert_eq!(file.audio_count(), 0);
}
}