diff --git a/core/src/cpu/disassembler.rs b/core/src/cpu/disassembler.rs index 5d3a3ef..7ffd516 100644 --- a/core/src/cpu/disassembler.rs +++ b/core/src/cpu/disassembler.rs @@ -2,7 +2,7 @@ use crate::cpu::op::AddressingMode::*; use crate::cpu::op::{AddressingMode, Instruction}; use crate::cpu::Cpu; -impl Cpu { +impl Cpu<'_> { pub fn disassemble_next_instr(&self) { let registers = self.registers; let op_code = self.memory_bus.get_byte(registers.pc); diff --git a/core/src/cpu/mod.rs b/core/src/cpu/mod.rs index 38b8d49..cf7f2a1 100644 --- a/core/src/cpu/mod.rs +++ b/core/src/cpu/mod.rs @@ -53,13 +53,13 @@ struct CpuRegisters { bitflags! { pub struct CpuStatus: u8 { - const Carry = 0b00000001; - const Zero = 0b00000010; - const InterruptDisable = 0b00000100; - const Decimal = 0b00001000; - const Break = 0b00010000; - const Overflow = 0b01000000; - const Negative = 0b10000000; + const Carry = 0b0000_0001; + const Zero = 0b0000_0010; + const InterruptDisable = 0b0000_0100; + const Decimal = 0b0000_1000; + const Break = 0b0001_0000; + const Overflow = 0b0100_0000; + const Negative = 0b1000_0000; } } @@ -131,8 +131,8 @@ pub trait CpuInternals { fn stack_pop_context(&mut self); } -impl Cpu { - pub fn new<'a>(memory_bus: &mut MemoryBus) -> Self { +impl<'a> Cpu<'a> { + pub fn new(memory_bus: &mut MemoryBus) -> Self { Cpu { registers: CpuRegisters { pc: 0x8000, @@ -151,7 +151,7 @@ impl Cpu { } } -impl CpuInternals for Cpu { +impl CpuInternals for Cpu<'_> { fn get_status_flag(&self, flag: CpuStatus) -> bool { *self.registers.status & flag } @@ -240,7 +240,7 @@ impl CpuInternals for Cpu { } } -impl Clock for Cpu { +impl Clock for Cpu<'_> { fn cycle(&mut self) { self.cycle += 1; diff --git a/core/src/cpu/op.rs b/core/src/cpu/op.rs index 8c92fc0..f0a5ff0 100644 --- a/core/src/cpu/op.rs +++ b/core/src/cpu/op.rs @@ -97,7 +97,7 @@ impl Operand { } // Operands -impl Cpu { +impl Cpu<'_> { /// 16x16 grid of 6502 opcodes. Matches datasheet matrix for easy lookup #[rustfmt::skip] pub const INSTRUCTIONS: [Instruction; 256] = [ diff --git a/core/src/cpu/operations.rs b/core/src/cpu/operations.rs index 3bf4028..5501d28 100644 --- a/core/src/cpu/operations.rs +++ b/core/src/cpu/operations.rs @@ -7,7 +7,7 @@ fn is_sign_overflow(val1: u8, val2: u8, result: u8) -> bool { (val1 & 0x80 == val2 & 0x80) && (val1 & 0x80 != result & 0x80) } -impl Cpu { +impl Cpu<'_> { pub fn exec_instruction(&mut self, instr: Instruction) { let operand = self.operand_decode(instr.addr_mode()); if operand.is_page_crossing { diff --git a/core/src/rom/ines.rs b/core/src/rom/ines.rs index 92e1233..5cea701 100644 --- a/core/src/rom/ines.rs +++ b/core/src/rom/ines.rs @@ -1,37 +1,278 @@ //! INes 2.0 Format implementation -pub struct INesRom {} +use crate::rom::{CpuTiming, NametableMirroring, Rom, RomLoader, RomReadError}; +use bitflags::bitflags; -struct INesHeader { - prg_rom_size: u16, - chr_rom_size: u16, +pub struct INesHeader { + prg_rom_size_lsb: u8, + chr_rom_size_lsb: u8, + + // Flags 6 + // D~7654 3210 + // --------- + // NNNN FTBM + // |||| |||+-- Hard-wired nametable layout + // |||| ||| 0: Vertical arrangement ("mirrored horizontally") or mapper-controlled + // |||| ||| 1: Horizontal arrangement ("mirrored vertically") + // |||| ||+--- "Battery" and other non-volatile memory + // |||| || 0: Not present + // |||| || 1: Present + // |||| |+--- 512-byte Trainer + // |||| | 0: Not present + // |||| | 1: Present between Header and PRG-ROM data + // |||| +---- Alternative nametables + // |||| 0: No + // |||| 1: Yes + // ++++------ Mapper Number D3..D0 flag_6: u8, + + // Flags 7 + // D~7654 3210 + // --------- + // NNNN 10TT + // |||| ||++- Console type + // |||| || 0: Nintendo Entertainment System/Family Computer + // |||| || 1: Nintendo Vs. System + // |||| || 2: Nintendo Playchoice 10 + // |||| || 3: Extended Console Type + // |||| ++--- NES 2.0 identifier + // ++++------ Mapper Number D7..D4 flag_7: u8, - mapper: u8, - sub_mapper: u8, - prg_ram_size: u8, - prg_nvram_size: u8, + + // Mapper MSB/Submapper + // D~7654 3210 + // --------- + // SSSS NNNN + // |||| ++++- Mapper number D11..D8 + // ++++------ Submapper number + mapper_msb_submapper: u8, + + // PRG-ROM/CHR-ROM size MSB + // D~7654 3210 + // --------- + // CCCC PPPP + // |||| ++++- PRG-ROM size MSB + // ++++------ CHR-ROM size MSB + rom_sizes_msb: u8, + + // PRG-RAM/EEPROM size + // D~7654 3210 + // --------- + // pppp PPPP + // |||| ++++- PRG-RAM (volatile) shift count + // ++++------ PRG-NVRAM/EEPROM (non-volatile) shift count + // If the shift count is zero, there is no PRG-(NV)RAM. + // If the shift count is non-zero, the actual size is + // "64 << shift count" bytes, i.e. 8192 bytes for a shift count of 7. + prg_ram_eeprom_size: u8, + + // CHR-RAM size + // D~7654 3210 + // --------- + // cccc CCCC + // |||| ++++- CHR-RAM size (volatile) shift count + // ++++------ CHR-NVRAM size (non-volatile) shift count + // If the shift count is zero, there is no CHR-(NV)RAM. + // If the shift count is non-zero, the actual size is + // "64 << shift count" bytes, i.e. 8192 bytes for a shift count of 7. chr_ram_size: u8, - cpu_trainer: CpuTimingMode, - misc_rom_count: u8, - default_expansion_device: u8, // vs_ppu_type: u8, - // vs_hardware_type: u8, + + // CPU/PPU Timing + // D~7654 3210 + // --------- + // .... ..VV + // ++- CPU/PPU timing mode + // 0: RP2C02 ("NTSC NES") + // 1: RP2C07 ("Licensed PAL NES") + // 2: Multiple-region + // 3: UA6538 ("Dendy") + cpu_ppu_timing: u8, + + // When Byte 7 AND 3 =1: Vs. System Type + // D~7654 3210 + // --------- + // MMMM PPPP + // |||| ++++- Vs. PPU Type + // ++++------ Vs. Hardware Type + // + // When Byte 7 AND 3 =3: Extended Console Type + // D~7654 3210 + // --------- + // .... CCCC + // ++++- Extended Console Type + system_type: u8, + + // Miscellaneous ROMs + // D~7654 3210 + // --------- + // .... ..RR + // ++- Number of miscellaneous ROMs present + misc_roms: u8, + + // Default Expansion Device + // D~7654 3210 + // --------- + // ..DD DDDD + // ++-++++- Default Expansion Device + default_expansion_device: u8, } -#[repr(u8)] -enum CpuTimingMode { - NTSC = 0, - PAL = 1, - Multiple = 2, - Dendy = 3, +impl INesHeader { + /// Identification characters at the start of a iNes2.0 ROM file - NES + const IDENTIFICATION: [u8; 4] = [0x4E, 0x45, 0x53, 0x1A]; + + fn get_prg_ram_size(&self) -> (usize, usize) { + let prg_ram_size = (self.prg_ram_eeprom_size & 0xF) as usize; + let prg_nvram_size = (self.prg_ram_eeprom_size >> 4) as usize; + + (prg_ram_size, prg_nvram_size) + } + + fn get_prg_rom_size(&self) -> usize { + let prg_rom_size_lsb = self.prg_rom_size_lsb as usize; + let prg_rom_size_msb = (self.rom_sizes_msb & 0x0F) as usize; + if prg_rom_size_msb < 0xF { + // LSB and MSB are the size of the PRG ROM in 16KiB units + ((prg_rom_size_msb << 8) | prg_rom_size_lsb) << 14 + } else { + // (2 ^ exponent) * multiplier + let multiplier = (prg_rom_size_lsb & 0x2) * 2 + 1; + let exponent = prg_rom_size_lsb >> 2; + (1 << exponent) * multiplier + } + } + + fn get_chr_ram_size(&self) -> (usize, usize) { + let chr_ram_shifts = (self.chr_ram_size & 0xF) as usize; + let chr_nvram_shifts = (self.chr_ram_size >> 4) as usize; + + let chr_ram_size = match chr_ram_shifts { + 0 => 0, + shifts => 64 << shifts, + }; + + let chr_nvram_size = match chr_nvram_shifts { + 0 => 0, + shifts => 64 << shifts, + }; + + (chr_ram_size, chr_nvram_size) + } + + fn get_chr_rom_size(&self) -> usize { + let chr_rom_size_lsb = self.chr_rom_size_lsb as usize; + let chr_rom_size_msb = (self.rom_sizes_msb >> 4) as usize; + if chr_rom_size_msb < 0xF { + // LSB and MSB are the size of the CHR ROM in 8KiB units + ((chr_rom_size_msb << 8) | chr_rom_size_lsb) << 13 + } else { + // (2 ^ exponent) * multiplier + let multiplier = (chr_rom_size_lsb & 0x2) * 2 + 1; + let exponent = chr_rom_size_lsb >> 2; + (1 << exponent) * multiplier + } + } + + fn get_trainer_size(&self) -> usize { + if self.flag_6 & 4 == 0 { + 0 + } else { + 512 + } + } + + fn get_nametable_mirroring(&self) -> NametableMirroring { + match self.flag_6 & 1 { + 0 => NametableMirroring::Horizontal, + _ => NametableMirroring::Vertical, + } + } + + fn get_cpu_timing(&self) -> Result { + Ok(match self.cpu_ppu_timing { + 0 => CpuTiming::Ntsc, + 1 => CpuTiming::Pal, + 2 => CpuTiming::Multiple, + 3 => CpuTiming::Dendy, + _ => { + return Err(RomReadError::InvalidHeader(format!( + "Invalid CPU timing mode: {}", + self.cpu_ppu_timing + ))) + } + }) + } + + fn parse(header: &[u8]) -> Self { + Self { + prg_rom_size_lsb: header[4], + chr_rom_size_lsb: header[5], + flag_6: header[6], + flag_7: header[7], + mapper_msb_submapper: header[8], + rom_sizes_msb: header[9], + prg_ram_eeprom_size: header[10], + chr_ram_size: header[11], + cpu_ppu_timing: header[12], + system_type: header[13], + misc_roms: header[14], + default_expansion_device: header[15], + } + } } -union CpuSystemType { - vs_system_type: VsSystemType, - extended_console_type: u8, -} +impl RomLoader for INesHeader { + fn format_matches(header: &[u8]) -> bool { + for i in 0..4 { + if header[i] != Self::IDENTIFICATION[i] { + // This is not a header from a iNes 2.0 ROM + return false; + } + } -struct VsSystemType { - ppu_type: u8, - hardware_type: u8, + // if header[7] & 0b00001100 != 0b00001000 { + // // This is a iNes 1.0 ROM + // } + + true + } + + fn load(header: &[u8], file_data: &[u8]) -> Result { + let ines_header = Self::parse(header); + let prg_rom_size = ines_header.get_prg_rom_size(); + let chr_rom_size = ines_header.get_chr_rom_size(); + let trainer_size = ines_header.get_trainer_size(); + + let rom_size = file_data.len(); + let required_size = prg_rom_size + chr_rom_size + trainer_size; + if rom_size < required_size { + return Err(RomReadError::RomLengthMismatch(required_size, rom_size)); + } + + let (prg_ram_size, prg_nvram_size) = ines_header.get_prg_ram_size(); + let (chr_ram_size, chr_nvram_size) = ines_header.get_chr_ram_size(); + let nametable_mirroring = ines_header.get_nametable_mirroring(); + let cpu_timing = ines_header.get_cpu_timing()?; + + let prg_rom = file_data[trainer_size..prg_rom_size] + .to_vec() + .into_boxed_slice(); + let chr_rom = file_data + [(trainer_size + prg_rom_size)..(trainer_size + prg_rom_size + chr_rom_size)] + .to_vec() + .into_boxed_slice(); + + Ok(Rom { + prg_ram_size, + prg_nvram_size, + prg_rom_size, + prg_rom, + chr_ram_size, + chr_nvram_size, + chr_rom_size, + chr_rom, + nametable_mirroring, + cpu_timing, + }) + } } diff --git a/core/src/rom/mod.rs b/core/src/rom/mod.rs index bd80ba3..ac6d57f 100644 --- a/core/src/rom/mod.rs +++ b/core/src/rom/mod.rs @@ -1 +1,121 @@ -mod ines; \ No newline at end of file +use crate::rom::ines::INesHeader; +use simplelog::{debug, info}; +use std::fmt::{Display, Formatter}; +use std::fs; + +mod ines; + +pub struct Rom { + /// The size of the program RAM in bytes + pub prg_ram_size: usize, + + /// The size of the non-volatile program RAM in bytes + pub prg_nvram_size: usize, + + /// The size of the program ROM in bytes + pub prg_rom_size: usize, + + /// The program ROM + pub prg_rom: Box<[u8]>, + + /// The size of the character RAM + pub chr_ram_size: usize, + + /// The size of the character RAM (non-volatile) + pub chr_nvram_size: usize, + + /// The size of the character ROM in bytes + pub chr_rom_size: usize, + + /// The character ROM + pub chr_rom: Box<[u8]>, + + /// The mirroring of the nametable + pub nametable_mirroring: NametableMirroring, + + /// The timing of the CPU, also the region of the ROM + pub cpu_timing: CpuTiming, +} + +pub enum NametableMirroring { + Horizontal, + Vertical, +} + +pub enum CpuTiming { + Ntsc, + Multiple, + Pal, + Dendy +} + +pub trait RomLoader { + /// Checks if a header is for this file format. + /// + /// # Arguments + /// * `header` - The content of the header + /// + /// # Returns + /// A boolean indicating if the header matches the ROM format. + fn format_matches(header: &[u8]) -> bool; + + /// Load a ROM file. + /// + /// # Arguments + /// * `header` - The header of the ROM + /// * `file_data` - The data from the file + /// + /// # Returns + /// The ROM that was loaded + fn load(header: &[u8], file_data: &[u8]) -> Result; +} + +impl Rom { + fn read(path: &str) -> Result<(), RomReadError> { + info!("ROM - Reading file from {}", path); + + let content = match fs::read(path) { + Ok(content) => content, + Err(error) => return Err(RomReadError::Io(error)), + }; + + if content.len() < 16 { + return Err(RomReadError::MissingHeader) + } + + let header = &content[0..16]; + if !INesHeader::format_matches(header) { + return Err(RomReadError::FormatNotSupported) + } + + info!("ROM - Matched format iNes 2.0"); + + let rom = INesHeader::load(header, &content[16..])?; + + debug!("ROM - PRG ROM: {} bytes, CHR ROM: {} bytes", rom.prg_rom_size, rom.chr_rom_size); + info!("ROM - Loading successful"); + + Ok(()) + } +} + +enum RomReadError { + FormatNotSupported, + Io(std::io::Error), + InvalidHeader(String), + MissingHeader, + RomLengthMismatch(usize, usize), +} + +impl Display for RomReadError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match *self { + RomReadError::FormatNotSupported => write!(f, "ROM didn't match any supported format"), + RomReadError::Io(ref err) => write!(f, "IO error: {}", err), + RomReadError::InvalidHeader(ref message) => write!(f, "Invalid ROM header: {}", message), + RomReadError::MissingHeader => write!(f, "File was too short to contain a header"), + RomReadError::RomLengthMismatch(required_length, actual_length) => + write!(f, "ROM didn't contain enough data according to the header; required {} bytes but contained {} bytes", required_length, actual_length), + } + } +}