mirror of
https://github.com/henryksloan/kind-nes.git
synced 2024-05-20 13:08:06 -04:00
Make iNES parsing more robust, and add mapper 71
This commit is contained in:
parent
27cadf6f5f
commit
54a628bbf9
|
@ -1,5 +1,7 @@
|
|||
// https://wiki.nesdev.com/w/index.php/INES
|
||||
// https://wiki.nesdev.com/w/index.php/NES_2.0#Header
|
||||
pub struct CartridgeMetadata {
|
||||
pub is_nes2: bool,
|
||||
pub n_prg_banks: u16,
|
||||
pub n_chr_banks: u16,
|
||||
pub hardwired_mirroring: Mirroring,
|
||||
|
@ -8,28 +10,28 @@ pub struct CartridgeMetadata {
|
|||
pub mapper_num: u16,
|
||||
|
||||
// Uncommon features
|
||||
pub submapper_num: u16,
|
||||
pub submapper_num: Option<u16>,
|
||||
pub console_type: ConsoleType,
|
||||
pub prg_ram_shifts: u8,
|
||||
pub prg_nvram_shifts: u8,
|
||||
pub chr_ram_shifts: u8,
|
||||
pub chr_nvram_shifts: u8,
|
||||
pub prg_ram_bytes: usize,
|
||||
pub prg_nvram_bytes: usize,
|
||||
pub chr_ram_bytes: usize,
|
||||
pub chr_nvram_bytes: usize,
|
||||
pub timing: ClockTiming,
|
||||
pub vs_system_type: u8,
|
||||
pub extended_console_type: u8,
|
||||
pub n_misc_roms: u8,
|
||||
pub default_expansion_device: u8,
|
||||
pub vs_system_type: Option<u8>,
|
||||
pub n_misc_roms: Option<u8>,
|
||||
pub default_expansion_device: Option<u8>,
|
||||
}
|
||||
|
||||
impl CartridgeMetadata {
|
||||
pub fn from_header(header: Vec<u8>) -> Result<Self, &'static str> {
|
||||
// TODO: Some garbage data in iNES 1.0 headers breaks this parsing
|
||||
if header[0..=3] != [b'N', b'E', b'S', 0x1A] {
|
||||
return Err("header does not begin with NES<EOF> identifier");
|
||||
}
|
||||
|
||||
let n_prg_banks = (((header[9] & 0x0F) as u16) << 8) | (header[4] as u16);
|
||||
let n_chr_banks = (((header[9] & 0xF0) as u16) << 4) | (header[5] as u16);
|
||||
let is_nes2 = (header[7] & 0b1100) == 0b1000;
|
||||
|
||||
let mut n_prg_banks = header[4] as u16;
|
||||
let mut n_chr_banks = header[5] as u16;
|
||||
|
||||
let hardwired_mirroring = if ((header[6] >> 3) & 1) == 1 {
|
||||
Mirroring::FourScreen
|
||||
|
@ -39,42 +41,108 @@ impl CartridgeMetadata {
|
|||
_ => Mirroring::Vertical,
|
||||
}
|
||||
};
|
||||
|
||||
let has_battery = ((header[6] >> 1) & 1) == 1;
|
||||
let has_trainer = ((header[6] >> 2) & 1) == 1;
|
||||
let mut mapper_num = (header[6] >> 4) as u16;
|
||||
|
||||
let mapper_num = ((header[6] >> 4) as u16)
|
||||
| ((header[7] & 0xF0) as u16)
|
||||
| (((header[8] & 0x0F) as u16) << 8);
|
||||
let submapper_num = (header[8] >> 4) as u16;
|
||||
let mut submapper_num = None;
|
||||
let mut timing = ClockTiming::NTSC;
|
||||
let mut vs_system_type = None;
|
||||
let mut n_misc_roms = None;
|
||||
let mut default_expansion_device = None;
|
||||
|
||||
let console_type = match header[7] & 0b11 {
|
||||
0 => ConsoleType::NESFamicom,
|
||||
1 => ConsoleType::VsSystem,
|
||||
2 => ConsoleType::Playchoice10,
|
||||
_ => ConsoleType::Extended,
|
||||
};
|
||||
let (console_type, prg_ram_bytes, prg_nvram_bytes, chr_ram_bytes, chr_nvram_bytes) =
|
||||
if is_nes2 {
|
||||
let console_type = match header[7] & 0b11 {
|
||||
0 => ConsoleType::NESFamicom,
|
||||
1 => ConsoleType::VsSystem,
|
||||
2 => ConsoleType::Playchoice10,
|
||||
_ => match header[13] & 0b1111 {
|
||||
0x0 => ConsoleType::NESFamicom,
|
||||
0x1 => ConsoleType::VsSystem,
|
||||
0x2 => ConsoleType::Playchoice10,
|
||||
0x3 => ConsoleType::DecimalFamiclone,
|
||||
0x4 => ConsoleType::VT01Monochrome,
|
||||
0x5 => ConsoleType::VT01STN,
|
||||
0x6 => ConsoleType::VT02,
|
||||
0x7 => ConsoleType::VT03,
|
||||
0x8 => ConsoleType::VT09,
|
||||
0x9 => ConsoleType::VT32,
|
||||
0xA => ConsoleType::VT369,
|
||||
0xB => ConsoleType::UM6578,
|
||||
_ => ConsoleType::Other,
|
||||
},
|
||||
};
|
||||
|
||||
let prg_ram_shifts = header[10] & 0x0F;
|
||||
let prg_nvram_shifts = (header[10] & 0xF0) >> 4;
|
||||
let chr_ram_shifts = header[11] & 0x0F;
|
||||
let chr_nvram_shifts = (header[11] & 0xF0) >> 4;
|
||||
mapper_num |= ((header[7] & 0xF0) as u16) | (((header[8] & 0x0F) as u16) << 8);
|
||||
submapper_num = Some((header[8] >> 4) as u16);
|
||||
|
||||
let timing = match header[12] & 0b11 {
|
||||
0 => ClockTiming::NTSC,
|
||||
1 => ClockTiming::PAL,
|
||||
2 => ClockTiming::MultiRegion,
|
||||
_ => ClockTiming::Dendy,
|
||||
};
|
||||
n_prg_banks |= ((header[9] & 0x0F) as u16) << 8;
|
||||
n_chr_banks |= ((header[9] & 0xF0) as u16) << 4;
|
||||
|
||||
// TODO: These can be enums
|
||||
let vs_system_type = header[13];
|
||||
let extended_console_type = header[13] & 0x0F;
|
||||
let prg_ram_bytes = 64usize << (header[10] & 0x0F);
|
||||
let prg_nvram_bytes = 64usize << ((header[10] & 0xF0) >> 4);
|
||||
let chr_ram_bytes = 64usize << (header[11] & 0x0F);
|
||||
let chr_nvram_bytes = 64usize << ((header[11] & 0xF0) >> 4);
|
||||
|
||||
let n_misc_roms = header[14] & 0b11;
|
||||
let default_expansion_device = header[14] & 0b11_1111;
|
||||
timing = match header[12] & 0b11 {
|
||||
0 => ClockTiming::NTSC,
|
||||
1 => ClockTiming::PAL,
|
||||
2 => ClockTiming::MultiRegion,
|
||||
_ => ClockTiming::Dendy,
|
||||
};
|
||||
|
||||
// This could be an enum if it's ever used
|
||||
vs_system_type = Some(header[13]);
|
||||
|
||||
n_misc_roms = Some(header[14] & 0b11);
|
||||
default_expansion_device = Some(header[14] & 0b11_1111);
|
||||
|
||||
(
|
||||
console_type,
|
||||
prg_ram_bytes,
|
||||
prg_nvram_bytes,
|
||||
chr_ram_bytes,
|
||||
chr_nvram_bytes,
|
||||
)
|
||||
} else {
|
||||
let console_type = match header[7] & 0b11 {
|
||||
0 => ConsoleType::NESFamicom,
|
||||
1 => ConsoleType::VsSystem,
|
||||
_ => ConsoleType::Playchoice10,
|
||||
};
|
||||
|
||||
// Either PRG-RAM or PRG-NVRAM, depending on the battery flag in byte 6
|
||||
let mut some_prg_ram_bytes = 0x2000;
|
||||
|
||||
// "A general rule of thumb: if the last 4 bytes are not all zero, and the header is not marked for NES 2.0 format,
|
||||
// an emulator should either mask off the upper 4 bits of the mapper number or simply refuse to load the ROM."
|
||||
if header[12] == 0 && header[13] == 0 && header[14] == 0 && header[15] == 0 {
|
||||
mapper_num |= (header[7] & 0xF0) as u16;
|
||||
|
||||
// "Size of PRG RAM in 8 KB units (Value 0 infers 8 KB for compatibility; see PRG RAM circuit)"
|
||||
some_prg_ram_bytes *= std::cmp::max(header[8], 1) as usize;
|
||||
}
|
||||
|
||||
let chr_ram_bytes = if n_chr_banks == 0 { 0x2000 } else { 0 };
|
||||
|
||||
let (prg_ram_bytes, prg_nvram_bytes) = if has_battery {
|
||||
(0, some_prg_ram_bytes)
|
||||
} else {
|
||||
(some_prg_ram_bytes, 0)
|
||||
};
|
||||
|
||||
(
|
||||
console_type,
|
||||
prg_ram_bytes,
|
||||
prg_nvram_bytes,
|
||||
chr_ram_bytes,
|
||||
0,
|
||||
)
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
is_nes2,
|
||||
n_prg_banks,
|
||||
n_chr_banks,
|
||||
hardwired_mirroring,
|
||||
|
@ -83,13 +151,12 @@ impl CartridgeMetadata {
|
|||
mapper_num,
|
||||
submapper_num,
|
||||
console_type,
|
||||
prg_ram_shifts,
|
||||
prg_nvram_shifts,
|
||||
chr_ram_shifts,
|
||||
chr_nvram_shifts,
|
||||
prg_ram_bytes,
|
||||
prg_nvram_bytes,
|
||||
chr_ram_bytes,
|
||||
chr_nvram_bytes,
|
||||
timing,
|
||||
vs_system_type,
|
||||
extended_console_type,
|
||||
n_misc_roms,
|
||||
default_expansion_device,
|
||||
})
|
||||
|
@ -109,7 +176,16 @@ pub enum ConsoleType {
|
|||
NESFamicom,
|
||||
VsSystem,
|
||||
Playchoice10,
|
||||
Extended,
|
||||
DecimalFamiclone,
|
||||
VT01Monochrome,
|
||||
VT01STN,
|
||||
VT02,
|
||||
VT03,
|
||||
VT09,
|
||||
VT32,
|
||||
VT369,
|
||||
UM6578,
|
||||
Other,
|
||||
}
|
||||
|
||||
pub enum ClockTiming {
|
||||
|
|
|
@ -196,7 +196,7 @@ impl Memory for Mapper1 {
|
|||
} else if 0xC000 <= addr && addr <= 0xDFFF {
|
||||
self.chr_bank_1 = self.shift_register;
|
||||
} else {
|
||||
self.prg_bank = self.shift_register;
|
||||
self.prg_bank = (self.shift_register) % (self.n_prg_banks as u8);
|
||||
}
|
||||
self.shift_register = 0b10000;
|
||||
self.shift_write_count = 0;
|
||||
|
|
67
nes/src/cartridge/mapper/mapper71.rs
Normal file
67
nes/src/cartridge/mapper/mapper71.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
use crate::cartridge::Mapper;
|
||||
use crate::cartridge::Mirroring;
|
||||
use memory::Memory;
|
||||
|
||||
// https://wiki.nesdev.com/w/index.php/INES_Mapper_071
|
||||
pub struct Mapper71 {
|
||||
n_prg_banks: u16,
|
||||
prg_rom: Vec<u8>,
|
||||
chr_mem: Vec<u8>,
|
||||
prg_bank: u8,
|
||||
|
||||
mirroring_option: Option<Mirroring>,
|
||||
}
|
||||
|
||||
impl Mapper for Mapper71 {
|
||||
fn get_nametable_mirroring(&self) -> Option<Mirroring> {
|
||||
self.mirroring_option
|
||||
}
|
||||
}
|
||||
|
||||
impl Mapper71 {
|
||||
pub fn new(n_prg_banks: u16, prg_data: Vec<u8>) -> Self {
|
||||
Self {
|
||||
n_prg_banks,
|
||||
chr_mem: vec![0; 0x2000],
|
||||
prg_rom: prg_data,
|
||||
prg_bank: 0,
|
||||
|
||||
mirroring_option: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Memory for Mapper71 {
|
||||
fn read(&mut self, addr: u16) -> u8 {
|
||||
self.peek(addr)
|
||||
}
|
||||
|
||||
fn peek(&self, addr: u16) -> u8 {
|
||||
if addr <= 0x1FFF {
|
||||
self.chr_mem[addr as usize % self.chr_mem.len()]
|
||||
} else if 0x8000 <= addr && addr <= 0xBFFF {
|
||||
self.prg_rom[self.prg_bank as usize * 0x4000 + (addr as usize - 0x8000)]
|
||||
} else if 0xC000 <= addr {
|
||||
self.prg_rom[(self.n_prg_banks as usize - 1) * 0x4000 + (addr as usize - 0xC000)]
|
||||
} else {
|
||||
0x0
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, addr: u16, data: u8) {
|
||||
if addr <= 0x1FFF {
|
||||
let len = self.chr_mem.len();
|
||||
self.chr_mem[addr as usize % len] = data;
|
||||
} else if 0x9000 <= addr && addr <= 0x9FFF {
|
||||
// "For compatibility without using a submapper, FCEUX begins all games with fixed mirroring,
|
||||
// and applies single screen mirroring only once $9000-9FFF is written, ignoring writes to $8000-8FFF."
|
||||
self.mirroring_option = Some(if (data >> 4) & 1 == 1 {
|
||||
Mirroring::SingleScreenUpper
|
||||
} else {
|
||||
Mirroring::SingleScreenLower
|
||||
});
|
||||
} else if addr >= 0xC000 {
|
||||
self.prg_bank = data % self.n_prg_banks as u8;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ mod mapper2;
|
|||
mod mapper3;
|
||||
mod mapper4;
|
||||
mod mapper7;
|
||||
mod mapper71;
|
||||
mod mapper9;
|
||||
|
||||
pub use self::mapper0::Mapper0;
|
||||
|
@ -15,8 +16,10 @@ pub use self::mapper2::Mapper2;
|
|||
pub use self::mapper3::Mapper3;
|
||||
pub use self::mapper4::Mapper4;
|
||||
pub use self::mapper7::Mapper7;
|
||||
pub use self::mapper71::Mapper71;
|
||||
pub use self::mapper9::Mapper9;
|
||||
|
||||
// TODO: Add reset to more mappers so NES::reset works
|
||||
pub trait Mapper: Memory {
|
||||
fn get_nametable_mirroring(&self) -> Option<Mirroring> {
|
||||
None // Unless otherwise specified, mirroring is hard-wired
|
||||
|
|
|
@ -52,10 +52,11 @@ impl Cartridge {
|
|||
n_chr_banks,
|
||||
prg_data,
|
||||
chr_data,
|
||||
meta.submapper_num == 1,
|
||||
meta.submapper_num == Some(1),
|
||||
)),
|
||||
7 => Box::from(Mapper7::new(n_prg_banks, prg_data)),
|
||||
9 => Box::from(Mapper9::new(n_prg_banks, prg_data, chr_data)),
|
||||
71 => Box::from(Mapper71::new(n_prg_banks, prg_data)),
|
||||
_ => return Err("unsupported mapper"),
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue