iNes 2.0 ROM loading
This commit is contained in:
parent
86463f59cd
commit
223fb6f55d
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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] = [
|
||||
|
@ -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 {
|
||||
|
@ -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<EOF>
|
||||
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<CpuTiming, RomReadError> {
|
||||
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<Rom, RomReadError> {
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1 +1,121 @@
|
||||
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<Rom, RomReadError>;
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user