rom_analyzer/console/
n64.rs1use serde::Serialize;
10
11use crate::error::RomAnalyzerError;
12use crate::region::{Region, check_region_mismatch};
13
14#[derive(Debug, PartialEq, Clone, Serialize)]
16pub struct N64Analysis {
17 pub source_name: String,
19 pub region: Region,
21 pub region_string: String,
23 pub region_mismatch: bool,
25 pub country_code: String,
27}
28
29impl N64Analysis {
30 pub fn print(&self) -> String {
32 format!(
33 "{}\n\
34 System: Nintendo 64 (N64)\n\
35 Region: {}\n\
36 Code: {}",
37 self.source_name, self.region, self.country_code
38 )
39 }
40}
41
42pub fn map_region(country_code: &str) -> (&'static str, Region) {
81 match country_code {
82 "E" => ("USA (NTSC)", Region::USA),
83 "J" => ("Japan (NTSC)", Region::JAPAN),
84 "P" => ("Europe (PAL)", Region::EUROPE),
85 "D" => ("Germany (PAL)", Region::EUROPE),
86 "F" => ("France (PAL)", Region::EUROPE),
87 "U" => ("USA (Legacy)", Region::USA),
88 _ => ("Unknown", Region::UNKNOWN),
89 }
90}
91
92pub fn analyze_n64_data(data: &[u8], source_name: &str) -> Result<N64Analysis, RomAnalyzerError> {
109 const HEADER_SIZE: usize = 0x40;
111 if data.len() < HEADER_SIZE {
112 return Err(RomAnalyzerError::DataTooSmall {
113 file_size: data.len(),
114 required_size: HEADER_SIZE,
115 details: "N64 header".to_string(),
116 });
117 }
118
119 let country_code = String::from_utf8_lossy(&data[0x3E..0x40])
122 .trim_matches(char::from(0))
123 .to_string();
124
125 let (region_name, region) = map_region(&country_code);
127
128 let region_mismatch = check_region_mismatch(source_name, region);
129
130 Ok(N64Analysis {
131 source_name: source_name.to_string(),
132 region,
133 region_string: region_name.to_string(),
134 region_mismatch,
135 country_code,
136 })
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 fn generate_n64_header(country_code: &str) -> Vec<u8> {
145 let mut data = vec![0; 0x40]; let mut cc_bytes = country_code.as_bytes().to_vec();
149 cc_bytes.resize(2, 0);
150 data[0x3E..0x40].copy_from_slice(&cc_bytes);
151
152 data
153 }
154
155 #[test]
156 fn test_analyze_n64_data_usa() -> Result<(), RomAnalyzerError> {
157 let data = generate_n64_header("E"); let analysis = analyze_n64_data(&data, "test_rom_us.n64")?;
159
160 assert_eq!(analysis.source_name, "test_rom_us.n64");
161 assert_eq!(analysis.region, Region::USA);
162 assert_eq!(analysis.region_string, "USA (NTSC)");
163 assert_eq!(analysis.country_code, "E");
164 assert_eq!(
165 analysis.print(),
166 "test_rom_us.n64\n\
167 System: Nintendo 64 (N64)\n\
168 Region: USA\n\
169 Code: E"
170 );
171 Ok(())
172 }
173
174 #[test]
175 fn test_analyze_n64_data_japan() -> Result<(), RomAnalyzerError> {
176 let data = generate_n64_header("J"); let analysis = analyze_n64_data(&data, "test_rom_jp.n64")?;
178
179 assert_eq!(analysis.source_name, "test_rom_jp.n64");
180 assert_eq!(analysis.region, Region::JAPAN);
181 assert_eq!(analysis.region_string, "Japan (NTSC)");
182 assert_eq!(analysis.country_code, "J");
183 Ok(())
184 }
185
186 #[test]
187 fn test_analyze_n64_data_europe() -> Result<(), RomAnalyzerError> {
188 let data = generate_n64_header("P"); let analysis = analyze_n64_data(&data, "test_rom_eur.n64")?;
190
191 assert_eq!(analysis.source_name, "test_rom_eur.n64");
192 assert_eq!(analysis.region, Region::EUROPE);
193 assert_eq!(analysis.region_string, "Europe (PAL)");
194 assert_eq!(analysis.country_code, "P");
195 Ok(())
196 }
197
198 #[test]
199 fn test_analyze_n64_data_germany() -> Result<(), RomAnalyzerError> {
200 let data = generate_n64_header("D"); let analysis = analyze_n64_data(&data, "test_rom_deu.n64")?;
202
203 assert_eq!(analysis.source_name, "test_rom_deu.n64");
204 assert_eq!(analysis.region, Region::EUROPE);
205 assert_eq!(analysis.region_string, "Germany (PAL)");
206 assert_eq!(analysis.country_code, "D");
207 Ok(())
208 }
209
210 #[test]
211 fn test_analyze_n64_data_france() -> Result<(), RomAnalyzerError> {
212 let data = generate_n64_header("F"); let analysis = analyze_n64_data(&data, "test_rom_fra.n64")?;
214
215 assert_eq!(analysis.source_name, "test_rom_fra.n64");
216 assert_eq!(analysis.region, Region::EUROPE);
217 assert_eq!(analysis.region_string, "France (PAL)");
218 assert_eq!(analysis.country_code, "F");
219 Ok(())
220 }
221
222 #[test]
223 fn test_analyze_n64_data_legacy_usa() -> Result<(), RomAnalyzerError> {
224 let data = generate_n64_header("U"); let analysis = analyze_n64_data(&data, "test_rom_usa_legacy.n64")?;
226
227 assert_eq!(analysis.source_name, "test_rom_usa_legacy.n64");
228 assert_eq!(analysis.region, Region::USA);
229 assert_eq!(analysis.region_string, "USA (Legacy)");
230 assert_eq!(analysis.country_code, "U");
231 Ok(())
232 }
233
234 #[test]
235 fn test_analyze_n64_data_unknown() -> Result<(), RomAnalyzerError> {
236 let data = generate_n64_header("X"); let analysis = analyze_n64_data(&data, "test_rom.n64")?;
238
239 assert_eq!(analysis.source_name, "test_rom.n64");
240 assert_eq!(analysis.region, Region::UNKNOWN);
241 assert_eq!(analysis.region_string, "Unknown");
242 assert_eq!(analysis.country_code, "X");
243 Ok(())
244 }
245
246 #[test]
247 fn test_analyze_n64_data_too_small() {
248 let data = vec![0; 30]; let result = analyze_n64_data(&data, "too_small.n64");
251 assert!(result.is_err());
252 assert!(result.unwrap_err().to_string().contains("too small"));
253 }
254}