1use core::str;
2use std::{fs::File, path::Path};
3
4use memmap2::Mmap;
5
6use crate::{
7 node::{NxNode, NxNodeData},
8 NxError, NxTryGet,
9};
10
11pub struct NxFile {
13 pub(crate) data: Mmap,
14 pub(crate) header: NxHeader,
15 pub(crate) root: NxNodeData,
16}
17
18impl NxFile {
19 pub fn open(path: &Path) -> Result<Self, NxError> {
21 let file = File::open(path)?;
22
23 let data = unsafe { Mmap::map(&file)? };
28 let header = NxHeader::new(&data)?;
29 let root = data.try_get_node_data(header.node_offset)?;
30
31 Ok(Self { data, header, root })
32 }
33
34 pub fn node_count(&self) -> u32 {
36 self.header.node_count
37 }
38
39 pub fn string_count(&self) -> u32 {
41 self.header.string_count
42 }
43
44 pub fn bitmap_count(&self) -> u32 {
46 self.header.bitmap_count
47 }
48
49 pub fn audio_count(&self) -> u32 {
51 self.header.audio_count
52 }
53
54 pub fn root(&self) -> NxNode {
56 NxNode {
57 data: self.root,
58 file: self,
59 }
60 }
61
62 pub(crate) fn get_str(&self, index: u32) -> Result<&str, NxError> {
64 let offset = self
65 .data
66 .try_get_u64(self.header.string_offset + (index as u64 * size_of::<u64>() as u64))?;
67
68 let len = self.data.try_get_u16(offset)?;
69 Ok(self.data.try_get_str(offset + 2, len)?)
70 }
71
72 pub(crate) fn get_bitmap(&self, index: u32) -> Result<&[u8], NxError> {
74 let offset = self
75 .data
76 .try_get_u64(self.header.bitmap_offset + (index as u64 * size_of::<u64>() as u64))?;
77
78 let len = self.data.try_get_u32(offset)?;
79 Ok(self.data.try_get_bytes(offset + 4, len as usize)?)
80 }
81}
82
83pub(crate) struct NxHeader {
84 node_count: u32,
85 pub(crate) node_offset: u64,
86 string_count: u32,
87 pub(crate) string_offset: u64,
88 bitmap_count: u32,
89 pub(crate) bitmap_offset: u64,
90 audio_count: u32,
91 pub(crate) audio_offset: u64,
92}
93
94impl NxHeader {
95 pub fn new(data: &Mmap) -> Result<Self, NxError> {
96 if data.try_get_u32(0)? != 0x34474B50 {
98 return Err(NxError::InvalidHeader);
99 }
100
101 Ok(Self {
102 node_count: data.try_get_u32(4).map_err(|_| NxError::InvalidHeader)?,
103 node_offset: data.try_get_u64(8).map_err(|_| NxError::InvalidHeader)?,
104 string_count: data.try_get_u32(16).map_err(|_| NxError::InvalidHeader)?,
105 string_offset: data.try_get_u64(20).map_err(|_| NxError::InvalidHeader)?,
106 bitmap_count: data.try_get_u32(28).map_err(|_| NxError::InvalidHeader)?,
107 bitmap_offset: data.try_get_u64(32).map_err(|_| NxError::InvalidHeader)?,
108 audio_count: data.try_get_u32(40).map_err(|_| NxError::InvalidHeader)?,
109 audio_offset: data.try_get_u64(44).map_err(|_| NxError::InvalidHeader)?,
110 })
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn open_file_does_not_exist() {
120 let result = NxFile::open(Path::new("data/file_that_does_not_exist.nx"));
121 assert!(result.is_err());
122 }
123
124 #[test]
125 fn open_file_with_invalid_header() {
126 let result = NxFile::open(Path::new("data/invalid_header.nx"));
127 assert!(result.is_err());
128 assert!(matches!(result.err().unwrap(), NxError::InvalidHeader));
129 }
130
131 #[test]
132 fn open_valid_file() {
133 let result = NxFile::open(Path::new("data/valid.nx"));
134 assert!(result.is_ok());
135
136 let file = result.unwrap();
137 assert_eq!(file.node_count(), 432);
138 assert_eq!(file.string_count(), 227);
139 assert_eq!(file.bitmap_count(), 0);
140 assert_eq!(file.audio_count(), 0);
141 }
142}