Initial Commit of nesfuzz

This commit is contained in:
Sarah Jamie Lewis 2021-11-21 23:20:04 -08:00
parent 4afae10ece
commit f4b677d52a
34 changed files with 1833 additions and 1111 deletions

6
.gitignore vendored
View File

@ -10,3 +10,9 @@ Cargo.lock
**/*.rs.bk **/*.rs.bk
roms roms
*.fm2
*.nes
*.cdl
*.nl
*.idea
*cdl

View File

@ -1,14 +1,13 @@
[package] [package]
name = "nestur" name = "nesfuzz"
version = "0.1.0" version = "0.1.0"
authors = ["Theron <tspiegl@gmail.com>"] authors = ["sarah@openprivacy.ca"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
sdl2 = { version = "0.33", features = ["bundled", "static-link"] } minifb = "0.19.3"
serde = { version = "1.0.104", features = ["derive"] } serde = { version = "1.0.104", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
cpuprofiler = "0.0.3" font8x8 = "0.3.1"
priority-queue = "1.2.0"
[profile.release] hamming = "0.1.3"
debug = true

View File

@ -1,65 +1,54 @@
# nestur # nesfuzz
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. nesfuzz is a fuzzer for Nes Games by [@SarahJamieLewis](https://twitter.com/sarahjamielewis)
- no use of `unsafe`
- NTSC timing
- supports mappers 0-4 which cover ~85% of [games](http://tuxnes.sourceforge.net/nesmapper.txt)
<img src="pics/smb.png" width=250> <img src="pics/zelda_dungeon.png" width=250> <img src="pics/kirby.png" width=250> <img src="pics/dk.png" width=250> <img src="pics/smb3.png" width=250> <img src="pics/excitebike.png" width=250> nessfuzz built on top of the [nestur](https://github.com/spieglt/nestur) emulator by [@spieglt](https://github.com/spieglt).
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. ## Usage & Methodology
## Controls To begin fuzzing you will need a rom file, and a sample input file. For sample inputs see [TasVids](http://tasvideos.org/).
```
Button | Key
___________________
| A | D |
| B | F |
| Start | Enter |
| Select | R-Shift|
| Up | Up |
| Down | Down |
| Left | Left |
| Right | Right |
-------------------
F2: reset console `nessfuzz <rom> <tas file>`
F5: save game state `nessfuzz smb.rom happylee-supermariobros,warped.fm2`
F9: load most recent save state
```
If the game is called `mygame.nes`, the save state files will be called `mygame-#.dat`. To load any previous save state, drag and drop a `.dat` file onto the window.
## Use nesfuzz uses the same input to see novel RAM configurations and search the possible input space. It will also
tile 28 (by default), windows to allow you to see the fuzzing happen.
Double-click or run the executable from a terminal by itself to launch with instructions. Then click Ok and drag a (iNES/`.nes`) ROM file onto the window. Or, drag and drop a ROM file onto the executable to run it directly, or use the path to the ROM file as the first argument to the terminal command. ![](./fuzzimages/screenshot.png)
If the game uses battery-backed RAM (if it can save data when the console is turned off), a save file like `rom_filename.sav` will be created in the same folder as the ROM when the program is exited. When Nestur is run again, it will look for a file matching the ROM name, with a `.sav` extension instead of `.nes`. ## Parameters
## Compilation Found at the top of `main.rs` a few parameters control the types and effectiveness of fuzzing.
1. Install [Rust](https://www.rust-lang.org/tools/install) // The number of cpu instances to spawn..
2. Have a C compiler const NUM_THREADS: usize = 28;
- Linux: `sudo apt install build-essential`
- Mac: [XCode](https://apps.apple.com/us/app/xcode/id497799835) // The number of frames to fuzz and process
- Windows: install the [Visual Studio Build Tools](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=16) (or [Visual Studio](https://docs.microsoft.com/en-us/cpp/build/vscpp-step-0-installation?view=vs-2019) with the "Desktop development with C++" workload). // A small number exploits the current point more at the expense of
3. Install CMake // large exploration - and vice versa.
- Linux: `sudo apt install cmake` const FRAMES_TO_CONSIDER: usize = 400;
- Mac: install [Homebrew](https://brew.sh/) and run `brew install cmake`
- [Windows](https://cmake.org/download/) // Same input should generate the same output...
4. `cd nestur/ && cargo build --release` (be sure to build/run with the release flag or it will run very slowly) // (I make no guarantee of that at the moment)
5. The `nestur` executable or `nestur.exe` will be in `nestur/target/release`. const RNG_SEED: u32 = 0x5463753;
## To do // If set to a low number, this disables start presses after the given frame
// Useful for some games where pausing does nothing to advance the game...
- support other controllers? const DISABLE_START_PRESSES_AFTER: usize = 50;
- more mappers? // The rate at which seed inputs become corrupted..
const MUTATION_RATE: f64 = 0.1;
- better save file organization?
// The rate at which seed inputs may become soft resets..
## Known problem games const MUTATION_RATE_SOFT_RESET: f64 = 0.000;
- None currently, please report any issues
Please also check out [Cloaker](https://github.com/spieglt/cloaker) and [Flying Carpet](https://github.com/spieglt/flyingcarpet)! ## Known Issues
The only game that really works as expected is Super Mario Bros. with the `happylee-supermariobros,warped.fm2` input.
This is probably because of issues in the underlying emulator / differences in the expected behaviour of the system the
tas inputs are produced for v.s. the emulator.
Other games like Legend of Zelda, Megaman, Super Mario Bros. 3, Final Fantasy II etc. will run, but I have had any
tas inputs from them quickly become out of sync with the actual gameplay. Further research is needed to as to why
that is. Help appreciated.

BIN
fuzzimages/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

View File

@ -1,5 +1,7 @@
// number of CPU cycles between sample output level being adjusted // number of CPU cycles between sample output level being adjusted
pub const SAMPLE_RATES: [u16; 16] = [428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54]; pub const SAMPLE_RATES: [u16; 16] = [
428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54,
];
#[derive(serde::Serialize, serde::Deserialize, Clone)] #[derive(serde::Serialize, serde::Deserialize, Clone)]
pub struct DMC { pub struct DMC {
@ -96,8 +98,16 @@ impl DMC {
self.cpu_cycles_left = SAMPLE_RATES[self.rate_index]; self.cpu_cycles_left = SAMPLE_RATES[self.rate_index];
if self.enabled { if self.enabled {
match self.shift_register & 1 { match self.shift_register & 1 {
0 => if self.sample >= 2 { self.sample -= 2}, 0 => {
1 => if self.sample <= 125 { self.sample += 2 }, if self.sample >= 2 {
self.sample -= 2
}
}
1 => {
if self.sample <= 125 {
self.sample += 2
}
}
_ => panic!("uh oh! magical bits!"), _ => panic!("uh oh! magical bits!"),
} }
} else { } else {
@ -117,7 +127,7 @@ impl DMC {
self.enabled = true; self.enabled = true;
self.shift_register = s; self.shift_register = s;
self.sample_buffer = None; self.sample_buffer = None;
}, }
None => self.enabled = false, None => self.enabled = false,
} }
} }
@ -133,18 +143,18 @@ impl DMC {
self.loop_flag = value & 0b0100_0000 != 0; self.loop_flag = value & 0b0100_0000 != 0;
self.rate_index = value as usize & 0b0000_1111; self.rate_index = value as usize & 0b0000_1111;
} }
pub fn direct_load(&mut self, value: u8) { pub fn direct_load(&mut self, value: u8) {
// $4011 -DDD.DDDD Direct load (write) // $4011 -DDD.DDDD Direct load (write)
self.sample = value as u16 & 0b0111_1111; self.sample = value as u16 & 0b0111_1111;
} }
pub fn write_sample_address(&mut self, value: u8) { pub fn write_sample_address(&mut self, value: u8) {
// $4012 AAAA.AAAA Sample address (write) // $4012 AAAA.AAAA Sample address (write)
// bits 7-0 AAAA.AAAA Sample address = %11AAAAAA.AA000000 = $C000 + (A * 64) // bits 7-0 AAAA.AAAA Sample address = %11AAAAAA.AA000000 = $C000 + (A * 64)
self.sample_address = ((value as usize) << 6) + 0xC000; self.sample_address = ((value as usize) << 6) + 0xC000;
} }
pub fn write_sample_length(&mut self, value: u8) { pub fn write_sample_length(&mut self, value: u8) {
// $4013 LLLL.LLLL Sample length (write) // $4013 LLLL.LLLL Sample length (write)
// bits 7-0 LLLL.LLLL Sample length = %LLLL.LLLL0001 = (L * 16) + 1 bytes // bits 7-0 LLLL.LLLL Sample length = %LLLL.LLLL0001 = (L * 16) + 1 bytes

View File

@ -2,8 +2,8 @@
pub struct Envelope { pub struct Envelope {
pub period: u16, // constant volume/envelope period pub period: u16, // constant volume/envelope period
divider: u16, divider: u16,
pub decay_counter: u16, // remainder of envelope divider pub decay_counter: u16, // remainder of envelope divider
pub start: bool, // restarts envelope pub start: bool, // restarts envelope
pub length_counter_halt: bool, // also the envelope loop flag pub length_counter_halt: bool, // also the envelope loop flag
} }

View File

@ -1,14 +1,14 @@
mod noise;
mod square;
mod triangle;
mod dmc; mod dmc;
mod envelope; mod envelope;
mod noise;
pub mod serialize; pub mod serialize;
mod square;
mod triangle;
use dmc::DMC;
use noise::Noise; use noise::Noise;
use square::Square; use square::Square;
use triangle::Triangle; use triangle::Triangle;
use dmc::DMC;
// APU clock ticks every other CPU cycle. // APU clock ticks every other CPU cycle.
// Frame counter only ticks every 3728.5 APU ticks, and in audio frames of 4 or 5. // Frame counter only ticks every 3728.5 APU ticks, and in audio frames of 4 or 5.
@ -16,17 +16,17 @@ use dmc::DMC;
const FRAME_COUNTER_STEPS: [usize; 5] = [3728, 7456, 11185, 14914, 18640]; const FRAME_COUNTER_STEPS: [usize; 5] = [3728, 7456, 11185, 14914, 18640];
const LENGTH_COUNTER_TABLE: [u8; 32] = [ const LENGTH_COUNTER_TABLE: [u8; 32] = [
10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, 10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, 12, 16, 24, 18, 48, 20, 96, 22,
12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30, 192, 24, 72, 26, 16, 28, 32, 30,
]; ];
#[derive(serde::Serialize, serde::Deserialize, Clone)] #[derive(serde::Serialize, serde::Deserialize, Clone)]
pub struct Apu { pub struct Apu {
square1: Square, square1: Square,
square2: Square, square2: Square,
triangle: Triangle, triangle: Triangle,
noise: Noise, noise: Noise,
pub dmc: DMC, pub dmc: DMC,
square_table: Vec<f32>, square_table: Vec<f32>,
tnd_table: Vec<f32>, tnd_table: Vec<f32>,
@ -41,14 +41,18 @@ pub struct Apu {
impl Apu { impl Apu {
pub fn new() -> Self { pub fn new() -> Self {
let square_table = (0..31).map(|x| 95.52/((8128.0 / x as f32) + 100.0)).collect(); let square_table = (0..31)
let tnd_table = (0..203).map(|x| 163.67/((24329.0 / x as f32) + 100.0)).collect(); .map(|x| 95.52 / ((8128.0 / x as f32) + 100.0))
.collect();
let tnd_table = (0..203)
.map(|x| 163.67 / ((24329.0 / x as f32) + 100.0))
.collect();
Apu { Apu {
square1: Square::new(true), square1: Square::new(true),
square2: Square::new(false), square2: Square::new(false),
triangle: Triangle::new(), triangle: Triangle::new(),
noise: Noise::new(), noise: Noise::new(),
dmc: DMC::new(), dmc: DMC::new(),
square_table: square_table, square_table: square_table,
tnd_table: tnd_table, tnd_table: tnd_table,
@ -86,7 +90,8 @@ impl Apu {
fn mix(&self) -> f32 { fn mix(&self) -> f32 {
let square_out = self.square_table[(self.square1.sample + self.square2.sample) as usize]; let square_out = self.square_table[(self.square1.sample + self.square2.sample) as usize];
let tnd_out = self.tnd_table[((3*self.triangle.sample)+(2*self.noise.sample) + self.dmc.sample) as usize]; let tnd_out = self.tnd_table
[((3 * self.triangle.sample) + (2 * self.noise.sample) + self.dmc.sample) as usize];
square_out + tnd_out square_out + tnd_out
} }
@ -160,31 +165,31 @@ impl Apu {
// Writing to this register clears the DMC interrupt flag. // Writing to this register clears the DMC interrupt flag.
self.dmc.interrupt = false; self.dmc.interrupt = false;
// Writing a zero to any of the channel enable bits will silence that channel and immediately set its length counter to 0. // Writing a zero to any of the channel enable bits will silence that channel and immediately set its length counter to 0.
if value & (1<<0) != 0 { if value & (1 << 0) != 0 {
self.square1.enabled = true; self.square1.enabled = true;
} else { } else {
self.square1.enabled = false; self.square1.enabled = false;
self.square1.length_counter = 0; self.square1.length_counter = 0;
} }
if value & (1<<1) != 0 { if value & (1 << 1) != 0 {
self.square2.enabled = true; self.square2.enabled = true;
} else { } else {
self.square2.enabled = false; self.square2.enabled = false;
self.square2.length_counter = 0; self.square2.length_counter = 0;
} }
if value & (1<<2) != 0 { if value & (1 << 2) != 0 {
self.triangle.enabled = true; self.triangle.enabled = true;
} else { } else {
self.triangle.enabled = false; self.triangle.enabled = false;
self.triangle.length_counter = 0; self.triangle.length_counter = 0;
} }
if value & (1<<3) != 0 { if value & (1 << 3) != 0 {
self.noise.enabled = true; self.noise.enabled = true;
} else { } else {
self.noise.enabled = false; self.noise.enabled = false;
self.noise.length_counter = 0; self.noise.length_counter = 0;
} }
if value & (1<<4) != 0 { if value & (1 << 4) != 0 {
self.dmc.enabled = true; self.dmc.enabled = true;
// If the DMC bit is set, the DMC sample will be restarted only if its bytes remaining is 0. // If the DMC bit is set, the DMC sample will be restarted only if its bytes remaining is 0.
// If there are bits remaining in the 1-byte sample buffer, these will finish playing before the next sample is fetched. // If there are bits remaining in the 1-byte sample buffer, these will finish playing before the next sample is fetched.
@ -205,26 +210,26 @@ impl Apu {
let mut val = 0; let mut val = 0;
// N/T/2/1 will read as 1 if the corresponding length counter is greater than 0. For the triangle channel, the status of the linear counter is irrelevant. // N/T/2/1 will read as 1 if the corresponding length counter is greater than 0. For the triangle channel, the status of the linear counter is irrelevant.
if self.square1.length_counter != 0 { if self.square1.length_counter != 0 {
val |= 1<<0; val |= 1 << 0;
} }
if self.square2.length_counter != 0 { if self.square2.length_counter != 0 {
val |= 1<<1; val |= 1 << 1;
} }
if self.triangle.length_counter != 0 { if self.triangle.length_counter != 0 {
val |= 1<<2; val |= 1 << 2;
} }
if self.noise.length_counter != 0 { if self.noise.length_counter != 0 {
val |= 1<<3; val |= 1 << 3;
} }
// D will read as 1 if the DMC bytes remaining is more than 0. // D will read as 1 if the DMC bytes remaining is more than 0.
if self.dmc.bytes_remaining != 0 { if self.dmc.bytes_remaining != 0 {
val |= 1<<4; val |= 1 << 4;
} }
if self.frame_interrupt { if self.frame_interrupt {
val |= 1<<6; val |= 1 << 6;
} }
if self.dmc.interrupt { if self.dmc.interrupt {
val |= 1<<7; val |= 1 << 7;
} }
// Reading this register clears the frame interrupt flag (but not the DMC interrupt flag). // Reading this register clears the frame interrupt flag (but not the DMC interrupt flag).
self.frame_interrupt = false; self.frame_interrupt = false;
@ -235,9 +240,9 @@ impl Apu {
// $4017 // $4017
fn write_frame_counter(&mut self, value: u8) { fn write_frame_counter(&mut self, value: u8) {
// 0 selects 4-step sequence, 1 selects 5-step sequence // 0 selects 4-step sequence, 1 selects 5-step sequence
self.frame_sequence = if value & (1<<7) == 0 { 4 } else { 5 }; self.frame_sequence = if value & (1 << 7) == 0 { 4 } else { 5 };
// If set, the frame interrupt flag is cleared, otherwise it is unaffected. // If set, the frame interrupt flag is cleared, otherwise it is unaffected.
if value & (1<<6) != 0 { if value & (1 << 6) != 0 {
self.interrupt_inhibit = false; self.interrupt_inhibit = false;
} }
// If the mode flag is set, then both "quarter frame" and "half frame" signals are also generated. // If the mode flag is set, then both "quarter frame" and "half frame" signals are also generated.

View File

@ -1,6 +1,8 @@
use super::envelope::Envelope; use super::envelope::Envelope;
const NOISE_TABLE: [u16; 16] = [4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068]; const NOISE_TABLE: [u16; 16] = [
4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068,
];
// $400E M---.PPPP Mode and period (write) // $400E M---.PPPP Mode and period (write)
// bit 7 M--- ---- Mode flag // bit 7 M--- ---- Mode flag

View File

@ -1,6 +1,6 @@
pub type ApuData = super::Apu; pub type ApuData = super::Apu;
impl super::Apu{ impl super::Apu {
pub fn save_state(&self) -> ApuData { pub fn save_state(&self) -> ApuData {
self.clone() self.clone()
} }

View File

@ -12,7 +12,7 @@ pub struct Square {
pub sample: u16, // output value that gets sent to the mixer pub sample: u16, // output value that gets sent to the mixer
pub enabled: bool, pub enabled: bool,
constant_volume_flag: bool, // (0: use volume from envelope; 1: use constant volume) constant_volume_flag: bool, // (0: use volume from envelope; 1: use constant volume)
first_channel: bool, // hack to detect timing difference in clock_sweep() first_channel: bool, // hack to detect timing difference in clock_sweep()
timer: u16, timer: u16,
timer_period: u16, timer_period: u16,
@ -68,14 +68,15 @@ impl Square {
self.sample = if self.duty_cycle[self.duty_counter] == 0 // the sequencer output is zero, or self.sample = if self.duty_cycle[self.duty_counter] == 0 // the sequencer output is zero, or
|| self.timer_period > 0x7FF // overflow from the sweep unit's adder is silencing the channel, || self.timer_period > 0x7FF // overflow from the sweep unit's adder is silencing the channel,
|| self.length_counter == 0 // the length counter is zero, or || self.length_counter == 0 // the length counter is zero, or
|| self.timer_period < 8 // the timer has a value less than eight. || self.timer_period < 8
{ // the timer has a value less than eight.
0 {
} else if self.constant_volume_flag { 0
self.envelope.period } else if self.constant_volume_flag {
} else { self.envelope.period
self.envelope.decay_counter } else {
}; self.envelope.decay_counter
};
} }
pub fn clock_length_counter(&mut self) { pub fn clock_length_counter(&mut self) {
@ -88,14 +89,19 @@ impl Square {
self.calculate_target_period(); self.calculate_target_period();
// When the frame counter sends a half-frame clock (at 120 or 96 Hz), two things happen. // When the frame counter sends a half-frame clock (at 120 or 96 Hz), two things happen.
// If the divider's counter is zero, the sweep is enabled, and the sweep unit is not muting the channel: The pulse's period is adjusted. // If the divider's counter is zero, the sweep is enabled, and the sweep unit is not muting the channel: The pulse's period is adjusted.
if self.sweep_counter == 0 && self.sweep_enabled && !(self.timer_period < 8 || self.target_period > 0x7FF) { if self.sweep_counter == 0
&& self.sweep_enabled
&& !(self.timer_period < 8 || self.target_period > 0x7FF)
{
self.timer_period = self.target_period; self.timer_period = self.target_period;
} }
// If the divider's counter is zero or the reload flag is true: The counter is set to P and the reload flag is cleared. Otherwise, the counter is decremented. // If the divider's counter is zero or the reload flag is true: The counter is set to P and the reload flag is cleared. Otherwise, the counter is decremented.
if self.sweep_counter == 0 || self.sweep_reload { if self.sweep_counter == 0 || self.sweep_reload {
self.sweep_counter = self.sweep_period; self.sweep_counter = self.sweep_period;
self.sweep_reload = false; self.sweep_reload = false;
if self.sweep_enabled { self.timer_period = self.target_period; } // This fixes the DK walking sound. Why? Not reflected in documentation. if self.sweep_enabled {
self.timer_period = self.target_period;
} // This fixes the DK walking sound. Why? Not reflected in documentation.
} else { } else {
self.sweep_counter -= 1; self.sweep_counter -= 1;
} }
@ -126,8 +132,8 @@ impl Square {
pub fn write_duty(&mut self, value: u8) { pub fn write_duty(&mut self, value: u8) {
// The duty cycle is changed (see table below), but the sequencer's current position isn't affected. // The duty cycle is changed (see table below), but the sequencer's current position isn't affected.
self.duty_cycle = DUTY_CYCLE_SEQUENCES[(value >> 6) as usize]; self.duty_cycle = DUTY_CYCLE_SEQUENCES[(value >> 6) as usize];
self.envelope.length_counter_halt = value & (1<<5) != 0; self.envelope.length_counter_halt = value & (1 << 5) != 0;
self.constant_volume_flag = value & (1<<4) != 0; self.constant_volume_flag = value & (1 << 4) != 0;
self.envelope.period = value as u16 & 0b1111; self.envelope.period = value as u16 & 0b1111;
} }

View File

@ -1,6 +1,6 @@
const WAVEFORM: [u16; 32] = [ const WAVEFORM: [u16; 32] = [
15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 13, 14, 15,
]; ];
#[derive(serde::Serialize, serde::Deserialize, Clone)] #[derive(serde::Serialize, serde::Deserialize, Clone)]
@ -53,7 +53,8 @@ impl Triangle {
// If the linear counter reload flag is set, the linear counter is reloaded with the counter reload value, // If the linear counter reload flag is set, the linear counter is reloaded with the counter reload value,
if self.linear_counter_reload { if self.linear_counter_reload {
self.linear_counter = self.counter_reload_value; self.linear_counter = self.counter_reload_value;
} else if self.linear_counter != 0 { // otherwise if the linear counter is non-zero, it is decremented. } else if self.linear_counter != 0 {
// otherwise if the linear counter is non-zero, it is decremented.
self.linear_counter -= 1; self.linear_counter -= 1;
} }
// If the control flag is clear, the linear counter reload flag is cleared. // If the control flag is clear, the linear counter reload flag is cleared.
@ -90,5 +91,4 @@ impl Triangle {
self.timer_period |= (timer_high as u16) << 8; self.timer_period |= (timer_high as u16) << 8;
self.linear_counter_reload = true; self.linear_counter_reload = true;
} }
} }

View File

@ -1,133 +1 @@
use std::sync::{Arc, Mutex};
use sdl2::Sdl;
use sdl2::audio::{AudioCallback, AudioSpecDesired};
use std::f32::consts::PI;
const APU_SAMPLE_RATE: f32 = 894_886.5;
const SDL_SAMPLE_RATE: i32 = 44_100;
// Video runs at 60Hz, so console is clocked by doing enough work to create one frame of video, then sending the video and audio to their respective SDL
// devices and then sleeping. So the audio device is set to play 44,100 samples per second, and grab them in 60 intervals over the course of that second.
const SAMPLES_PER_FRAME: u16 = SDL_SAMPLE_RATE as u16/60;
pub struct ApuSampler {
// This buffer receives all of the raw audio produced by the APU.
// The callback will take what it needs when it needs it and truncate the buffer for smooth audio output.
buffer: Arc<Mutex<Vec<f32>>>,
sample_ratio: f32,
prev_input_90_hz: f32,
prev_output_90_hz: f32,
gamma_90_hz: f32,
prev_input_440_hz: f32,
prev_output_440_hz: f32,
gamma_440_hz: f32,
prev_input_14_khz: f32,
prev_output_14_khz: f32,
gamma_14_khz: f32,
}
impl ApuSampler {
fn high_pass_90_hz(&self, sample: f32) -> f32 {
// y[i] := α × y[i1] + α × (x[i] x[i1])
(self.gamma_90_hz * self.prev_output_90_hz) + (sample - self.prev_input_90_hz)
}
fn high_pass_440_hz(&self, sample: f32) -> f32 {
(self.gamma_440_hz * self.prev_output_440_hz) + (sample - self.prev_input_440_hz)
}
fn low_pass_14_khz(&self, sample: f32) -> f32 {
((1. - self.gamma_14_khz) * self.prev_output_14_khz) + (self.gamma_14_khz * sample)
}
}
impl AudioCallback for ApuSampler {
type Channel = f32;
fn callback(&mut self, out: &mut [f32]) {
let mut b = self.buffer.lock().unwrap();
// if we have data in the buffer
if b.len() > 0 {
// copy samples at the appropriate interval from the raw APU buffer to the output device
for (i, x) in out.iter_mut().enumerate() {
let sample_idx = ((i as f32) * self.sample_ratio) as usize;
if sample_idx < b.len() {
let sample = b[sample_idx];
let filtered_90_hz = self.high_pass_90_hz(sample);
self.prev_input_90_hz = sample;
self.prev_output_90_hz = filtered_90_hz;
let filtered_440_hz = self.high_pass_440_hz(filtered_90_hz);
self.prev_input_440_hz = filtered_90_hz;
self.prev_output_440_hz = filtered_440_hz;
let filtered_14_khz = self.low_pass_14_khz(filtered_440_hz);
self.prev_input_14_khz = filtered_440_hz;
self.prev_output_14_khz = filtered_14_khz;
*x = filtered_14_khz;
}
}
let l = b.len();
let target = (SAMPLES_PER_FRAME as f32 * self.sample_ratio) as usize;
if l > target {
*b = b.split_off(target);
}
} else {
println!("buffer empty!"); // happens when the callback fires twice between video frames
}
}
}
pub fn initialize(sdl_context: &Sdl, buffer: Arc<Mutex<Vec<f32>>>)
-> Result<sdl2::audio::AudioDevice<ApuSampler>, String>
{
let audio_subsystem = sdl_context.audio()?;
let desired_spec = AudioSpecDesired {
freq: Some(SDL_SAMPLE_RATE),
channels: Some(1), // mono
samples: Some(SAMPLES_PER_FRAME)
};
audio_subsystem.open_playback(None, &desired_spec, |_spec| {
// println!("{:?}", _spec);
ApuSampler{
buffer,
sample_ratio: APU_SAMPLE_RATE / (SDL_SAMPLE_RATE as f32),
prev_input_90_hz: 0.,
prev_output_90_hz: 0.,
gamma_90_hz: high_pass_coefficient(90.),
prev_input_440_hz: 0.,
prev_output_440_hz: 0.,
gamma_440_hz: high_pass_coefficient(440.),
prev_input_14_khz: 0.,
prev_output_14_khz: 0.,
gamma_14_khz: low_pass_coefficient(14_000.),
}
})
}
fn low_pass_coefficient(cutoff_freq: f32) -> f32 {
(2.*PI*cutoff_freq/SDL_SAMPLE_RATE as f32) / ((2.*PI*cutoff_freq/SDL_SAMPLE_RATE as f32) + 1.)
}
fn high_pass_coefficient(cutoff_freq: f32) -> f32 {
1. / ((2.*PI*cutoff_freq/SDL_SAMPLE_RATE as f32) + 1.)
}
/*
https://en.wikipedia.org/wiki/High-pass_filter
https://en.wikipedia.org/wiki/Low-pass_filter
low pass filter:
y = (1 - gamma) * y + gamma * x
high pass filter:
y[i] := gamma * y[i1] + gamma * (x[i] x[i1])
*/

View File

@ -1,4 +1,4 @@
use super::{Cartridge, Mapper, Mirror, serialize::*}; use super::{serialize::*, Cartridge, Mapper, Mirror};
pub struct Cnrom { pub struct Cnrom {
cart: Cartridge, cart: Cartridge,
@ -7,7 +7,7 @@ pub struct Cnrom {
impl Cnrom { impl Cnrom {
pub fn new(cart: Cartridge) -> Self { pub fn new(cart: Cartridge) -> Self {
Cnrom{ Cnrom {
cart: cart, cart: cart,
chr_bank_select: 0, chr_bank_select: 0,
} }
@ -21,8 +21,11 @@ impl Mapper for Cnrom {
match address { match address {
0x0000..=0x1FFF => self.cart.chr_rom[self.chr_bank_select][address], 0x0000..=0x1FFF => self.cart.chr_rom[self.chr_bank_select][address],
0x8000..=0xBFFF => self.cart.prg_rom[0][addr], 0x8000..=0xBFFF => self.cart.prg_rom[0][addr],
0xC000..=0xFFFF => self.cart.prg_rom[pl-1][addr], 0xC000..=0xFFFF => self.cart.prg_rom[pl - 1][addr],
_ => {println!("bad address read from CNROM mapper: 0x{:X}", address); 0}, _ => {
println!("bad address read from CNROM mapper: 0x{:X}", address);
0
}
} }
} }
@ -40,15 +43,15 @@ impl Mapper for Cnrom {
fn load_battery_backed_ram(&mut self) {} fn load_battery_backed_ram(&mut self) {}
fn save_battery_backed_ram(&self) {} fn save_battery_backed_ram(&self) {}
fn clock(&mut self) {} fn clock(&mut self) {}
fn check_irq(&mut self) -> bool {false} fn check_irq(&mut self) -> bool {
false
}
fn save_state(&self) -> MapperData { fn save_state(&self) -> MapperData {
MapperData::Cnrom( MapperData::Cnrom(CnromData {
CnromData { cart: self.cart.clone(),
cart: self.cart.clone(), chr_bank_select: self.chr_bank_select,
chr_bank_select: self.chr_bank_select, })
}
)
} }
fn load_state(&mut self, mapper_data: MapperData) { fn load_state(&mut self, mapper_data: MapperData) {

View File

@ -1,4 +1,4 @@
use super::{Cartridge, Mapper, Mirror, serialize::*}; use super::{serialize::*, Cartridge, Mapper, Mirror};
use std::fs::File; use std::fs::File;
use std::io::{Read, Write}; use std::io::{Read, Write};
@ -85,13 +85,15 @@ impl Mmc1 {
_ => panic!("invalid mirroring value"), _ => panic!("invalid mirroring value"),
}; };
self.prg_bank_mode = (value >> 2) & 0b11; self.prg_bank_mode = (value >> 2) & 0b11;
self.chr_bank_mode = if value & (1<<4) == 0 {false} else {true}; self.chr_bank_mode = if value & (1 << 4) == 0 { false } else { true };
} }
fn write_chr_bank_low(&mut self, value: u8) { fn write_chr_bank_low(&mut self, value: u8) {
if self.chr_bank_mode { // 4 KB mode if self.chr_bank_mode {
// 4 KB mode
self.chr_low_bank = value as usize; self.chr_low_bank = value as usize;
} else { // 8 KB mode } else {
// 8 KB mode
let v = value & (0xFF - 1); // turn off low bit let v = value & (0xFF - 1); // turn off low bit
self.chr_low_bank = v as usize; self.chr_low_bank = v as usize;
self.chr_high_bank = (v + 1) as usize; self.chr_high_bank = (v + 1) as usize;
@ -99,7 +101,8 @@ impl Mmc1 {
} }
fn write_chr_bank_high(&mut self, value: u8) { fn write_chr_bank_high(&mut self, value: u8) {
if self.chr_bank_mode { // 4 KB mode only, ignored in 8 KB mode if self.chr_bank_mode {
// 4 KB mode only, ignored in 8 KB mode
self.chr_high_bank = value as usize; self.chr_high_bank = value as usize;
} }
} }
@ -127,48 +130,51 @@ impl Mapper for Mmc1 {
_ => panic!("bad address read from MMC1: 0x{:X}", address), _ => panic!("bad address read from MMC1: 0x{:X}", address),
}; };
let chunk_num = bank / 2; let chunk_num = bank / 2;
let chunk_half = if bank % 2 == 0 {0x0} else {0x1000}; let chunk_half = if bank % 2 == 0 { 0x0 } else { 0x1000 };
self.cart.chr_rom[chunk_num][chunk_half + offset] self.cart.chr_rom[chunk_num][chunk_half + offset]
} else { } else {
// if we're in 8K bank mode, the whole $0000-$1FFF region will be the 8K range referred to by chr_low_bank // if we're in 8K bank mode, the whole $0000-$1FFF region will be the 8K range referred to by chr_low_bank
self.cart.chr_rom[self.chr_low_bank][address] self.cart.chr_rom[self.chr_low_bank][address]
} }
} }
}, }
0x6000..=0x7FFF => self.prg_ram_bank[address % 0x2000], 0x6000..=0x7FFF => self.prg_ram_bank[address % 0x2000],
0x8000..=0xBFFF => { 0x8000..=0xBFFF => {
match self.prg_bank_mode { match self.prg_bank_mode {
0 | 1 => { // switch 32 KB at $8000, ignoring low bit of bank number 0 | 1 => {
// switch 32 KB at $8000, ignoring low bit of bank number
let low_bank = self.prg_bank_select & (0xFF - 1); let low_bank = self.prg_bank_select & (0xFF - 1);
self.cart.prg_rom[low_bank][address % 0x4000] self.cart.prg_rom[low_bank][address % 0x4000]
}, }
2 => self.cart.prg_rom[0][address % 0x4000], 2 => self.cart.prg_rom[0][address % 0x4000],
3 => self.cart.prg_rom[self.prg_bank_select][address % 0x4000], 3 => self.cart.prg_rom[self.prg_bank_select][address % 0x4000],
_ => panic!("invalid PRG bank mode"), _ => panic!("invalid PRG bank mode"),
} }
}, }
0xC000..=0xFFFF => { 0xC000..=0xFFFF => {
match self.prg_bank_mode { match self.prg_bank_mode {
0 | 1 => { // switch 32 KB at $8000, ignoring low bit of bank number 0 | 1 => {
// switch 32 KB at $8000, ignoring low bit of bank number
let high_bank = (self.prg_bank_select & (0xFF - 1)) + 1; let high_bank = (self.prg_bank_select & (0xFF - 1)) + 1;
self.cart.prg_rom[high_bank][address % 0x4000] self.cart.prg_rom[high_bank][address % 0x4000]
}, }
2 => self.cart.prg_rom[self.prg_bank_select][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], 3 => self.cart.prg_rom[self.cart.prg_rom_size - 1][address % 0x4000],
_ => panic!("invalid PRG bank mode"), _ => panic!("invalid PRG bank mode"),
} }
}, }
_ => panic!("invalid address passed to MMC1: 0x{:X}", address), _ => panic!("invalid address passed to MMC1: 0x{:X}", address),
} }
} }
fn write(&mut self, address: usize, value: u8) { fn write(&mut self, address: usize, value: u8) {
match address { match address {
0x0000..=0x1FFF => { // if we don't have CHR-ROM, write to CHR-RAM 0x0000..=0x1FFF => {
// if we don't have CHR-ROM, write to CHR-RAM
if self.cart.chr_rom_size == 0 { if self.cart.chr_rom_size == 0 {
self.chr_ram_bank[address] = value; self.chr_ram_bank[address] = value;
} }
}, }
0x6000..=0x7FFF => self.prg_ram_bank[address % 0x2000] = value, 0x6000..=0x7FFF => self.prg_ram_bank[address % 0x2000] = value,
0x8000..=0xFFFF => self.write_serial_port(address, value), 0x8000..=0xFFFF => self.write_serial_port(address, value),
_ => panic!("bad address write to MMC1: 0x{:X}", address), _ => panic!("bad address write to MMC1: 0x{:X}", address),
@ -186,9 +192,11 @@ impl Mapper for Mmc1 {
let mut save_file = p.join(stem); let mut save_file = p.join(stem);
save_file.set_extension("sav"); save_file.set_extension("sav");
if Path::new(&save_file).exists() { if Path::new(&save_file).exists() {
let mut f = File::open(save_file.clone()).expect("save file exists but could not open it"); let mut f =
File::open(save_file.clone()).expect("save file exists but could not open it");
let mut battery_backed_ram_data = vec![]; let mut battery_backed_ram_data = vec![];
f.read_to_end(&mut battery_backed_ram_data).expect("error reading save file"); f.read_to_end(&mut battery_backed_ram_data)
.expect("error reading save file");
println!("loading battery-backed RAM from file: {:?}", save_file); println!("loading battery-backed RAM from file: {:?}", save_file);
self.prg_ram_bank = battery_backed_ram_data; self.prg_ram_bank = battery_backed_ram_data;
} }
@ -204,31 +212,32 @@ impl Mapper for Mmc1 {
println!("saving battery-backed RAM to file: {:?}", save_file); println!("saving battery-backed RAM to file: {:?}", save_file);
let mut f = File::create(&save_file) let mut f = File::create(&save_file)
.expect("could not create output file for battery-backed RAM"); .expect("could not create output file for battery-backed RAM");
f.write_all(&self.prg_ram_bank).expect("could not write battery-backed RAM to file"); f.write_all(&self.prg_ram_bank)
.expect("could not write battery-backed RAM to file");
} }
} }
fn clock(&mut self) {} fn clock(&mut self) {}
fn check_irq(&mut self) -> bool {false} fn check_irq(&mut self) -> bool {
false
}
fn save_state(&self) -> MapperData { fn save_state(&self) -> MapperData {
MapperData::Mmc1( MapperData::Mmc1(Mmc1Data {
Mmc1Data { cart: self.cart.clone(),
cart: self.cart.clone(), step: self.step,
step: self.step, shift_register: self.shift_register,
shift_register: self.shift_register, mirroring: self.mirroring,
mirroring: self.mirroring, control: self.control,
control: self.control, prg_ram_bank: self.prg_ram_bank.clone(),
prg_ram_bank: self.prg_ram_bank.clone(), prg_ram_enabled: self.prg_ram_enabled,
prg_ram_enabled: self.prg_ram_enabled, prg_bank_mode: self.prg_bank_mode,
prg_bank_mode: self.prg_bank_mode, prg_bank_select: self.prg_bank_select,
prg_bank_select: self.prg_bank_select, chr_ram_bank: self.chr_ram_bank.clone(),
chr_ram_bank: self.chr_ram_bank.clone(), chr_low_bank: self.chr_low_bank,
chr_low_bank: self.chr_low_bank, chr_high_bank: self.chr_high_bank,
chr_high_bank: self.chr_high_bank, chr_bank_mode: self.chr_bank_mode,
chr_bank_mode: self.chr_bank_mode, })
}
)
} }
fn load_state(&mut self, mapper_data: MapperData) { fn load_state(&mut self, mapper_data: MapperData) {

View File

@ -1,4 +1,4 @@
use super::{Cartridge, Mapper, Mirror, serialize::*}; use super::{serialize::*, Cartridge, Mapper, Mirror};
pub struct Mmc3 { pub struct Mmc3 {
cart: Cartridge, cart: Cartridge,
@ -20,7 +20,6 @@ pub struct Mmc3 {
prg_rom_bank_mode: bool, prg_rom_bank_mode: bool,
// 0: two 2 KB banks at $0000-$0FFF, four 1 KB banks at $1000-$1FFF // 0: two 2 KB banks at $0000-$0FFF, four 1 KB banks at $1000-$1FFF
// 1: two 2 KB banks at $1000-$1FFF, four 1 KB banks at $0000-$0FFF // 1: two 2 KB banks at $1000-$1FFF, four 1 KB banks at $0000-$0FFF
chr_rom_bank_mode: bool, chr_rom_bank_mode: bool,
chr_ram_bank: Vec<u8>, // used if cartridge doesn't have any CHR-ROM, 8KB, $0000-$1FFF chr_ram_bank: Vec<u8>, // used if cartridge doesn't have any CHR-ROM, 8KB, $0000-$1FFF
} }
@ -28,7 +27,7 @@ pub struct Mmc3 {
impl Mmc3 { impl Mmc3 {
pub fn new(cart: Cartridge) -> Self { pub fn new(cart: Cartridge) -> Self {
let m = cart.mirroring; let m = cart.mirroring;
Mmc3{ Mmc3 {
cart: cart, cart: cart,
mirroring: m, mirroring: m,
bank_registers: vec![0, 0, 0, 0, 0, 0, 0, 0], bank_registers: vec![0, 0, 0, 0, 0, 0, 0, 0],
@ -49,8 +48,8 @@ impl Mmc3 {
fn bank_select(&mut self, value: u8) { fn bank_select(&mut self, value: u8) {
self.next_bank = value & 0b111; self.next_bank = value & 0b111;
// ?? = value & (1<<5); // Nothing on the MMC3, see MMC6 // ?? = value & (1<<5); // Nothing on the MMC3, see MMC6
self.prg_rom_bank_mode = value & (1<<6) != 0; self.prg_rom_bank_mode = value & (1 << 6) != 0;
self.chr_rom_bank_mode = value & (1<<7) != 0; self.chr_rom_bank_mode = value & (1 << 7) != 0;
} }
fn bank_data(&mut self, value: u8) { fn bank_data(&mut self, value: u8) {
@ -69,77 +68,72 @@ impl Mmc3 {
impl Mapper for Mmc3 { impl Mapper for Mmc3 {
fn read(&self, address: usize) -> u8 { fn read(&self, address: usize) -> u8 {
let val = match address { let val = match address {
0x0000..=0x1FFF => { // reading from CHR-ROM 0x0000..=0x1FFF => {
// reading from CHR-ROM
let offset_1k = address % 0x400; let offset_1k = address % 0x400;
let offset_2k = address % 0x800; let offset_2k = address % 0x800;
let bank_reg_num = match self.chr_rom_bank_mode { let bank_reg_num = match self.chr_rom_bank_mode {
true => { true => match address {
match address { 0x0000..=0x03FF => 2,
0x0000..=0x03FF => 2, 0x0400..=0x07FF => 3,
0x0400..=0x07FF => 3, 0x0800..=0x0BFF => 4,
0x0800..=0x0BFF => 4, 0x0C00..=0x0FFF => 5,
0x0C00..=0x0FFF => 5, 0x1000..=0x17FF => 0,
0x1000..=0x17FF => 0, 0x1800..=0x1FFF => 1,
0x1800..=0x1FFF => 1, _ => panic!("oh no"),
_ => panic!("oh no"),
}
}, },
false => { false => match address {
match address { 0x0000..=0x07FF => 0,
0x0000..=0x07FF => 0, 0x0800..=0x0FFF => 1,
0x0800..=0x0FFF => 1, 0x1000..=0x13FF => 2,
0x1000..=0x13FF => 2, 0x1400..=0x17FF => 3,
0x1400..=0x17FF => 3, 0x1800..=0x1BFF => 4,
0x1800..=0x1BFF => 4, 0x1C00..=0x1FFF => 5,
0x1C00..=0x1FFF => 5, _ => panic!("oh no"),
_ => panic!("oh no"),
}
}, },
}; };
let bank_num = self.bank_registers[bank_reg_num]; let bank_num = self.bank_registers[bank_reg_num];
let chunk_num = bank_num / 8; let chunk_num = bank_num / 8;
let chunk_eighth = (bank_num % 8) * 0x400; let chunk_eighth = (bank_num % 8) * 0x400;
if bank_reg_num == 0 || bank_reg_num == 1 { // dealing with 2K banks of 8K chunks if bank_reg_num == 0 || bank_reg_num == 1 {
// dealing with 2K banks of 8K chunks
self.cart.chr_rom[chunk_num][chunk_eighth + offset_2k] self.cart.chr_rom[chunk_num][chunk_eighth + offset_2k]
} else { // dealing with 1K banks of 8K chunks } else {
// dealing with 1K banks of 8K chunks
self.cart.chr_rom[chunk_num][chunk_eighth + offset_1k] self.cart.chr_rom[chunk_num][chunk_eighth + offset_1k]
} }
}, }
0x6000..=0x7FFF => self.prg_ram_bank[address % 0x2000], // PRG-RAM 0x6000..=0x7FFF => self.prg_ram_bank[address % 0x2000], // PRG-RAM
0x8000..=0xFFFF => { // reading from PRG ROM, dealing with 8K banks of 16K chunks 0x8000..=0xFFFF => {
// reading from PRG ROM, dealing with 8K banks of 16K chunks
let offset_8k = address % 0x2000; let offset_8k = address % 0x2000;
let num_banks = self.cart.prg_rom_size * 2; let num_banks = self.cart.prg_rom_size * 2;
let bank_num = match self.prg_rom_bank_mode { let bank_num = match self.prg_rom_bank_mode {
true => { true => match address {
match address { 0x8000..=0x9FFF => num_banks - 2,
0x8000..=0x9FFF => num_banks - 2, 0xA000..=0xBFFF => self.bank_registers[7],
0xA000..=0xBFFF => self.bank_registers[7], 0xC000..=0xDFFF => self.bank_registers[6],
0xC000..=0xDFFF => self.bank_registers[6], 0xE000..=0xFFFF => num_banks - 1,
0xE000..=0xFFFF => num_banks - 1, _ => panic!("oh no"),
_ => panic!("oh no"),
}
}, },
false => { false => match address {
match address { 0x8000..=0x9FFF => self.bank_registers[6],
0x8000..=0x9FFF => self.bank_registers[6], 0xA000..=0xBFFF => self.bank_registers[7],
0xA000..=0xBFFF => self.bank_registers[7], 0xC000..=0xDFFF => num_banks - 2,
0xC000..=0xDFFF => num_banks - 2, 0xE000..=0xFFFF => num_banks - 1,
0xE000..=0xFFFF => num_banks - 1, _ => panic!("oh no"),
_ => panic!("oh no"),
}
}, },
}; };
let chunk_num = bank_num / 2; let chunk_num = bank_num / 2;
let chunk_half = (bank_num % 2) * 0x2000; let chunk_half = (bank_num % 2) * 0x2000;
self.cart.prg_rom[chunk_num][chunk_half + offset_8k] self.cart.prg_rom[chunk_num][chunk_half + offset_8k]
}
},
_ => { _ => {
println!("bad address read from MMC3: 0x{:X}", address); println!("bad address read from MMC3: 0x{:X}", address);
0 0
}, }
}; };
val val
} }
@ -149,20 +143,31 @@ impl Mapper for Mmc3 {
if self.cart.chr_rom_size == 0 { if self.cart.chr_rom_size == 0 {
self.chr_ram_bank[address] = value; self.chr_ram_bank[address] = value;
} }
return return;
} }
match address % 2 == 0 { match address % 2 == 0 {
true => { // even true => {
// even
match address { match address {
0x6000..=0x7FFF => self.prg_ram_bank[address % 0x2000] = value, // PRG-RAM 0x6000..=0x7FFF => self.prg_ram_bank[address % 0x2000] = value, // PRG-RAM
0x8000..=0x9FFF => self.bank_select(value), 0x8000..=0x9FFF => self.bank_select(value),
0xA000..=0xBFFF => self.mirroring = if value & 1 == 0 {Mirror::Vertical} else {Mirror::Horizontal}, 0xA000..=0xBFFF => {
self.mirroring = if value & 1 == 0 {
Mirror::Vertical
} else {
Mirror::Horizontal
}
}
0xC000..=0xDFFF => self.irq_latch = value, 0xC000..=0xDFFF => self.irq_latch = value,
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. 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), _ => println!("bad address written to MMC3: 0x{:X}", address),
} }
}, }
false => { // odd false => {
// odd
match address { match address {
0x6000..=0x7FFF => self.prg_ram_bank[address % 0x2000] = value, // PRG-RAM 0x6000..=0x7FFF => self.prg_ram_bank[address % 0x2000] = value, // PRG-RAM
0x8000..=0x9FFF => self.bank_data(value), 0x8000..=0x9FFF => self.bank_data(value),
@ -171,7 +176,7 @@ impl Mapper for Mmc3 {
0xE000..=0xFFFF => self.irq_enable = true, 0xE000..=0xFFFF => self.irq_enable = true,
_ => println!("bad address written to MMC3: 0x{:X}", address), _ => println!("bad address written to MMC3: 0x{:X}", address),
} }
}, }
} }
} }
@ -224,24 +229,22 @@ impl Mapper for Mmc3 {
} }
fn save_state(&self) -> MapperData { fn save_state(&self) -> MapperData {
MapperData::Mmc3( MapperData::Mmc3(Mmc3Data {
Mmc3Data { cart: self.cart.clone(),
cart: self.cart.clone(), mirroring: self.mirroring,
mirroring: self.mirroring, bank_registers: self.bank_registers.clone(),
bank_registers: self.bank_registers.clone(), next_bank: self.next_bank,
next_bank: self.next_bank, irq_latch: self.irq_latch,
irq_latch: self.irq_latch, irq_counter: self.irq_counter,
irq_counter: self.irq_counter, irq_enable: self.irq_enable,
irq_enable: self.irq_enable, trigger_irq: self.trigger_irq,
trigger_irq: self.trigger_irq, reload_counter: self.reload_counter,
reload_counter: self.reload_counter, irq_delay: self.irq_delay,
irq_delay: self.irq_delay, prg_ram_bank: self.prg_ram_bank.clone(),
prg_ram_bank: self.prg_ram_bank.clone(), prg_rom_bank_mode: self.prg_rom_bank_mode,
prg_rom_bank_mode: self.prg_rom_bank_mode, chr_rom_bank_mode: self.chr_rom_bank_mode,
chr_rom_bank_mode: self.chr_rom_bank_mode, chr_ram_bank: self.chr_ram_bank.clone(),
chr_ram_bank: self.chr_ram_bank.clone(), })
}
)
} }
fn load_state(&mut self, mapper_data: MapperData) { fn load_state(&mut self, mapper_data: MapperData) {

View File

@ -1,15 +1,15 @@
mod nrom;
mod mmc1;
mod uxrom;
mod cnrom; mod cnrom;
mod mmc1;
mod mmc3; mod mmc3;
mod nrom;
pub mod serialize; pub mod serialize;
mod uxrom;
use nrom::Nrom;
use mmc1::Mmc1;
use uxrom::Uxrom;
use cnrom::Cnrom; use cnrom::Cnrom;
use mmc1::Mmc1;
use mmc3::Mmc3; use mmc3::Mmc3;
use nrom::Nrom;
use uxrom::Uxrom;
use std::cell::RefCell; use std::cell::RefCell;
use std::fs::File; use std::fs::File;
@ -55,12 +55,11 @@ pub struct Cartridge {
filename: String, filename: String,
prg_rom_size: usize, prg_rom_size: usize,
chr_rom_size: usize, chr_rom_size: usize,
pub mirroring: Mirror, // 0 horizontal, 1 vertical pub mirroring: Mirror, // 0 horizontal, 1 vertical
battery_backed_ram: bool, // 1: Cartridge contains battery-backed PRG RAM ($6000-7FFF) or other persistent memory battery_backed_ram: bool, // 1: Cartridge contains battery-backed PRG RAM ($6000-7FFF) or other persistent memory
trainer_present: bool, // 1: 512-byte trainer at $7000-$71FF (stored before PRG data) trainer_present: bool, // 1: 512-byte trainer at $7000-$71FF (stored before PRG data)
four_screen_vram: bool, // 1: Ignore mirroring control or above mirroring bit; instead provide four-screen VRAM four_screen_vram: bool, // 1: Ignore mirroring control or above mirroring bit; instead provide four-screen VRAM
// TODO: other iNES header flags // TODO: other iNES header flags
pub prg_rom: Vec<Vec<u8>>, // 16 KiB chunks for CPU pub prg_rom: Vec<Vec<u8>>, // 16 KiB chunks for CPU
pub chr_rom: Vec<Vec<u8>>, // 8 KiB chunks for PPU pub chr_rom: Vec<Vec<u8>>, // 8 KiB chunks for PPU
@ -73,16 +72,23 @@ impl Cartridge {
let mut f = std::fs::File::open(&filename).expect("could not open {}"); let mut f = std::fs::File::open(&filename).expect("could not open {}");
let mut data = vec![]; let mut data = vec![];
f.read_to_end(&mut data).unwrap(); f.read_to_end(&mut data).unwrap();
assert!(data[0..4] == [0x4E, 0x45, 0x53, 0x1A], "signature mismatch, not an iNES file"); assert!(
data[0..4] == [0x4E, 0x45, 0x53, 0x1A],
"signature mismatch, not an iNES file"
);
let mapper_num = ((data[7] >> 4) << 4) + (data[6] >> 4); let mapper_num = ((data[7] >> 4) << 4) + (data[6] >> 4);
let mut cart = Cartridge { let mut cart = Cartridge {
filename: filename.to_string(), filename: filename.to_string(),
prg_rom_size: data[4] as usize, prg_rom_size: data[4] as usize,
chr_rom_size: data[5] as usize, chr_rom_size: data[5] as usize,
mirroring: if data[6] & (1 << 0) == 0 {Mirror::Horizontal} else {Mirror::Vertical}, mirroring: if data[6] & (1 << 0) == 0 {
Mirror::Horizontal
} else {
Mirror::Vertical
},
battery_backed_ram: data[6] & (1 << 1) != 0, battery_backed_ram: data[6] & (1 << 1) != 0,
trainer_present: data[6] & (1 << 2) != 0, trainer_present: data[6] & (1 << 2) != 0,
four_screen_vram: data[6] & (1 << 3) != 0, four_screen_vram: data[6] & (1 << 3) != 0,
prg_rom: Vec::new(), prg_rom: Vec::new(),
chr_rom: Vec::new(), chr_rom: Vec::new(),
all_data: data, all_data: data,
@ -93,24 +99,23 @@ impl Cartridge {
} }
fn fill(&mut self) { fn fill(&mut self) {
let prg_chunk_size: usize = 1<<14; let prg_chunk_size: usize = 1 << 14;
let chr_chunk_size: usize = 1<<13; let chr_chunk_size: usize = 1 << 13;
let prg_offset: usize = 0x10 + if self.trainer_present { 0x200 } else { 0 }; // header plus trainer if present let prg_offset: usize = 0x10 + if self.trainer_present { 0x200 } else { 0 }; // header plus trainer if present
let chr_offset: usize = prg_offset + (self.prg_rom_size * prg_chunk_size); // chr comes after prg let chr_offset: usize = prg_offset + (self.prg_rom_size * prg_chunk_size); // chr comes after prg
// fill vecs with chunks // fill vecs with chunks
for i in 0..self.prg_rom_size { for i in 0..self.prg_rom_size {
let offset = prg_offset + (i * prg_chunk_size); let offset = prg_offset + (i * prg_chunk_size);
let chunk = self.all_data[offset..(offset + prg_chunk_size)].to_vec(); let chunk = self.all_data[offset..(offset + prg_chunk_size)].to_vec();
self.prg_rom.push(chunk.clone()); self.prg_rom.push(chunk.clone());
}; }
for i in 0..self.chr_rom_size { for i in 0..self.chr_rom_size {
let offset = chr_offset + (i * chr_chunk_size); let offset = chr_offset + (i * chr_chunk_size);
let chunk = self.all_data[offset..offset + chr_chunk_size].to_vec(); let chunk = self.all_data[offset..offset + chr_chunk_size].to_vec();
self.chr_rom.push(chunk); self.chr_rom.push(chunk);
}; }
self.all_data.clear(); self.all_data.clear();
} }
} }
pub fn check_signature(filename: &str) -> Result<(), String> { pub fn check_signature(filename: &str) -> Result<(), String> {

View File

@ -1,4 +1,4 @@
use super::{Cartridge, Mapper, Mirror, serialize::*}; use super::{serialize::*, Cartridge, Mapper, Mirror};
pub struct Nrom { pub struct Nrom {
cart: Cartridge, cart: Cartridge,
@ -7,7 +7,7 @@ pub struct Nrom {
impl Nrom { impl Nrom {
pub fn new(cart: Cartridge) -> Self { pub fn new(cart: Cartridge) -> Self {
Nrom{ Nrom {
cart: cart, cart: cart,
chr_ram: vec![0; 0x2000], chr_ram: vec![0; 0x2000],
} }
@ -24,14 +24,13 @@ impl Mapper for Nrom {
} else { } else {
self.chr_ram[address] self.chr_ram[address]
} }
}, }
0x8000..=0xBFFF => { 0x8000..=0xBFFF => self.cart.prg_rom[0][addr],
self.cart.prg_rom[0][addr] 0xC000..=0xFFFF => self.cart.prg_rom[self.cart.prg_rom_size - 1][addr],
}, _ => {
0xC000..=0xFFFF => { println!("bad address read from NROM mapper: 0x{:X}", address);
self.cart.prg_rom[self.cart.prg_rom_size - 1][addr] 0
}, }
_ => {println!("bad address read from NROM mapper: 0x{:X}", address); 0},
} }
} }
@ -42,7 +41,7 @@ impl Mapper for Nrom {
if self.cart.chr_rom_size == 0 { if self.cart.chr_rom_size == 0 {
self.chr_ram[address] = value; self.chr_ram[address] = value;
} }
}, }
0x8000..=0xBFFF => (), 0x8000..=0xBFFF => (),
0xC000..=0xFFFF => (), 0xC000..=0xFFFF => (),
_ => println!("bad address written to NROM mapper: 0x{:X}", address), _ => println!("bad address written to NROM mapper: 0x{:X}", address),
@ -56,15 +55,15 @@ impl Mapper for Nrom {
fn load_battery_backed_ram(&mut self) {} fn load_battery_backed_ram(&mut self) {}
fn save_battery_backed_ram(&self) {} fn save_battery_backed_ram(&self) {}
fn clock(&mut self) {} fn clock(&mut self) {}
fn check_irq(&mut self) -> bool {false} fn check_irq(&mut self) -> bool {
false
}
fn save_state(&self) -> MapperData { fn save_state(&self) -> MapperData {
MapperData::Nrom( MapperData::Nrom(NromData {
NromData { cart: self.cart.clone(),
cart: self.cart.clone(), chr_ram: self.chr_ram.clone(),
chr_ram: self.chr_ram.clone(), })
}
)
} }
fn load_state(&mut self, mapper_data: MapperData) { fn load_state(&mut self, mapper_data: MapperData) {

View File

@ -1,6 +1,6 @@
use super::{Cartridge, Mirror}; use super::{Cartridge, Mirror};
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize, Clone)]
pub enum MapperData { pub enum MapperData {
Nrom(NromData), Nrom(NromData),
Mmc1(Mmc1Data), Mmc1(Mmc1Data),
@ -9,14 +9,13 @@ pub enum MapperData {
Mmc3(Mmc3Data), Mmc3(Mmc3Data),
} }
#[derive(serde::Serialize, serde::Deserialize, Clone)]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct NromData { pub struct NromData {
pub cart: Cartridge, pub cart: Cartridge,
pub chr_ram: Vec<u8>, pub chr_ram: Vec<u8>,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize, Clone)]
pub struct Mmc1Data { pub struct Mmc1Data {
pub cart: Cartridge, pub cart: Cartridge,
pub step: u8, pub step: u8,
@ -33,20 +32,20 @@ pub struct Mmc1Data {
pub chr_bank_mode: bool, pub chr_bank_mode: bool,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize, Clone)]
pub struct UxromData { pub struct UxromData {
pub cart: Cartridge, pub cart: Cartridge,
pub chr_ram: Vec<u8>, pub chr_ram: Vec<u8>,
pub bank_select: usize, pub bank_select: usize,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize, Clone)]
pub struct CnromData { pub struct CnromData {
pub cart: Cartridge, pub cart: Cartridge,
pub chr_bank_select: usize, pub chr_bank_select: usize,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize, Clone)]
pub struct Mmc3Data { pub struct Mmc3Data {
pub cart: Cartridge, pub cart: Cartridge,
pub mirroring: Mirror, pub mirroring: Mirror,
@ -61,5 +60,5 @@ pub struct Mmc3Data {
pub prg_ram_bank: Vec<u8>, pub prg_ram_bank: Vec<u8>,
pub prg_rom_bank_mode: bool, pub prg_rom_bank_mode: bool,
pub chr_rom_bank_mode: bool, pub chr_rom_bank_mode: bool,
pub chr_ram_bank: Vec<u8>, pub chr_ram_bank: Vec<u8>,
} }

View File

@ -1,4 +1,4 @@
use super::{Cartridge, Mapper, Mirror, serialize::*}; use super::{serialize::*, Cartridge, Mapper, Mirror};
pub struct Uxrom { pub struct Uxrom {
cart: Cartridge, cart: Cartridge,
@ -8,7 +8,7 @@ pub struct Uxrom {
impl Uxrom { impl Uxrom {
pub fn new(cart: Cartridge) -> Self { pub fn new(cart: Cartridge) -> Self {
Uxrom{ Uxrom {
cart: cart, cart: cart,
chr_ram: vec![0; 0x2000], chr_ram: vec![0; 0x2000],
bank_select: 0, bank_select: 0,
@ -25,10 +25,13 @@ impl Mapper for Uxrom {
} else { } else {
self.chr_ram[address] self.chr_ram[address]
} }
}, }
0x8000..=0xBFFF => self.cart.prg_rom[self.bank_select][address % 0x4000], 0x8000..=0xBFFF => self.cart.prg_rom[self.bank_select][address % 0x4000],
0xC000..=0xFFFF => self.cart.prg_rom[self.cart.prg_rom.len()-1][address % 0x4000], 0xC000..=0xFFFF => self.cart.prg_rom[self.cart.prg_rom.len() - 1][address % 0x4000],
_ => {println!("bad address read from UxROM mapper: 0x{:X}", address); 0}, _ => {
println!("bad address read from UxROM mapper: 0x{:X}", address);
0
}
} }
} }
@ -38,7 +41,7 @@ impl Mapper for Uxrom {
if self.cart.chr_rom_size == 0 { if self.cart.chr_rom_size == 0 {
self.chr_ram[address] = value; self.chr_ram[address] = value;
} }
}, }
0x8000..=0xFFFF => self.bank_select = value as usize, 0x8000..=0xFFFF => self.bank_select = value as usize,
_ => println!("bad address written to UxROM mapper: 0x{:X}", address), _ => println!("bad address written to UxROM mapper: 0x{:X}", address),
} }
@ -51,16 +54,16 @@ impl Mapper for Uxrom {
fn load_battery_backed_ram(&mut self) {} fn load_battery_backed_ram(&mut self) {}
fn save_battery_backed_ram(&self) {} fn save_battery_backed_ram(&self) {}
fn clock(&mut self) {} fn clock(&mut self) {}
fn check_irq(&mut self) -> bool {false} fn check_irq(&mut self) -> bool {
false
}
fn save_state(&self) -> MapperData { fn save_state(&self) -> MapperData {
MapperData::Uxrom( MapperData::Uxrom(UxromData {
UxromData { cart: self.cart.clone(),
cart: self.cart.clone(), chr_ram: self.chr_ram.clone(),
chr_ram: self.chr_ram.clone(), bank_select: self.bank_select,
bank_select: self.bank_select, })
}
)
} }
fn load_state(&mut self, mapper_data: MapperData) { fn load_state(&mut self, mapper_data: MapperData) {

View File

@ -1,10 +1,9 @@
impl super::Cpu { impl super::Cpu {
pub fn absolute(&mut self) -> usize { pub fn absolute(&mut self) -> usize {
self.clock += 4; self.clock += 4;
<usize>::from( <usize>::from(
((self.read(self.pc + 2) as usize) << 8) + // high byte, little endian ((self.read(self.pc + 2) as usize) << 8) + // high byte, little endian
(self.read(self.pc + 1)) as usize // low byte (self.read(self.pc + 1)) as usize, // low byte
) )
} }
@ -13,10 +12,10 @@ impl super::Cpu {
let old_address = self.absolute(); let old_address = self.absolute();
let new_address = old_address + self.x as usize; let new_address = old_address + self.x as usize;
match current_opcode { match current_opcode {
0x1C | 0x1D | 0x3C | 0x3D | 0x5C | 0x5D | 0x7C | 0x7D | 0xBC | 0xBD | 0xDC | 0xDD | 0xFC | 0xFD 0x1C | 0x1D | 0x3C | 0x3D | 0x5C | 0x5D | 0x7C | 0x7D | 0xBC | 0xBD | 0xDC | 0xDD
=> self.address_page_cross(old_address, new_address), | 0xFC | 0xFD => self.address_page_cross(old_address, new_address),
0x1E | 0x1F | 0x3E | 0x3F | 0x5E | 0x5F | 0x7E | 0x7F | 0x9D | 0xC3 | 0xC7 | 0xCF | 0xD3 | 0xD7 | 0xDB | 0xDE | 0xDF | 0xFE | 0xFF 0x1E | 0x1F | 0x3E | 0x3F | 0x5E | 0x5F | 0x7E | 0x7F | 0x9D | 0xC3 | 0xC7 | 0xCF
=> self.clock += 1, | 0xD3 | 0xD7 | 0xDB | 0xDE | 0xDF | 0xFE | 0xFF => self.clock += 1,
_ => panic!("illegal opcode using abs x: {:02x}", current_opcode), _ => panic!("illegal opcode using abs x: {:02x}", current_opcode),
} }
new_address new_address
@ -62,8 +61,8 @@ impl super::Cpu {
} }
pub fn indirect(&mut self) -> usize { pub fn indirect(&mut self) -> usize {
let operand_address = ((self.read(self.pc + 2) as usize) << 8) let operand_address =
+ (self.read(self.pc + 1) as usize); ((self.read(self.pc + 2) as usize) << 8) + (self.read(self.pc + 1) as usize);
let low_byte = self.read(operand_address) as usize; let low_byte = self.read(operand_address) as usize;
// BUG TIME! from https://wiki.nesdev.com/w/index.php/Errata // BUG TIME! from https://wiki.nesdev.com/w/index.php/Errata
// "JMP ($xxyy), or JMP indirect, does not advance pages if the lower eight bits // "JMP ($xxyy), or JMP indirect, does not advance pages if the lower eight bits
@ -118,5 +117,4 @@ impl super::Cpu {
self.clock += 4; self.clock += 4;
operand.wrapping_add(self.y) as usize operand.wrapping_add(self.y) as usize
} }
} }

View File

@ -1,12 +1,13 @@
mod addressing_modes; mod addressing_modes;
mod opcodes; mod opcodes;
mod utility;
pub mod serialize; pub mod serialize;
mod utility;
use std::cell::RefCell;
use std::rc::Rc;
use serde::{Serialize, Deserialize};
use crate::cartridge::Mapper; use crate::cartridge::Mapper;
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::collections::HashSet;
use std::rc::Rc;
// RAM locations // RAM locations
const STACK_OFFSET: usize = 0x100; const STACK_OFFSET: usize = 0x100;
@ -15,26 +16,36 @@ const RESET_VECTOR: usize = 0xFFFC;
const IRQ_VECTOR: usize = 0xFFFE; const IRQ_VECTOR: usize = 0xFFFE;
// status register flags // status register flags
const CARRY_FLAG: u8 = 1 << 0; const CARRY_FLAG: u8 = 1 << 0;
const ZERO_FLAG: u8 = 1 << 1; const ZERO_FLAG: u8 = 1 << 1;
const INTERRUPT_DISABLE_FLAG: u8 = 1 << 2; const INTERRUPT_DISABLE_FLAG: u8 = 1 << 2;
const DECIMAL_FLAG: u8 = 1 << 3; const DECIMAL_FLAG: u8 = 1 << 3;
// bits 4 and 5 are unused except when status register is copied to stack // bits 4 and 5 are unused except when status register is copied to stack
const OVERFLOW_FLAG: u8 = 1 << 6; const OVERFLOW_FLAG: u8 = 1 << 6;
const NEGATIVE_FLAG: u8 = 1 << 7; const NEGATIVE_FLAG: u8 = 1 << 7;
#[derive(Clone, Copy, Debug, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Serialize, Deserialize, Hash)]
pub enum Mode { pub enum Mode {
ABS, ABX, ABY, ACC, ABS,
IMM, IMP, IDX, IND, ABX,
INX, REL, ZPG, ZPX, ABY,
ACC,
IMM,
IMP,
IDX,
IND,
INX,
REL,
ZPG,
ZPX,
ZPY, ZPY,
} }
type AddressingFunction = fn(&mut Cpu) -> usize; type AddressingFunction = fn(&mut Cpu) -> usize;
impl Mode { impl Mode {
fn get(&self) -> (AddressingFunction, usize) { // usize is number of bytes the instruction takes, used for debug printing fn get(&self) -> (AddressingFunction, usize) {
// usize is number of bytes the instruction takes, used for debug printing
match self { match self {
Mode::ABS => (Cpu::absolute, 3), Mode::ABS => (Cpu::absolute, 3),
Mode::ABX => (Cpu::absolute_x, 3), Mode::ABX => (Cpu::absolute_x, 3),
@ -53,16 +64,17 @@ impl Mode {
} }
} }
#[derive(Clone)]
pub struct Cpu { pub struct Cpu {
mem: Vec<u8>, // CPU's RAM, $0000-$1FFF pub mem: Vec<u8>, // CPU's RAM, $0000-$1FFF
a: u8, // accumulator a: u8, // accumulator
x: u8, // general purpose x: u8, // general purpose
y: u8, // general purpose y: u8, // general purpose
pc: usize, // 16-bit program counter pc: usize, // 16-bit program counter
s: u8, // stack pointer s: u8, // stack pointer
p: u8, // status p: u8, // status
pub addresses_fetched: HashSet<usize>,
clock: u64, // number of ticks in current cycle clock: u64, // number of ticks in current cycle
delay: usize, // for skipping cycles during OAM DMA delay: usize, // for skipping cycles during OAM DMA
pub mapper: Rc<RefCell<dyn Mapper>>, // cartridge data pub mapper: Rc<RefCell<dyn Mapper>>, // cartridge data
@ -72,17 +84,20 @@ pub struct Cpu {
// controller // controller
pub strobe: u8, // signals to the controller that button inputs should be read pub strobe: u8, // signals to the controller that button inputs should be read
pub button_states: u8, // Player 1 controller pub button_states: u8, // Player 1 controller
pub button_states2: u8, // Player 2 controller
button_number: u8, // counter that scans the bits of the input register serially button_number: u8, // counter that scans the bits of the input register serially
opcode_table: Vec<fn(&mut Self, usize, Mode)>, // function table opcode_table: Vec<fn(&mut Self, usize, Mode)>, // function table
mode_table: Vec<Mode>, // address mode table mode_table: Vec<Mode>, // address mode table
} }
impl Cpu { impl Cpu {
pub fn new(mapper: Rc<RefCell<dyn Mapper>>, ppu: super::Ppu, apu: super::Apu) -> Self { pub fn new(mapper: Rc<RefCell<dyn Mapper>>, ppu: super::Ppu, apu: super::Apu) -> Self {
let mut cpu = Cpu{ let mut cpu = Cpu {
mem: vec![0; 0x2000], mem: vec![0; 0x2000],
a: 0, x: 0, y: 0, a: 0,
x: 0,
y: 0,
pc: 0, pc: 0,
s: 0xFD, s: 0xFD,
p: 0x24, p: 0x24,
@ -93,52 +108,540 @@ impl Cpu {
apu: apu, apu: apu,
strobe: 0, strobe: 0,
button_states: 0, button_states: 0,
button_states2: 0,
button_number: 0, button_number: 0,
addresses_fetched: HashSet::new(),
opcode_table: vec![ opcode_table: vec![
// 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F // 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
/*00*/ Cpu::brk, Cpu::ora, Cpu::bad, Cpu::slo, Cpu::nop, Cpu::ora, Cpu::asl, Cpu::slo, Cpu::php, Cpu::ora, Cpu::asl, Cpu::nop, Cpu::nop, Cpu::ora, Cpu::asl, Cpu::slo, /*00*/ /*00*/
/*10*/ Cpu::bpl, Cpu::ora, Cpu::bad, Cpu::slo, Cpu::nop, Cpu::ora, Cpu::asl, Cpu::slo, Cpu::clc, Cpu::ora, Cpu::nop, Cpu::slo, Cpu::nop, Cpu::ora, Cpu::asl, Cpu::slo, /*10*/ Cpu::brk,
/*20*/ Cpu::jsr, Cpu::and, Cpu::bad, Cpu::rla, Cpu::bit, Cpu::and, Cpu::rol, Cpu::rla, Cpu::plp, Cpu::and, Cpu::rol, Cpu::nop, Cpu::bit, Cpu::and, Cpu::rol, Cpu::rla, /*20*/ Cpu::ora,
/*30*/ Cpu::bmi, Cpu::and, Cpu::bad, Cpu::rla, Cpu::nop, Cpu::and, Cpu::rol, Cpu::rla, Cpu::sec, Cpu::and, Cpu::nop, Cpu::rla, Cpu::nop, Cpu::and, Cpu::rol, Cpu::rla, /*30*/ Cpu::bad,
/*40*/ Cpu::rti, Cpu::eor, Cpu::bad, Cpu::sre, Cpu::nop, Cpu::eor, Cpu::lsr, Cpu::sre, Cpu::pha, Cpu::eor, Cpu::lsr, Cpu::nop, Cpu::jmp, Cpu::eor, Cpu::lsr, Cpu::sre, /*40*/ Cpu::slo,
/*50*/ Cpu::bvc, Cpu::eor, Cpu::bad, Cpu::sre, Cpu::nop, Cpu::eor, Cpu::lsr, Cpu::sre, Cpu::cli, Cpu::eor, Cpu::nop, Cpu::sre, Cpu::nop, Cpu::eor, Cpu::lsr, Cpu::sre, /*50*/ Cpu::nop,
/*60*/ Cpu::rts, Cpu::adc, Cpu::bad, Cpu::rra, Cpu::nop, Cpu::adc, Cpu::ror, Cpu::rra, Cpu::pla, Cpu::adc, Cpu::ror, Cpu::nop, Cpu::jmp, Cpu::adc, Cpu::ror, Cpu::rra, /*60*/ Cpu::ora,
/*70*/ Cpu::bvs, Cpu::adc, Cpu::bad, Cpu::rra, Cpu::nop, Cpu::adc, Cpu::ror, Cpu::rra, Cpu::sei, Cpu::adc, Cpu::nop, Cpu::rra, Cpu::nop, Cpu::adc, Cpu::ror, Cpu::rra, /*70*/ Cpu::asl,
/*80*/ Cpu::nop, Cpu::sta, Cpu::nop, Cpu::sax, Cpu::sty, Cpu::sta, Cpu::stx, Cpu::sax, Cpu::dey, Cpu::nop, Cpu::txa, Cpu::nop, Cpu::sty, Cpu::sta, Cpu::stx, Cpu::sax, /*80*/ Cpu::slo,
/*90*/ Cpu::bcc, Cpu::sta, Cpu::bad, Cpu::nop, Cpu::sty, Cpu::sta, Cpu::stx, Cpu::sax, Cpu::tya, Cpu::sta, Cpu::txs, Cpu::nop, Cpu::nop, Cpu::sta, Cpu::nop, Cpu::nop, /*90*/ Cpu::php,
/*A0*/ Cpu::ldy, Cpu::lda, Cpu::ldx, Cpu::lax, Cpu::ldy, Cpu::lda, Cpu::ldx, Cpu::lax, Cpu::tay, Cpu::lda, Cpu::tax, Cpu::nop, Cpu::ldy, Cpu::lda, Cpu::ldx, Cpu::lax, /*A0*/ Cpu::ora,
/*B0*/ Cpu::bcs, Cpu::lda, Cpu::bad, Cpu::lax, Cpu::ldy, Cpu::lda, Cpu::ldx, Cpu::lax, Cpu::clv, Cpu::lda, Cpu::tsx, Cpu::nop, Cpu::ldy, Cpu::lda, Cpu::ldx, Cpu::lax, /*B0*/ Cpu::asl,
/*C0*/ Cpu::cpy, Cpu::cmp, Cpu::nop, Cpu::dcp, Cpu::cpy, Cpu::cmp, Cpu::dec, Cpu::dcp, Cpu::iny, Cpu::cmp, Cpu::dex, Cpu::nop, Cpu::cpy, Cpu::cmp, Cpu::dec, Cpu::dcp, /*C0*/ Cpu::nop,
/*D0*/ Cpu::bne, Cpu::cmp, Cpu::bad, Cpu::dcp, Cpu::nop, Cpu::cmp, Cpu::dec, Cpu::dcp, Cpu::cld, Cpu::cmp, Cpu::nop, Cpu::dcp, Cpu::nop, Cpu::cmp, Cpu::dec, Cpu::dcp, /*D0*/ Cpu::nop,
/*E0*/ Cpu::cpx, Cpu::sbc, Cpu::nop, Cpu::isc, Cpu::cpx, Cpu::sbc, Cpu::inc, Cpu::isc, Cpu::inx, Cpu::sbc, Cpu::nop, Cpu::sbc, Cpu::cpx, Cpu::sbc, Cpu::inc, Cpu::isc, /*E0*/ Cpu::ora,
/*F0*/ Cpu::beq, Cpu::sbc, Cpu::bad, Cpu::isc, Cpu::nop, Cpu::sbc, Cpu::inc, Cpu::isc, Cpu::sed, Cpu::sbc, Cpu::nop, Cpu::isc, Cpu::nop, Cpu::sbc, Cpu::inc, Cpu::isc, /*F0*/ Cpu::asl,
Cpu::slo, /*00*/
/*10*/ Cpu::bpl,
Cpu::ora,
Cpu::bad,
Cpu::slo,
Cpu::nop,
Cpu::ora,
Cpu::asl,
Cpu::slo,
Cpu::clc,
Cpu::ora,
Cpu::nop,
Cpu::slo,
Cpu::nop,
Cpu::ora,
Cpu::asl,
Cpu::slo, /*10*/
/*20*/ Cpu::jsr,
Cpu::and,
Cpu::bad,
Cpu::rla,
Cpu::bit,
Cpu::and,
Cpu::rol,
Cpu::rla,
Cpu::plp,
Cpu::and,
Cpu::rol,
Cpu::nop,
Cpu::bit,
Cpu::and,
Cpu::rol,
Cpu::rla, /*20*/
/*30*/ Cpu::bmi,
Cpu::and,
Cpu::bad,
Cpu::rla,
Cpu::nop,
Cpu::and,
Cpu::rol,
Cpu::rla,
Cpu::sec,
Cpu::and,
Cpu::nop,
Cpu::rla,
Cpu::nop,
Cpu::and,
Cpu::rol,
Cpu::rla, /*30*/
/*40*/ Cpu::rti,
Cpu::eor,
Cpu::bad,
Cpu::sre,
Cpu::nop,
Cpu::eor,
Cpu::lsr,
Cpu::sre,
Cpu::pha,
Cpu::eor,
Cpu::lsr,
Cpu::nop,
Cpu::jmp,
Cpu::eor,
Cpu::lsr,
Cpu::sre, /*40*/
/*50*/ Cpu::bvc,
Cpu::eor,
Cpu::bad,
Cpu::sre,
Cpu::nop,
Cpu::eor,
Cpu::lsr,
Cpu::sre,
Cpu::cli,
Cpu::eor,
Cpu::nop,
Cpu::sre,
Cpu::nop,
Cpu::eor,
Cpu::lsr,
Cpu::sre, /*50*/
/*60*/ Cpu::rts,
Cpu::adc,
Cpu::bad,
Cpu::rra,
Cpu::nop,
Cpu::adc,
Cpu::ror,
Cpu::rra,
Cpu::pla,
Cpu::adc,
Cpu::ror,
Cpu::nop,
Cpu::jmp,
Cpu::adc,
Cpu::ror,
Cpu::rra, /*60*/
/*70*/ Cpu::bvs,
Cpu::adc,
Cpu::bad,
Cpu::rra,
Cpu::nop,
Cpu::adc,
Cpu::ror,
Cpu::rra,
Cpu::sei,
Cpu::adc,
Cpu::nop,
Cpu::rra,
Cpu::nop,
Cpu::adc,
Cpu::ror,
Cpu::rra, /*70*/
/*80*/ Cpu::nop,
Cpu::sta,
Cpu::nop,
Cpu::sax,
Cpu::sty,
Cpu::sta,
Cpu::stx,
Cpu::sax,
Cpu::dey,
Cpu::nop,
Cpu::txa,
Cpu::nop,
Cpu::sty,
Cpu::sta,
Cpu::stx,
Cpu::sax, /*80*/
/*90*/ Cpu::bcc,
Cpu::sta,
Cpu::bad,
Cpu::nop,
Cpu::sty,
Cpu::sta,
Cpu::stx,
Cpu::sax,
Cpu::tya,
Cpu::sta,
Cpu::txs,
Cpu::nop,
Cpu::nop,
Cpu::sta,
Cpu::nop,
Cpu::nop, /*90*/
/*A0*/ Cpu::ldy,
Cpu::lda,
Cpu::ldx,
Cpu::lax,
Cpu::ldy,
Cpu::lda,
Cpu::ldx,
Cpu::lax,
Cpu::tay,
Cpu::lda,
Cpu::tax,
Cpu::nop,
Cpu::ldy,
Cpu::lda,
Cpu::ldx,
Cpu::lax, /*A0*/
/*B0*/ Cpu::bcs,
Cpu::lda,
Cpu::bad,
Cpu::lax,
Cpu::ldy,
Cpu::lda,
Cpu::ldx,
Cpu::lax,
Cpu::clv,
Cpu::lda,
Cpu::tsx,
Cpu::nop,
Cpu::ldy,
Cpu::lda,
Cpu::ldx,
Cpu::lax, /*B0*/
/*C0*/ Cpu::cpy,
Cpu::cmp,
Cpu::nop,
Cpu::dcp,
Cpu::cpy,
Cpu::cmp,
Cpu::dec,
Cpu::dcp,
Cpu::iny,
Cpu::cmp,
Cpu::dex,
Cpu::nop,
Cpu::cpy,
Cpu::cmp,
Cpu::dec,
Cpu::dcp, /*C0*/
/*D0*/ Cpu::bne,
Cpu::cmp,
Cpu::bad,
Cpu::dcp,
Cpu::nop,
Cpu::cmp,
Cpu::dec,
Cpu::dcp,
Cpu::cld,
Cpu::cmp,
Cpu::nop,
Cpu::dcp,
Cpu::nop,
Cpu::cmp,
Cpu::dec,
Cpu::dcp, /*D0*/
/*E0*/ Cpu::cpx,
Cpu::sbc,
Cpu::nop,
Cpu::isc,
Cpu::cpx,
Cpu::sbc,
Cpu::inc,
Cpu::isc,
Cpu::inx,
Cpu::sbc,
Cpu::nop,
Cpu::sbc,
Cpu::cpx,
Cpu::sbc,
Cpu::inc,
Cpu::isc, /*E0*/
/*F0*/ Cpu::beq,
Cpu::sbc,
Cpu::bad,
Cpu::isc,
Cpu::nop,
Cpu::sbc,
Cpu::inc,
Cpu::isc,
Cpu::sed,
Cpu::sbc,
Cpu::nop,
Cpu::isc,
Cpu::nop,
Cpu::sbc,
Cpu::inc,
Cpu::isc, /*F0*/
], ],
mode_table: vec![ mode_table: vec![
// 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F // 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
/*00*/ Mode::IMP, Mode::IDX, Mode::IMP, Mode::IDX, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::IMP, Mode::IMM, Mode::ACC, Mode::IMM, Mode::ABS, Mode::ABS, Mode::ABS, Mode::ABS, /*00*/ /*00*/
/*10*/ Mode::REL, Mode::INX, Mode::IMP, Mode::INX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::IMP, Mode::ABY, Mode::IMP, Mode::ABY, Mode::ABX, Mode::ABX, Mode::ABX, Mode::ABX, /*10*/ Mode::IMP,
/*20*/ Mode::ABS, Mode::IDX, Mode::IMP, Mode::IDX, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::IMP, Mode::IMM, Mode::ACC, Mode::IMM, Mode::ABS, Mode::ABS, Mode::ABS, Mode::ABS, /*20*/ Mode::IDX,
/*30*/ Mode::REL, Mode::INX, Mode::IMP, Mode::INX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::IMP, Mode::ABY, Mode::IMP, Mode::ABY, Mode::ABX, Mode::ABX, Mode::ABX, Mode::ABX, /*30*/ Mode::IMP,
/*40*/ Mode::IMP, Mode::IDX, Mode::IMP, Mode::IDX, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::IMP, Mode::IMM, Mode::ACC, Mode::IMM, Mode::ABS, Mode::ABS, Mode::ABS, Mode::ABS, /*40*/ Mode::IDX,
/*50*/ Mode::REL, Mode::INX, Mode::IMP, Mode::INX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::IMP, Mode::ABY, Mode::IMP, Mode::ABY, Mode::ABX, Mode::ABX, Mode::ABX, Mode::ABX, /*50*/ Mode::ZPG,
/*60*/ Mode::IMP, Mode::IDX, Mode::IMP, Mode::IDX, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::IMP, Mode::IMM, Mode::ACC, Mode::IMM, Mode::IND, Mode::ABS, Mode::ABS, Mode::ABS, /*60*/ Mode::ZPG,
/*70*/ Mode::REL, Mode::INX, Mode::IMP, Mode::INX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::IMP, Mode::ABY, Mode::IMP, Mode::ABY, Mode::ABX, Mode::ABX, Mode::ABX, Mode::ABX, /*70*/ Mode::ZPG,
/*80*/ Mode::IMM, Mode::IDX, Mode::IMM, Mode::IDX, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::IMP, Mode::IMM, Mode::IMP, Mode::IMM, Mode::ABS, Mode::ABS, Mode::ABS, Mode::ABS, /*80*/ Mode::ZPG,
/*90*/ Mode::REL, Mode::INX, Mode::IMP, Mode::INX, Mode::ZPX, Mode::ZPX, Mode::ZPY, Mode::ZPY, Mode::IMP, Mode::ABY, Mode::IMP, Mode::ABY, Mode::ABX, Mode::ABX, Mode::ABY, Mode::ABY, /*90*/ Mode::IMP,
/*A0*/ Mode::IMM, Mode::IDX, Mode::IMM, Mode::IDX, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::IMP, Mode::IMM, Mode::IMP, Mode::IMM, Mode::ABS, Mode::ABS, Mode::ABS, Mode::ABS, /*A0*/ Mode::IMM,
/*B0*/ Mode::REL, Mode::INX, Mode::IMP, Mode::INX, Mode::ZPX, Mode::ZPX, Mode::ZPY, Mode::ZPY, Mode::IMP, Mode::ABY, Mode::IMP, Mode::ABY, Mode::ABX, Mode::ABX, Mode::ABY, Mode::ABY, /*B0*/ Mode::ACC,
/*C0*/ Mode::IMM, Mode::IDX, Mode::IMM, Mode::IDX, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::IMP, Mode::IMM, Mode::IMP, Mode::IMM, Mode::ABS, Mode::ABS, Mode::ABS, Mode::ABS, /*C0*/ Mode::IMM,
/*D0*/ Mode::REL, Mode::INX, Mode::IMP, Mode::INX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::IMP, Mode::ABY, Mode::IMP, Mode::ABY, Mode::ABX, Mode::ABX, Mode::ABX, Mode::ABX, /*D0*/ Mode::ABS,
/*E0*/ Mode::IMM, Mode::IDX, Mode::IMM, Mode::IDX, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::IMP, Mode::IMM, Mode::IMP, Mode::IMM, Mode::ABS, Mode::ABS, Mode::ABS, Mode::ABS, /*E0*/ Mode::ABS,
/*F0*/ Mode::REL, Mode::INX, Mode::IMP, Mode::INX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::IMP, Mode::ABY, Mode::IMP, Mode::ABY, Mode::ABX, Mode::ABX, Mode::ABX, Mode::ABX, /*F0*/ Mode::ABS,
Mode::ABS, /*00*/
/*10*/ Mode::REL,
Mode::INX,
Mode::IMP,
Mode::INX,
Mode::ZPX,
Mode::ZPX,
Mode::ZPX,
Mode::ZPX,
Mode::IMP,
Mode::ABY,
Mode::IMP,
Mode::ABY,
Mode::ABX,
Mode::ABX,
Mode::ABX,
Mode::ABX, /*10*/
/*20*/ Mode::ABS,
Mode::IDX,
Mode::IMP,
Mode::IDX,
Mode::ZPG,
Mode::ZPG,
Mode::ZPG,
Mode::ZPG,
Mode::IMP,
Mode::IMM,
Mode::ACC,
Mode::IMM,
Mode::ABS,
Mode::ABS,
Mode::ABS,
Mode::ABS, /*20*/
/*30*/ Mode::REL,
Mode::INX,
Mode::IMP,
Mode::INX,
Mode::ZPX,
Mode::ZPX,
Mode::ZPX,
Mode::ZPX,
Mode::IMP,
Mode::ABY,
Mode::IMP,
Mode::ABY,
Mode::ABX,
Mode::ABX,
Mode::ABX,
Mode::ABX, /*30*/
/*40*/ Mode::IMP,
Mode::IDX,
Mode::IMP,
Mode::IDX,
Mode::ZPG,
Mode::ZPG,
Mode::ZPG,
Mode::ZPG,
Mode::IMP,
Mode::IMM,
Mode::ACC,
Mode::IMM,
Mode::ABS,
Mode::ABS,
Mode::ABS,
Mode::ABS, /*40*/
/*50*/ Mode::REL,
Mode::INX,
Mode::IMP,
Mode::INX,
Mode::ZPX,
Mode::ZPX,
Mode::ZPX,
Mode::ZPX,
Mode::IMP,
Mode::ABY,
Mode::IMP,
Mode::ABY,
Mode::ABX,
Mode::ABX,
Mode::ABX,
Mode::ABX, /*50*/
/*60*/ Mode::IMP,
Mode::IDX,
Mode::IMP,
Mode::IDX,
Mode::ZPG,
Mode::ZPG,
Mode::ZPG,
Mode::ZPG,
Mode::IMP,
Mode::IMM,
Mode::ACC,
Mode::IMM,
Mode::IND,
Mode::ABS,
Mode::ABS,
Mode::ABS, /*60*/
/*70*/ Mode::REL,
Mode::INX,
Mode::IMP,
Mode::INX,
Mode::ZPX,
Mode::ZPX,
Mode::ZPX,
Mode::ZPX,
Mode::IMP,
Mode::ABY,
Mode::IMP,
Mode::ABY,
Mode::ABX,
Mode::ABX,
Mode::ABX,
Mode::ABX, /*70*/
/*80*/ Mode::IMM,
Mode::IDX,
Mode::IMM,
Mode::IDX,
Mode::ZPG,
Mode::ZPG,
Mode::ZPG,
Mode::ZPG,
Mode::IMP,
Mode::IMM,
Mode::IMP,
Mode::IMM,
Mode::ABS,
Mode::ABS,
Mode::ABS,
Mode::ABS, /*80*/
/*90*/ Mode::REL,
Mode::INX,
Mode::IMP,
Mode::INX,
Mode::ZPX,
Mode::ZPX,
Mode::ZPY,
Mode::ZPY,
Mode::IMP,
Mode::ABY,
Mode::IMP,
Mode::ABY,
Mode::ABX,
Mode::ABX,
Mode::ABY,
Mode::ABY, /*90*/
/*A0*/ Mode::IMM,
Mode::IDX,
Mode::IMM,
Mode::IDX,
Mode::ZPG,
Mode::ZPG,
Mode::ZPG,
Mode::ZPG,
Mode::IMP,
Mode::IMM,
Mode::IMP,
Mode::IMM,
Mode::ABS,
Mode::ABS,
Mode::ABS,
Mode::ABS, /*A0*/
/*B0*/ Mode::REL,
Mode::INX,
Mode::IMP,
Mode::INX,
Mode::ZPX,
Mode::ZPX,
Mode::ZPY,
Mode::ZPY,
Mode::IMP,
Mode::ABY,
Mode::IMP,
Mode::ABY,
Mode::ABX,
Mode::ABX,
Mode::ABY,
Mode::ABY, /*B0*/
/*C0*/ Mode::IMM,
Mode::IDX,
Mode::IMM,
Mode::IDX,
Mode::ZPG,
Mode::ZPG,
Mode::ZPG,
Mode::ZPG,
Mode::IMP,
Mode::IMM,
Mode::IMP,
Mode::IMM,
Mode::ABS,
Mode::ABS,
Mode::ABS,
Mode::ABS, /*C0*/
/*D0*/ Mode::REL,
Mode::INX,
Mode::IMP,
Mode::INX,
Mode::ZPX,
Mode::ZPX,
Mode::ZPX,
Mode::ZPX,
Mode::IMP,
Mode::ABY,
Mode::IMP,
Mode::ABY,
Mode::ABX,
Mode::ABX,
Mode::ABX,
Mode::ABX, /*D0*/
/*E0*/ Mode::IMM,
Mode::IDX,
Mode::IMM,
Mode::IDX,
Mode::ZPG,
Mode::ZPG,
Mode::ZPG,
Mode::ZPG,
Mode::IMP,
Mode::IMM,
Mode::IMP,
Mode::IMM,
Mode::ABS,
Mode::ABS,
Mode::ABS,
Mode::ABS, /*E0*/
/*F0*/ Mode::REL,
Mode::INX,
Mode::IMP,
Mode::INX,
Mode::ZPX,
Mode::ZPX,
Mode::ZPX,
Mode::ZPX,
Mode::IMP,
Mode::ABY,
Mode::IMP,
Mode::ABY,
Mode::ABX,
Mode::ABX,
Mode::ABX,
Mode::ABX, /*F0*/
], ],
}; };
cpu.pc = ((cpu.read(RESET_VECTOR + 1) as usize) << 8) + cpu.read(RESET_VECTOR) as usize; cpu.pc = ((cpu.read(RESET_VECTOR + 1) as usize) << 8) + cpu.read(RESET_VECTOR) as usize;
cpu cpu
} }
pub fn step(&mut self) -> u64 { pub fn soft_reset(&mut self) {
self.pc = ((self.read(RESET_VECTOR + 1) as usize) << 8) + self.read(RESET_VECTOR) as usize;
}
pub fn step(&mut self) -> u64 {
// The CPU is stalled for up to 4 CPU cycles to allow the longest possible write (the return address and write after an IRQ) to finish. // The CPU is stalled for up to 4 CPU cycles to allow the longest possible write (the return address and write after an IRQ) to finish.
// If OAM DMA is in progress, it is paused for two cycles. The sample fetch always occurs on an even CPU cycle due to its alignment with the APU. // If OAM DMA is in progress, it is paused for two cycles. The sample fetch always occurs on an even CPU cycle due to its alignment with the APU.
// Specific delay cases: // Specific delay cases:
@ -152,7 +655,7 @@ impl Cpu {
self.delay = 3; // TODO: not correct self.delay = 3; // TODO: not correct
self.apu.dmc.cpu_stall = false; self.apu.dmc.cpu_stall = false;
} }
// skip cycles from OAM DMA if necessary // skip cycles from OAM DMA if necessary
if self.delay > 0 { if self.delay > 0 {
self.delay -= 1; self.delay -= 1;
@ -163,8 +666,9 @@ impl Cpu {
// back up clock so we know how many cycles we complete // back up clock so we know how many cycles we complete
let clock = self.clock; let clock = self.clock;
let opcode = <usize>::from(self.read(self.pc));
let opcode = <usize>::from(self.read(self.pc));
self.addresses_fetched.insert(self.pc);
// get addressing mode // get addressing mode
let mode = self.mode_table[opcode].clone(); let mode = self.mode_table[opcode].clone();
let (address_func, _num_bytes) = mode.get(); let (address_func, _num_bytes) = mode.get();
@ -181,12 +685,15 @@ impl Cpu {
// memory interface // memory interface
pub fn read(&mut self, address: usize) -> u8 { pub fn read(&mut self, address: usize) -> u8 {
// self.addresses_fetched.insert(address);
let val = match address { let val = match address {
0x0000..=0x1FFF => self.mem[address % 0x0800], 0x0000..=0x1FFF => self.mem[address % 0x0800],
0x2000..=0x3FFF => self.read_ppu_reg(address % 8), 0x2000..=0x3FFF => self.read_ppu_reg(address % 8),
0x4014 => self.read_ppu_reg(8), 0x4014 => self.read_ppu_reg(8),
0x4015 => self.apu.read_status(), 0x4015 => self.apu.read_status(),
0x4016 => self.read_controller(), 0x4016 => self.read_controller(),
// FIXME: Uncomment for player 2..
// 0x4017 => self.read_controller2(),
0x4000..=0x4017 => 0, // can't read from these APU registers 0x4000..=0x4017 => 0, // can't read from these APU registers
0x4018..=0x401F => 0, // APU and I/O functionality that is normally disabled. See CPU Test Mode. 0x4018..=0x401F => 0, // APU and I/O functionality that is normally disabled. See CPU Test Mode.
0x4020..=0xFFFF => self.mapper.borrow().read(address), 0x4020..=0xFFFF => self.mapper.borrow().read(address),
@ -197,11 +704,12 @@ impl Cpu {
// memory interface // memory interface
fn write(&mut self, address: usize, val: u8) { fn write(&mut self, address: usize, val: u8) {
// self.addresses_fetched.insert(address);
match address { match address {
0x0000..=0x1FFF => self.mem[address % 0x0800] = val, 0x0000..=0x1FFF => self.mem[address % 0x0800] = val,
0x2000..=0x3FFF => self.write_ppu_reg(address % 8, val), 0x2000..=0x3FFF => self.write_ppu_reg(address % 8, val),
0x4014 => self.write_ppu_reg(8, val), 0x4014 => self.write_ppu_reg(8, val),
0x4016 => self.write_controller(val), 0x4016 => self.write_controller(val),
0x4000..=0x4017 => self.apu.write_reg(address, val), 0x4000..=0x4017 => self.apu.write_reg(address, val),
0x4018..=0x401F => (), // APU and I/O functionality that is normally disabled. See CPU Test Mode. 0x4018..=0x401F => (), // APU and I/O functionality that is normally disabled. See CPU Test Mode.
0x4020..=0xFFFF => self.mapper.borrow_mut().write(address, val), 0x4020..=0xFFFF => self.mapper.borrow_mut().write(address, val),
@ -211,7 +719,7 @@ impl Cpu {
fn read_controller(&mut self) -> u8 { fn read_controller(&mut self) -> u8 {
let bit = match self.button_number < 8 { let bit = match self.button_number < 8 {
true => (self.button_states & (1<<self.button_number) != 0) as u8, true => (self.button_states & (1 << self.button_number) != 0) as u8,
false => 1, false => 1,
}; };
if self.strobe & 1 != 0 { if self.strobe & 1 != 0 {
@ -219,7 +727,20 @@ impl Cpu {
} else { } else {
self.button_number += 1; self.button_number += 1;
} }
bit | 0x40 bit
}
fn read_controller2(&mut self) -> u8 {
let bit = match self.button_number < 8 {
true => (self.button_states2 & (1 << self.button_number) != 0) as u8,
false => 1,
};
if self.strobe & 1 != 0 {
self.button_number = 0;
} else {
self.button_number += 1;
}
bit
} }
fn write_controller(&mut self, val: u8) { fn write_controller(&mut self, val: u8) {
@ -256,8 +777,8 @@ impl Cpu {
} }
self.ppu.write_oam_dma(data); self.ppu.write_oam_dma(data);
let is_odd = self.clock % 2 != 0; let is_odd = self.clock % 2 != 0;
self.delay = 513 + if is_odd {1} else {0}; self.delay = 513 + if is_odd { 1 } else { 0 };
}, }
_ => panic!("wrote to bad ppu reg: {}", reg_num), _ => panic!("wrote to bad ppu reg: {}", reg_num),
} }
} }
@ -278,7 +799,7 @@ impl Cpu {
self.irq(); self.irq();
} }
// TODO: should checks for APU and MMC3 IRQs be combined and acknowledged together? // TODO: should checks for APU and MMC3 IRQs be combined and acknowledged together?
// At any time, if the interrupt flag is set, the CPU's IRQ line is continuously asserted until the interrupt flag is cleared. // At any time, if the interrupt flag is set, the CPU's IRQ line is continuously asserted until the interrupt flag is cleared.
// The processor will continue on from where it was stalled. // The processor will continue on from where it was stalled.
if self.apu.dmc.interrupt { if self.apu.dmc.interrupt {
@ -291,19 +812,27 @@ impl Cpu {
let operands = match num_bytes { let operands = match num_bytes {
1 => " ".to_string(), 1 => " ".to_string(),
2 => format!("{:02X} ", self.read(pc + 1)), 2 => format!("{:02X} ", self.read(pc + 1)),
3 => format!("{:02X} {:02X}", self.read(pc + 1), self.read(pc+2)), 3 => format!("{:02X} {:02X}", self.read(pc + 1), self.read(pc + 2)),
_ => "error".to_string(), _ => "error".to_string(),
}; };
println!("{:04X} {:02X} {} {} A:{:02X} X:{:02X} Y:{:02X} P:{:02X} SP:{:02X}", println!(
pc, self.read(pc), operands, _OPCODE_DISPLAY_NAMES[opcode], "{:04X} {:02X} {} {} A:{:02X} X:{:02X} Y:{:02X} P:{:02X} SP:{:02X}",
self.a, self.x, self.y, self.p, self.s, pc,
self.read(pc),
operands,
_OPCODE_DISPLAY_NAMES[opcode],
self.a,
self.x,
self.y,
self.p,
self.s,
); );
} }
pub fn _memory_at(&mut self, address: usize, amount: usize) -> Vec<u8> { pub fn _memory_at(&mut self, address: usize, amount: usize) -> Vec<u8> {
let mut ret = vec![]; let mut ret = vec![];
for i in 0..amount { for i in 0..amount {
ret.push(self.read(address+i)); ret.push(self.read(address + i));
} }
ret ret
} }
@ -324,36 +853,24 @@ $4020-$FFFF $BFE0 Cartridge space: PRG ROM, PRG RAM, and mapper registers (See
// For debug output // For debug output
const _OPCODE_DISPLAY_NAMES: [&str; 256] = [ const _OPCODE_DISPLAY_NAMES: [&str; 256] = [
"BRK", "ORA", "BAD", "SLO", "NOP", "ORA", "ASL", "SLO", "BRK", "ORA", "BAD", "SLO", "NOP", "ORA", "ASL", "SLO", "PHP", "ORA", "ASL", "ANC", "NOP",
"PHP", "ORA", "ASL", "ANC", "NOP", "ORA", "ASL", "SLO", "ORA", "ASL", "SLO", "BPL", "ORA", "BAD", "SLO", "NOP", "ORA", "ASL", "SLO", "CLC", "ORA",
"BPL", "ORA", "BAD", "SLO", "NOP", "ORA", "ASL", "SLO", "NOP", "SLO", "NOP", "ORA", "ASL", "SLO", "JSR", "AND", "BAD", "RLA", "BIT", "AND", "ROL",
"CLC", "ORA", "NOP", "SLO", "NOP", "ORA", "ASL", "SLO", "RLA", "PLP", "AND", "ROL", "ANC", "BIT", "AND", "ROL", "RLA", "BMI", "AND", "BAD", "RLA",
"JSR", "AND", "BAD", "RLA", "BIT", "AND", "ROL", "RLA", "NOP", "AND", "ROL", "RLA", "SEC", "AND", "NOP", "RLA", "NOP", "AND", "ROL", "RLA", "RTI",
"PLP", "AND", "ROL", "ANC", "BIT", "AND", "ROL", "RLA", "EOR", "BAD", "SRE", "NOP", "EOR", "LSR", "SRE", "PHA", "EOR", "LSR", "ALR", "JMP", "EOR",
"BMI", "AND", "BAD", "RLA", "NOP", "AND", "ROL", "RLA", "LSR", "SRE", "BVC", "EOR", "BAD", "SRE", "NOP", "EOR", "LSR", "SRE", "CLI", "EOR", "NOP",
"SEC", "AND", "NOP", "RLA", "NOP", "AND", "ROL", "RLA", "SRE", "NOP", "EOR", "LSR", "SRE", "RTS", "ADC", "BAD", "RRA", "NOP", "ADC", "ROR", "RRA",
"RTI", "EOR", "BAD", "SRE", "NOP", "EOR", "LSR", "SRE", "PLA", "ADC", "ROR", "ARR", "JMP", "ADC", "ROR", "RRA", "BVS", "ADC", "BAD", "RRA", "NOP",
"PHA", "EOR", "LSR", "ALR", "JMP", "EOR", "LSR", "SRE", "ADC", "ROR", "RRA", "SEI", "ADC", "NOP", "RRA", "NOP", "ADC", "ROR", "RRA", "NOP", "STA",
"BVC", "EOR", "BAD", "SRE", "NOP", "EOR", "LSR", "SRE", "NOP", "SAX", "STY", "STA", "STX", "SAX", "DEY", "NOP", "TXA", "XAA", "STY", "STA", "STX",
"CLI", "EOR", "NOP", "SRE", "NOP", "EOR", "LSR", "SRE", "SAX", "BCC", "STA", "BAD", "AHX", "STY", "STA", "STX", "SAX", "TYA", "STA", "TXS", "TAS",
"RTS", "ADC", "BAD", "RRA", "NOP", "ADC", "ROR", "RRA", "SHY", "STA", "SHX", "AHX", "LDY", "LDA", "LDX", "LAX", "LDY", "LDA", "LDX", "LAX", "TAY",
"PLA", "ADC", "ROR", "ARR", "JMP", "ADC", "ROR", "RRA", "LDA", "TAX", "LAX", "LDY", "LDA", "LDX", "LAX", "BCS", "LDA", "BAD", "LAX", "LDY", "LDA",
"BVS", "ADC", "BAD", "RRA", "NOP", "ADC", "ROR", "RRA", "LDX", "LAX", "CLV", "LDA", "TSX", "LAS", "LDY", "LDA", "LDX", "LAX", "CPY", "CMP", "NOP",
"SEI", "ADC", "NOP", "RRA", "NOP", "ADC", "ROR", "RRA", "DCP", "CPY", "CMP", "DEC", "DCP", "INY", "CMP", "DEX", "AXS", "CPY", "CMP", "DEC", "DCP",
"NOP", "STA", "NOP", "SAX", "STY", "STA", "STX", "SAX", "BNE", "CMP", "BAD", "DCP", "NOP", "CMP", "DEC", "DCP", "CLD", "CMP", "NOP", "DCP", "NOP",
"DEY", "NOP", "TXA", "XAA", "STY", "STA", "STX", "SAX", "CMP", "DEC", "DCP", "CPX", "SBC", "NOP", "ISC", "CPX", "SBC", "INC", "ISC", "INX", "SBC",
"BCC", "STA", "BAD", "AHX", "STY", "STA", "STX", "SAX", "NOP", "SBC", "CPX", "SBC", "INC", "ISC", "BEQ", "SBC", "BAD", "ISC", "NOP", "SBC", "INC",
"TYA", "STA", "TXS", "TAS", "SHY", "STA", "SHX", "AHX", "ISC", "SED", "SBC", "NOP", "ISC", "NOP", "SBC", "INC", "ISC",
"LDY", "LDA", "LDX", "LAX", "LDY", "LDA", "LDX", "LAX",
"TAY", "LDA", "TAX", "LAX", "LDY", "LDA", "LDX", "LAX",
"BCS", "LDA", "BAD", "LAX", "LDY", "LDA", "LDX", "LAX",
"CLV", "LDA", "TSX", "LAS", "LDY", "LDA", "LDX", "LAX",
"CPY", "CMP", "NOP", "DCP", "CPY", "CMP", "DEC", "DCP",
"INY", "CMP", "DEX", "AXS", "CPY", "CMP", "DEC", "DCP",
"BNE", "CMP", "BAD", "DCP", "NOP", "CMP", "DEC", "DCP",
"CLD", "CMP", "NOP", "DCP", "NOP", "CMP", "DEC", "DCP",
"CPX", "SBC", "NOP", "ISC", "CPX", "SBC", "INC", "ISC",
"INX", "SBC", "NOP", "SBC", "CPX", "SBC", "INC", "ISC",
"BEQ", "SBC", "BAD", "ISC", "NOP", "SBC", "INC", "ISC",
"SED", "SBC", "NOP", "ISC", "NOP", "SBC", "INC", "ISC",
]; ];

View File

@ -1,15 +1,17 @@
use super::{CARRY_FLAG, DECIMAL_FLAG, INTERRUPT_DISABLE_FLAG, IRQ_VECTOR, NEGATIVE_FLAG, NMI_VECTOR, OVERFLOW_FLAG, ZERO_FLAG, Mode}; use super::{
Mode, CARRY_FLAG, DECIMAL_FLAG, INTERRUPT_DISABLE_FLAG, IRQ_VECTOR, NEGATIVE_FLAG, NMI_VECTOR,
OVERFLOW_FLAG, ZERO_FLAG,
};
// TODO: check unofficial opcodes for page crosses // TODO: check unofficial opcodes for page crosses
impl super::Cpu { impl super::Cpu {
pub fn adc(&mut self, _address: usize, _mode: Mode) { pub fn adc(&mut self, _address: usize, _mode: Mode) {
let byte = self.read(_address); let byte = self.read(_address);
let carry_bit = if self.p & CARRY_FLAG == 0 {0} else {1}; let carry_bit = if self.p & CARRY_FLAG == 0 { 0 } else { 1 };
let mut new_val = self.a.wrapping_add(byte); // add the byte at the _address to accum let mut new_val = self.a.wrapping_add(byte); // add the byte at the _address to accum
new_val = new_val.wrapping_add(carry_bit); // add carry flag to accumulator new_val = new_val.wrapping_add(carry_bit); // add carry flag to accumulator
// set carry flag if we wrapped around and added something // set carry flag if we wrapped around and added something
if new_val <= self.a && (byte != 0 || carry_bit != 0) { if new_val <= self.a && (byte != 0 || carry_bit != 0) {
self.p |= CARRY_FLAG; self.p |= CARRY_FLAG;
} else { } else {
@ -41,10 +43,10 @@ impl super::Cpu {
_ => { _ => {
self.clock += 2; self.clock += 2;
self.read(_address) self.read(_address)
}, }
}; };
// put top bit in carry flag // put top bit in carry flag
if val & (1<<7) != 0 { if val & (1 << 7) != 0 {
self.p |= CARRY_FLAG; self.p |= CARRY_FLAG;
} else { } else {
self.p &= 0xFF - CARRY_FLAG; self.p &= 0xFF - CARRY_FLAG;
@ -292,7 +294,7 @@ impl super::Cpu {
_ => { _ => {
self.clock += 2; self.clock += 2;
self.read(_address) self.read(_address)
}, }
}; };
if val & 0x1 == 0x1 { if val & 0x1 == 0x1 {
self.p |= CARRY_FLAG; self.p |= CARRY_FLAG;
@ -308,8 +310,7 @@ impl super::Cpu {
self.set_negative_flag(val); self.set_negative_flag(val);
} }
pub fn nop(&mut self, _address: usize, _mode: Mode) { pub fn nop(&mut self, _address: usize, _mode: Mode) {}
}
pub fn ora(&mut self, _address: usize, _mode: Mode) { pub fn ora(&mut self, _address: usize, _mode: Mode) {
self.a |= self.read(_address); self.a |= self.read(_address);
@ -369,18 +370,21 @@ impl super::Cpu {
_ => { _ => {
self.clock += 2; self.clock += 2;
self.read(_address) self.read(_address)
}, }
}; };
let carry_flag_bit = if self.p & CARRY_FLAG != 0 {1} else {0}; let carry_flag_bit = if self.p & CARRY_FLAG != 0 { 1 } else { 0 };
let new_cfb = if val & 0x80 != 0 {1} else {0}; let new_cfb = if val & 0x80 != 0 { 1 } else { 0 };
val <<= 1; val <<= 1;
val += carry_flag_bit; val += carry_flag_bit;
match _mode { match _mode {
Mode::ACC => self.a = val, Mode::ACC => self.a = val,
_ => self.write(_address, val), _ => self.write(_address, val),
}; };
if new_cfb != 0 { self.p |= CARRY_FLAG; } if new_cfb != 0 {
else { self.p &= 0xFF - CARRY_FLAG; } self.p |= CARRY_FLAG;
} else {
self.p &= 0xFF - CARRY_FLAG;
}
self.set_zero_flag(val); self.set_zero_flag(val);
self.set_negative_flag(val); self.set_negative_flag(val);
} }
@ -393,12 +397,15 @@ impl super::Cpu {
self.read(_address) self.read(_address)
} }
}; };
let cfb = if self.p & CARRY_FLAG != 0 {1} else {0}; let cfb = if self.p & CARRY_FLAG != 0 { 1 } else { 0 };
let new_cfb = val & 0x1; let new_cfb = val & 0x1;
val >>= 1; val >>= 1;
val += cfb * 0x80; val += cfb * 0x80;
if new_cfb != 0 { self.p |= CARRY_FLAG; } if new_cfb != 0 {
else { self.p &= 0xFF - CARRY_FLAG; } self.p |= CARRY_FLAG;
} else {
self.p &= 0xFF - CARRY_FLAG;
}
match _mode { match _mode {
Mode::ACC => self.a = val, Mode::ACC => self.a = val,
_ => self.write(_address, val), _ => self.write(_address, val),
@ -433,7 +440,7 @@ impl super::Cpu {
pub fn sbc(&mut self, _address: usize, _mode: Mode) { pub fn sbc(&mut self, _address: usize, _mode: Mode) {
let byte = self.read(_address); let byte = self.read(_address);
let carry_bit = if self.p & CARRY_FLAG == 0 {1} else {0}; let carry_bit = if self.p & CARRY_FLAG == 0 { 1 } else { 0 };
let mut new_val = self.a.wrapping_sub(byte); let mut new_val = self.a.wrapping_sub(byte);
new_val = new_val.wrapping_sub(carry_bit); new_val = new_val.wrapping_sub(carry_bit);
// if overflow occurs and we subtracted something, CLEAR the carry bit // if overflow occurs and we subtracted something, CLEAR the carry bit
@ -552,5 +559,4 @@ impl super::Cpu {
+ (self.read(IRQ_VECTOR) as usize); // and low byte + (self.read(IRQ_VECTOR) as usize); // and low byte
self.clock += 7; self.clock += 7;
} }
} }

View File

@ -1,10 +1,10 @@
use super::Mode; use super::Mode;
use serde::{Serialize, Deserialize}; use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CpuData { pub struct CpuData {
mem: Vec<u8>, pub(crate) mem: Vec<u8>,
a: u8, a: u8,
x: u8, x: u8,
y: u8, y: u8,
@ -15,13 +15,14 @@ pub struct CpuData {
delay: usize, delay: usize,
strobe: u8, strobe: u8,
button_states: u8, button_states: u8,
button_states2: u8,
button_number: u8, button_number: u8,
mode_table: Vec<Mode>, mode_table: Vec<Mode>,
} }
impl super::Cpu { impl super::Cpu {
pub fn save_state(&self) -> CpuData { pub fn save_state(&self) -> CpuData {
CpuData{ CpuData {
mem: self.mem.clone(), mem: self.mem.clone(),
a: self.a, a: self.a,
x: self.x, x: self.x,
@ -33,6 +34,7 @@ impl super::Cpu {
delay: self.delay, delay: self.delay,
strobe: self.strobe, strobe: self.strobe,
button_states: self.button_states, button_states: self.button_states,
button_states2: self.button_states2,
button_number: self.button_number, button_number: self.button_number,
mode_table: self.mode_table.clone(), mode_table: self.mode_table.clone(),
} }
@ -50,6 +52,7 @@ impl super::Cpu {
self.delay = data.delay; self.delay = data.delay;
self.strobe = data.strobe; self.strobe = data.strobe;
self.button_states = data.button_states; self.button_states = data.button_states;
self.button_states2 = data.button_states2;
self.button_number = data.button_number; self.button_number = data.button_number;
self.mode_table = data.mode_table; self.mode_table = data.mode_table;
} }

View File

@ -1,7 +1,6 @@
use super::{CARRY_FLAG, NEGATIVE_FLAG, STACK_OFFSET, ZERO_FLAG, Mode}; use super::{Mode, CARRY_FLAG, NEGATIVE_FLAG, STACK_OFFSET, ZERO_FLAG};
impl super::Cpu { impl super::Cpu {
pub fn advance_pc(&mut self, mode: Mode) { pub fn advance_pc(&mut self, mode: Mode) {
self.pc += match mode { self.pc += match mode {
Mode::ABS => 3, Mode::ABS => 3,
@ -25,13 +24,13 @@ impl super::Cpu {
true => { true => {
let decoded_offset = offset as usize; let decoded_offset = offset as usize;
self.pc += decoded_offset; self.pc += decoded_offset;
}, }
false => { false => {
// instr_test-v5/rom_singles/11-stack.nes: // instr_test-v5/rom_singles/11-stack.nes:
// letting decoded_offset be (-offset) as usize was allowing for overflow if offset was -128/0b10000000 // letting decoded_offset be (-offset) as usize was allowing for overflow if offset was -128/0b10000000
let decoded_offset = (-offset) as u8; let decoded_offset = (-offset) as u8;
self.pc -= decoded_offset as usize; self.pc -= decoded_offset as usize;
}, }
} }
} }
@ -62,7 +61,7 @@ impl super::Cpu {
} else { } else {
self.p &= 0xFF - CARRY_FLAG; self.p &= 0xFF - CARRY_FLAG;
} }
self.set_zero_flag(if reg == byte {0} else {1}); self.set_zero_flag(if reg == byte { 0 } else { 1 });
let diff = reg.wrapping_sub(byte); let diff = reg.wrapping_sub(byte);
self.set_negative_flag(diff); self.set_negative_flag(diff);
} }
@ -93,5 +92,4 @@ impl super::Cpu {
self.p &= 0xFF - ZERO_FLAG; self.p &= 0xFF - ZERO_FLAG;
} }
} }
} }

63
src/fuzzing_state.rs Normal file
View File

@ -0,0 +1,63 @@
use crate::apu::Apu;
use crate::cartridge::get_mapper;
use crate::cartridge::serialize::MapperData;
use crate::cpu::serialize::CpuData;
use crate::cpu::Cpu;
use crate::input::FuzzingInput;
use crate::ppu::serialize::PpuData;
use crate::ppu::Ppu;
use std::hash::{Hash, Hasher};
#[derive(Clone)]
pub struct FuzzingState {
pub(crate) cpu_state: CpuData,
ppu_state: PpuData,
mapper_state: MapperData,
pub(crate) frames: usize,
}
impl FuzzingState {
pub fn save_state(cpu: Cpu, frames: usize) -> FuzzingState {
FuzzingState {
cpu_state: cpu.save_state(),
ppu_state: cpu.ppu.save_state(),
mapper_state: cpu.mapper.borrow().save_state(),
frames: frames,
}
}
pub fn default(filename: String) -> FuzzingState {
let mapper = get_mapper(filename);
let ppu = Ppu::new(mapper.clone());
let apu = Apu::new();
let cpu = Cpu::new(mapper.clone(), ppu, apu);
return FuzzingState::save_state(cpu, 0);
}
pub fn load_state(&self, filename: String) -> (Cpu, usize) {
let mapper = get_mapper(filename);
mapper.borrow_mut().load_state(self.mapper_state.clone());
let mut ppu = Ppu::new(mapper.clone());
ppu.load_state(self.ppu_state.clone());
let apu = Apu::new();
let mut cpu = Cpu::new(mapper.clone(), ppu, apu);
cpu.load_state(self.cpu_state.clone());
return (cpu, self.frames);
}
}
pub struct FuzzingInputState(pub FuzzingInput, pub FuzzingState);
impl Hash for FuzzingInputState {
fn hash<H: Hasher>(&self, state: &mut H) {
return self.0.hash(state);
}
}
impl PartialEq<Self> for FuzzingInputState {
fn eq(&self, other: &Self) -> bool {
self.0.eq(&other.0)
}
}
impl Eq for FuzzingInputState {}

View File

@ -1,25 +1,148 @@
use sdl2::keyboard::Scancode; use crate::{Rng, DISABLE_START_PRESSES_AFTER, MUTATION_RATE, MUTATION_RATE_SOFT_RESET};
use std::collections::HashSet; use std::fs::File;
use std::io;
use std::io::BufRead;
pub fn poll_buttons(strobe: &u8, event_pump: &sdl2::EventPump) -> Option<u8> { #[derive(Clone, Debug, Eq, PartialEq, Hash)]
if *strobe & 1 == 1 { pub enum ConsoleAction {
let mut button_states = 0; None,
let pressed_keys: HashSet<Scancode> = event_pump.keyboard_state().pressed_scancodes().collect(); SoftReset,
for key in pressed_keys.iter() { }
match key {
Scancode::D => button_states |= 1 << 0, // A #[derive(Clone, Debug, Eq, PartialEq, Hash)]
Scancode::F => button_states |= 1 << 1, // B pub struct FrameInput {
Scancode::RShift => button_states |= 1 << 2, // Select pub console_action: ConsoleAction,
Scancode::Return => button_states |= 1 << 3, // Start pub player_1_input: u8,
Scancode::Up => button_states |= 1 << 4, // Up pub player_2_input: u8,
Scancode::Down => button_states |= 1 << 5, // Down }
Scancode::Left => button_states |= 1 << 6, // Left
Scancode::Right => button_states |= 1 << 7, // Right #[derive(Clone, Eq, PartialEq, Hash)]
_ => (), pub struct FuzzingInput {
frames: Vec<FrameInput>,
disable_start_after: usize,
pub(crate) mutated: bool,
}
impl FuzzingInput {
pub fn disable_start_after(&mut self, frames: usize) {
self.disable_start_after = frames;
}
pub fn mutate_from(&mut self, rng: &mut Rng, frame: usize, frames_to_consider: usize) {
self.mutated = true;
for frame_num in frame..(frame + frames_to_consider) {
while frame_num >= self.frames.len() {
self.frames.push(FrameInput {
console_action: ConsoleAction::None,
player_1_input: rng.next() as u8,
player_2_input: 0,
});
}
let check = (rng.next() % frames_to_consider as u32) as f64;
if (check / frames_to_consider as f64) < MUTATION_RATE {
if frame_num < self.frames.len() {
self.frames[frame_num].player_1_input = rng.next() as u8;
}
}
if (check / frames_to_consider as f64) < MUTATION_RATE_SOFT_RESET {
if frame_num < self.frames.len() {
self.frames[frame_num].console_action = ConsoleAction::SoftReset
}
}
if frame_num > DISABLE_START_PRESSES_AFTER {
self.frames[frame_num].player_1_input &= 0b11110011;
} }
} }
Some(button_states) }
} else {
None // Get the fuzzing input for the give frame...
pub fn get_frame_input(&self, frame: usize) -> Option<&FrameInput> {
self.frames.get(frame)
}
// Hacky code to parse an fm2 movie/input file into our initial fuzzing input
pub fn load(fm2: &str) -> FuzzingInput {
let file = File::open(fm2).unwrap();
let mut frames = vec![];
let lines = io::BufReader::new(file).lines();
for (_line_num, line) in lines.enumerate() {
if let Ok(lip) = line {
let ip = lip.as_bytes();
if ip.is_empty() {
continue;
}
if ip[0] as char == '|' {
let mut frame_input = FrameInput {
console_action: ConsoleAction::None,
player_1_input: 0,
player_2_input: 0,
};
if ip[1] as char == '1' {
frame_input.console_action = ConsoleAction::SoftReset;
}
// RLDUTSBA
if ip[3] as char == 'R' {
frame_input.player_1_input |= 128
}
if ip[4] as char == 'L' {
frame_input.player_1_input |= 64
}
if ip[5] as char == 'D' {
frame_input.player_1_input |= 32
}
if ip[6] as char == 'U' {
frame_input.player_1_input |= 16
}
if ip[7] as char == 'T' {
frame_input.player_1_input |= 8
}
if ip[8] as char == 'S' {
frame_input.player_1_input |= 4
}
if ip[9] as char == 'B' {
frame_input.player_1_input |= 2
}
if ip[10] as char == 'A' {
frame_input.player_1_input |= 1
}
// RLDUTSBA
if ip[12] as char == 'R' {
frame_input.player_2_input |= 128
}
if ip[13] as char == 'L' {
frame_input.player_2_input |= 64
}
if ip[14] as char == 'D' {
frame_input.player_2_input |= 32
}
if ip[15] as char == 'U' {
frame_input.player_2_input |= 16
}
if ip[16] as char == 'T' {
frame_input.player_2_input |= 8
}
if ip[17] as char == 'S' {
frame_input.player_2_input |= 4
}
if ip[18] as char == 'B' {
frame_input.player_2_input |= 2
}
if ip[19] as char == 'A' {
frame_input.player_2_input |= 1
}
frames.push(frame_input);
}
}
}
FuzzingInput {
frames,
disable_start_after: 0xFFFFFFFF,
mutated: false,
}
} }
} }

View File

@ -1,255 +1,319 @@
mod cpu; #![feature(total_cmp)]
mod ppu;
mod apu; mod apu;
mod cartridge;
mod input;
mod screen;
mod audio; mod audio;
mod cartridge;
mod cpu;
mod fuzzing_state;
mod input;
mod ppu;
mod screen;
mod state; mod state;
use cpu::Cpu;
use ppu::Ppu;
use apu::Apu; use apu::Apu;
use cartridge::{check_signature, get_mapper}; use ppu::Ppu;
use input::poll_buttons;
use screen::{init_window, draw_pixel, draw_to_window};
use state::{save_state, load_state, find_next_filename, find_last_save_state};
use std::path::{Path, PathBuf}; use crate::fuzzing_state::{FuzzingInputState, FuzzingState};
use std::sync::{Arc, Mutex}; use crate::input::{ConsoleAction, FuzzingInput};
use std::time::{Instant, Duration}; use crate::screen::Screen;
use minifb::{ScaleMode, Window, WindowOptions};
use priority_queue::priority_queue::PriorityQueue;
use std::collections::HashSet;
use std::sync::mpsc::{Receiver, Sender};
use std::sync::{mpsc, Arc, Mutex};
use std::thread;
use sdl2::Sdl; // The number of cpu instances to spawn..
use sdl2::render::{Canvas, Texture}; const NUM_THREADS: usize = 28;
use sdl2::keyboard::Keycode;
use sdl2::EventPump;
use sdl2::event::Event;
use sdl2::pixels::PixelFormatEnum;
use sdl2::video::Window;
use sdl2::messagebox::*;
// use cpuprofiler::PROFILER; // The number of frames to fuzz and process
// A small number exploits the current point more at the expense of
// large exploration - and vice versa.
const FRAMES_TO_CONSIDER: usize = 400;
enum GameExitMode { // Same input should generate the same output...
QuitApplication, // (I make no guarantee of that at the moment)
NewGame(String), const RNG_SEED: u32 = 0x5463753;
Reset,
Nothing, // If set to a low number, this disables start presses after the given frame
} // Useful for some games where pausing does nothing to advance the game...
const DISABLE_START_PRESSES_AFTER: usize = 50;
// The rate at which seed inputs become corrupted..
const MUTATION_RATE: f64 = 0.1;
// The rate at which seed inputs may become soft resets..
const MUTATION_RATE_SOFT_RESET: f64 = 0.000;
fn main() -> Result<(), String> { fn main() -> Result<(), String> {
// Set up screen
let sdl_context = sdl2::init()?;
let mut event_pump = sdl_context.event_pump()?;
let (mut canvas, texture_creator) = init_window(&sdl_context).expect("Could not create window");
let mut texture = texture_creator.create_texture_streaming(
PixelFormatEnum::RGB24, 256*screen::SCALE_FACTOR as u32, 240*screen::SCALE_FACTOR as u32)
.map_err(|e| e.to_string())?;
let byte_width = 256 * 3 * screen::SCALE_FACTOR; // 256 NES pixels, 3 bytes for each pixel (RGB 24-bit), and NES-to-SDL scale factor
let byte_height = 240 * screen::SCALE_FACTOR; // NES image is 240 pixels tall, multiply by scale factor for total number of rows needed
let mut screen_buffer = vec![0; byte_width * byte_height]; // contains raw RGB data for the screen
let argv = std::env::args().collect::<Vec<String>>(); let argv = std::env::args().collect::<Vec<String>>();
let mut filename = if argv.len() > 1 {
argv[1].to_string() if argv.len() < 3 {
} else { println!("usage: fuzz <rom> <fm2 seed input>")
show_simple_message_box( }
MessageBoxFlag::INFORMATION, "Welcome to Nestur!", INSTRUCTIONS, canvas.window()
).map_err(|e| e.to_string())?; // The rom file...mostly tested with Super Mario Bros
let name; let rom_filename = argv[1].to_string();
'waiting: loop { // The tas file to use as seed input e.g. happylee-supermariobros,warped.fm2
for event in event_pump.poll_iter() { let fm2_file = argv[2].to_string();
match event {
Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. } // Seed for our Random Number Generator...
=> return Ok(()), let mut rng = Rng::init(RNG_SEED);
Event::DropFile{ filename: f, .. } => {
match check_signature(&f) { // Mutex for launching windows others we get double frees in the
Ok(()) => { // windowing library...(yup)
name = f; let mutex = Arc::new(Mutex::new(0));
break 'waiting;
}, let mut seed_input = FuzzingInput::load(fm2_file.as_str());
Err(e) => println!("{}", e), seed_input.disable_start_after(DISABLE_START_PRESSES_AFTER);
}
}, let mut fuzzing_queue: PriorityQueue<FuzzingInputState, u64> = PriorityQueue::new();
_ => (), fuzzing_queue.push(
} FuzzingInputState(
} seed_input.clone(),
std::thread::sleep(Duration::from_millis(100)); FuzzingState::default(rom_filename.clone()),
} ),
name 0,
}; );
for _i in 1..NUM_THREADS {
let mut mutated_input = seed_input.clone();
mutated_input.mutate_from(&mut rng, 0, FRAMES_TO_CONSIDER);
fuzzing_queue.push(
FuzzingInputState(
mutated_input.clone(),
FuzzingState::default(rom_filename.clone()),
),
0,
);
}
let mut result_channels = vec![];
for i in 0..NUM_THREADS {
let i = i.clone();
let mutex = mutex.clone();
let rom_filename = rom_filename.clone();
let (tx_inputs, rx_input): (
Sender<(FuzzingInput, FuzzingState)>,
Receiver<(FuzzingInput, FuzzingState)>,
) = mpsc::channel();
let (tx_results, rx_results): (
Sender<(FuzzingInput, FuzzingState)>,
Receiver<(FuzzingInput, FuzzingState)>,
) = mpsc::channel();
result_channels.push((tx_inputs, rx_results));
thread::spawn(move || {
run_game(i as isize, mutex, rom_filename, rx_input, tx_results);
});
}
let mut previous_states: HashSet<Vec<u8>> = HashSet::new();
previous_states.insert(vec![0; 8192]);
loop { loop {
let res = run_game(&sdl_context, &mut event_pump, &mut screen_buffer, &mut canvas, &mut texture, &filename); println!("Prospective Cases: {}", fuzzing_queue.len());
match res {
Ok(Some(GameExitMode::Reset)) => (), let mut temp_scores = vec![];
Ok(Some(GameExitMode::NewGame(next_file))) => filename = next_file, for i in 0..NUM_THREADS {
Ok(None) | Ok(Some(GameExitMode::QuitApplication)) => return Ok(()), if fuzzing_queue.is_empty() == false {
Err(e) => return Err(e), let (state, score) = fuzzing_queue.pop().unwrap();
Ok(Some(GameExitMode::Nothing)) => panic!("shouldn't have returned exit mode Nothing to main()"), temp_scores.push(score);
// Send to thread for process...
result_channels[i]
.0
.send((state.0.clone(), state.1.clone()))
.expect("error sending new fuzzing input to thread...");
} else {
result_channels[i]
.0
.send((
seed_input.clone(),
FuzzingState::default(rom_filename.clone()),
))
.expect("error sending new fuzzing input to thread");
}
} }
// Gather results from each thread
for i in 0..NUM_THREADS {
match result_channels[i].1.recv() {
Ok((fuzzing_input, fuzzing_state)) => {
let mut lowest_similarity = u64::MAX;
for (_j, state) in previous_states.iter().enumerate() {
let similiarty = hamming::distance(
state.as_slice(),
fuzzing_state.cpu_state.mem.as_slice(),
);
// println!("{} {} {} ",i,j,similiarty);
if similiarty < lowest_similarity {
lowest_similarity = similiarty
}
}
previous_states.insert(fuzzing_state.cpu_state.mem.clone());
// Seed Thread
if fuzzing_input.mutated == false {
// Seed input handling...
fuzzing_queue.push(
FuzzingInputState(fuzzing_input.clone(), fuzzing_state.clone()),
10000,
);
// Add 10 random cases..
for _i in 0..10 {
let mut mutated_input: FuzzingInput = fuzzing_input.clone();
mutated_input.mutate_from(
&mut rng,
fuzzing_state.frames,
FRAMES_TO_CONSIDER,
);
// Add the mutated input and the regular input to the queue...
fuzzing_queue.push(
FuzzingInputState(mutated_input.clone(), fuzzing_state.clone()),
lowest_similarity,
);
}
} else if lowest_similarity > 0 {
// Only add to the queue if strictly better...
fuzzing_queue.push(
FuzzingInputState(fuzzing_input.clone(), fuzzing_state.clone()),
lowest_similarity,
);
let mut mutated_input: FuzzingInput = fuzzing_input.clone();
mutated_input.mutate_from(
&mut rng,
fuzzing_state.frames,
FRAMES_TO_CONSIDER,
);
// Add the mutated input and the regular input to the queue...
fuzzing_queue.push(
FuzzingInputState(mutated_input.clone(), fuzzing_state.clone()),
lowest_similarity,
);
}
}
_ => {}
}
}
}
}
pub struct Rng {
state: u32,
}
impl Rng {
pub fn init(state: u32) -> Rng {
Rng { state }
}
pub fn next(&mut self) -> u32 {
self.state ^= self.state << 13;
self.state ^= self.state >> 17;
self.state ^= self.state << 5;
return self.state;
} }
} }
fn run_game( fn run_game(
sdl_context: &Sdl, i: isize,
event_pump: &mut EventPump, mutex: Arc<Mutex<u8>>,
screen_buffer: &mut Vec<u8>, filename: String,
canvas: &mut Canvas<Window>, refresh_inputs: Receiver<(FuzzingInput, FuzzingState)>,
texture: &mut Texture, send_results: Sender<(FuzzingInput, FuzzingState)>,
filename: &str ) -> FuzzingState {
) -> Result<Option<GameExitMode>, String> { let mut window = match mutex.lock() {
Ok(_x) => {
let mut window = Window::new(
"NesFuzz",
256,
240,
WindowOptions {
resize: true,
borderless: true,
scale_mode: ScaleMode::UpperLeft,
..WindowOptions::default()
},
)
.unwrap_or_else(|x| panic!("Unable to create window {}", x));
println!("loading game {}", filename); window.set_position(10 + ((i % 7) * 260), (240 * (i / 7)) + 100);
window
// Set up audio
let mut temp_buffer = vec![]; // receives one sample each time the APU ticks. this is a staging buffer so we don't have to lock the mutex too much.
let apu_buffer = Arc::new(Mutex::new(Vec::<f32>::new())); // stays in this thread, receives raw samples between frames
let sdl_buffer = Arc::clone(&apu_buffer); // used in audio device's callback to select the samples it needs
let audio_device = audio::initialize(sdl_context, sdl_buffer).expect("Could not create audio device");
let mut half_cycle = false;
let mut audio_started = false;
// Initialize hardware components
let filepath = Path::new(filename).to_path_buf();
let mapper = get_mapper(filename.to_string());
let ppu = Ppu::new(mapper.clone());
let apu = Apu::new();
let mut cpu = Cpu::new(mapper.clone(), ppu, apu);
// For throttling to 60 FPS
let mut timer = Instant::now();
let mut fps_timer = Instant::now();
let mut fps = 0;
// PROFILER.lock().unwrap().start("./main.profile").unwrap();
'running: loop {
// step CPU: perform 1 cpu instruction, getting back number of clock cycles it took
let cpu_cycles = cpu.step();
// clock APU every other CPU cycle
let mut apu_cycles = cpu_cycles / 2;
if cpu_cycles & 1 == 1 { // if cpu step took an odd number of cycles
if half_cycle { // and we have a half-cycle stored
apu_cycles += 1; // use it
half_cycle = false;
} else {
half_cycle = true; // or save it for next odd cpu step
}
} }
for _ in 0..apu_cycles { _ => {
// can't read CPU from APU so have to pass byte in here panic!("unable to acquire mutex")
let sample_byte = cpu.read(cpu.apu.dmc.current_address);
temp_buffer.push(cpu.apu.clock(sample_byte));
} }
// clock PPU three times for every CPU cycle };
for _ in 0..cpu_cycles * 3 {
let (pixel, end_of_frame) = cpu.ppu.clock();
match pixel {
Some((x, y, color)) => draw_pixel(screen_buffer, x, y, color),
None => (),
};
if end_of_frame {
fps += 1; // keep track of how many frames we've rendered this second
draw_to_window(texture, canvas, &screen_buffer)?; // draw the buffer to the window with SDL
let mut b = apu_buffer.lock().unwrap(); // unlock mutex to the real buffer
b.append(&mut temp_buffer); // send this frame's audio data, emptying the temp buffer
if !audio_started {
audio_started = true;
audio_device.resume();
}
let now = Instant::now();
// if we're running faster than 60Hz, kill time
if now < timer + Duration::from_millis(1000/60) {
std::thread::sleep(timer + Duration::from_millis(1000/60) - now);
}
timer = Instant::now();
let outcome = process_events(event_pump, &filepath, &mut cpu);
match outcome {
GameExitMode::QuitApplication => break 'running,
GameExitMode::Reset => return Ok(Some(GameExitMode::Reset)),
GameExitMode::NewGame(g) => return Ok(Some(GameExitMode::NewGame(g))),
GameExitMode::Nothing => (),
}
}
}
// handle keyboard events
match poll_buttons(&cpu.strobe, &event_pump) {
Some(button_states) => cpu.button_states = button_states,
None => (),
};
// calculate fps
let now = Instant::now();
if now > fps_timer + Duration::from_secs(1) {
println!("frames per second: {}", fps);
fps = 0;
fps_timer = now;
}
}
// PROFILER.lock().unwrap().stop().unwrap();
mapper.borrow().save_battery_backed_ram();
Ok(None)
}
fn process_events(event_pump: &mut EventPump, filepath: &PathBuf, cpu: &mut Cpu) -> GameExitMode { let mut screen = Screen::new();
for event in event_pump.poll_iter() {
match event { loop {
Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. } match refresh_inputs.recv() {
=> return GameExitMode::QuitApplication, Err(_x) => {}
Event::KeyDown{ keycode: Some(Keycode::F2), .. } Ok((fuzzing_input, fuzzing_state)) => {
=> return GameExitMode::Reset, let mut new_frames = 0;
Event::KeyDown{ keycode: Some(Keycode::F5), .. } => { // Initialize hardware components
let save_file = find_next_filename(filepath, Some("dat")) let (mut cpu, mut frames) = fuzzing_state.load_state(filename.clone());
.expect("could not generate save state filename");
let res: Result<(), String> = save_state(cpu, &save_file) while new_frames < FRAMES_TO_CONSIDER {
.or_else(|e| {println!("{}", e); Ok(())}); // step CPU: perform 1 cpu instruction, getting back number of clock cycles it took
res.unwrap(); let cpu_cycles = cpu.step();
},
Event::KeyDown{ keycode: Some(Keycode::F9), .. } => { // clock PPU three times for every CPU cycle
match find_last_save_state(filepath, Some("dat")) { for _ in 0..cpu_cycles * 3 {
Some(p) => { let (pixel, end_of_frame) = cpu.ppu.clock();
let res: Result<(), String> = load_state(cpu, &p) match pixel {
.or_else(|e| { println!("{}", e); Ok(()) } ); Some((x, y, color)) => screen.plot_pixel(
res.unwrap(); x,
}, y,
None => println!("no save state found for {:?}", filepath) color.0 as u32,
} color.1 as u32,
}, color.2 as u32,
Event::DropFile{ timestamp: _t, window_id: _w, filename: f } => { ),
if f.len() > 4 && &f[f.len()-4..] == ".dat" { None => (),
let p = Path::new(&f).to_path_buf(); };
let res: Result<(), String> = load_state(cpu, &p) if end_of_frame {
.or_else(|e| {println!("{}", e); Ok(())}); screen.draw_string(10, 210, &*format!("Frames: {}", new_frames));
res.unwrap(); window
// } else if f.len() > 4 && &f[f.len()-4..] == ".nes" { .update_with_buffer(&screen.display, 256, 240)
} else { .unwrap();
match check_signature(&f) { frames += 1;
Ok(()) => return GameExitMode::NewGame(f), new_frames += 1;
Err(e) => println!("{}", e), }
} }
// Checking for Inputs...
match fuzzing_input.get_frame_input(frames) {
Some(input) => {
match input.console_action {
ConsoleAction::None => {}
ConsoleAction::SoftReset => {
cpu.soft_reset();
}
}
if cpu.strobe & 0x01 == 0x01 {
cpu.button_states = input.player_1_input;
// FIXME PLayer 2 doesn't play nicely with some games (e.g. mario)
// So to enable player 2 controls you also have to uncomment the
// bus in cpu/mod.rs
cpu.button_states2 = input.player_2_input;
}
}
_ => {}
};
} }
},
_ => (), send_results
.send((fuzzing_input.clone(), FuzzingState::save_state(cpu, frames)))
.expect("error sending result to thread");
}
} }
} }
return GameExitMode::Nothing
} }
const INSTRUCTIONS: &str = "To play a game, drag an INES file (extension .nes) onto the main window.
To save the game state, press F5. To load the most recent save state, press F9.
To load another save state file, drag a .dat file onto the window while the game is running.
Battery-backed RAM saves (what the NES cartridges have) will be written to a .sav file if used.
To reset the console/current game, press F2.
Controls
------------
A: D
B: F
Start: enter
Select: (right) shift
Up/Down/Left/Right: arrow keys
";
/* /*
TODO: TODO:
- untangle CPU and APU/PPU? - untangle CPU and APU/PPU?
- better save file organization? - better save file organization?

View File

@ -1,27 +1,26 @@
impl super::Ppu { impl super::Ppu {
// cpu writes to 0x2000, PPUCTRL // cpu writes to 0x2000, PPUCTRL
pub fn write_controller(&mut self, byte: u8) { pub fn write_controller(&mut self, byte: u8) {
// VRAM address increment per CPU read/write of PPUDATA // VRAM address increment per CPU read/write of PPUDATA
self.address_increment = match byte & (1<<2) == 0 { // (0: add 1, going across; 1: add 32, going down) self.address_increment = match byte & (1 << 2) == 0 {
// (0: add 1, going across; 1: add 32, going down)
true => 1, true => 1,
false => 32, false => 32,
}; };
// Sprite pattern table address for 8x8 sprites // Sprite pattern table address for 8x8 sprites
self.sprite_pattern_table_base = match byte & (1<<3) == 0 { self.sprite_pattern_table_base = match byte & (1 << 3) == 0 {
true => 0x0, true => 0x0,
false => 0x1000, false => 0x1000,
}; };
// Background pattern table address // Background pattern table address
self.background_pattern_table_base = match byte & (1<<4) == 0 { self.background_pattern_table_base = match byte & (1 << 4) == 0 {
true => 0x0, true => 0x0,
false => 0x1000, false => 0x1000,
}; };
// Sprite size (0: 8x8 pixels; 1: 8x16 pixels) // Sprite size (0: 8x8 pixels; 1: 8x16 pixels)
self.sprite_size = if byte & (1<<5) != 0 { 16 } else {8}; self.sprite_size = if byte & (1 << 5) != 0 { 16 } else { 8 };
// Ignoring PPU master/slave select for now // Ignoring PPU master/slave select for now
self.should_generate_nmi = byte & (1<<7) != 0; self.should_generate_nmi = byte & (1 << 7) != 0;
self.nmi_change(); self.nmi_change();
// Take care of t // Take care of t
set_bit(&mut self.t, 10, byte as u16, 0); set_bit(&mut self.t, 10, byte as u16, 0);
@ -30,22 +29,22 @@ impl super::Ppu {
// cpu writes to 0x2001, PPUMASK // cpu writes to 0x2001, PPUMASK
pub fn write_mask(&mut self, byte: u8) { pub fn write_mask(&mut self, byte: u8) {
self.grayscale = byte & (1<<0) != 0; self.grayscale = byte & (1 << 0) != 0;
self.show_background_left = byte & (1<<1) != 0; self.show_background_left = byte & (1 << 1) != 0;
self.show_sprites_left = byte & (1<<2) != 0; self.show_sprites_left = byte & (1 << 2) != 0;
self.show_background = byte & (1<<3) != 0; self.show_background = byte & (1 << 3) != 0;
self.show_sprites = byte & (1<<4) != 0; self.show_sprites = byte & (1 << 4) != 0;
self.emphasize_red = byte & (1<<5) != 0; self.emphasize_red = byte & (1 << 5) != 0;
self.emphasize_green = byte & (1<<6) != 0; self.emphasize_green = byte & (1 << 6) != 0;
self.emphasize_blue = byte & (1<<7) != 0; self.emphasize_blue = byte & (1 << 7) != 0;
} }
// cpu reads ppu status from 0x2002, PPUSTATUS // cpu reads ppu status from 0x2002, PPUSTATUS
pub fn read_status(&mut self) -> u8 { pub fn read_status(&mut self) -> u8 {
let mut byte = self.recent_bits & 0b0001_1111; let mut byte = self.recent_bits & 0b0001_1111;
byte |= if self.sprite_overflow { 0b0010_0000 } else {0}; byte |= if self.sprite_overflow { 0b0010_0000 } else { 0 };
byte |= if self.sprite_zero_hit { 0b0100_0000 } else {0}; byte |= if self.sprite_zero_hit { 0b0100_0000 } else { 0 };
byte |= if self.vertical_blank { 0b1000_0000 } else {0}; byte |= if self.vertical_blank { 0b1000_0000 } else { 0 };
self.w = 0; self.w = 0;
self.vertical_blank = false; self.vertical_blank = false;
self.nmi_change(); self.nmi_change();
@ -71,16 +70,18 @@ impl super::Ppu {
// cpu writes to 0x2005, PPUSCROLL // cpu writes to 0x2005, PPUSCROLL
pub fn write_scroll(&mut self, val: u8) { pub fn write_scroll(&mut self, val: u8) {
match self.w { // first write match self.w {
// first write
0 => { 0 => {
// t: ....... ...HGFED = d: HGFED... // t: ....... ...HGFED = d: HGFED...
self.t &= !((1<<5)-1); // turn off bottom 5 bits of t self.t &= !((1 << 5) - 1); // turn off bottom 5 bits of t
self.t |= val as u16 >> 3; // set bottom 5 bits of t to top 5 bits of d self.t |= val as u16 >> 3; // set bottom 5 bits of t to top 5 bits of d
// x: CBA = d: .....CBA // x: CBA = d: .....CBA
self.x = val & ((1<<3) - 1); self.x = val & ((1 << 3) - 1);
self.w = 1; self.w = 1;
}, }
1 => { // second write 1 => {
// second write
let d = val as u16; let d = val as u16;
// t: CBA..HG FED..... = d: HGFEDCBA // t: CBA..HG FED..... = d: HGFEDCBA
set_bit(&mut self.t, 0xC, d, 0x0); set_bit(&mut self.t, 0xC, d, 0x0);
@ -92,7 +93,7 @@ impl super::Ppu {
set_bit(&mut self.t, 0x8, d, 0x6); set_bit(&mut self.t, 0x8, d, 0x6);
set_bit(&mut self.t, 0x9, d, 0x7); set_bit(&mut self.t, 0x9, d, 0x7);
self.w = 0; self.w = 0;
}, }
_ => panic!("uh oh, somehow w was incremented past 1 to {}", self.w), _ => panic!("uh oh, somehow w was incremented past 1 to {}", self.w),
} }
} }
@ -100,7 +101,8 @@ impl super::Ppu {
// cpu writes to 0x2006, PPUADDR // cpu writes to 0x2006, PPUADDR
pub fn write_address(&mut self, val: u8) { pub fn write_address(&mut self, val: u8) {
let d = val as u16; let d = val as u16;
match self.w { // first write match self.w {
// first write
0 => { 0 => {
// t: .FEDCBA ........ = d: ..FEDCBA // t: .FEDCBA ........ = d: ..FEDCBA
set_bit(&mut self.t, 0x8, d, 0x0); set_bit(&mut self.t, 0x8, d, 0x0);
@ -114,14 +116,15 @@ impl super::Ppu {
// setting the 16th instead of the 15th bit in the line above was the longest, most frustrating oversight. // 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. // caused weird problems with vertical scrolling and backgrounds that I initially thought were bugs with MMC3.
self.w = 1; self.w = 1;
}, }
1 => { // second write 1 => {
// second write
// t: ....... HGFEDCBA = d: HGFEDCBA // t: ....... HGFEDCBA = d: HGFEDCBA
self.t &= 0xFF00; // mask off bottom byte self.t &= 0xFF00; // mask off bottom byte
self.t += d; // apply d self.t += d; // apply d
self.v = self.t; // After t is updated, contents of t copied into v self.v = self.t; // After t is updated, contents of t copied into v
self.w = 0; self.w = 0;
}, }
_ => panic!("uh oh, somehow w was incremented past 1 to {}", self.w), _ => panic!("uh oh, somehow w was incremented past 1 to {}", self.w),
} }
} }
@ -164,11 +167,11 @@ impl super::Ppu {
0x0000..=0x3EFF => { 0x0000..=0x3EFF => {
ret_val = self.read_buffer; ret_val = self.read_buffer;
self.read_buffer = mem_val; self.read_buffer = mem_val;
}, }
0x3F00..=0x3FFF => { 0x3F00..=0x3FFF => {
ret_val = mem_val; ret_val = mem_val;
self.read_buffer = self.read(self.v as usize - 0x1000); self.read_buffer = self.read(self.v as usize - 0x1000);
}, }
_ => panic!("reading from invalid PPU address: 0x{:04x}", self.v), _ => panic!("reading from invalid PPU address: 0x{:04x}", self.v),
}; };
if self.rendering() && (self.scanline < 240 || self.scanline == 261) { if self.rendering() && (self.scanline < 240 || self.scanline == 261) {
@ -201,10 +204,9 @@ impl super::Ppu {
pub fn write_oam_dma(&mut self, data: Vec<u8>) { pub fn write_oam_dma(&mut self, data: Vec<u8>) {
self.primary_oam = data; self.primary_oam = data;
} }
} }
pub fn set_bit(dest: &mut u16, dest_pos: usize, src: u16, src_pos: usize) { pub fn set_bit(dest: &mut u16, dest_pos: usize, src: u16, src_pos: usize) {
*dest &= 0xFFFF - (1 << dest_pos); // mask off destination bit *dest &= 0xFFFF - (1 << dest_pos); // mask off destination bit
*dest += (if src & (1 << src_pos) == 0 {0} else {1}) << dest_pos; // apply bit from src in src position *dest += (if src & (1 << src_pos) == 0 { 0 } else { 1 }) << dest_pos; // apply bit from src in src position
} }

View File

@ -1,7 +1,6 @@
use crate::cartridge::Mirror; use crate::cartridge::Mirror;
impl super::Ppu { impl super::Ppu {
pub fn read(&mut self, address: usize) -> u8 { pub fn read(&mut self, address: usize) -> u8 {
match address { match address {
0x0000..=0x1FFF => self.mapper.borrow().read(address), 0x0000..=0x1FFF => self.mapper.borrow().read(address),
@ -10,7 +9,7 @@ impl super::Ppu {
let a = address % 0x0020; let a = address % 0x0020;
let value = self.palette_ram[a]; let value = self.palette_ram[a];
value value
}, }
_ => 0, _ => 0,
} }
} }
@ -30,22 +29,22 @@ impl super::Ppu {
0x00 => { 0x00 => {
self.palette_ram[0] = value; self.palette_ram[0] = value;
self.palette_ram[0x10] = value; self.palette_ram[0x10] = value;
}, }
0x04 => { 0x04 => {
self.palette_ram[0x04] = value; self.palette_ram[0x04] = value;
self.palette_ram[0x14] = value; self.palette_ram[0x14] = value;
}, }
0x08 => { 0x08 => {
self.palette_ram[0x08] = value; self.palette_ram[0x08] = value;
self.palette_ram[0x18] = value; self.palette_ram[0x18] = value;
}, }
0x0C => { 0x0C => {
self.palette_ram[0x0C] = value; self.palette_ram[0x0C] = value;
self.palette_ram[0x1C] = value; self.palette_ram[0x1C] = value;
}, }
_ => self.palette_ram[address % 0x0020] = value, _ => self.palette_ram[address % 0x0020] = value,
} }
}, }
_ => (), _ => (),
} }
} }
@ -56,28 +55,22 @@ impl super::Ppu {
match self.mapper.borrow().get_mirroring() { match self.mapper.borrow().get_mirroring() {
Mirror::LowBank => self.nametable_a[offset], Mirror::LowBank => self.nametable_a[offset],
Mirror::HighBank => self.nametable_b[offset], Mirror::HighBank => self.nametable_b[offset],
Mirror::Horizontal => { Mirror::Horizontal => match base {
match base { 0x0000..=0x07FF => self.nametable_a[offset],
0x0000..=0x07FF => self.nametable_a[offset], 0x0800..=0x0FFF => self.nametable_b[offset],
0x0800..=0x0FFF => self.nametable_b[offset], _ => panic!("panicked reading nametable base: {}", base),
_ => panic!("panicked reading nametable base: {}", base),
}
}, },
Mirror::Vertical => { Mirror::Vertical => match base {
match base { 0x0000..=0x03FF | 0x0800..=0x0BFF => self.nametable_a[offset],
0x0000..=0x03FF | 0x0800..=0x0BFF => self.nametable_a[offset], 0x0400..=0x07FF | 0x0C00..=0x0FFF => self.nametable_b[offset],
0x0400..=0x07FF | 0x0C00..=0x0FFF => self.nametable_b[offset], _ => panic!("panicked reading nametable base: {}", base),
_ => panic!("panicked reading nametable base: {}", base),
}
}, },
Mirror::FourScreen => { Mirror::FourScreen => match base {
match base { 0x0000..=0x03FF => self.nametable_a[offset],
0x0000..=0x03FF => self.nametable_a[offset], 0x0400..=0x07FF => self.nametable_b[offset],
0x0400..=0x07FF => self.nametable_b[offset], 0x0800..=0x0BFF => self.nametable_c[offset],
0x0800..=0x0BFF => self.nametable_c[offset], 0x0C00..=0x0FFF => self.nametable_d[offset],
0x0C00..=0x0FFF => self.nametable_d[offset], _ => panic!("panicked reading nametable base: {}", base),
_ => panic!("panicked reading nametable base: {}", base),
}
}, },
} }
} }
@ -88,28 +81,22 @@ impl super::Ppu {
match self.mapper.borrow().get_mirroring() { match self.mapper.borrow().get_mirroring() {
Mirror::LowBank => self.nametable_a[offset] = value, Mirror::LowBank => self.nametable_a[offset] = value,
Mirror::HighBank => self.nametable_b[offset] = value, Mirror::HighBank => self.nametable_b[offset] = value,
Mirror::Horizontal => { Mirror::Horizontal => match base {
match base { 0x0000..=0x07FF => self.nametable_a[offset] = value,
0x0000..=0x07FF => self.nametable_a[offset] = value, 0x0800..=0x0FFF => self.nametable_b[offset] = value,
0x0800..=0x0FFF => self.nametable_b[offset] = value, _ => panic!("panicked writing nametable base: {}", base),
_ => panic!("panicked writing nametable base: {}", base),
}
}, },
Mirror::Vertical => { Mirror::Vertical => match base {
match base { 0x0000..=0x03FF | 0x0800..=0x0BFF => self.nametable_a[offset] = value,
0x0000..=0x03FF | 0x0800..=0x0BFF => self.nametable_a[offset] = value, 0x0400..=0x07FF | 0x0C00..=0x0FFF => self.nametable_b[offset] = value,
0x0400..=0x07FF | 0x0C00..=0x0FFF => self.nametable_b[offset] = value, _ => panic!("panicked writing nametable base: {}", base),
_ => panic!("panicked writing nametable base: {}", base),
}
}, },
Mirror::FourScreen => { Mirror::FourScreen => match base {
match base { 0x0000..=0x03FF => self.nametable_a[offset] = value,
0x0000..=0x03FF => self.nametable_a[offset] = value, 0x0400..=0x07FF => self.nametable_b[offset] = value,
0x0400..=0x07FF => self.nametable_b[offset] = value, 0x0800..=0x0BFF => self.nametable_c[offset] = value,
0x0800..=0x0BFF => self.nametable_c[offset] = value, 0x0C00..=0x0FFF => self.nametable_d[offset] = value,
0x0C00..=0x0FFF => self.nametable_d[offset] = value, _ => panic!("panicked writing nametable base: {}", base),
_ => panic!("panicked writing nametable base: {}", base),
}
}, },
} }
} }

View File

@ -1,16 +1,17 @@
mod cpu_registers; mod cpu_registers;
mod rendering;
mod memory; mod memory;
mod rendering;
pub mod serialize; pub mod serialize;
use crate::cartridge::Mapper;
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use crate::cartridge::Mapper;
#[derive(Clone)]
pub struct Ppu { pub struct Ppu {
line_cycle: usize, // x coordinate line_cycle: usize, // x coordinate
scanline: usize, // y coordinate scanline: usize, // y coordinate
frame: usize, frame: usize,
// Internal registers // Internal registers
v: u16, v: u16,
@ -38,116 +39,116 @@ pub struct Ppu {
palette_ram: Vec<u8>, // Palette RAM indexes. palette_ram: Vec<u8>, // Palette RAM indexes.
// Background pattern shift registers and latches // Background pattern shift registers and latches
background_pattern_sr_low: u16, // 2 16-bit shift registers - These contain the pattern table data for two tiles. Every 8 cycles, the data for the next tile is loaded background_pattern_sr_low: u16, // 2 16-bit shift registers - These contain the pattern table data for two tiles. Every 8 cycles, the data for the next tile is loaded
background_pattern_sr_high: u16, // into the upper 8 bits of this shift register. Meanwhile, the pixel to render is fetched from one of the lower 8 bits. background_pattern_sr_high: u16, // into the upper 8 bits of this shift register. Meanwhile, the pixel to render is fetched from one of the lower 8 bits.
nametable_byte: u8, // "The data fetched from these accesses is placed into internal latches, nametable_byte: u8, // "The data fetched from these accesses is placed into internal latches,
attribute_table_byte: u8, // and then fed to the appropriate shift registers when it's time to do so attribute_table_byte: u8, // and then fed to the appropriate shift registers when it's time to do so
low_pattern_table_byte: u8, // (every 8 cycles)." low_pattern_table_byte: u8, // (every 8 cycles)."
high_pattern_table_byte: u8, high_pattern_table_byte: u8,
// Background palette shift registers and latches // Background palette shift registers and latches
background_palette_sr_low: u8, // 2 8-bit shift registers - background_palette_sr_low: u8, // 2 8-bit shift registers -
background_palette_sr_high: u8, // These contain the palette attributes for the lower 8 pixels of the 16-bit [pattern/tile] shift register. background_palette_sr_high: u8, // These contain the palette attributes for the lower 8 pixels of the 16-bit [pattern/tile] shift register.
background_palette_latch: u8, // These registers are fed by a latch which contains the palette attribute for the next tile. Every 8 cycles, background_palette_latch: u8, // These registers are fed by a latch which contains the palette attribute for the next tile. Every 8 cycles,
// the latch is loaded with the palette attribute for the next tile. Because the PPU can only fetch an attribute byte every 8 cycles, each // the latch is loaded with the palette attribute for the next tile. Because the PPU can only fetch an attribute byte every 8 cycles, each
// sequential string of 8 pixels is forced to have the same palette attribute. // sequential string of 8 pixels is forced to have the same palette attribute.
// Sprite memory, shift registers, and latch // Sprite memory, shift registers, and latch
pub primary_oam: Vec<u8>, // Primary OAM (holds 64 sprites for the frame) pub primary_oam: Vec<u8>, // Primary OAM (holds 64 sprites for the frame)
secondary_oam: Vec<u8>, // Secondary OAM (holds 8 sprites for the current scanline) secondary_oam: Vec<u8>, // Secondary OAM (holds 8 sprites for the current scanline)
sprite_attribute_latches: Vec<u8>, // 8 latches - These contain the attribute bytes [palette data] for up to 8 sprites. sprite_attribute_latches: Vec<u8>, // 8 latches - These contain the attribute bytes [palette data] for up to 8 sprites.
sprite_counters: Vec<u8>, // 8 counters - These contain the X positions for up to 8 sprites. sprite_counters: Vec<u8>, // 8 counters - These contain the X positions for up to 8 sprites.
sprite_indexes: Vec<u8>, // Indexes of the sprites-in-the-attribute-latches' within primary OAM sprite_indexes: Vec<u8>, // Indexes of the sprites-in-the-attribute-latches' within primary OAM
sprite_pattern_table_srs: Vec<(u8, u8)>, // 8 pairs of 8-bit shift registers - These contain the pattern table data for up to 8 sprites, to be rendered on the current scanline. sprite_pattern_table_srs: Vec<(u8, u8)>, // 8 pairs of 8-bit shift registers - These contain the pattern table data for up to 8 sprites, to be rendered on the current scanline.
// Unused sprites are loaded with an all-transparent set of values. // Unused sprites are loaded with an all-transparent set of values.
num_sprites: usize, // Number of sprites in the shift registers for the current scanline num_sprites: usize, // Number of sprites in the shift registers for the current scanline
// Various flags set by registers // Various flags set by registers
address_increment: u16, address_increment: u16,
sprite_pattern_table_base: usize, sprite_pattern_table_base: usize,
background_pattern_table_base: usize, background_pattern_table_base: usize,
oam_address: usize, oam_address: usize,
sprite_size: u8, sprite_size: u8,
grayscale: bool, grayscale: bool,
show_background_left: bool, // 1: Show background in leftmost 8 pixels of screen, 0: Hide show_background_left: bool, // 1: Show background in leftmost 8 pixels of screen, 0: Hide
show_sprites_left: bool, // 1: Show sprites in leftmost 8 pixels of screen, 0: Hide show_sprites_left: bool, // 1: Show sprites in leftmost 8 pixels of screen, 0: Hide
show_background: bool, // 1: Show background show_background: bool, // 1: Show background
show_sprites: bool, // 1: Show sprites show_sprites: bool, // 1: Show sprites
emphasize_red: bool, // Emphasize red emphasize_red: bool, // Emphasize red
emphasize_green: bool, // Emphasize green emphasize_green: bool, // Emphasize green
emphasize_blue: bool, // Emphasize blue emphasize_blue: bool, // Emphasize blue
sprite_overflow: bool, // Set if there are more than 8 sprites on a single line sprite_overflow: bool, // Set if there are more than 8 sprites on a single line
sprite_zero_hit: bool, // Set when the first pixel of the sprite in the zero index of primary OAM is rendered sprite_zero_hit: bool, // Set when the first pixel of the sprite in the zero index of primary OAM is rendered
should_generate_nmi: bool, // Allows CPU to control whether NMIs trigger should_generate_nmi: bool, // Allows CPU to control whether NMIs trigger
vertical_blank: bool, // true == in vertical blank, false == not vertical_blank: bool, // true == in vertical blank, false == not
// These three: god knows. // These three: god knows.
// TODO: experiment more with NMI // TODO: experiment more with NMI
pub trigger_nmi: bool, // triggers NMI in the CPU when it checks in its step() pub trigger_nmi: bool, // triggers NMI in the CPU when it checks in its step()
previous_nmi: bool, previous_nmi: bool,
nmi_delay: usize, nmi_delay: usize,
read_buffer: u8, // used with PPUDATA register read_buffer: u8, // used with PPUDATA register
pub recent_bits: u8, // Least significant bits previously written into a PPU register pub recent_bits: u8, // Least significant bits previously written into a PPU register
previous_a12: u8, previous_a12: u8,
} }
impl Ppu { impl Ppu {
pub fn new(mapper: Rc<RefCell<dyn Mapper>>) -> Self { pub fn new(mapper: Rc<RefCell<dyn Mapper>>) -> Self {
Ppu { Ppu {
line_cycle: 0, line_cycle: 0,
scanline: 0, scanline: 0,
frame: 0, frame: 0,
v: 0, v: 0,
t: 0, t: 0,
x: 0, x: 0,
w: 0, w: 0,
mapper: mapper, mapper: mapper,
nametable_a: vec![0u8; 0x0400], nametable_a: vec![0u8; 0x0400],
nametable_b: vec![0u8; 0x0400], nametable_b: vec![0u8; 0x0400],
nametable_c: vec![0u8; 0x0400], nametable_c: vec![0u8; 0x0400],
nametable_d: vec![0u8; 0x0400], nametable_d: vec![0u8; 0x0400],
palette_ram: vec![0u8; 0x0020], palette_ram: vec![0u8; 0x0020],
background_pattern_sr_low: 0, background_pattern_sr_low: 0,
background_pattern_sr_high: 0, background_pattern_sr_high: 0,
nametable_byte: 0, nametable_byte: 0,
attribute_table_byte: 0, attribute_table_byte: 0,
low_pattern_table_byte: 0, low_pattern_table_byte: 0,
high_pattern_table_byte: 0, high_pattern_table_byte: 0,
background_palette_sr_low: 0, background_palette_sr_low: 0,
background_palette_sr_high: 0, background_palette_sr_high: 0,
background_palette_latch: 0, background_palette_latch: 0,
primary_oam: vec![0u8; 0x0100], primary_oam: vec![0u8; 0x0100],
secondary_oam: vec![0u8; 0x0020], secondary_oam: vec![0u8; 0x0020],
sprite_attribute_latches: vec![0u8; 8], sprite_attribute_latches: vec![0u8; 8],
sprite_counters: vec![0u8; 8], sprite_counters: vec![0u8; 8],
sprite_indexes: vec![0u8; 8], sprite_indexes: vec![0u8; 8],
sprite_pattern_table_srs: vec![(0u8, 0u8); 8], sprite_pattern_table_srs: vec![(0u8, 0u8); 8],
num_sprites: 0, num_sprites: 0,
address_increment: 0, address_increment: 0,
sprite_pattern_table_base: 0, sprite_pattern_table_base: 0,
background_pattern_table_base: 0, background_pattern_table_base: 0,
oam_address: 0, oam_address: 0,
sprite_size: 0, sprite_size: 0,
grayscale: false, grayscale: false,
show_background_left: false, show_background_left: false,
show_sprites_left: false, show_sprites_left: false,
show_background: false, show_background: false,
show_sprites: false, show_sprites: false,
emphasize_red: false, emphasize_red: false,
emphasize_green: false, emphasize_green: false,
emphasize_blue: false, emphasize_blue: false,
sprite_overflow: false, sprite_overflow: false,
sprite_zero_hit: false, sprite_zero_hit: false,
should_generate_nmi: false, should_generate_nmi: false,
vertical_blank: false, vertical_blank: false,
trigger_nmi: false, trigger_nmi: false,
previous_nmi: false, previous_nmi: false,
nmi_delay: 0, nmi_delay: 0,
read_buffer: 0, read_buffer: 0,
recent_bits: 0, recent_bits: 0,
previous_a12: 0, previous_a12: 0,
} }
} }
@ -174,13 +175,13 @@ impl Ppu {
self.load_data_into_registers(); self.load_data_into_registers();
self.shift_registers(); self.shift_registers();
self.perform_memory_fetch(); self.perform_memory_fetch();
}, }
257 => self.copy_horizontal(), // At dot 257 of each scanline, if rendering is enabled, the PPU copies all bits related to horizontal position from t to v 257 => self.copy_horizontal(), // At dot 257 of each scanline, if rendering is enabled, the PPU copies all bits related to horizontal position from t to v
321..=336 => { 321..=336 => {
self.load_data_into_registers(); self.load_data_into_registers();
self.shift_registers(); self.shift_registers();
self.perform_memory_fetch(); self.perform_memory_fetch();
}, }
x if x > 340 => panic!("cycle beyond 340"), x if x > 340 => panic!("cycle beyond 340"),
_ => (), _ => (),
} }
@ -193,7 +194,7 @@ impl Ppu {
257 => { 257 => {
self.evaluate_sprites(); // ignoring all timing details self.evaluate_sprites(); // ignoring all timing details
self.fetch_sprites(); self.fetch_sprites();
}, }
321..=340 => (), // Read the first byte in secondary OAM (while the PPU fetches the first two background tiles for the next scanline) 321..=340 => (), // Read the first byte in secondary OAM (while the PPU fetches the first two background tiles for the next scanline)
_ => (), _ => (),
} }
@ -248,10 +249,7 @@ impl Ppu {
// deal with mapper MMC3 // deal with mapper MMC3
let current_a12 = if self.v & 1 << 12 != 0 { 1 } else { 0 }; let current_a12 = if self.v & 1 << 12 != 0 { 1 } else { 0 };
if rendering if rendering && (0..241).contains(&self.scanline) && current_a12 != self.previous_a12 {
&& (0..241).contains(&self.scanline)
&& current_a12 != self.previous_a12
{
self.mapper.borrow_mut().clock() self.mapper.borrow_mut().clock()
} }
self.previous_a12 = current_a12; self.previous_a12 = current_a12;
@ -260,9 +258,69 @@ impl Ppu {
} }
} }
const PALETTE_TABLE: [(u8,u8,u8); 64] = [ const PALETTE_TABLE: [(u8, u8, u8); 64] = [
( 84, 84, 84), ( 0, 30, 116), ( 8, 16, 144), ( 48, 0, 136), ( 68, 0, 100), ( 92, 0, 48), ( 84, 4, 0), ( 60, 24, 0), ( 32, 42, 0), ( 8, 58, 0), ( 0, 64, 0), ( 0, 60, 0), ( 0, 50, 60), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), (84, 84, 84),
(152, 150, 152), ( 8, 76, 196), ( 48, 50, 236), ( 92, 30, 228), (136, 20, 176), (160, 20, 100), (152, 34, 32), (120, 60, 0), ( 84, 90, 0), ( 40, 114, 0), ( 8, 124, 0), ( 0, 118, 40), ( 0, 102, 120), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), (0, 30, 116),
(236, 238, 236), ( 76, 154, 236), (120, 124, 236), (176, 98, 236), (228, 84, 236), (236, 88, 180), (236, 106, 100), (212, 136, 32), (160, 170, 0), (116, 196, 0), ( 76, 208, 32), ( 56, 204, 108), ( 56, 180, 204), ( 60, 60, 60), ( 0, 0, 0), ( 0, 0, 0), (8, 16, 144),
(236, 238, 236), (168, 204, 236), (188, 188, 236), (212, 178, 236), (236, 174, 236), (236, 174, 212), (236, 180, 176), (228, 196, 144), (204, 210, 120), (180, 222, 120), (168, 226, 144), (152, 226, 180), (160, 214, 228), (160, 162, 160), ( 0, 0, 0), ( 0, 0, 0), (48, 0, 136),
(68, 0, 100),
(92, 0, 48),
(84, 4, 0),
(60, 24, 0),
(32, 42, 0),
(8, 58, 0),
(0, 64, 0),
(0, 60, 0),
(0, 50, 60),
(0, 0, 0),
(0, 0, 0),
(0, 0, 0),
(152, 150, 152),
(8, 76, 196),
(48, 50, 236),
(92, 30, 228),
(136, 20, 176),
(160, 20, 100),
(152, 34, 32),
(120, 60, 0),
(84, 90, 0),
(40, 114, 0),
(8, 124, 0),
(0, 118, 40),
(0, 102, 120),
(0, 0, 0),
(0, 0, 0),
(0, 0, 0),
(236, 238, 236),
(76, 154, 236),
(120, 124, 236),
(176, 98, 236),
(228, 84, 236),
(236, 88, 180),
(236, 106, 100),
(212, 136, 32),
(160, 170, 0),
(116, 196, 0),
(76, 208, 32),
(56, 204, 108),
(56, 180, 204),
(60, 60, 60),
(0, 0, 0),
(0, 0, 0),
(236, 238, 236),
(168, 204, 236),
(188, 188, 236),
(212, 178, 236),
(236, 174, 236),
(236, 174, 212),
(236, 180, 176),
(228, 196, 144),
(204, 210, 120),
(180, 222, 120),
(168, 226, 144),
(152, 226, 180),
(160, 214, 228),
(160, 162, 160),
(0, 0, 0),
(0, 0, 0),
]; ];

View File

@ -1,7 +1,6 @@
use super::cpu_registers::set_bit; use super::cpu_registers::set_bit;
impl super::Ppu { impl super::Ppu {
pub fn perform_memory_fetch(&mut self) { pub fn perform_memory_fetch(&mut self) {
match self.line_cycle % 8 { match self.line_cycle % 8 {
0 => self.inc_coarse_x(), 0 => self.inc_coarse_x(),
@ -27,7 +26,8 @@ impl super::Ppu {
} }
pub fn load_data_into_registers(&mut self) { pub fn load_data_into_registers(&mut self) {
if self.line_cycle % 8 == 1 { // The shifters are reloaded during ticks 9, 17, 25, ..., 257. if self.line_cycle % 8 == 1 {
// The shifters are reloaded during ticks 9, 17, 25, ..., 257.
// These contain the pattern table data for two tiles. Every 8 cycles, the data for the next // These contain the pattern table data for two tiles. Every 8 cycles, the data for the next
// tile is loaded into the upper 8 bits of this shift register. Meanwhile, the pixel to render is fetched from one of the lower 8 bits. // tile is loaded into the upper 8 bits of this shift register. Meanwhile, the pixel to render is fetched from one of the lower 8 bits.
self.background_pattern_sr_low |= self.low_pattern_table_byte as u16; self.background_pattern_sr_low |= self.low_pattern_table_byte as u16;
@ -46,16 +46,16 @@ impl super::Ppu {
let byte = self.read(address as usize); let byte = self.read(address as usize);
// figure out which two bits are being represented, ignoring fine x and fine y // figure out which two bits are being represented, ignoring fine x and fine y
// left or right: // left or right:
let coarse_x = self.v & 0b00000000_00011111; let coarse_x = self.v & 0b00000000_00011111;
let coarse_y = (self.v & 0b00000011_11100000) >> 5; let coarse_y = (self.v & 0b00000011_11100000) >> 5;
let left_or_right = (coarse_x / 2) % 2; // 0 == left, 1 == right let left_or_right = (coarse_x / 2) % 2; // 0 == left, 1 == right
let top_or_bottom = (coarse_y / 2) % 2; // 0 == top, 1 == bottom let top_or_bottom = (coarse_y / 2) % 2; // 0 == top, 1 == bottom
// grab the needed two bits // grab the needed two bits
self.attribute_table_byte = match (top_or_bottom, left_or_right) { self.attribute_table_byte = match (top_or_bottom, left_or_right) {
(0,0) => (byte >> 0) & 0b00000011, (0, 0) => (byte >> 0) & 0b00000011,
(0,1) => (byte >> 2) & 0b00000011, (0, 1) => (byte >> 2) & 0b00000011,
(1,0) => (byte >> 4) & 0b00000011, (1, 0) => (byte >> 4) & 0b00000011,
(1,1) => (byte >> 6) & 0b00000011, (1, 1) => (byte >> 6) & 0b00000011,
_ => panic!("should not get here"), _ => panic!("should not get here"),
}; };
} }
@ -84,8 +84,8 @@ impl super::Ppu {
let (mut sprite_pixel, current_sprite) = self.select_sprite_pixel(); let (mut sprite_pixel, current_sprite) = self.select_sprite_pixel();
// extract low and high bits from palette shift registers according to fine x, starting from left // extract low and high bits from palette shift registers according to fine x, starting from left
let low_palette_bit = (self.background_palette_sr_low & (1 << (7-self.x)) != 0) as u8; let low_palette_bit = (self.background_palette_sr_low & (1 << (7 - self.x)) != 0) as u8;
let high_palette_bit = (self.background_palette_sr_high & (1 << (7-self.x)) != 0) as u8; let high_palette_bit = (self.background_palette_sr_high & (1 << (7 - self.x)) != 0) as u8;
let palette_offset = (high_palette_bit << 1) | low_palette_bit; let palette_offset = (high_palette_bit << 1) | low_palette_bit;
if x < 8 && !self.show_background_left { if x < 8 && !self.show_background_left {
@ -95,18 +95,22 @@ impl super::Ppu {
sprite_pixel = 0; sprite_pixel = 0;
} }
let mut palette_address = 0; let mut palette_address = 0;
if background_pixel == 0 && sprite_pixel != 0 { // displaying the sprite if background_pixel == 0 && sprite_pixel != 0 {
// displaying the sprite
palette_address += 0x10; // second half of palette table, "Background/Sprite select" palette_address += 0x10; // second half of palette table, "Background/Sprite select"
palette_address += (self.sprite_attribute_latches[current_sprite] & 0b11) << 2; // bottom two bits of attribute byte, left shifted by two palette_address += (self.sprite_attribute_latches[current_sprite] & 0b11) << 2; // bottom two bits of attribute byte, left shifted by two
palette_address += sprite_pixel; // bottom two bits are the value of the sprite pixel from pattern table palette_address += sprite_pixel; // bottom two bits are the value of the sprite pixel from pattern table
} else if background_pixel != 0 && sprite_pixel == 0 { // displaying the background pixel } else if background_pixel != 0 && sprite_pixel == 0 {
// displaying the background pixel
palette_address += palette_offset << 2; // Palette number from attribute table or OAM palette_address += palette_offset << 2; // Palette number from attribute table or OAM
palette_address += background_pixel; // Pixel value from tile data palette_address += background_pixel; // Pixel value from tile data
} else if background_pixel != 0 && sprite_pixel != 0 { } else if background_pixel != 0 && sprite_pixel != 0 {
if self.sprite_indexes[current_sprite] == 0 { // don't access index current_sprite. need to know which sprite we're on horizontally. if self.sprite_indexes[current_sprite] == 0 {
// don't access index current_sprite. need to know which sprite we're on horizontally.
self.sprite_zero_hit = true; self.sprite_zero_hit = true;
} }
if self.sprite_attribute_latches[current_sprite] & (1 << 5) == 0 { // sprite has high priority if self.sprite_attribute_latches[current_sprite] & (1 << 5) == 0 {
// sprite has high priority
palette_address += 0x10; palette_address += 0x10;
palette_address += (self.sprite_attribute_latches[current_sprite] & 0b11) << 2; palette_address += (self.sprite_attribute_latches[current_sprite] & 0b11) << 2;
palette_address += sprite_pixel; palette_address += sprite_pixel;
@ -133,7 +137,7 @@ impl super::Ppu {
color.1 = deemphasize(&color.1); color.1 = deemphasize(&color.1);
color.2 = emphasize(&color.2); color.2 = emphasize(&color.2);
} }
(x,y,color) (x, y, color)
} }
pub fn select_background_pixel(&mut self) -> u8 { pub fn select_background_pixel(&mut self) -> u8 {
@ -141,7 +145,7 @@ impl super::Ppu {
// Returned background pixel is a value between 0 and 3. // Returned background pixel is a value between 0 and 3.
// the bit from background_pattern_sr_low (low pattern table byte) in the 0th place, // the bit from background_pattern_sr_low (low pattern table byte) in the 0th place,
// and the value of the background_pattern_sr_high (high pattern table byte) in the 1st place. // and the value of the background_pattern_sr_high (high pattern table byte) in the 1st place.
let low_bit = (self.background_pattern_sr_low & (1 << (15 - self.x)) != 0) as u8; let low_bit = (self.background_pattern_sr_low & (1 << (15 - self.x)) != 0) as u8;
let high_bit = (self.background_pattern_sr_high & (1 << (15 - self.x)) != 0) as u8; let high_bit = (self.background_pattern_sr_high & (1 << (15 - self.x)) != 0) as u8;
(high_bit << 1) | low_bit (high_bit << 1) | low_bit
} else { } else {
@ -153,7 +157,7 @@ impl super::Ppu {
// Returns (sprite_pixel, index of sprite_pixel within secondary_oam/shift registers) // Returns (sprite_pixel, index of sprite_pixel within secondary_oam/shift registers)
if self.show_sprites { if self.show_sprites {
// sprite pixel is a value between 0 and 3 representing the two sprite pattern table shift registers // sprite pixel is a value between 0 and 3 representing the two sprite pattern table shift registers
let mut low_bit = 0; let mut low_bit = 0;
let mut high_bit = 0; let mut high_bit = 0;
let mut secondary_index = 0; let mut secondary_index = 0;
for i in 0..self.num_sprites { for i in 0..self.num_sprites {
@ -163,8 +167,8 @@ impl super::Ppu {
// The current pixel for each "active" sprite is checked (from highest to lowest priority), // The current pixel for each "active" sprite is checked (from highest to lowest priority),
// and the first non-transparent pixel moves on to a multiplexer, where it joins the BG pixel. // and the first non-transparent pixel moves on to a multiplexer, where it joins the BG pixel.
secondary_index = i; secondary_index = i;
low_bit = (self.sprite_pattern_table_srs[i].0 & 1<<7 != 0) as u8; low_bit = (self.sprite_pattern_table_srs[i].0 & 1 << 7 != 0) as u8;
high_bit = (self.sprite_pattern_table_srs[i].1 & 1<<7 != 0) as u8; high_bit = (self.sprite_pattern_table_srs[i].1 & 1 << 7 != 0) as u8;
if !(low_bit == 0 && high_bit == 0) { if !(low_bit == 0 && high_bit == 0) {
break; break;
} }
@ -192,10 +196,10 @@ impl super::Ppu {
pub fn evaluate_sprites(&mut self) { pub fn evaluate_sprites(&mut self) {
let mut sprite_count = 0; let mut sprite_count = 0;
for n in 0..64 { for n in 0..64 {
let y_coord = self.primary_oam[(n*4)+0]; let y_coord = self.primary_oam[(n * 4) + 0];
if self.y_in_range(y_coord) { if self.y_in_range(y_coord) {
for i in 0..4 { for i in 0..4 {
self.secondary_oam[(sprite_count*4)+i] = self.primary_oam[(n*4)+i]; self.secondary_oam[(sprite_count * 4) + i] = self.primary_oam[(n * 4) + i];
} }
self.sprite_indexes[sprite_count] = n as u8; self.sprite_indexes[sprite_count] = n as u8;
sprite_count += 1; sprite_count += 1;
@ -207,22 +211,21 @@ impl super::Ppu {
} }
} }
self.num_sprites = sprite_count; self.num_sprites = sprite_count;
} }
pub fn fetch_sprites(&mut self) { pub fn fetch_sprites(&mut self) {
for i in 0..self.num_sprites { for i in 0..self.num_sprites {
let mut address: usize; let mut address: usize;
let sprite_y_position = self.secondary_oam[(4*i)+0] as usize; // byte 0 of sprite, sprite's vertical position on screen let sprite_y_position = self.secondary_oam[(4 * i) + 0] as usize; // byte 0 of sprite, sprite's vertical position on screen
let sprite_tile_index = self.secondary_oam[(4*i)+1] as usize; // byte 1 of sprite, sprite's location within pattern table let sprite_tile_index = self.secondary_oam[(4 * i) + 1] as usize; // byte 1 of sprite, sprite's location within pattern table
let sprite_attributes = self.secondary_oam[(4*i)+2]; // byte 2 of sprite, sprite's palette, priority, and flip attributes let sprite_attributes = self.secondary_oam[(4 * i) + 2]; // byte 2 of sprite, sprite's palette, priority, and flip attributes
let sprite_x_position = self.secondary_oam[(4*i)+3]; // byte 3 of sprite, sprite's horizontal position on screen let sprite_x_position = self.secondary_oam[(4 * i) + 3]; // byte 3 of sprite, sprite's horizontal position on screen
let flipped_vertically = sprite_attributes & (1<<7) != 0; let flipped_vertically = sprite_attributes & (1 << 7) != 0;
let flipped_horizontally = sprite_attributes & (1<<6) != 0; let flipped_horizontally = sprite_attributes & (1 << 6) != 0;
// For 8x8 sprites, this is the tile number of this sprite within the pattern table selected in bit 3 of PPUCTRL ($2000). // For 8x8 sprites, this is the tile number of this sprite within the pattern table selected in bit 3 of PPUCTRL ($2000).
if self.sprite_size == 8 { if self.sprite_size == 8 {
address = self.sprite_pattern_table_base; address = self.sprite_pattern_table_base;
address += sprite_tile_index*16; address += sprite_tile_index * 16;
address += if !flipped_vertically { address += if !flipped_vertically {
self.scanline - sprite_y_position // row-within-sprite offset is difference between current scanline and top of sprite self.scanline - sprite_y_position // row-within-sprite offset is difference between current scanline and top of sprite
} else { } else {
@ -230,8 +233,12 @@ impl super::Ppu {
}; };
// For 8x16 sprites, the PPU ignores the pattern table selection and selects a pattern table from bit 0 of this number. // For 8x16 sprites, the PPU ignores the pattern table selection and selects a pattern table from bit 0 of this number.
} else { } else {
address = if sprite_tile_index & 1 == 0 { 0x0 } else { 0x1000 }; address = if sprite_tile_index & 1 == 0 {
address += (sprite_tile_index & 0xFFFF-1) << 4; // turn off bottom bit BEFORE shifting 0x0
} else {
0x1000
};
address += (sprite_tile_index & 0xFFFF - 1) << 4; // turn off bottom bit BEFORE shifting
let fine_y = if !flipped_vertically { let fine_y = if !flipped_vertically {
self.scanline - sprite_y_position self.scanline - sprite_y_position
} else { } else {
@ -248,7 +255,10 @@ impl super::Ppu {
let high_pattern_table_byte = self.read(address + 8); let high_pattern_table_byte = self.read(address + 8);
let mut shift_reg_vals = (0, 0); let mut shift_reg_vals = (0, 0);
for j in 0..8 { for j in 0..8 {
let current_bits = (low_pattern_table_byte & (1 << j), high_pattern_table_byte & (1 << j)); let current_bits = (
low_pattern_table_byte & (1 << j),
high_pattern_table_byte & (1 << j),
);
if !flipped_horizontally { if !flipped_horizontally {
// just copy each bit in same order // just copy each bit in same order
shift_reg_vals.0 |= current_bits.0; shift_reg_vals.0 |= current_bits.0;
@ -269,9 +279,10 @@ impl super::Ppu {
} }
pub fn inc_coarse_x(&mut self) { pub fn inc_coarse_x(&mut self) {
if self.v & 0x001F == 0x001F { // if coarse X == 31 if self.v & 0x001F == 0x001F {
self.v &= !0x001F; // coarse X = 0 // if coarse X == 31
self.v ^= 1<<10; // switch horizontal nametable self.v &= !0x001F; // coarse X = 0
self.v ^= 1 << 10; // switch horizontal nametable
} else { } else {
self.v += 1; self.v += 1;
} }
@ -280,7 +291,7 @@ impl super::Ppu {
pub fn inc_y(&mut self) { pub fn inc_y(&mut self) {
// If rendering is enabled, fine Y is incremented at dot 256 of each scanline, // If rendering is enabled, fine Y is incremented at dot 256 of each scanline,
// overflowing to coarse Y, and finally adjusted to wrap among the nametables vertically. // overflowing to coarse Y, and finally adjusted to wrap among the nametables vertically.
let mut fine_y = (self.v & 0b01110000_00000000) >> 12; let mut fine_y = (self.v & 0b01110000_00000000) >> 12;
let mut coarse_y = (self.v & 0b00000011_11100000) >> 5; let mut coarse_y = (self.v & 0b00000011_11100000) >> 5;
if fine_y < 7 { if fine_y < 7 {
fine_y += 1; fine_y += 1;
@ -290,7 +301,7 @@ impl super::Ppu {
// incrementing coarse Y from 29, the vertical nametable is switched by toggling bit // incrementing coarse Y from 29, the vertical nametable is switched by toggling bit
// 11, and coarse Y wraps to row 0. // 11, and coarse Y wraps to row 0.
if coarse_y == 29 { if coarse_y == 29 {
self.v ^= 1<<11; self.v ^= 1 << 11;
coarse_y = 0; coarse_y = 0;
// Coarse Y can be set out of bounds (> 29), which will cause the PPU to read the // Coarse Y can be set out of bounds (> 29), which will cause the PPU to read the
// attribute data stored there as tile data. If coarse Y is incremented from 31, // attribute data stored there as tile data. If coarse Y is incremented from 31,
@ -317,8 +328,8 @@ impl super::Ppu {
// v: ....F.. ...EDCBA = t: ....F.. ...EDCBA // v: ....F.. ...EDCBA = t: ....F.. ...EDCBA
let mask = 0b00000100_00011111; let mask = 0b00000100_00011111;
let t_vals = self.t & mask; // grab bits of t let t_vals = self.t & mask; // grab bits of t
self.v &= !mask; // turn off bits of v self.v &= !mask; // turn off bits of v
self.v |= t_vals; // apply bits of t self.v |= t_vals; // apply bits of t
} }
pub fn copy_vertical(&mut self) { pub fn copy_vertical(&mut self) {
@ -334,8 +345,8 @@ impl super::Ppu {
} }
pub fn y_in_range(&self, y_coord: u8) -> bool { pub fn y_in_range(&self, y_coord: u8) -> bool {
self.scanline >= (y_coord as usize) && self.scanline >= (y_coord as usize)
self.scanline - (y_coord as usize) < self.sprite_size as usize && self.scanline - (y_coord as usize) < self.sprite_size as usize
} }
pub fn nmi_change(&mut self) { pub fn nmi_change(&mut self) {

View File

@ -1,6 +1,6 @@
use serde::{Serialize, Deserialize}; use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, Clone, Hash)]
pub struct PpuData { pub struct PpuData {
line_cycle: usize, line_cycle: usize,
scanline: usize, scanline: usize,
@ -32,7 +32,7 @@ pub struct PpuData {
num_sprites: usize, num_sprites: usize,
address_increment: u16, address_increment: u16,
sprite_pattern_table_base: usize, sprite_pattern_table_base: usize,
background_pattern_table_base:usize, background_pattern_table_base: usize,
oam_address: usize, oam_address: usize,
sprite_size: u8, sprite_size: u8,
grayscale: bool, grayscale: bool,
@ -57,7 +57,7 @@ pub struct PpuData {
impl super::Ppu { impl super::Ppu {
pub fn save_state(&self) -> PpuData { pub fn save_state(&self) -> PpuData {
PpuData{ PpuData {
line_cycle: self.line_cycle, line_cycle: self.line_cycle,
scanline: self.scanline, scanline: self.scanline,
frame: self.frame, frame: self.frame,

View File

@ -1,51 +1,41 @@
extern crate sdl2; use font8x8::legacy::BASIC_LEGACY;
use sdl2::Sdl; pub struct Screen {
use sdl2::pixels::Color; pub(crate) display: Vec<u32>,
use sdl2::render::{Canvas, Texture, TextureCreator};
use sdl2::video::{Window, WindowContext};
pub const SCALE_FACTOR: usize = 3;
const BYTES_IN_COL: usize = SCALE_FACTOR * 3; // 3 bytes per pixel in RGB24. This represents a thick, SCALE_FACTOR-pixel-wide column.
const BYTES_IN_ROW: usize = BYTES_IN_COL * 256; // 256 = screen width in NES pixels. This represents a thin, one-SDL-pixel-tall row.
type RGBColor = (u8, u8, u8);
pub fn init_window(context: &Sdl) -> Result<(Canvas<Window>, TextureCreator<WindowContext>), String> {
let video_subsystem = context.video()?;
let window = video_subsystem.window("NESTUR", (256 * SCALE_FACTOR) as u32, (240 * SCALE_FACTOR) as u32)
.position_centered()
.opengl()
.build()
.map_err(|e| e.to_string())?;
let mut canvas = window.into_canvas().build().map_err(|e| e.to_string())?;
let texture_creator = canvas.texture_creator();
canvas.set_draw_color(Color::RGB(0, 0, 0));
canvas.clear();
canvas.present();
Ok((canvas, texture_creator))
} }
pub fn draw_pixel(buffer: &mut Vec<u8>, x: usize, y: usize, color: RGBColor) { impl Screen {
let (r, g, b) = color; pub fn new() -> Screen {
let nes_y_offset = y * BYTES_IN_ROW * SCALE_FACTOR; // find offset for thick, SCALE_FACTOR-pixel tall row Screen {
for sdl_row_num in 0..SCALE_FACTOR { // looping over one-pixel tall rows up to SCALE_FACTOR display: vec![0; 256 * 240],
let row_offset = nes_y_offset + (sdl_row_num * BYTES_IN_ROW); // row_offset is the offset within buffer of the thin row we're on }
let nes_x_offset = x * BYTES_IN_COL; // find horizontal offset within row (in byte terms) of NES x-coordinate }
for sdl_col_num in 0..SCALE_FACTOR { // for pixels up to SCALE_FACTOR, moving horizontally
let col_offset = nes_x_offset + (sdl_col_num * 3); // skip 3 bytes at a time, R/G/B for each pixel pub fn plot_pixel(&mut self, ox: usize, oy: usize, r: u32, g: u32, b: u32) {
let offset = row_offset + col_offset; let color = (r << 16) + (g << 8) + b;
buffer[offset + 0] = r;
buffer[offset + 1] = g; let x = ox.clamp(0, 256 - 1);
buffer[offset + 2] = b; let y = oy.clamp(0, 240 - 1);
self.display[(y * 256) + x] = color
}
pub fn draw_string(&mut self, ox: usize, oy: usize, text: &str) {
let mut xpos = ox;
let mut ypos = oy;
for char in text.chars() {
for x in &BASIC_LEGACY[char as usize] {
for bit in 0..8 {
match *x & 1 << bit {
0 => {} //self.plot_pixel(xpos, ypos, 0, 0, 0),
_ => self.plot_pixel(xpos, ypos, 0xff, 0xff, 0xff),
}
xpos += 1
}
xpos -= 8;
ypos += 1;
}
xpos += 10;
ypos -= 8;
} }
} }
} }
pub fn draw_to_window(texture: &mut Texture, canvas: &mut Canvas<Window>, buffer: &Vec<u8>) -> Result<(), String> {
texture.update(None, buffer, 256*3*SCALE_FACTOR)
.map_err(|e| e.to_string())?;
canvas.copy(&texture, None, None)?;
canvas.present();
Ok(())
}

View File

@ -1,7 +1,7 @@
use super::cpu;
use super::ppu;
use super::apu; use super::apu;
use super::cartridge; use super::cartridge;
use super::cpu;
use super::ppu;
use std::fs::{DirEntry, File}; use std::fs::{DirEntry, File};
use std::io::{Read, Write}; use std::io::{Read, Write};
@ -16,16 +16,14 @@ struct SaveState {
} }
pub fn save_state(cpu: &cpu::Cpu, save_file: &PathBuf) -> Result<(), String> { pub fn save_state(cpu: &cpu::Cpu, save_file: &PathBuf) -> Result<(), String> {
let data = SaveState{ let data = SaveState {
cpu: cpu.save_state(), cpu: cpu.save_state(),
ppu: cpu.ppu.save_state(), ppu: cpu.ppu.save_state(),
apu: cpu.apu.save_state(), apu: cpu.apu.save_state(),
mapper: cpu.mapper.borrow().save_state(), mapper: cpu.mapper.borrow_mut().save_state(),
}; };
let serialized = serde_json::to_string(&data) let serialized = serde_json::to_string(&data).map_err(|e| e.to_string())?;
.map_err(|e| e.to_string())?; let mut f = File::create(&save_file).expect("could not create output file for save state");
let mut f = File::create(&save_file)
.expect("could not create output file for save state");
f.write_all(serialized.as_bytes()) f.write_all(serialized.as_bytes())
.map_err(|_| "couldn't write serialized data to file".to_string())?; .map_err(|_| "couldn't write serialized data to file".to_string())?;
println!("state saved to file: {:?}", save_file); println!("state saved to file: {:?}", save_file);
@ -34,15 +32,13 @@ pub fn save_state(cpu: &cpu::Cpu, save_file: &PathBuf) -> Result<(), String> {
pub fn load_state(cpu: &mut cpu::Cpu, save_file: &PathBuf) -> Result<(), String> { pub fn load_state(cpu: &mut cpu::Cpu, save_file: &PathBuf) -> Result<(), String> {
if Path::new(&save_file).exists() { if Path::new(&save_file).exists() {
let mut f = File::open(save_file.clone()) let mut f = File::open(save_file.clone()).map_err(|e| e.to_string())?;
.map_err(|e| e.to_string())?;
let mut serialized_data = vec![]; let mut serialized_data = vec![];
f.read_to_end(&mut serialized_data) f.read_to_end(&mut serialized_data)
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
let serialized_string = std::str::from_utf8(&serialized_data) let serialized_string = std::str::from_utf8(&serialized_data).map_err(|e| e.to_string())?;
.map_err(|e| e.to_string())?; let state: SaveState =
let state: SaveState = serde_json::from_str(serialized_string) serde_json::from_str(serialized_string).map_err(|e| e.to_string())?;
.map_err(|e| e.to_string())?;
cpu.load_state(state.cpu); cpu.load_state(state.cpu);
cpu.ppu.load_state(state.ppu); cpu.ppu.load_state(state.ppu);
cpu.apu.load_state(state.apu); cpu.apu.load_state(state.apu);
@ -67,7 +63,7 @@ pub fn find_next_filename(filepath: &PathBuf, new_ext: Option<&str>) -> Option<P
let current_name = format!("{}{}{}-{}.{}", path, sep, stem, i, ext); let current_name = format!("{}{}{}-{}.{}", path, sep, stem, i, ext);
let save_file = PathBuf::from(&current_name); let save_file = PathBuf::from(&current_name);
if !save_file.exists() { if !save_file.exists() {
return Some(save_file) return Some(save_file);
} }
i += 1; i += 1;
} }
@ -88,7 +84,7 @@ pub fn find_last_save_state(filepath: &PathBuf, new_ext: Option<&str>) -> Option
name.len() >= stem.len() name.len() >= stem.len()
&& name.len() >= ext.len() && name.len() >= ext.len()
&& &name[..stem.len()] == stem && &name[..stem.len()] == stem
&& &name[name.len()-ext.len()..] == ext && &name[name.len() - ext.len()..] == ext
}) })
.collect::<Vec<std::io::Result<DirEntry>>>(); .collect::<Vec<std::io::Result<DirEntry>>>();
save_states.sort_by(|a, b| { save_states.sort_by(|a, b| {