Initial Commit of nesfuzz
This commit is contained in:
parent
4afae10ece
commit
f4b677d52a
|
@ -10,3 +10,9 @@ Cargo.lock
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
|
|
||||||
roms
|
roms
|
||||||
|
*.fm2
|
||||||
|
*.nes
|
||||||
|
*.cdl
|
||||||
|
*.nl
|
||||||
|
*.idea
|
||||||
|
*cdl
|
||||||
|
|
13
Cargo.toml
13
Cargo.toml
|
@ -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
|
|
||||||
|
|
95
README.md
95
README.md
|
@ -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.
|
Binary file not shown.
After Width: | Height: | Size: 79 KiB |
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
132
src/audio.rs
132
src/audio.rs
|
@ -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[i−1] + α × (x[i] − x[i−1])
|
|
||||||
(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[i−1] + gamma * (x[i] − x[i−1])
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
737
src/cpu/mod.rs
737
src/cpu/mod.rs
|
@ -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",
|
|
||||||
];
|
];
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {}
|
163
src/input.rs
163
src/input.rs
|
@ -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,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
510
src/main.rs
510
src/main.rs
|
@ -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?
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
262
src/ppu/mod.rs
262
src/ppu/mod.rs
|
@ -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),
|
||||||
];
|
];
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(())
|
|
||||||
}
|
|
||||||
|
|
28
src/state.rs
28
src/state.rs
|
@ -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(¤t_name);
|
let save_file = PathBuf::from(¤t_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| {
|
||||||
|
|
Loading…
Reference in New Issue