use serde::Serialize;
use crate::error::RomAnalyzerError;
use crate::region::{Region, check_region_mismatch};
#[derive(Debug, PartialEq, Clone, Serialize)]
pub struct N64Analysis {
pub source_name: String,
pub region: Region,
pub region_string: String,
pub region_mismatch: bool,
pub country_code: String,
}
impl N64Analysis {
pub fn print(&self) -> String {
format!(
"{}\n\
System: Nintendo 64 (N64)\n\
Region: {}\n\
Code: {}",
self.source_name, self.region, self.country_code
)
}
}
pub fn map_region(country_code: &str) -> (&'static str, Region) {
match country_code {
"E" => ("USA (NTSC)", Region::USA),
"J" => ("Japan (NTSC)", Region::JAPAN),
"P" => ("Europe (PAL)", Region::EUROPE),
"D" => ("Germany (PAL)", Region::EUROPE),
"F" => ("France (PAL)", Region::EUROPE),
"U" => ("USA (Legacy)", Region::USA),
_ => ("Unknown", Region::UNKNOWN),
}
}
pub fn analyze_n64_data(data: &[u8], source_name: &str) -> Result<N64Analysis, RomAnalyzerError> {
const HEADER_SIZE: usize = 0x40;
if data.len() < HEADER_SIZE {
return Err(RomAnalyzerError::DataTooSmall {
file_size: data.len(),
required_size: HEADER_SIZE,
details: "N64 header".to_string(),
});
}
let country_code = String::from_utf8_lossy(&data[0x3E..0x40])
.trim_matches(char::from(0))
.to_string();
let (region_name, region) = map_region(&country_code);
let region_mismatch = check_region_mismatch(source_name, region);
Ok(N64Analysis {
source_name: source_name.to_string(),
region,
region_string: region_name.to_string(),
region_mismatch,
country_code,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn generate_n64_header(country_code: &str) -> Vec<u8> {
let mut data = vec![0; 0x40];
let mut cc_bytes = country_code.as_bytes().to_vec();
cc_bytes.resize(2, 0);
data[0x3E..0x40].copy_from_slice(&cc_bytes);
data
}
#[test]
fn test_analyze_n64_data_usa() -> Result<(), RomAnalyzerError> {
let data = generate_n64_header("E"); let analysis = analyze_n64_data(&data, "test_rom_us.n64")?;
assert_eq!(analysis.source_name, "test_rom_us.n64");
assert_eq!(analysis.region, Region::USA);
assert_eq!(analysis.region_string, "USA (NTSC)");
assert_eq!(analysis.country_code, "E");
assert_eq!(
analysis.print(),
"test_rom_us.n64\n\
System: Nintendo 64 (N64)\n\
Region: USA\n\
Code: E"
);
Ok(())
}
#[test]
fn test_analyze_n64_data_japan() -> Result<(), RomAnalyzerError> {
let data = generate_n64_header("J"); let analysis = analyze_n64_data(&data, "test_rom_jp.n64")?;
assert_eq!(analysis.source_name, "test_rom_jp.n64");
assert_eq!(analysis.region, Region::JAPAN);
assert_eq!(analysis.region_string, "Japan (NTSC)");
assert_eq!(analysis.country_code, "J");
Ok(())
}
#[test]
fn test_analyze_n64_data_europe() -> Result<(), RomAnalyzerError> {
let data = generate_n64_header("P"); let analysis = analyze_n64_data(&data, "test_rom_eur.n64")?;
assert_eq!(analysis.source_name, "test_rom_eur.n64");
assert_eq!(analysis.region, Region::EUROPE);
assert_eq!(analysis.region_string, "Europe (PAL)");
assert_eq!(analysis.country_code, "P");
Ok(())
}
#[test]
fn test_analyze_n64_data_germany() -> Result<(), RomAnalyzerError> {
let data = generate_n64_header("D"); let analysis = analyze_n64_data(&data, "test_rom_deu.n64")?;
assert_eq!(analysis.source_name, "test_rom_deu.n64");
assert_eq!(analysis.region, Region::EUROPE);
assert_eq!(analysis.region_string, "Germany (PAL)");
assert_eq!(analysis.country_code, "D");
Ok(())
}
#[test]
fn test_analyze_n64_data_france() -> Result<(), RomAnalyzerError> {
let data = generate_n64_header("F"); let analysis = analyze_n64_data(&data, "test_rom_fra.n64")?;
assert_eq!(analysis.source_name, "test_rom_fra.n64");
assert_eq!(analysis.region, Region::EUROPE);
assert_eq!(analysis.region_string, "France (PAL)");
assert_eq!(analysis.country_code, "F");
Ok(())
}
#[test]
fn test_analyze_n64_data_legacy_usa() -> Result<(), RomAnalyzerError> {
let data = generate_n64_header("U"); let analysis = analyze_n64_data(&data, "test_rom_usa_legacy.n64")?;
assert_eq!(analysis.source_name, "test_rom_usa_legacy.n64");
assert_eq!(analysis.region, Region::USA);
assert_eq!(analysis.region_string, "USA (Legacy)");
assert_eq!(analysis.country_code, "U");
Ok(())
}
#[test]
fn test_analyze_n64_data_unknown() -> Result<(), RomAnalyzerError> {
let data = generate_n64_header("X"); let analysis = analyze_n64_data(&data, "test_rom.n64")?;
assert_eq!(analysis.source_name, "test_rom.n64");
assert_eq!(analysis.region, Region::UNKNOWN);
assert_eq!(analysis.region_string, "Unknown");
assert_eq!(analysis.country_code, "X");
Ok(())
}
#[test]
fn test_analyze_n64_data_too_small() {
let data = vec![0; 30]; let result = analyze_n64_data(&data, "too_small.n64");
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("too small"));
}
}