diff --git a/src/cartridge/mmc1.rs b/src/cartridge/mmc1.rs index 0c632cd..7ce6949 100644 --- a/src/cartridge/mmc1.rs +++ b/src/cartridge/mmc1.rs @@ -4,11 +4,21 @@ pub struct Mmc1 { cart: Cartridge, step: u8, shift_register: u8, - prg_low_bank: usize, - prg_high_bank: usize, - chr_low_bank: usize, - chr_high_bank: usize, mirroring: Mirror, + control: u8, + + prg_ram_bank: Vec, // CPU $6000-$7FFF + prg_ram_enabled: bool, + + prg_low_bank: usize, // CPU $8000-$BFFF + prg_high_bank: usize, // CPU $C000-$FFFF + prg_bank_mode: u8, + prg_bank_select: usize, // selects among the PRG-RAM chunks in the cartridge + + chr_ram_bank: Vec, // used if cartridge doesn't have any CHR-ROM, 8KB, $0000-$1FFF + chr_low_bank: usize, // PPU $0000-$0FFF + chr_high_bank: usize, // PPU $1000-$1FFF + chr_bank_mode: bool, // false: switch 8 KB at a time; true: switch two separate 4 KB banks } impl Mmc1 { @@ -18,22 +28,142 @@ impl Mmc1 { cart: cart, step: 0, shift_register: 0, + mirroring: m, + control: 0, + prg_ram_bank: vec![0; 0x2000], + prg_ram_enabled: false, prg_low_bank: 0, prg_high_bank: 0, + prg_bank_mode: 3, + prg_bank_select: 0, + chr_ram_bank: vec![0; 0x2000], chr_low_bank: 0, chr_high_bank: 0, - mirroring: m, + chr_bank_mode: false, } } + + fn write_serial_port(&mut self, address: usize, value: u8) { + // if reset flag is on, reset + if value & 0b1000_0000 != 0 { + self.step = 0; + self.shift_register = 0; + // locking PRG ROM at $C000-$FFFF to the last bank + // self.prg_high_bank = self.cart.prg_rom_size - 1; + self.write_control(self.control | 0xC) + } else { + // otherwise perform normal write + self.shift_register >>= 1; + self.shift_register |= (value & 1) << 7; + if self.step == 4 { + // println!("writing 0x{:X} to 0x{:04X}", self.shift_register, address); + // shift register values will be in top 5 bits, so cut it down to size before moving on to where it's used + self.shift_register >>= 3; + match address { + 0x8000..=0x9FFF => self.write_control(self.shift_register), + 0xA000..=0xBFFF => self.write_chr_bank_low(self.shift_register), + 0xC000..=0xDFFF => self.write_chr_bank_high(self.shift_register), + 0xE000..=0xFFFF => self.write_prg_bank(self.shift_register), + _ => panic!("bad address write to MMC1: 0x{:X}", address), + } + self.step = 0; + self.shift_register = 0; + } else { + self.step += 1; + } + } + } + + fn write_control(&mut self, value: u8) { + self.control = value; + self.mirroring = match value & 0b11 { + 0 => Mirror::LowBank, + 1 => Mirror::HighBank, + 2 => Mirror::Vertical, + 3 => Mirror::Horizontal, + _ => panic!("invalid mirroring value"), + }; + self.prg_bank_mode = (value >> 2) & 0b11; + self.chr_bank_mode = if value & (1<<4) == 0 {false} else {true}; + } + + fn write_chr_bank_low(&mut self, value: u8) { + if self.chr_bank_mode { // 4 KB mode + self.chr_low_bank = value as usize; + } else { // 8 KB mode + let v = value & (0xFF - 1); // turn off low bit + self.chr_low_bank = v as usize; + self.chr_high_bank = (v + 1) as usize; + } + } + + fn write_chr_bank_high(&mut self, value: u8) { + if self.chr_bank_mode { // 4 KB mode only, ignored in 8 KB mode + self.chr_high_bank = value as usize; + } + } + + fn write_prg_bank(&mut self, value: u8) { + self.prg_bank_select = (value & 0b1111) as usize; + self.prg_ram_enabled = value & 0b10000 != 0; + } } impl Mapper for Mmc1 { fn read(&mut self, address: usize) -> u8 { - 0 + match address { + 0x0000..=0x0FFF => { + if self.cart.chr_rom_size > self.chr_low_bank { + self.cart.chr_rom[self.chr_low_bank][address % 0x1000] + } else { + self.chr_ram_bank[address] + } + }, + 0x1000..=0x1FFF => { + if self.cart.chr_rom_size > self.chr_high_bank { + self.cart.chr_rom[self.chr_high_bank][address % 0x1000] + } else { + self.chr_ram_bank[address] + } + }, + 0x6000..=0x7FFF => self.prg_ram_bank[address % 0x2000], + 0x8000..=0xBFFF => { + match self.prg_bank_mode { + 0 | 1 => { // switch 32 KB at $8000, ignoring low bit of bank number + let low_bank = self.prg_bank_select & (0xFF - 1); + self.cart.prg_rom[low_bank][address % 0x4000] + }, + 2 => self.cart.prg_rom[0][address % 0x4000], + 3 => self.cart.prg_rom[self.prg_bank_select][address % 0x4000], + _ => panic!("invalid PRG bank mode"), + } + }, + 0xC000..=0xFFFF => { + match self.prg_bank_mode { + 0 | 1 => { // switch 32 KB at $8000, ignoring low bit of bank number + let high_bank = (self.prg_bank_select & (0xFF - 1)) + 1; + self.cart.prg_rom[high_bank][address % 0x4000] + }, + 2 => self.cart.prg_rom[self.prg_bank_select][address % 0x4000], + 3 => self.cart.prg_rom[self.cart.prg_rom_size - 1][address % 0x4000], + _ => panic!("invalid PRG bank mode"), + } + }, + _ => panic!("invalid address passed to MMC1: 0x{:X}", address), + } } fn write(&mut self, address: usize, value: u8) { - + match address { + 0x0000..=0x1FFF => { // if we don't have CHR-ROM, write to CHR-RAM + if self.cart.chr_rom_size == 0 { + self.chr_ram_bank[address] = value; + } + }, + 0x6000..=0x7FFF => self.prg_ram_bank[address % 0x2000] = value, + 0x8000..=0xFFFF => self.write_serial_port(address, value), + _ => panic!("bad address write to MMC1: 0x{:X}", address), + } } fn get_mirroring(&mut self) -> Mirror { diff --git a/src/cartridge/mod.rs b/src/cartridge/mod.rs index 86281d6..aef1404 100644 --- a/src/cartridge/mod.rs +++ b/src/cartridge/mod.rs @@ -14,7 +14,7 @@ pub trait Mapper { fn get_mirroring(&mut self) -> Mirror; } -#[derive(Copy, Clone, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq)] pub enum Mirror { LowBank, HighBank, diff --git a/src/main.rs b/src/main.rs index 41a63f0..83caeb9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -110,11 +110,11 @@ fn main() -> Result<(), String> { // calculate fps let now = Instant::now(); if now > fps_timer + Duration::from_secs(1) { - println!("fps: {}", fps); + // println!("fps: {}", fps); fps = 0; fps_timer = now; - println!("samples per second: {}", sps); + // println!("samples per second: {}", sps); sps = 0; } } @@ -125,6 +125,7 @@ fn main() -> Result<(), String> { /* TODO: +- fix 8x16 sprites - common mappers - untangle CPU and PPU - DMC audio channel, high- and low-pass filters, refactor envelope, fix static diff --git a/src/ppu/memory.rs b/src/ppu/memory.rs index 53cdcf0..f4cafd0 100644 --- a/src/ppu/memory.rs +++ b/src/ppu/memory.rs @@ -54,56 +54,50 @@ impl super::Ppu { fn read_nametable(&mut self, address: usize) -> u8 { let base = address % 0x1000; let offset = base % 0x0400; - if self.mapper.borrow_mut().get_mirroring() == Mirror::Horizontal { - match base { - 0x0000..=0x07FF => { - self.nametable_0[offset] - }, - 0x0800..=0x0FFF => { - self.nametable_2[offset] - }, - _ => panic!("panicked writing nametable base: {}", base), - } - } else { // vertical - match base { - 0x0000..=0x03FF | 0x0800..=0x0BFF => { - self.nametable_0[offset] - }, - 0x0400..=0x07FF | 0x0C00..=0x0FFF => { - self.nametable_1[offset] - }, - _ => panic!("panicked writing nametable base: {}", base), - } + match self.mapper.borrow_mut().get_mirroring() { + Mirror::LowBank => self.nametable_low_bank[offset], + Mirror::HighBank => self.nametable_high_bank[offset], + Mirror::Horizontal => { + match base { + 0x0000..=0x07FF => self.nametable_low_bank[offset], + 0x0800..=0x0FFF => self.nametable_high_bank[offset], + _ => panic!("panicked reading nametable base: {}", base), + } + }, + Mirror::Vertical => { + match base { + 0x0000..=0x03FF | 0x0800..=0x0BFF => self.nametable_low_bank[offset], + 0x0400..=0x07FF | 0x0C00..=0x0FFF => self.nametable_high_bank[offset], + _ => panic!("panicked reading nametable base: {}", base), + } + }, } } fn write_nametable(&mut self, address: usize, value: u8) { let base = address % 0x1000; let offset = base % 0x0400; - if self.mapper.borrow_mut().get_mirroring() == Mirror::Horizontal { // horizontal - match base { - 0x0000..=0x07FF => { - self.nametable_0[offset] = value; - self.nametable_1[offset] = value; - }, - 0x0800..=0x0FFF => { - self.nametable_2[offset] = value; - self.nametable_3[offset] = value; - }, - _ => panic!("panicked writing nametable base: {}", base), - } - } else { // vertical - match base { - 0x0000..=0x03FF | 0x0800..=0x0BFF => { - self.nametable_0[offset] = value; - self.nametable_2[offset] = value; - }, - 0x0400..=0x07FF | 0x0C00..=0x0FFF => { - self.nametable_1[offset] = value; - self.nametable_3[offset] = value; - }, - _ => panic!("panicked writing nametable base: {}", base), - } + match self.mapper.borrow_mut().get_mirroring() { + Mirror::LowBank => { + self.nametable_low_bank[offset] = value; + }, + Mirror::HighBank => { + self.nametable_high_bank[offset] = value; + }, + Mirror::Horizontal => { + match base { + 0x0000..=0x07FF => self.nametable_low_bank[offset] = value, + 0x0800..=0x0FFF => self.nametable_high_bank[offset] = value, + _ => panic!("panicked writing nametable base: {}", base), + } + }, + Mirror::Vertical => { + match base { + 0x0000..=0x03FF | 0x0800..=0x0BFF => self.nametable_low_bank[offset] = value, + 0x0400..=0x07FF | 0x0C00..=0x0FFF => self.nametable_high_bank[offset] = value, + _ => panic!("panicked writing nametable base: {}", base), + } + }, } } } diff --git a/src/ppu/mod.rs b/src/ppu/mod.rs index 6cc3e79..8222fe1 100644 --- a/src/ppu/mod.rs +++ b/src/ppu/mod.rs @@ -22,10 +22,12 @@ pub struct Ppu { // Each nametable byte is a reference to the start of an 8-byte sequence in the pattern table. // That sequence represents an 8x8 tile, from top row to bottom. - nametable_0: Vec, // Nametable 0, 0x2000 - nametable_1: Vec, // Nametable 1, 0x2400 - nametable_2: Vec, // Nametable 2, 0x2800 - nametable_3: Vec, // Nametable 3, 0x2C00 + // First interpretation of how nametables work was wrong. There are two banks. (Or up to 4 on some games.) + // Pictures on http://wiki.nesdev.com/w/index.php/Mirroring refer to them as A and B. + // http://wiki.nesdev.com/w/index.php/MMC1 calls them higher and lower. + // They can be mirrored at certain memory ranges. + nametable_low_bank: Vec, + nametable_high_bank: Vec, // The palette shared by both background and sprites. // Consists of 32 bytes, each of which represents an index into the global PALETTE_TABLE. @@ -97,10 +99,8 @@ impl Ppu { x: 0, w: 0, mapper: mapper, - nametable_0: vec![0u8; 0x0400], - nametable_1: vec![0u8; 0x0400], - nametable_2: vec![0u8; 0x0400], - nametable_3: vec![0u8; 0x0400], + nametable_low_bank: vec![0u8; 0x0400], + nametable_high_bank: vec![0u8; 0x0400], palette_ram: vec![0u8; 0x0020], background_pattern_sr_low: 0, background_pattern_sr_high: 0, diff --git a/src/ppu/rendering.rs b/src/ppu/rendering.rs index 9824083..727d782 100644 --- a/src/ppu/rendering.rs +++ b/src/ppu/rendering.rs @@ -207,19 +207,35 @@ impl super::Ppu { if self.sprite_size == 8 { address = self.sprite_pattern_table_base; address += sprite_tile_index*16; + address += if sprite_attributes & (1<<7) == 0 { + self.scanline - sprite_y_position + } else { + self.sprite_size as usize - 1 - (self.scanline - sprite_y_position) + }; // For 8x16 sprites, the PPU ignores the pattern table selection and selects a pattern table from bit 0 of this number. } else { address = if sprite_tile_index & 1 == 0 { 0x0 } else { 0x1000 }; - address += (sprite_tile_index*16) & (0xFF - 1); // turn off bottom bit + address += (sprite_tile_index*16) & (0xFFFF - 1); // turn off bottom bit + let fine_y = if sprite_attributes & (1<<7) == 0 { + self.scanline - sprite_y_position + } else { + self.sprite_size as usize - 1 - (self.scanline - sprite_y_position) + }; + if fine_y > 7 { + address += 16; + address += fine_y - 8; + } else { + address += fine_y; + } } - let fine_y: usize; + // let fine_y: usize; // Handle vertical and horizontal flips, then write to shift registers - if sprite_attributes & (1<<7) == 0 { // if vertical flip bit not set - fine_y = self.scanline - sprite_y_position; // row-within-sprite offset is difference between current scanline and top of sprite - } else { // if flipped vertically - fine_y = self.sprite_size as usize - 1 - (self.scanline - sprite_y_position); - } - address += fine_y; + // if sprite_attributes & (1<<7) == 0 { // if vertical flip bit not set + // fine_y = self.scanline - sprite_y_position; // row-within-sprite offset is difference between current scanline and top of sprite + // } else { // if flipped vertically + // fine_y = self.sprite_size as usize - 1 - (self.scanline - sprite_y_position); + // } + // address += fine_y; let low_pattern_table_byte = self.read(address); let high_pattern_table_byte = self.read(address + 8); let mut shift_reg_vals = (0, 0);