diff --git a/README.md b/README.md
index 1c4ddae..4a24ef6 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,12 @@
# nestur
-Nestur is an NES emulator and a work in progress. There are likely still several bugs, and MMC3 support is not great. There are plenty of full-featured emulators out there; this is primarily an educational project but I do want it to run well.
+Nestur is an NES emulator. There are plenty of full-featured emulators out there; this is primarily an educational project but it is usable. There may still be many bugs, but I'm probably not aware of them so please submit issues.
- SDL2 is the only dependency
- no use of `unsafe`
- NTSC timing
-- may only implement mappers 0-4 as these cover ~85% of games according to http://tuxnes.sourceforge.net/nesmapper.txt
+- supports mappers 0-4 which cover ~85% of [games](http://tuxnes.sourceforge.net/nesmapper.txt)
-
+
## Controls:
```
@@ -24,11 +24,11 @@ ___________________
```
The code aims to follow the explanations from the [NES dev wiki](https://wiki.nesdev.com/w/index.php/NES_reference_guide) where possible, especially in the PPU, and the comments quote from it often. Thanks to everyone who contributes to that wiki/forum, and to Michael Fogleman's [NES](https://github.com/fogleman/nes) and Scott Ferguson's [Fergulator](https://github.com/scottferg/Fergulator) for getting me unstuck at several points.
-## Compilation and Use
+## Compilation and use
-1. Install Rust: https://www.rust-lang.org/tools/install
+1. Install [Rust](https://www.rust-lang.org/tools/install)
2. Configure SDL2 for your platform:
- - Windows: `SDL2.dll` is already in the repo so you don't have to do anything.
+ - Windows: `SDL2.dll` is already in the repo so you don't have to do anything
- macOS: Install [Homebrew](https://brew.sh/) and run `brew install sdl2`
- Linux: `sudo apt-get install libsdl2-dev` (or whatever your package manager is)
3. `cd nestur/ && cargo build --release` (be sure to build/run with the release flag or it will run very slowly)
@@ -38,13 +38,17 @@ The code aims to follow the explanations from the [NES dev wiki](https://wiki.ne
## To do:
-- Fix any bugs with mappers 0-3 and improve mapper 4 (MMC3)
-
- DMC audio channel, high- and low-pass filters
-- Save state/load functionality
+- Better GUI and distributable solution
+
+- Save states
- Player 2 controller?
+## Known problem games
+
+- Paperboy: input doesn't work
+
Please also check out [Cloaker](https://github.com/spieglt/cloaker) and [Flying Carpet](https://github.com/spieglt/flyingcarpet)!
diff --git a/pics/contra.png b/pics/contra.png
new file mode 100644
index 0000000..370e6ab
Binary files /dev/null and b/pics/contra.png differ
diff --git a/pics/dk.png b/pics/dk.png
new file mode 100644
index 0000000..9ad7cc5
Binary files /dev/null and b/pics/dk.png differ
diff --git a/pics/excitebike.png b/pics/excitebike.png
new file mode 100644
index 0000000..05ad84b
Binary files /dev/null and b/pics/excitebike.png differ
diff --git a/pics/final_fantasy.png b/pics/final_fantasy.png
new file mode 100644
index 0000000..0368aaa
Binary files /dev/null and b/pics/final_fantasy.png differ
diff --git a/pics/kirby.png b/pics/kirby.png
new file mode 100644
index 0000000..9af6706
Binary files /dev/null and b/pics/kirby.png differ
diff --git a/pics/metroid.png b/pics/metroid.png
new file mode 100644
index 0000000..366a6a2
Binary files /dev/null and b/pics/metroid.png differ
diff --git a/pics/smb3.png b/pics/smb3.png
new file mode 100644
index 0000000..ca97378
Binary files /dev/null and b/pics/smb3.png differ
diff --git a/src/audio.rs b/src/audio.rs
index 5fcfddb..f7362a8 100644
--- a/src/audio.rs
+++ b/src/audio.rs
@@ -51,8 +51,8 @@ pub fn initialize(sdl_context: &Sdl, buffer: Arc>>)
channels: Some(1), // mono
samples: Some(SAMPLES_PER_FRAME)
};
- audio_subsystem.open_playback(None, &desired_spec, |spec| {
- // println!("{:?}", spec);
+ audio_subsystem.open_playback(None, &desired_spec, |_spec| {
+ // println!("{:?}", _spec);
ApuSampler{buffer, sample_ratio: APU_SAMPLE_RATE / (SDL_SAMPLE_RATE as f32)}
})
}
diff --git a/src/cartridge/mmc3.rs b/src/cartridge/mmc3.rs
index 2ba55f3..5f7f51e 100644
--- a/src/cartridge/mmc3.rs
+++ b/src/cartridge/mmc3.rs
@@ -158,7 +158,7 @@ impl Mapper for Mmc3 {
0x8000..=0x9FFF => self.bank_select(value),
0xA000..=0xBFFF => self.mirroring = if value & 1 == 0 {Mirror::Vertical} else {Mirror::Horizontal},
0xC000..=0xDFFF => self.irq_latch = value,
- 0xE000..=0xFFFF => self.irq_enable = false,
+ 0xE000..=0xFFFF => {self.irq_enable = false; self.trigger_irq = false}, // Writing any value to this register will disable MMC3 interrupts AND acknowledge any pending interrupts.
_ => println!("bad address written to MMC3: 0x{:X}", address),
}
},
@@ -186,19 +186,16 @@ impl Mapper for Mmc3 {
fn load_battery_backed_ram(&mut self) {}
fn save_battery_backed_ram(&self) {}
+ // This function is called by the PPU when the A12 address line changes.
+ // It's supposed to only be called when A12 goes from 0 to 1, but that doesn't work
+ // for my emulator for some reason.
fn clock(&mut self) {
if self.reload_counter {
self.irq_counter = self.irq_latch;
self.reload_counter = false;
}
- // "Should reload and set IRQ every clock when reload is 0."
- if self.irq_latch == 0 && self.irq_enable {
- self.irq_counter = self.irq_latch;
- self.trigger_irq = true;
- }
if self.irq_counter == 0 {
self.irq_counter = self.irq_latch;
- return;
} else {
self.irq_counter -= 1;
}
@@ -207,16 +204,15 @@ impl Mapper for Mmc3 {
}
}
+ // This function is called by the CPU every step (which takes more than one CPU clock cycle).
+ // I think I'm supposed to be tracking IRQ delays by the PPU, not letting an IRQ fire if
+ // there was one within the last 15 PPU cycles, but that didn't work and this does.
fn check_irq(&mut self) -> bool {
- // if self.trigger_irq {
- // self.trigger_irq = false;
- // true
- // } else {
- // false
- // }
if self.trigger_irq {
self.trigger_irq = false;
- self.irq_delay = 15;
+ if self.irq_delay == 0 {
+ self.irq_delay = 5;
+ }
}
if self.irq_delay > 0 {
self.irq_delay -= 1;
diff --git a/src/cartridge/nrom.rs b/src/cartridge/nrom.rs
index a32d894..5f11758 100644
--- a/src/cartridge/nrom.rs
+++ b/src/cartridge/nrom.rs
@@ -2,51 +2,49 @@ use super::{Cartridge, Mapper, Mirror};
pub struct Nrom {
cart: Cartridge,
+ chr_ram: Vec,
}
impl Nrom {
pub fn new(cart: Cartridge) -> Self {
Nrom{
cart: cart,
+ chr_ram: vec![0; 0x2000],
}
}
}
impl Mapper for Nrom {
fn read(&mut self, address: usize) -> u8 {
- let cl = self.cart.chr_rom.len();
- let pl = self.cart.prg_rom.len();
let addr = address % 0x4000;
match address {
0x0000..=0x1FFF => {
- if cl > 0 {
- self.cart.chr_rom[cl-1][address]
+ if self.cart.chr_rom_size > 0 {
+ self.cart.chr_rom[0][address]
} else {
- 0
+ self.chr_ram[address]
}
},
0x8000..=0xBFFF => {
self.cart.prg_rom[0][addr]
},
0xC000..=0xFFFF => {
- self.cart.prg_rom[pl-1][addr]
+ self.cart.prg_rom[self.cart.prg_rom_size - 1][addr]
},
_ => {println!("bad address read from NROM mapper: 0x{:X}", address); 0},
}
}
fn write(&mut self, address: usize, value: u8) {
- let cl = self.cart.chr_rom.len();
- let pl = self.cart.prg_rom.len();
- let addr = address % 0x4000;
match address {
0x0000..=0x1FFF => {
- if cl > 0 {
- self.cart.chr_rom[cl-1][address] = value;
+ // ROM isn't written to
+ if self.cart.chr_rom_size == 0 {
+ self.chr_ram[address] = value;
}
},
- 0x8000..=0xBFFF => self.cart.prg_rom[0][addr] = value,
- 0xC000..=0xFFFF => self.cart.prg_rom[pl-1][addr] = value,
+ 0x8000..=0xBFFF => (),
+ 0xC000..=0xFFFF => (),
_ => println!("bad address written to NROM mapper: 0x{:X}", address),
}
}
diff --git a/src/cpu/mod.rs b/src/cpu/mod.rs
index 2e8624c..aff624f 100644
--- a/src/cpu/mod.rs
+++ b/src/cpu/mod.rs
@@ -158,6 +158,8 @@ impl Cpu {
if self.mapper.borrow_mut().check_irq() && (self.P & INTERRUPT_DISABLE_FLAG == 0) {
self.irq();
}
+ // TODO: should checks for APU and MMC3 IRQs be combined and acknowledged together?
+
// back up clock so we know how many cycles we complete
let clock = self.clock;
diff --git a/src/main.rs b/src/main.rs
index dc7db24..ed8fb46 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -118,7 +118,6 @@ fn main() -> Result<(), String> {
/*
TODO:
-- fix MMC3
- high- and low-pass audio filters
- DMC audio channel
- untangle CPU and APU/PPU?
@@ -142,13 +141,4 @@ Failed tests from instr_test-v5/rom_singles/:
CB AXS #n
7, abs_xy, 'illegal opcode using abs x: 9c'
-
-
-
-
-
-1. A12 stuff controls when IRQs fire. Don't think there are serious problems with when IRQs fire anymore,
-though vertical scroll is still shaky in Kirby.
-2. Don't think the timing stuff is related to the palette and background table issues in SMB2/3.
-How can the palette be wrong? Or is it not, but the attribute bits are reading wrong?
*/
diff --git a/src/ppu/cpu_registers.rs b/src/ppu/cpu_registers.rs
index eee053e..44d9867 100644
--- a/src/ppu/cpu_registers.rs
+++ b/src/ppu/cpu_registers.rs
@@ -110,7 +110,9 @@ impl super::Ppu {
set_bit(&mut self.t, 0xC, d, 0x4);
set_bit(&mut self.t, 0xD, d, 0x5);
// t: X...... ........ = 0
- set_bit(&mut self.t, 0xF, 0, 0);
+ set_bit(&mut self.t, 0xE, 0, 0);
+ // setting the 16th instead of the 15th bit in the line above was the longest, most frustrating oversight.
+ // caused weird problems with vertical scrolling and backgrounds that I initially thought were bugs with MMC3.
self.w = 1;
},
1 => { // second write
@@ -169,7 +171,6 @@ impl super::Ppu {
},
_ => panic!("reading from invalid PPU address: 0x{:04x}", self.v),
};
-
if self.rendering() && (self.scanline < 240 || self.scanline == 261) {
// During rendering (on the pre-render line and the visible lines 0-239, provided either background or sprite rendering is enabled),
// it will update v in an odd way, triggering a coarse X increment and a Y increment simultaneously (with normal wrapping behavior).
diff --git a/src/ppu/memory.rs b/src/ppu/memory.rs
index c7f2988..343647f 100644
--- a/src/ppu/memory.rs
+++ b/src/ppu/memory.rs
@@ -2,9 +2,8 @@ use crate::cartridge::Mirror;
impl super::Ppu {
- pub fn read(&mut self, addr: usize) -> u8 {
- let address = addr % 0x4000;
- match addr {
+ pub fn read(&mut self, address: usize) -> u8 {
+ match address {
0x0000..=0x1FFF => self.mapper.borrow_mut().read(address),
0x2000..=0x3EFF => self.read_nametable(address),
0x3F00..=0x3FFF => {
@@ -16,9 +15,9 @@ impl super::Ppu {
}
}
- pub fn write(&mut self, addr: usize, value: u8) {
- let address = addr % 0x4000;
- match addr {
+ pub fn write(&mut self, address: usize, value: u8) {
+ // let address = addr % 0x4000;
+ match address {
0x0000..=0x1FFF => self.mapper.borrow_mut().write(address, value),
0x2000..=0x3EFF => self.write_nametable(address, value),
0x3F00..=0x3FFF => {
diff --git a/src/ppu/mod.rs b/src/ppu/mod.rs
index 1359d5b..728c640 100644
--- a/src/ppu/mod.rs
+++ b/src/ppu/mod.rs
@@ -249,13 +249,10 @@ impl Ppu {
let current_a12 = if self.v & 1 << 12 != 0 { 1 } else { 0 };
if rendering
&& (0..241).contains(&self.scanline)
- // && (current_a12 == 1 && self.previous_a12 == 0)
&& current_a12 != self.previous_a12
{
- // println!("clocking");
self.mapper.borrow_mut().clock()
}
- // println!("current: {}, previous: {}", current_a12, self.previous_a12);
self.previous_a12 = current_a12;
(pixel, end_of_frame)