From 445d9dcdef6254bcd42257f6816f7a30d1cc2cd5 Mon Sep 17 00:00:00 2001 From: JakeBreath Date: Fri, 7 Aug 2026 22:16:35 -0500 Subject: [PATCH] added tests and all pass with this implementation --- src/cartridge.rs | 77 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/src/cartridge.rs b/src/cartridge.rs index 76204fc..3b08a82 100644 --- a/src/cartridge.rs +++ b/src/cartridge.rs @@ -1,6 +1,7 @@ - +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Mirroring { Horizontal, Vertical, FourScreen } +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Cartridge { prg_rom: Vec, chr_rom: Vec, @@ -14,6 +15,10 @@ pub struct Cartridge { impl Cartridge { pub fn from_file(path: &str) -> Result { let data: Vec = std::fs::read(path).map_err(|e| format!("couldn't read {path}: {e}"))?; + Self::parse_data(data) + } + + pub fn parse_data(data: Vec) -> Result { if data.len() < 16 { return Err("file is too short to be an iNES ROM".to_string()) } @@ -82,4 +87,74 @@ impl Cartridge { debug_assert!(address < 0x2000, "CHR writes outside $0000-$1FFF; Address from CPU: {address:#06X}"); self.chr_rom[address as usize] = value } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn load(name: &str) -> Cartridge { + let path = format!("{}/roms/{}", env!("CARGO_MANIFEST_DIR"), name); + Cartridge::from_file(&path).expect("test ROM should load") + } + + // Header parsing requires real test ROMs or actual game backups + #[test] + fn smb_header() { + let cart = load("backup/Super Mario Bros. (Japan, USA).nes"); + assert_eq!(cart.prg_rom.len(), 32 * 1024); + assert_eq!(cart.chr_rom.len(), 8 * 1024); + assert_eq!(cart.mapper, 0); + assert_eq!(cart.mirroring, Mirroring::Vertical); + } + + #[test] + fn nestest_header() { + let cart = load("test/cpu/nestest/nestest.nes"); + assert_eq!(cart.prg_rom.len(), 16 * 1024); + assert_eq!(cart.mirroring, Mirroring::Horizontal); + } + + // PRG ROM mappings + #[test] + fn nestest_prg_mirrors() { + let cart = load("test/cpu/nestest/nestest.nes"); // 16KiB - Mirrored + assert_eq!(cart.read_prg(0x8000), cart.read_prg(0xC000)); + } + + #[test] + fn smb_prg_no_mirror() { + let cart = load("backup/Super Mario Bros. (Japan, USA).nes"); + assert_eq!(cart.read_prg(0x8000), cart.prg_rom[0]); + assert_eq!(cart.read_prg(0xC000), cart.prg_rom[0x4000]); + } + + // Errors via parse_data + #[test] + fn rejects_bad_magic() { + assert!(Cartridge::parse_data(vec![0x00; 16]).is_err()); + } + + #[test] + fn rejects_short_file() { + assert!(Cartridge::parse_data(vec![b'N', b'E', b'S', 0x1A]).is_err()); + } + + #[test] + fn rejects_mmc1() { + let mut hdr = vec![0u8; 16]; + hdr[..4].copy_from_slice(b"NES\x1a"); + hdr[4] = 0x10; // 16 PRG banks + hdr[6] = 0x10; // mapper low nibble = 1 + let err = Cartridge::parse_data(hdr).unwrap_err(); + assert!(err.contains("mapper 1"), "got: {err}"); + } + + // CHR-RAM roundtrip (coredump has 0 CHR banks we make CHR-RAM) + #[test] + fn chr_ram_roundtrip() { + let mut cart = load("test/cpu/coredump-v1.3.nes"); + cart.write_chr(0x1234, 0xAB); + assert_eq!(cart.read_chr(0x1234), 0xAB); + } } \ No newline at end of file