1use std::fs::File;
7use std::io::{self, Read};
8use std::path::Path;
9
10#[derive(Debug, Clone)]
12pub struct INesHeader {
13 pub prg_rom_size: u8,
15 pub chr_rom_size: u8,
17 pub mapper: u8,
19 pub mirroring: u8,
21 pub battery: bool,
23 pub trainer: bool,
25 pub four_screen: bool,
27}
28
29impl INesHeader {
30 pub fn parse(header: &[u8; 16]) -> Result<Self, String> {
36 if header[0] != b'N' || header[1] != b'E' || header[2] != b'S' || header[3] != 0x1A {
38 return Err("Invalid iNES header magic number".to_string());
39 }
40
41 let prg_rom_size = header[4];
42 let chr_rom_size = header[5];
43 let flags6 = header[6];
44 let flags7 = header[7];
45
46 let mapper_lo = (flags6 & 0xF0) >> 4;
48 let mapper_hi = flags7 & 0xF0;
49 let mapper = mapper_hi | mapper_lo;
50
51 let mirroring = flags6 & 0x01;
53 let battery = (flags6 & 0x02) != 0;
54 let trainer = (flags6 & 0x04) != 0;
55 let four_screen = (flags6 & 0x08) != 0;
56
57 Ok(Self {
58 prg_rom_size,
59 chr_rom_size,
60 mapper,
61 mirroring,
62 battery,
63 trainer,
64 four_screen,
65 })
66 }
67}
68
69#[derive(Debug, Clone)]
71pub struct INesRom {
72 pub header: INesHeader,
74 pub prg_rom: Vec<u8>,
76 pub chr_rom: Vec<u8>,
78}
79
80impl INesRom {
81 pub fn load<P: AsRef<Path>>(path: P) -> io::Result<Self> {
87 let mut file = File::open(path)?;
88 let mut buffer = Vec::new();
89 file.read_to_end(&mut buffer)?;
90
91 Self::from_bytes(&buffer).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
92 }
93
94 pub fn from_bytes(data: &[u8]) -> Result<Self, String> {
101 if data.len() < 16 {
102 return Err("ROM file too small".to_string());
103 }
104
105 let mut header_bytes = [0u8; 16];
107 header_bytes.copy_from_slice(&data[0..16]);
108 let header = INesHeader::parse(&header_bytes)?;
109
110 let mut offset = 16;
111
112 if header.trainer {
114 offset += 512;
115 }
116
117 let prg_rom_bytes = (header.prg_rom_size as usize) * 16384;
119 if data.len() < offset + prg_rom_bytes {
120 return Err("Invalid PRG-ROM size".to_string());
121 }
122 let prg_rom = data[offset..offset + prg_rom_bytes].to_vec();
123 offset += prg_rom_bytes;
124
125 let chr_rom_bytes = (header.chr_rom_size as usize) * 8192;
127 let chr_rom = if chr_rom_bytes > 0 {
128 if data.len() < offset + chr_rom_bytes {
129 return Err("Invalid CHR-ROM size".to_string());
130 }
131 data[offset..offset + chr_rom_bytes].to_vec()
132 } else {
133 vec![0; 8192]
135 };
136
137 Ok(Self {
138 header,
139 prg_rom,
140 chr_rom,
141 })
142 }
143
144 pub fn prg_rom_size(&self) -> usize {
146 self.prg_rom.len()
147 }
148
149 pub fn chr_rom_size(&self) -> usize {
151 self.chr_rom.len()
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn test_ines_header_parse() {
161 let header_bytes = [
162 b'N', b'E', b'S', 0x1A, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
168 ];
169
170 let header = INesHeader::parse(&header_bytes).unwrap();
171 assert_eq!(header.prg_rom_size, 2);
172 assert_eq!(header.chr_rom_size, 1);
173 assert_eq!(header.mapper, 0);
174 assert_eq!(header.mirroring, 0);
175 assert!(!header.battery);
176 assert!(!header.trainer);
177 }
178
179 #[test]
180 fn test_ines_header_invalid_magic() {
181 let header_bytes = [
182 b'N', b'E', b'X', 0x1A, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
184 ];
185
186 assert!(INesHeader::parse(&header_bytes).is_err());
187 }
188}