use std::fmt;
use bitflags::bitflags;
use serde::Serialize;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub struct Region: u8 {
const UNKNOWN = 0;
const JAPAN = 1 << 0;
const USA = 1 << 1;
const EUROPE = 1 << 2;
const RUSSIA = 1 << 3;
const ASIA = 1 << 4;
const CHINA = 1 << 5;
const KOREA = 1 << 6;
const WORLD = Self::JAPAN.bits() | Self::USA.bits() | Self::EUROPE.bits() | Self::RUSSIA.bits();
}
}
impl fmt::Display for Region {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_empty() {
return write!(f, "Unknown");
}
if self.bits() == Region::WORLD.bits() {
return write!(f, "World");
}
let regions: Vec<&str> = self
.iter()
.map(|flag| match flag {
Region::JAPAN => "Japan",
Region::USA => "USA",
Region::EUROPE => "Europe",
Region::RUSSIA => "Russia",
Region::ASIA => "Asia",
Region::CHINA => "China",
Region::KOREA => "Korea",
_ => "",
})
.filter(|s| !s.is_empty())
.collect();
write!(f, "{}", regions.join("/"))
}
}
pub fn infer_region_from_filename(name: &str) -> Region {
let lower_name = name.to_lowercase();
let mut region = Region::UNKNOWN;
let region_patterns = [
(vec!["jap", "jp", "(j)", "[j]", "ntsc-j"], Region::JAPAN),
(vec!["usa", "(u)", "[u]", "ntsc-u", "ntsc-us"], Region::USA),
(vec!["eur", "(e)", "[e]", "pal", "ntsc-e"], Region::EUROPE),
(vec!["russia", "dendy"], Region::RUSSIA),
(vec!["(world)", "[world]", "(w)", "[w]"], Region::WORLD),
];
for (patterns, flag) in region_patterns {
for pattern in patterns {
if lower_name.contains(pattern) {
region |= flag;
break;
}
}
}
region
}
pub fn check_region_mismatch(source_name: &str, header_region: Region) -> bool {
let inferred_region = infer_region_from_filename(source_name);
if inferred_region.is_empty() || header_region.is_empty() {
return false;
}
!inferred_region.intersects(header_region)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_infer_region_from_filename_japan() {
assert_eq!(infer_region_from_filename("game (J).zip"), Region::JAPAN);
assert_eq!(infer_region_from_filename("game [J].zip"), Region::JAPAN);
assert_eq!(
infer_region_from_filename("game (Japan).zip"),
Region::JAPAN
);
assert_eq!(
infer_region_from_filename("game (NTSC-J).zip"),
Region::JAPAN
);
}
#[test]
fn test_infer_region_from_filename_usa() {
assert_eq!(infer_region_from_filename("game (U).zip"), Region::USA);
assert_eq!(infer_region_from_filename("game [U].zip"), Region::USA);
assert_eq!(infer_region_from_filename("game (USA).zip"), Region::USA);
assert_eq!(infer_region_from_filename("game (NTSC-U).zip"), Region::USA);
assert_eq!(
infer_region_from_filename("game (NTSC-US).zip"),
Region::USA
);
}
#[test]
fn test_infer_region_from_filename_europe() {
assert_eq!(infer_region_from_filename("game (E).zip"), Region::EUROPE);
assert_eq!(infer_region_from_filename("game [E].zip"), Region::EUROPE);
assert_eq!(
infer_region_from_filename("game (Europe).zip"),
Region::EUROPE
);
assert_eq!(infer_region_from_filename("game (PAL).zip"), Region::EUROPE);
assert_eq!(
infer_region_from_filename("game (NTSC-E).zip"),
Region::EUROPE
);
}
#[test]
fn test_infer_region_from_filename_none() {
assert_eq!(
infer_region_from_filename("game (unmarked).zip"),
Region::UNKNOWN
);
assert_eq!(
infer_region_from_filename("another game.zip"),
Region::UNKNOWN
);
}
#[test]
fn test_check_region_mismatch_no_mismatch_japan() {
assert_eq!(check_region_mismatch("game (J).zip", Region::JAPAN), false);
assert_eq!(
check_region_mismatch("game (Japan).zip", Region::JAPAN),
false
);
assert_eq!(check_region_mismatch("game (J).zip", Region::JAPAN), false);
}
#[test]
fn test_check_region_mismatch_no_mismatch_usa() {
assert_eq!(check_region_mismatch("game (U).zip", Region::USA), false);
assert_eq!(check_region_mismatch("game (USA).zip", Region::USA), false);
assert_eq!(check_region_mismatch("game (U).zip", Region::USA), false);
}
#[test]
fn test_check_region_mismatch_no_mismatch_europe() {
assert_eq!(check_region_mismatch("game (E).zip", Region::EUROPE), false);
assert_eq!(
check_region_mismatch("game (Europe).zip", Region::EUROPE),
false
);
assert_eq!(check_region_mismatch("game (E).zip", Region::EUROPE), false);
}
#[test]
fn test_check_region_mismatch_mismatch_japan_usa() {
assert_eq!(check_region_mismatch("game (J).zip", Region::USA), true);
assert_eq!(check_region_mismatch("game (Japan).zip", Region::USA), true);
}
#[test]
fn test_check_region_mismatch_mismatch_usa_europe() {
assert_eq!(check_region_mismatch("game (U).zip", Region::EUROPE), true);
assert_eq!(
check_region_mismatch("game (USA).zip", Region::EUROPE),
true
);
}
#[test]
fn test_check_region_mismatch_mismatch_europe_japan() {
assert_eq!(check_region_mismatch("game (E).zip", Region::JAPAN), true);
assert_eq!(
check_region_mismatch("game (Europe).zip", Region::JAPAN),
true
);
}
#[test]
fn test_check_region_mismatch_filename_has_region_header_unknown() {
assert_eq!(
check_region_mismatch("game (J).zip", Region::UNKNOWN),
false
);
assert_eq!(
check_region_mismatch("game (U).zip", Region::UNKNOWN),
false
);
assert_eq!(
check_region_mismatch("game (E).zip", Region::UNKNOWN),
false
);
}
#[test]
fn test_check_region_mismatch_filename_unknown_header_has_region() {
assert_eq!(check_region_mismatch("game.zip", Region::JAPAN), false);
assert_eq!(
check_region_mismatch("another game.zip", Region::USA),
false
);
assert_eq!(check_region_mismatch("game_title", Region::EUROPE), false);
}
#[test]
fn test_check_region_mismatch_both_unknown() {
assert_eq!(check_region_mismatch("game.zip", Region::UNKNOWN), false);
assert_eq!(
check_region_mismatch("another game.zip", Region::UNKNOWN),
false
);
assert_eq!(check_region_mismatch("game_title", Region::UNKNOWN), false);
}
#[test]
fn test_check_region_mismatch_case_insensitivity_filename() {
assert_eq!(
check_region_mismatch("game (JapAn).zip", Region::JAPAN),
false
);
assert_eq!(check_region_mismatch("game (uSa).zip", Region::USA), false);
assert_eq!(
check_region_mismatch("game (EuRoPe).zip", Region::EUROPE),
false
);
}
#[test]
fn test_overlap_logic() {
let filename_region = infer_region_from_filename("Contra (U).nes"); let header_region = Region::USA | Region::JAPAN;
assert!(filename_region.intersects(header_region));
assert_eq!(check_region_mismatch("Contra (U).nes", Region::USA), false);
}
#[test]
fn test_strict_mismatch() {
let filename_region = infer_region_from_filename("Contra (E).nes"); let header_region = Region::USA | Region::JAPAN;
assert!(!filename_region.intersects(header_region));
assert_eq!(check_region_mismatch("Contra (E).nes", Region::USA), true);
}
#[test]
fn test_world_rom() {
assert_eq!(check_region_mismatch("Game (W).bin", Region::USA), false);
}
#[test]
fn test_multiple_region_filename_display() {
let filename = "Super Game (U) (J).nes";
let region = infer_region_from_filename(filename).to_string();
assert_eq!(region, "Japan/USA")
}
}