use serde::Serialize;
use crate::error::RomAnalyzerError;
use crate::region::{Region, check_region_mismatch};
#[derive(Debug, PartialEq, Clone, Serialize)]
pub struct PsxAnalysis {
pub source_name: String,
pub region: Region,
pub region_string: String,
pub region_mismatch: bool,
pub code: String,
}
impl PsxAnalysis {
pub fn print(&self) -> String {
let executable_prefix_not_found = if self.code == "N/A" {
"\nNote: Executable prefix (SLUS/SLES/SLPS) not found in header area. Requires main data track (.bin or .iso)."
} else {
""
};
format!(
"{}\n\
System: Sony PlayStation (PSX)\n\
Region: {}\n\
Code: {}\
{}",
self.source_name, self.region, self.code, executable_prefix_not_found
)
}
}
pub fn map_region(region_code: &str) -> (&'static str, Region) {
match region_code {
"SLUS" => ("North America (NTSC-U)", Region::USA),
"SLES" => ("Europe (PAL)", Region::EUROPE),
"SLPS" => ("Japan (NTSC-J)", Region::JAPAN),
_ => ("Unknown", Region::UNKNOWN),
}
}
pub fn analyze_psx_data(data: &[u8], source_name: &str) -> Result<PsxAnalysis, RomAnalyzerError> {
let check_size = std::cmp::min(data.len(), 0x20000);
if check_size < 0x2000 {
return Err(RomAnalyzerError::DataTooSmall {
file_size: data.len(),
required_size: 0x2000,
details: "PSX boot file analysis".to_string(),
});
}
let data_sample = &data[..check_size];
let mut found_code = "N/A".to_string();
let mut region_name = "Unknown";
let mut region = Region::UNKNOWN;
for prefix in ["SLUS", "SLES", "SLPS"] {
if data_sample
.windows(prefix.len())
.any(|window| window.eq_ignore_ascii_case(prefix.as_bytes()))
{
found_code = prefix.to_string();
let (region_str, region_mask) = map_region(prefix);
region_name = region_str;
region = region_mask;
break;
}
}
let region_mismatch = check_region_mismatch(source_name, region);
Ok(PsxAnalysis {
source_name: source_name.to_string(),
region,
region_string: region_name.to_string(),
region_mismatch,
code: found_code,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_analyze_psx_data_slus() -> Result<(), RomAnalyzerError> {
let mut data = vec![0; 0x2000];
data[0x100..0x104].copy_from_slice(b"SLUS"); let analysis = analyze_psx_data(&data, "test_rom_us.iso")?;
assert_eq!(analysis.source_name, "test_rom_us.iso");
assert_eq!(analysis.region, Region::USA);
assert_eq!(analysis.region_string, "North America (NTSC-U)");
assert_eq!(analysis.code, "SLUS");
assert_eq!(
analysis.print(),
"test_rom_us.iso\n\
System: Sony PlayStation (PSX)\n\
Region: USA\n\
Code: SLUS"
);
Ok(())
}
#[test]
fn test_analyze_psx_data_sles() -> Result<(), RomAnalyzerError> {
let mut data = vec![0; 0x2000];
data[0x100..0x104].copy_from_slice(b"SLES"); let analysis = analyze_psx_data(&data, "test_rom_eur.iso")?;
assert_eq!(analysis.source_name, "test_rom_eur.iso");
assert_eq!(analysis.region, Region::EUROPE);
assert_eq!(analysis.region_string, "Europe (PAL)");
assert_eq!(analysis.code, "SLES");
Ok(())
}
#[test]
fn test_analyze_psx_data_slps() -> Result<(), RomAnalyzerError> {
let mut data = vec![0; 0x2000];
data[0x100..0x104].copy_from_slice(b"SLPS"); let analysis = analyze_psx_data(&data, "test_rom_jp.iso")?;
assert_eq!(analysis.source_name, "test_rom_jp.iso");
assert_eq!(analysis.region, Region::JAPAN);
assert_eq!(analysis.region_string, "Japan (NTSC-J)");
assert_eq!(analysis.code, "SLPS");
Ok(())
}
#[test]
fn test_analyze_psx_data_unknown() -> Result<(), RomAnalyzerError> {
let data = vec![0; 0x2000];
let analysis = analyze_psx_data(&data, "test_rom.iso")?;
assert_eq!(analysis.source_name, "test_rom.iso");
assert_eq!(analysis.region, Region::UNKNOWN);
assert_eq!(analysis.region_string, "Unknown");
assert_eq!(analysis.code, "N/A");
assert_eq!(
analysis.print(),
"test_rom.iso\n\
System: Sony PlayStation (PSX)\n\
Region: Unknown\n\
Code: N/A\n\
Note: Executable prefix (SLUS/SLES/SLPS) not found in header area. Requires main data track (.bin or .iso)."
);
Ok(())
}
#[test]
fn test_analyze_psx_data_too_small() {
let data = vec![0; 100]; let result = analyze_psx_data(&data, "too_small.iso");
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("too small"));
}
#[test]
fn test_analyze_psx_data_case_insensitivity() -> Result<(), RomAnalyzerError> {
let mut data = vec![0; 0x2000];
data[0x100..0x104].copy_from_slice(b"sLuS"); let analysis = analyze_psx_data(&data, "test_rom_mixedcase.iso")?;
assert_eq!(analysis.source_name, "test_rom_mixedcase.iso");
assert_eq!(analysis.region, Region::USA);
assert_eq!(analysis.region_string, "North America (NTSC-U)");
assert_eq!(analysis.code, "SLUS");
Ok(())
}
}