mmc1 seems to work, fixed nametables, fixed most 8x16 sprites though hearts in zelda still wrong.
This commit is contained in:
parent
7fadf62e40
commit
4f63139cdf
|
@ -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<u8>, // 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<u8>, // 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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<u8>, // Nametable 0, 0x2000
|
||||
nametable_1: Vec<u8>, // Nametable 1, 0x2400
|
||||
nametable_2: Vec<u8>, // Nametable 2, 0x2800
|
||||
nametable_3: Vec<u8>, // 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<u8>,
|
||||
nametable_high_bank: Vec<u8>,
|
||||
|
||||
// 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,
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue