diff --git a/emulator/src/cartridge/cartridge.rs b/emulator/src/cartridge/cartridge.rs index fcc74b2..ef10767 100644 --- a/emulator/src/cartridge/cartridge.rs +++ b/emulator/src/cartridge/cartridge.rs @@ -1,6 +1,8 @@ use crate::cartridge::common::consts::NES_FILE_MAGIC_BYTES; -use crate::cartridge::common::enums::Nes; -use crate::cartridge::file_loader::{FileLoadable, NesRomReadError}; +use crate::cartridge::common::enums::errors::NesRomReadError; +use crate::cartridge::common::enums::nes::Nes; +use crate::cartridge::common::traits::cartridge_data::CartridgeData; +use crate::cartridge::common::traits::file_loadable::FileLoadable; use crate::cartridge::formats::i_nes::Ines; use crate::cartridge::formats::nes_2::Nes2; use crate::cartridge::registers::chr_rom::ChrRom; @@ -9,11 +11,6 @@ use std::fs::File; use std::io::{BufReader, Read, Seek, SeekFrom}; use std::path::Path; -pub trait CartridgeData { - fn prg_rom(&self) -> &PrgRom; - fn chr_rom(&self) -> &ChrRom; -} - pub struct Cartridge { data: Box, } @@ -72,6 +69,8 @@ impl CartridgeData for Cartridge { #[cfg(test)] mod tests { use super::*; + use crate::cartridge::common::consts::{CHR_UNIT_SIZE, PRG_UNIT_SIZE}; + #[test] fn test_from_file() { // Super Mario Bros @@ -90,13 +89,7 @@ mod tests { let chr_rom = cartridge.chr_rom(); - assert_eq!( - prg_rom.size(), - 2 * crate::cartridge::file_loader::PRG_UNIT_SIZE as usize - ); - assert_eq!( - chr_rom.size(), - 1 * crate::cartridge::file_loader::CHR_UNIT_SIZE as usize - ); + assert_eq!(prg_rom.size(), 2 * PRG_UNIT_SIZE as usize); + assert_eq!(chr_rom.size(), 1 * CHR_UNIT_SIZE as usize); } } diff --git a/emulator/src/cartridge/common/enums/errors.rs b/emulator/src/cartridge/common/enums/errors.rs new file mode 100644 index 0000000..9629c48 --- /dev/null +++ b/emulator/src/cartridge/common/enums/errors.rs @@ -0,0 +1,11 @@ +#[derive(thiserror::Error, Debug)] +pub enum NesRomReadError { + #[error("file format not supported")] + FileFormatNotSupported, + + #[error("missing magic bytes")] + MissingMagicBytes, + + #[error("missing prg rom")] + MissingPrgRom, +} diff --git a/emulator/src/cartridge/common/enums.rs b/emulator/src/cartridge/common/enums/mirroring.rs similarity index 67% rename from emulator/src/cartridge/common/enums.rs rename to emulator/src/cartridge/common/enums/mirroring.rs index c8bc6dd..be129ab 100644 --- a/emulator/src/cartridge/common/enums.rs +++ b/emulator/src/cartridge/common/enums/mirroring.rs @@ -1,26 +1,5 @@ use std::fmt::Debug; -pub enum Nes { - Ines, - Nes2, -} - -impl Debug for Nes { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Nes::Ines => write!(f, "Ines"), - Nes::Nes2 => write!(f, "Nes2"), - } - } -} -impl PartialEq for Nes { - fn eq(&self, other: &Self) -> bool { - matches!( - (self, other), - (Nes::Ines, Nes::Ines) | (Nes::Nes2, Nes::Nes2) - ) - } -} pub enum Mirroring { Horizontal, Vertical, diff --git a/emulator/src/cartridge/common/enums/mod.rs b/emulator/src/cartridge/common/enums/mod.rs new file mode 100644 index 0000000..76e9071 --- /dev/null +++ b/emulator/src/cartridge/common/enums/mod.rs @@ -0,0 +1,4 @@ +pub mod mirroring; + +pub mod errors; +pub mod nes; diff --git a/emulator/src/cartridge/common/enums/nes.rs b/emulator/src/cartridge/common/enums/nes.rs new file mode 100644 index 0000000..1209060 --- /dev/null +++ b/emulator/src/cartridge/common/enums/nes.rs @@ -0,0 +1,24 @@ +use std::fmt::Debug; + +pub enum Nes { + Ines, + Nes2, +} + +impl Debug for Nes { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Nes::Ines => write!(f, "Ines"), + Nes::Nes2 => write!(f, "Nes2"), + } + } +} + +impl PartialEq for Nes { + fn eq(&self, other: &Self) -> bool { + matches!( + (self, other), + (Nes::Ines, Nes::Ines) | (Nes::Nes2, Nes::Nes2) + ) + } +} diff --git a/emulator/src/cartridge/common/mod.rs b/emulator/src/cartridge/common/mod.rs index 0ed713e..7932426 100644 --- a/emulator/src/cartridge/common/mod.rs +++ b/emulator/src/cartridge/common/mod.rs @@ -1,2 +1,4 @@ pub mod consts; pub mod enums; +pub mod traits; +pub mod utils; diff --git a/emulator/src/cartridge/common/traits/cartridge_data.rs b/emulator/src/cartridge/common/traits/cartridge_data.rs new file mode 100644 index 0000000..91c9eb3 --- /dev/null +++ b/emulator/src/cartridge/common/traits/cartridge_data.rs @@ -0,0 +1,7 @@ +use crate::cartridge::registers::chr_rom::ChrRom; +use crate::cartridge::registers::prg_rom::PrgRom; + +pub trait CartridgeData { + fn prg_rom(&self) -> &PrgRom; + fn chr_rom(&self) -> &ChrRom; +} diff --git a/emulator/src/cartridge/common/traits/file_loadable.rs b/emulator/src/cartridge/common/traits/file_loadable.rs new file mode 100644 index 0000000..e698236 --- /dev/null +++ b/emulator/src/cartridge/common/traits/file_loadable.rs @@ -0,0 +1,7 @@ +use std::path::Path; + +pub trait FileLoadable { + fn from_file>(path: P) -> anyhow::Result + where + Self: Sized; +} diff --git a/emulator/src/cartridge/common/traits/mod.rs b/emulator/src/cartridge/common/traits/mod.rs new file mode 100644 index 0000000..9d3b459 --- /dev/null +++ b/emulator/src/cartridge/common/traits/mod.rs @@ -0,0 +1,2 @@ +pub mod cartridge_data; +pub mod file_loadable; diff --git a/emulator/src/cartridge/file_loader.rs b/emulator/src/cartridge/common/utils/file.rs similarity index 62% rename from emulator/src/cartridge/file_loader.rs rename to emulator/src/cartridge/common/utils/file.rs index c5e1ade..fab0d85 100644 --- a/emulator/src/cartridge/file_loader.rs +++ b/emulator/src/cartridge/common/utils/file.rs @@ -1,27 +1,4 @@ use std::io::Read; -use std::path::Path; - -pub const NES_FILE_MAGIC_BYTES: [u8; 4] = ['N' as u8, 'E' as u8, 'S' as u8, 0x1A]; -pub const PRG_UNIT_SIZE: u16 = 16; -pub const CHR_UNIT_SIZE: u16 = 8; - -#[derive(thiserror::Error, Debug)] -pub enum NesRomReadError { - #[error("file format not supported")] - FileFormatNotSupported, - - #[error("missing magic bytes")] - MissingMagicBytes, - - #[error("missing prg rom")] - MissingPrgRom, -} - -pub trait FileLoadable { - fn from_file>(path: P) -> anyhow::Result - where - Self: Sized; -} pub fn read_banks( file: &mut R, @@ -39,8 +16,7 @@ pub fn read_banks( #[cfg(test)] mod tests { - use super::*; - + use crate::cartridge::common::utils::file::read_banks; #[test] fn test_read_banks_2_4() { let data = vec![1, 2, 3, 4, 5, 6, 7, 8]; diff --git a/emulator/src/cartridge/common/utils/mod.rs b/emulator/src/cartridge/common/utils/mod.rs new file mode 100644 index 0000000..2e172cd --- /dev/null +++ b/emulator/src/cartridge/common/utils/mod.rs @@ -0,0 +1 @@ +pub mod file; diff --git a/emulator/src/cartridge/formats/i_nes.rs b/emulator/src/cartridge/formats/i_nes.rs index 307fe96..a3e8b5f 100644 --- a/emulator/src/cartridge/formats/i_nes.rs +++ b/emulator/src/cartridge/formats/i_nes.rs @@ -1,15 +1,15 @@ -use crate::cartridge::cartridge::CartridgeData; -use crate::cartridge::common::enums::Mirroring; -use crate::cartridge::file_loader::read_banks; -use crate::cartridge::file_loader::{ - FileLoadable, NesRomReadError, CHR_UNIT_SIZE, NES_FILE_MAGIC_BYTES, PRG_UNIT_SIZE, -}; +use crate::cartridge::common::enums::mirroring::Mirroring; +use crate::cartridge::common::traits::cartridge_data::CartridgeData; +use crate::cartridge::common::traits::file_loadable::FileLoadable; +use crate::cartridge::common::utils::file::read_banks; use crate::cartridge::registers::chr_rom::ChrRom; use crate::cartridge::registers::prg_rom::PrgRom; use std::fs::File; use std::io::{BufReader, Read}; use std::path::Path; +use crate::cartridge::common::consts::{CHR_UNIT_SIZE, NES_FILE_MAGIC_BYTES, PRG_UNIT_SIZE}; +use crate::cartridge::common::enums::errors::NesRomReadError; use std::fmt::Debug; // Bytes Description @@ -188,13 +188,16 @@ impl CartridgeData for Ines { } fn chr_rom(&self) -> &ChrRom { - self.chr_rom.as_ref().unwrap() + match self.chr_rom.as_ref() { + Some(x) => x, + None => panic!("CHR ROM is not present"), + } } } #[cfg(test)] mod tests { use super::*; - use crate::cartridge::formats::i_nes::FileLoadable; + use crate::cartridge::common::traits::file_loadable::FileLoadable; use std::io::Cursor; #[test] diff --git a/emulator/src/cartridge/formats/nes_2.rs b/emulator/src/cartridge/formats/nes_2.rs index 39cfed8..3d57350 100644 --- a/emulator/src/cartridge/formats/nes_2.rs +++ b/emulator/src/cartridge/formats/nes_2.rs @@ -1,16 +1,84 @@ -use crate::cartridge::cartridge::CartridgeData; -use crate::cartridge::file_loader::{FileLoadable, NesRomReadError, NES_FILE_MAGIC_BYTES}; +use crate::cartridge::common::consts::NES_FILE_MAGIC_BYTES; +use crate::cartridge::common::consts::PRG_UNIT_SIZE; +use crate::cartridge::common::enums::errors::NesRomReadError; +use crate::cartridge::common::enums::mirroring::Mirroring; +use crate::cartridge::common::traits::cartridge_data::CartridgeData; +use crate::cartridge::common::traits::file_loadable::FileLoadable; +use crate::cartridge::common::utils::file::read_banks; +use crate::cartridge::registers::chr_ram::ChrRam; use crate::cartridge::registers::chr_rom::ChrRom; +use crate::cartridge::registers::prg_ram::PrgRam; use crate::cartridge::registers::prg_rom::PrgRom; +use std::fmt::Debug; use std::fs::File; use std::io::{BufReader, Read}; use std::path::Path; -struct Nes2Header {} -#[allow(dead_code)] +// TODO: implement CartridgeData for Nes2 +// TODO: implement FileLoadable for Nes2 +// TODO: Fix the code +// TODO: Extended Console Type +// TODO: VS Unisystem +struct Nes2Header { + prg_rom_size: u8, + chr_rom_size: u8, + flags_6: u8, + flags_7: u8, + mapper: u8, + submapper: u8, + prg_ram_size: u8, + chr_ram_size: u8, + cpu_ppu_timing_mode: u8, + vs_unisystem: Option, + extended_console_type: Option, + misc_rom_count: u8, + default_expansion_device: u8, +} + +impl Debug for Nes2Header { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Nes2Header") + .field("prg_rom_size", &self.prg_rom_size) + .field("chr_rom_size", &self.chr_rom_size) + .field("flags_6", &self.flags_6) + .field("flags_7", &self.flags_7) + .field("mapper", &self.mapper) + .field("submapper", &self.submapper) + .field("prg_ram_size", &self.prg_ram_size) + .field("chr_ram_size", &self.chr_ram_size) + .field("cpu_ppu_timing_mode", &self.cpu_ppu_timing_mode) + .field("vs_unisystem", &self.vs_unisystem) + .field("extended_console_type", &self.extended_console_type) + .field("misc_rom_count", &self.misc_rom_count) + .field("default_expansion_device", &self.default_expansion_device) + .finish() + } +} + pub struct Nes2 { header: Nes2Header, + prg_rom: PrgRom, + chr_rom: Option, + trainer: Option<[u8; 512]>, + prg_ram: Option, + chr_ram: Option, + mirroring: Mirroring, } + +impl Debug for Nes2 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Nes2") + .field("header", &self.header) + .field("prg_rom", &self.prg_rom) + .field("chr_rom", &self.chr_rom) + .field("trainer", &self.trainer) + .field("prg_ram", &self.prg_ram) + .field("chr_ram", &self.chr_ram) + .field("mirroring", &self.mirroring) + .finish() + } +} + impl Nes2 { fn header_from_file(file: &mut R) -> anyhow::Result { let mut header = [0; 16]; @@ -24,17 +92,56 @@ impl Nes2 { return Err(NesRomReadError::FileFormatNotSupported.into()); } - Ok(Nes2Header {}) + let prg_rom_size = header[4]; + let chr_rom_size = header[5]; + let flags_6 = header[6]; + let flags_7 = header[7]; + let mapper = (flags_6 & 0xF0) | (flags_7 >> 4); + let submapper = (flags_6 & 0x0F) | (flags_7 & 0x0F); + let prg_ram_size = header[8]; + let chr_ram_size = header[9]; + let cpu_ppu_timing_mode = header[10]; + let vs_unisystem = if header[11] != 0 { + Some(header[11]) + } else { + None + }; + let extended_console_type = if header[12] != 0 { + Some(header[12]) + } else { + None + }; + let misc_rom_count = header[13]; + let default_expansion_device = header[14]; + + Ok(Nes2Header { + prg_rom_size, + chr_rom_size, + flags_6, + flags_7, + mapper, + submapper, + prg_ram_size, + chr_ram_size, + cpu_ppu_timing_mode, + vs_unisystem, + extended_console_type, + misc_rom_count, + default_expansion_device, + }) } } impl CartridgeData for Nes2 { fn prg_rom(&self) -> &PrgRom { - unimplemented!() + &self.prg_rom } fn chr_rom(&self) -> &ChrRom { - unimplemented!() + match self.chr_rom.as_ref() { + Some(x) => x, + None => panic!("CHR ROM is not present"), + } } } @@ -43,7 +150,55 @@ impl FileLoadable for Nes2 { let mut file = BufReader::new(File::open(path)?); let header = Nes2::header_from_file(&mut file)?; - Ok(Nes2 { header }) + let is_trainer_present = header.flags_6 & 0b00000100 != 0; + + let mirroring = if header.flags_6 & 0b00000001 != 0 { + Mirroring::Vertical + } else { + Mirroring::Horizontal + }; + + let mut trainer = None; + if is_trainer_present { + let mut trainer_data = [0; 512]; + file.read_exact(&mut trainer_data)?; + trainer = Some(trainer_data); + } + + let prg_rom = + PrgRom::new_with_data(read_banks(&mut file, header.prg_rom_size, PRG_UNIT_SIZE)?); + + let chr_rom = if header.chr_rom_size != 0 { + Some(ChrRom::new_with_data(read_banks( + &mut file, + header.chr_rom_size, + PRG_UNIT_SIZE, + )?)) + } else { + None + }; + + let prg_ram = if header.prg_ram_size != 0 { + Some(PrgRam::new(header.prg_ram_size as usize)) + } else { + None + }; + + let chr_ram = if header.chr_ram_size != 0 { + Some(ChrRam::new(header.chr_ram_size as usize)) + } else { + None + }; + + Ok(Nes2 { + header, + prg_rom, + chr_rom, + trainer, + prg_ram, + chr_ram, + mirroring, + }) } } diff --git a/emulator/src/cartridge/mod.rs b/emulator/src/cartridge/mod.rs index 2695ec3..bedc303 100644 --- a/emulator/src/cartridge/mod.rs +++ b/emulator/src/cartridge/mod.rs @@ -1,6 +1,5 @@ pub mod cartridge; pub mod common; -pub mod file_loader; mod formats; mod registers; diff --git a/emulator/src/cartridge/registers/chr_ram.rs b/emulator/src/cartridge/registers/chr_ram.rs new file mode 100644 index 0000000..e825319 --- /dev/null +++ b/emulator/src/cartridge/registers/chr_ram.rs @@ -0,0 +1,30 @@ +use crate::addressing::Addressable; +use std::fmt::Debug; + +pub struct ChrRam { + ram: Vec, +} + +impl Debug for ChrRam { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ChrRam").field("ram", &self.ram).finish() + } +} + +impl Addressable for ChrRam { + fn read(&mut self, address: u16) -> u8 { + self.ram[address as usize] + } + + fn write(&mut self, address: u16, data: u8) { + self.ram[address as usize] = data; + } +} + +impl ChrRam { + pub fn new(size: usize) -> ChrRam { + ChrRam { + ram: Vec::with_capacity(size), + } + } +} diff --git a/emulator/src/cartridge/registers/chr_rom.rs b/emulator/src/cartridge/registers/chr_rom.rs index 130e1d3..715b118 100644 --- a/emulator/src/cartridge/registers/chr_rom.rs +++ b/emulator/src/cartridge/registers/chr_rom.rs @@ -1,9 +1,15 @@ use crate::addressing::Addressable; -#[derive(Debug)] +use std::fmt::Debug; pub struct ChrRom { rom: Vec, } +impl Debug for ChrRom { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ChrRom").field("rom", &self.rom).finish() + } +} + impl Addressable for ChrRom { fn read(&mut self, address: u16) -> u8 { self.rom[address as usize] diff --git a/emulator/src/cartridge/registers/mod.rs b/emulator/src/cartridge/registers/mod.rs index e0f713b..4740234 100644 --- a/emulator/src/cartridge/registers/mod.rs +++ b/emulator/src/cartridge/registers/mod.rs @@ -1,2 +1,4 @@ +pub mod chr_ram; pub mod chr_rom; +pub mod prg_ram; pub mod prg_rom; diff --git a/emulator/src/cartridge/registers/prg_ram.rs b/emulator/src/cartridge/registers/prg_ram.rs new file mode 100644 index 0000000..002904b --- /dev/null +++ b/emulator/src/cartridge/registers/prg_ram.rs @@ -0,0 +1,30 @@ +use crate::addressing::Addressable; +use std::fmt::Debug; + +pub struct PrgRam { + ram: Vec, +} + +impl Debug for PrgRam { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PrgRam").field("ram", &self.ram).finish() + } +} + +impl Addressable for PrgRam { + fn read(&mut self, address: u16) -> u8 { + self.ram[address as usize] + } + + fn write(&mut self, address: u16, data: u8) { + self.ram[address as usize] = data; + } +} + +impl PrgRam { + pub fn new(size: usize) -> PrgRam { + PrgRam { + ram: Vec::with_capacity(size), + } + } +} diff --git a/emulator/src/cartridge/registers/prg_rom.rs b/emulator/src/cartridge/registers/prg_rom.rs index ed6a7a3..0dffc73 100644 --- a/emulator/src/cartridge/registers/prg_rom.rs +++ b/emulator/src/cartridge/registers/prg_rom.rs @@ -1,9 +1,15 @@ use crate::addressing::Addressable; -#[derive(Debug)] +use std::fmt::Debug; pub struct PrgRom { rom: Vec, } +impl Debug for PrgRom { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PrgRom").field("rom", &self.rom).finish() + } +} + impl Addressable for PrgRom { fn read(&mut self, address: u16) -> u8 { self.rom[address as usize]