├── .github └── FUNDING.yml ├── .gitignore ├── Cargo.toml ├── LICENSE.txt ├── README.md ├── assets ├── 5x3font.png ├── chicago8x8.chr └── troll8x8.chr ├── audio_pipe.sh ├── ntscpalette.pal └── src ├── addressing.rs ├── apu ├── audio_channel.rs ├── dmc.rs ├── filters.rs ├── length_counter.rs ├── mod.rs ├── noise.rs ├── pulse.rs ├── ring_buffer.rs ├── triangle.rs └── volume_envelope.rs ├── asm.rs ├── cartridge.rs ├── cycle_cpu.rs ├── fds.rs ├── ines.rs ├── lib.rs ├── memory.rs ├── memoryblock.rs ├── mmc ├── action53.rs ├── axrom.rs ├── bnrom.rs ├── cnrom.rs ├── fds.rs ├── fme7.rs ├── gxrom.rs ├── ines31.rs ├── mapper.rs ├── mirroring.rs ├── mmc1.rs ├── mmc3.rs ├── mmc5.rs ├── mod.rs ├── n163.rs ├── none.rs ├── nrom.rs ├── nsf.rs ├── pxrom.rs ├── rainbow.rs ├── uxrom.rs ├── vrc6.rs └── vrc7.rs ├── nes.rs ├── nsf.rs ├── opcode_info.rs ├── opcodes.rs ├── palettes.rs ├── ppu.rs ├── tracked_events.rs └── unofficial_opcodes.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: "zeta0134" 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target/ 3 | tests/ 4 | **/*.rs.bk 5 | *.nes 6 | *.zip 7 | *.ogg 8 | *.flac 9 | *.raw 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rusticnes-core" 3 | version = "0.2.0" 4 | authors = ["Nicholas Flynt "] 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 zeta0134 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Archival Notice 2 | 3 | This project has been renamed to Rustico, and moved to a shiny new monorepo over here: https://github.com/zeta0134/rustico 4 | 5 | Please update your bookmarks! All new development will proceed in the monorepo, and I'll eventually remove these individual repositories. 6 | 7 | # RusticNES-Core 8 | 9 | This is an NES emulator written in the Rust programming language. I began this project because I wanted to teach myself Rust, and having already written [another emulator](https://github.com/zeta0134/LuaGB), I figured this was as good a way to introduce myself to the language as any. 10 | 11 | The emulator is split up into the Core library (this repository) and platform specific shells which depend on this library. rusticnes-core contains the entire emulator with as few external dependencies as possible (presently just Rust's standard FileIO functions) so that it remains portable. All platform specific code is the responsibility of the shell. 12 | 13 | If you're looking to compile and run a working copy of the emulator for PCs, you want [RusticNES-SDL](https://github.com/zeta0134/rusticnes-sdl), which is the reference implementation. I've tested this on Windows and Arch Linux, and it should run on Mac, and any other platform that [rust-sdl2](https://github.com/Rust-SDL2/rust-sdl2) supports. I may update this README with usage instructions for the core library after the project stabilizes a bit. At the moment the project is in constant flux and lacks what I'd call a stable API, so I'll instead refer you to [RusticNES-SDL](https://github.com/zeta0134/rusticnes-sdl) for the reference implementation. 14 | 15 | I'm striving for cycle accuracy with the emulator. While it works and runs many games, it presently falls short of this goal. I am presently most focused on getting the base emulator to run properly, and pass various [accuracy tests](http://tasvideos.org/EmulatorResources/NESAccuracyTests.html). Mapper support should be easy to add as I go. Here is the current state of the major systems: 16 | 17 | ## 6502 CPU 18 | 19 | - All instructions, including unofficial instructions, NOPs, and STPs 20 | - Mostly cycle accurate, which should include additional reads / writes on dummy cycles. 21 | - Missing proper read delay implementation, needed for DMC DMA read delay, and proper interaction between DMC and OAM DMA during simultaneous operation. 22 | 23 | ## APU 24 | 25 | - Feature complete as far as I can tell. Pulse, Triangle, Noise, and DMC are all working properly. 26 | - DMC wait delay is not implemented. 27 | - Audio is not mixed properly, relative channel volumes are therefore sometimes quite incorrect. It's close enough that things sound okay unless you know what to listen for. 28 | - No interpolation or filtering, which can make especially high frequencies sound a bit off. The APU is producing the correct output, but the subsequent clamping to 44.1 KHz introduces artifacts. 29 | - Triangle channel intentionally does not emulate extremely high frequencies, to avoid artifacts in the handful of games that use this to "silence" the channel 30 | 31 | ## PPU 32 | 33 | - Memory mapping, including cartridge mapper support, is all implemented and should be working. 34 | - Nametable mirroring modes appear to work correctly, and are controlled by the mapper. 35 | - Cycle timing should be very close to accurate. Tricky games like Battletoads appear to run correctly, though there may still be bugs here and there. 36 | - Sprite overflow is implemented correctly. The sprite overflow bug is not, so games relying on the behavior of the sprite overflow flag will encounter accuracy problems relative to real hardware. 37 | 38 | ## Input 39 | 40 | - A single Standard Controller plugged into port 1 is implemented. 41 | - Multiple controllers and additional peripheral support (Light Zapper, Track and Field Mat, Knitting Machine, etc) is planned, but not implemented. 42 | 43 | ## Mappers 44 | 45 | - Currently supported: AxROM, CnROM, GxROM, MMC1, MMC3, NROM, PxROM, UxROM 46 | - Currently unsupported: Everything else. 47 | - Behavior seems mostly correct, but accuracy is not guaranteed. 48 | - Some of blarggs mapper tests do not pass, especially those involving timing, which may be due to missing RDY line implementation 49 | - FDS and non-NTSC NES features (PAL, Vs System, etc) are entirely unsupported. 50 | -------------------------------------------------------------------------------- /assets/5x3font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeta0134/rusticnes-core/857f7cf3a54c68d35e3164f80dcfb3b8c3737aa4/assets/5x3font.png -------------------------------------------------------------------------------- /assets/chicago8x8.chr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeta0134/rusticnes-core/857f7cf3a54c68d35e3164f80dcfb3b8c3737aa4/assets/chicago8x8.chr -------------------------------------------------------------------------------- /assets/troll8x8.chr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeta0134/rusticnes-core/857f7cf3a54c68d35e3164f80dcfb3b8c3737aa4/assets/troll8x8.chr -------------------------------------------------------------------------------- /audio_pipe.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rm audiodump.raw 3 | touch audiodump.raw 4 | tail -f audiodump.raw | play --type raw --encoding signed-integer --bits 16 --endian big --channels 1 --rate 44100 - 5 | -------------------------------------------------------------------------------- /ntscpalette.pal: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeta0134/rusticnes-core/857f7cf3a54c68d35e3164f80dcfb3b8c3737aa4/ntscpalette.pal -------------------------------------------------------------------------------- /src/apu/audio_channel.rs: -------------------------------------------------------------------------------- 1 | // This is a generic trait, which all audio channels should implement. It is 2 | // primarily meant for use with debug features that display data about the 3 | // audio channels in realtime. 4 | 5 | use super::RingBuffer; 6 | 7 | #[derive(Clone)] 8 | pub enum PlaybackRate { 9 | FundamentalFrequency { frequency: f32 }, 10 | LfsrRate { index: usize, max: usize }, 11 | SampleRate { frequency: f32 }, 12 | } 13 | 14 | #[derive(Clone)] 15 | pub enum Volume { 16 | VolumeIndex { index: usize, max: usize }, 17 | } 18 | 19 | #[derive(Clone)] 20 | pub enum Timbre { 21 | DutyIndex { index: usize, max: usize }, 22 | LsfrMode { index: usize, max: usize }, 23 | PatchIndex { index: usize, max: usize }, 24 | } 25 | 26 | pub trait AudioChannelState { 27 | fn name(&self) -> String; 28 | fn chip(&self) -> String; 29 | fn sample_buffer(&self) -> &RingBuffer; 30 | // TODO: Remove this default implementation, once edge buffer 31 | // is properly supported in all channel types 32 | fn edge_buffer(&self) -> &RingBuffer; 33 | fn min_sample(&self) -> i16 {return i16::MIN;} 34 | fn max_sample(&self) -> i16 {return i16::MAX;} 35 | fn record_current_output(&mut self); 36 | fn muted(&self) -> bool; 37 | fn mute(&mut self); 38 | fn unmute(&mut self); 39 | 40 | fn playing(&self) -> bool { return false; } 41 | fn rate(&self) -> PlaybackRate { return PlaybackRate::SampleRate{frequency: 0.0}; } 42 | fn volume(&self) -> Option {return None} 43 | fn timbre(&self) -> Option {return None} 44 | fn amplitude(&self) -> f32 { 45 | /* pre-mixed volume, allows chips using non-linear mixing to tailor this value. 46 | results should be based on 2A03 pulse, where 1.0 corresponds to 0xF */ 47 | if !self.playing() {return 0.0} 48 | match self.volume() { 49 | Some(Volume::VolumeIndex{index, max}) => {return index as f32 / (max + 1) as f32}, 50 | None => {return 1.0} 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/apu/dmc.rs: -------------------------------------------------------------------------------- 1 | use mmc::mapper::Mapper; 2 | use super::audio_channel::AudioChannelState; 3 | use super::ring_buffer::RingBuffer; 4 | use super::filters; 5 | use super::filters::DspFilter; 6 | 7 | pub struct DmcState { 8 | pub name: String, 9 | pub chip: String, 10 | pub debug_disable: bool, 11 | pub output_buffer: RingBuffer, 12 | pub edge_buffer: RingBuffer, 13 | pub last_edge: bool, 14 | pub debug_filter: filters::HighPassIIR, 15 | 16 | pub looping: bool, 17 | pub period_initial: u16, 18 | pub period_current: u16, 19 | pub output_level: u8, 20 | pub starting_address: u16, 21 | pub sample_length: u16, 22 | 23 | pub current_address: u16, 24 | pub sample_buffer: u8, 25 | pub shift_register: u8, 26 | pub sample_buffer_empty: bool, 27 | pub bits_remaining: u8, 28 | pub bytes_remaining: u16, 29 | pub silence_flag: bool, 30 | 31 | pub interrupt_enabled: bool, 32 | pub interrupt_flag: bool, 33 | pub rdy_line: bool, 34 | pub rdy_delay: u8, 35 | } 36 | 37 | impl DmcState { 38 | pub fn new(channel_name: &str, chip_name: &str) -> DmcState { 39 | return DmcState { 40 | name: String::from(channel_name), 41 | chip: String::from(chip_name), 42 | debug_disable: false, 43 | output_buffer: RingBuffer::new(32768), 44 | edge_buffer: RingBuffer::new(32768), 45 | last_edge: false, 46 | debug_filter: filters::HighPassIIR::new(44100.0, 300.0), 47 | 48 | looping: false, 49 | period_initial: 428, 50 | period_current: 0, 51 | output_level: 0, 52 | starting_address: 0, 53 | sample_length: 0, 54 | 55 | current_address: 0, 56 | sample_buffer: 0, 57 | shift_register: 0, 58 | sample_buffer_empty: true, 59 | bits_remaining: 8, 60 | bytes_remaining: 0, 61 | silence_flag: false, 62 | interrupt_enabled: true, 63 | interrupt_flag: false, 64 | rdy_line: false, 65 | rdy_delay: 0, 66 | } 67 | } 68 | 69 | pub fn debug_status(&self) -> String { 70 | return format!("Rate: {:3} - Divisor: {:3} - Start: {:04X} - Current: {:04X} - Length: {:4} - R.Bytes: {:4} - R.Bits: {:1}", 71 | self.period_initial, self.period_current, self.starting_address, self.current_address, self.sample_length, 72 | self.bytes_remaining, self.bits_remaining); 73 | } 74 | 75 | pub fn read_next_sample(&mut self, mapper: &mut dyn Mapper) { 76 | match mapper.read_cpu(0x8000 | (self.current_address & 0x7FFF)) { 77 | Some(byte) => self.sample_buffer = byte, 78 | None => self.sample_buffer = 0, 79 | } 80 | self.current_address = self.current_address.wrapping_add(1); 81 | self.bytes_remaining -= 1; 82 | if self.bytes_remaining == 0 { 83 | if self.looping { 84 | self.current_address = self.starting_address; 85 | self.bytes_remaining = self.sample_length; 86 | self.last_edge = true; 87 | } else { 88 | if self.interrupt_enabled { 89 | self.interrupt_flag = true; 90 | } 91 | } 92 | } 93 | self.sample_buffer_empty = false; 94 | self.rdy_line = false; 95 | self.rdy_delay = 0; 96 | } 97 | 98 | pub fn begin_output_cycle(&mut self) { 99 | self.bits_remaining = 8; 100 | if self.sample_buffer_empty { 101 | self.silence_flag = true; 102 | } else { 103 | self.silence_flag = false; 104 | self.shift_register = self.sample_buffer; 105 | self.sample_buffer_empty = true; 106 | } 107 | } 108 | 109 | pub fn update_output_unit(&mut self) { 110 | if !(self.silence_flag) { 111 | let mut target_output = self.output_level; 112 | if (self.shift_register & 0b1) == 0 { 113 | if self.output_level >= 2 { 114 | target_output -= 2; 115 | } 116 | } else { 117 | if self.output_level <= 125 { 118 | target_output += 2; 119 | } 120 | } 121 | self.output_level = target_output; 122 | } 123 | self.shift_register = self.shift_register >> 1; 124 | self.bits_remaining -= 1; 125 | if self.bits_remaining == 0 { 126 | self.begin_output_cycle(); 127 | } 128 | } 129 | 130 | pub fn clock(&mut self, mapper: &mut dyn Mapper) { 131 | if self.period_current == 0 { 132 | self.period_current = self.period_initial - 1; 133 | self.update_output_unit(); 134 | } else { 135 | self.period_current -= 1; 136 | } 137 | if self.sample_buffer_empty && self.bytes_remaining > 0 { 138 | self.rdy_line = true; 139 | self.rdy_delay += 1; 140 | if self.rdy_delay > 2 { 141 | self.read_next_sample(mapper); 142 | } 143 | } else { 144 | self.rdy_line = false; 145 | self.rdy_delay = 0; 146 | } 147 | } 148 | 149 | pub fn output(&self) -> i16 { 150 | return self.output_level as i16; 151 | } 152 | } 153 | 154 | impl AudioChannelState for DmcState { 155 | fn name(&self) -> String { 156 | return self.name.clone(); 157 | } 158 | 159 | fn chip(&self) -> String { 160 | return self.chip.clone(); 161 | } 162 | 163 | fn edge_buffer(&self) -> &RingBuffer { 164 | return &self.edge_buffer; 165 | } 166 | 167 | fn sample_buffer(&self) -> &RingBuffer { 168 | return &self.output_buffer; 169 | } 170 | 171 | fn record_current_output(&mut self) { 172 | self.debug_filter.consume(self.output() as f32); 173 | self.output_buffer.push((self.debug_filter.output() * -4.0) as i16); 174 | self.edge_buffer.push(self.last_edge as i16); 175 | self.last_edge = false; 176 | } 177 | 178 | fn min_sample(&self) -> i16 { 179 | return -512; 180 | } 181 | 182 | fn max_sample(&self) -> i16 { 183 | return 512; 184 | } 185 | 186 | fn muted(&self) -> bool { 187 | return self.debug_disable; 188 | } 189 | 190 | fn mute(&mut self) { 191 | self.debug_disable = true; 192 | } 193 | 194 | fn unmute(&mut self) { 195 | self.debug_disable = false; 196 | } 197 | 198 | fn playing(&self) -> bool { 199 | return self.amplitude() > 0.0; 200 | } 201 | 202 | fn amplitude(&self) -> f32 { 203 | let buffer = self.output_buffer.buffer(); 204 | let mut index = (self.output_buffer.index() - 256) % buffer.len(); 205 | let mut max = buffer[index]; 206 | let mut min = buffer[index]; 207 | for _i in 0 .. 256 { 208 | if buffer[index] > max {max = buffer[index];} 209 | if buffer[index] < min {min = buffer[index];} 210 | index += 1; 211 | index = index % buffer.len(); 212 | } 213 | return (max - min) as f32 / 256.0; 214 | } 215 | } -------------------------------------------------------------------------------- /src/apu/filters.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | 3 | use std::f32::consts::PI; 4 | 5 | pub trait DspFilter: Send { 6 | fn consume(&mut self, sample: f32); 7 | fn output(&self) -> f32; 8 | } 9 | 10 | pub struct IdentityFilter { 11 | sample: f32 12 | } 13 | 14 | impl IdentityFilter { 15 | pub fn new() -> IdentityFilter { 16 | return IdentityFilter { 17 | sample: 0.0 18 | } 19 | } 20 | } 21 | 22 | impl DspFilter for IdentityFilter { 23 | fn consume(&mut self, new_input: f32) { 24 | self.sample = new_input; 25 | } 26 | 27 | fn output(&self) -> f32 { 28 | return self.sample; 29 | } 30 | } 31 | 32 | pub struct HighPassIIR { 33 | alpha: f32, 34 | previous_output: f32, 35 | previous_input: f32, 36 | delta: f32, 37 | } 38 | 39 | impl HighPassIIR { 40 | pub fn new(sample_rate: f32, cutoff_frequency: f32) -> HighPassIIR { 41 | let delta_t = 1.0 / sample_rate; 42 | let time_constant = 1.0 / cutoff_frequency; 43 | let alpha = time_constant / (time_constant + delta_t); 44 | return HighPassIIR { 45 | alpha: alpha, 46 | previous_output: 0.0, 47 | previous_input: 0.0, 48 | delta: 0.0, 49 | } 50 | } 51 | } 52 | 53 | impl DspFilter for HighPassIIR { 54 | fn consume(&mut self, new_input: f32) { 55 | self.previous_output = self.output(); 56 | self.delta = new_input - self.previous_input; 57 | self.previous_input = new_input; 58 | } 59 | 60 | fn output(&self) -> f32 { 61 | return self.alpha * self.previous_output + self.alpha * self.delta; 62 | } 63 | } 64 | 65 | pub struct LowPassIIR { 66 | alpha: f32, 67 | previous_output: f32, 68 | delta: f32, 69 | } 70 | 71 | impl LowPassIIR { 72 | pub fn new(sample_rate: f32, cutoff_frequency: f32) -> LowPassIIR { 73 | let delta_t = 1.0 / sample_rate; 74 | let time_constant = 1.0 / (2.0 * PI * cutoff_frequency); 75 | let alpha = delta_t / (time_constant + delta_t); 76 | return LowPassIIR { 77 | alpha: alpha, 78 | previous_output: 0.0, 79 | delta: 0.0, 80 | } 81 | } 82 | } 83 | 84 | impl DspFilter for LowPassIIR { 85 | fn consume(&mut self, new_input: f32) { 86 | self.previous_output = self.output(); 87 | self.delta = new_input - self.previous_output; 88 | } 89 | 90 | fn output(&self) -> f32 { 91 | return self.previous_output + self.alpha * self.delta; 92 | } 93 | } 94 | 95 | fn blackman_window(index: usize, window_size: usize) -> f32 { 96 | let i = index as f32; 97 | let M = window_size as f32; 98 | return 0.42 - 0.5 * ((2.0 * PI * i) / M).cos() + 0.08 * ((4.0 * PI * i) / M).cos(); 99 | } 100 | 101 | fn sinc(index: usize, window_size: usize, fc: f32) -> f32 { 102 | let i = index as f32; 103 | let M = window_size as f32; 104 | let shifted_index = i - (M / 2.0); 105 | if index == (window_size / 2) { 106 | return 2.0 * PI * fc; 107 | } else { 108 | return (2.0 * PI * fc * shifted_index).sin() / shifted_index; 109 | } 110 | } 111 | 112 | fn normalize(input: Vec) -> Vec { 113 | let sum: f32 = input.clone().into_iter().sum(); 114 | let output = input.into_iter().map(|x| x / sum).collect(); 115 | return output; 116 | } 117 | 118 | fn windowed_sinc_kernel(fc: f32, window_size: usize) -> Vec { 119 | let mut kernel: Vec = Vec::new(); 120 | for i in 0 ..= window_size { 121 | kernel.push(sinc(i, window_size, fc) * blackman_window(i, window_size)); 122 | } 123 | return normalize(kernel); 124 | } 125 | 126 | pub struct LowPassFIR { 127 | kernel: Vec, 128 | inputs: Vec, 129 | input_index: usize, 130 | } 131 | 132 | impl LowPassFIR { 133 | pub fn new(sample_rate: f32, cutoff_frequency: f32, window_size: usize) -> LowPassFIR { 134 | let fc = cutoff_frequency / sample_rate; 135 | let kernel = windowed_sinc_kernel(fc, window_size); 136 | let mut inputs = Vec::new(); 137 | inputs.resize(window_size + 1, 0.0); 138 | 139 | return LowPassFIR { 140 | kernel: kernel, 141 | inputs: inputs, 142 | input_index: 0, 143 | } 144 | } 145 | } 146 | 147 | impl DspFilter for LowPassFIR { 148 | fn consume(&mut self, new_input: f32) { 149 | self.inputs[self.input_index] = new_input; 150 | self.input_index = (self.input_index + 1) % self.inputs.len(); 151 | } 152 | 153 | fn output(&self) -> f32 { 154 | let mut output: f32 = 0.0; 155 | for i in 0 .. self.inputs.len() { 156 | let buffer_index = (self.input_index + i) % self.inputs.len(); 157 | output += self.kernel[i] * self.inputs[buffer_index]; 158 | } 159 | return output; 160 | } 161 | } 162 | 163 | // essentially a thin wrapper around a DspFilter, with some bonus data to track 164 | // state when used in a larger chain 165 | pub struct ChainedFilter { 166 | wrapped_filter: Box, 167 | sampling_period: f32, 168 | period_counter: f32, 169 | } 170 | 171 | pub struct FilterChain { 172 | filters: Vec, 173 | } 174 | 175 | impl FilterChain { 176 | pub fn new() -> FilterChain { 177 | let identity = IdentityFilter::new(); 178 | return FilterChain { 179 | filters: vec![ChainedFilter{ 180 | wrapped_filter: Box::new(identity), 181 | sampling_period: 1.0, 182 | period_counter: 0.0, 183 | }], 184 | } 185 | } 186 | 187 | pub fn add(&mut self, filter: Box, sample_rate: f32) { 188 | self.filters.push(ChainedFilter { 189 | wrapped_filter: filter, 190 | sampling_period: (1.0 / sample_rate), 191 | period_counter: 0.0 192 | }); 193 | } 194 | 195 | pub fn consume(&mut self, input_sample: f32, delta_time: f32) { 196 | // Always advance the identity filter with the new current sample 197 | self.filters[0].wrapped_filter.consume(input_sample); 198 | // Now for every remaining filter in the chain, advance and sample the previous 199 | // filter as required 200 | for i in 1 .. self.filters.len() { 201 | let previous = i - 1; 202 | let current = i; 203 | self.filters[current].period_counter += delta_time; 204 | while self.filters[current].period_counter >= self.filters[current].sampling_period { 205 | self.filters[current].period_counter -= self.filters[current].sampling_period; 206 | let previous_output = self.filters[previous].wrapped_filter.output(); 207 | self.filters[current].wrapped_filter.consume(previous_output); 208 | } 209 | } 210 | } 211 | 212 | pub fn output(&self) -> f32 { 213 | let final_filter = self.filters.last().unwrap(); 214 | return final_filter.wrapped_filter.output(); 215 | } 216 | } -------------------------------------------------------------------------------- /src/apu/length_counter.rs: -------------------------------------------------------------------------------- 1 | pub struct LengthCounterState { 2 | pub length: u8, 3 | pub halt_flag: bool, 4 | pub channel_enabled: bool, 5 | } 6 | 7 | impl LengthCounterState{ 8 | pub fn new() -> LengthCounterState { 9 | return LengthCounterState { 10 | length: 0, 11 | halt_flag: false, 12 | channel_enabled: false, 13 | } 14 | } 15 | 16 | pub fn clock(&mut self) { 17 | if self.channel_enabled { 18 | if self.length > 0 && !(self.halt_flag) { 19 | self.length -= 1; 20 | } 21 | } else { 22 | self.length = 0; 23 | } 24 | } 25 | 26 | pub fn set_length(&mut self, index: u8) { 27 | if self.channel_enabled { 28 | let table = [ 29 | 10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, 30 | 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30]; 31 | self.length = table[index as usize]; 32 | } else { 33 | self.length = 0 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/apu/noise.rs: -------------------------------------------------------------------------------- 1 | use super::length_counter::LengthCounterState; 2 | use super::volume_envelope::VolumeEnvelopeState; 3 | use super::audio_channel::AudioChannelState; 4 | use super::audio_channel::PlaybackRate; 5 | use super::audio_channel::Volume; 6 | use super::audio_channel::Timbre; 7 | use super::ring_buffer::RingBuffer; 8 | use super::filters; 9 | use super::filters::DspFilter; 10 | 11 | pub struct NoiseChannelState { 12 | pub name: String, 13 | pub chip: String, 14 | pub debug_disable: bool, 15 | pub output_buffer: RingBuffer, 16 | pub edge_buffer: RingBuffer, 17 | pub last_edge: bool, 18 | pub debug_filter: filters::HighPassIIR, 19 | pub length: u8, 20 | pub length_halt_flag: bool, 21 | 22 | pub envelope: VolumeEnvelopeState, 23 | pub length_counter: LengthCounterState, 24 | 25 | pub mode: u8, 26 | pub period_initial: u16, 27 | pub period_current: u16, 28 | 29 | // Actually a 15-bit register 30 | pub shift_register: u16, 31 | } 32 | 33 | impl NoiseChannelState { 34 | pub fn new(channel_name: &str, chip_name: &str, ) -> NoiseChannelState { 35 | return NoiseChannelState { 36 | name: String::from(channel_name), 37 | chip: String::from(chip_name), 38 | debug_disable: false, 39 | output_buffer: RingBuffer::new(32768), 40 | edge_buffer: RingBuffer::new(32768), 41 | last_edge: false, 42 | debug_filter: filters::HighPassIIR::new(44100.0, 300.0), 43 | length: 0, 44 | length_halt_flag: false, 45 | 46 | envelope: VolumeEnvelopeState::new(), 47 | length_counter: LengthCounterState::new(), 48 | mode: 0, 49 | period_initial: 0, 50 | period_current: 0, 51 | 52 | // Actually a 15-bit register 53 | shift_register: 1, 54 | } 55 | } 56 | 57 | pub fn clock(&mut self) { 58 | if self.period_current == 0 { 59 | self.period_current = self.period_initial - 1; 60 | 61 | let mut feedback = self.shift_register & 0b1; 62 | if self.mode == 1 { 63 | feedback ^= (self.shift_register >> 6) & 0b1; 64 | } else { 65 | feedback ^= (self.shift_register >> 1) & 0b1; 66 | } 67 | self.shift_register = self.shift_register >> 1; 68 | self.shift_register |= feedback << 14; 69 | self.last_edge = true; 70 | } else { 71 | self.period_current -= 1; 72 | } 73 | } 74 | 75 | pub fn output(&self) -> i16 { 76 | if self.length_counter.length > 0 { 77 | let mut sample = (self.shift_register & 0b1) as i16; 78 | sample *= self.envelope.current_volume() as i16; 79 | return sample; 80 | } else { 81 | return 0; 82 | } 83 | } 84 | } 85 | 86 | impl AudioChannelState for NoiseChannelState { 87 | fn name(&self) -> String { 88 | return self.name.clone(); 89 | } 90 | 91 | fn chip(&self) -> String { 92 | return self.chip.clone(); 93 | } 94 | 95 | fn sample_buffer(&self) -> &RingBuffer { 96 | return &self.output_buffer; 97 | } 98 | 99 | fn edge_buffer(&self) -> &RingBuffer { 100 | return &self.edge_buffer; 101 | } 102 | 103 | fn record_current_output(&mut self) { 104 | self.debug_filter.consume(self.output() as f32); 105 | self.output_buffer.push((self.debug_filter.output() * -4.0) as i16); 106 | self.edge_buffer.push(self.last_edge as i16); 107 | self.last_edge = false; 108 | } 109 | 110 | fn min_sample(&self) -> i16 { 111 | return -60; 112 | } 113 | 114 | fn max_sample(&self) -> i16 { 115 | return 60; 116 | } 117 | 118 | fn muted(&self) -> bool { 119 | return self.debug_disable; 120 | } 121 | 122 | fn mute(&mut self) { 123 | self.debug_disable = true; 124 | } 125 | 126 | fn unmute(&mut self) { 127 | self.debug_disable = false; 128 | } 129 | 130 | fn playing(&self) -> bool { 131 | return 132 | (self.length_counter.length > 0) && 133 | (self.envelope.current_volume() > 0); 134 | } 135 | 136 | fn rate(&self) -> PlaybackRate { 137 | let lsfr_index = match self.period_initial { 138 | 4 => {0xF}, 139 | 8 => {0xE}, 140 | 16 => {0xD}, 141 | 32 => {0xC}, 142 | 64 => {0xB}, 143 | 96 => {0xA}, 144 | 128 => {0x9}, 145 | 160 => {0x8}, 146 | 202 => {0x7}, 147 | 254 => {0x6}, 148 | 380 => {0x5}, 149 | 508 => {0x4}, 150 | 762 => {0x3}, 151 | 1016 => {0x2}, 152 | 2034 => {0x1}, 153 | 4068 => {0x0}, 154 | _ => {0x0} // also unreachable 155 | }; 156 | return PlaybackRate::LfsrRate {index: lsfr_index, max: 0xF}; 157 | } 158 | 159 | fn volume(&self) -> Option { 160 | return Some(Volume::VolumeIndex{ index: self.envelope.current_volume() as usize, max: 15 }); 161 | } 162 | 163 | fn timbre(&self) -> Option { 164 | return Some(Timbre::LsfrMode{index: self.mode as usize, max: 1}); 165 | } 166 | } -------------------------------------------------------------------------------- /src/apu/pulse.rs: -------------------------------------------------------------------------------- 1 | use super::length_counter::LengthCounterState; 2 | use super::volume_envelope::VolumeEnvelopeState; 3 | use super::audio_channel::AudioChannelState; 4 | use super::audio_channel::PlaybackRate; 5 | use super::audio_channel::Volume; 6 | use super::audio_channel::Timbre; 7 | use super::ring_buffer::RingBuffer; 8 | use super::filters; 9 | use super::filters::DspFilter; 10 | 11 | pub struct PulseChannelState { 12 | pub name: String, 13 | pub chip: String, 14 | pub debug_disable: bool, 15 | pub output_buffer: RingBuffer, 16 | pub edge_buffer: RingBuffer, 17 | pub last_edge: bool, 18 | pub debug_filter: filters::HighPassIIR, 19 | pub envelope: VolumeEnvelopeState, 20 | pub length_counter: LengthCounterState, 21 | 22 | // Frequency Sweep 23 | pub sweep_enabled: bool, 24 | pub sweep_period: u8, 25 | pub sweep_divider: u8, 26 | pub sweep_negate: bool, 27 | pub sweep_shift: u8, 28 | pub sweep_reload: bool, 29 | // Variance between Pulse 1 and Pulse 2 causes negation to work slightly differently 30 | pub sweep_ones_compliment: bool, 31 | 32 | pub duty: u8, 33 | pub sequence_counter: u8, 34 | pub period_initial: u16, 35 | pub period_current: u16, 36 | 37 | pub cpu_clock_rate: u64, 38 | } 39 | 40 | impl PulseChannelState { 41 | pub fn new(channel_name: &str, chip_name: &str, cpu_clock_rate: u64, sweep_ones_compliment: bool) -> PulseChannelState { 42 | return PulseChannelState { 43 | name: String::from(channel_name), 44 | chip: String::from(chip_name), 45 | debug_disable: false, 46 | output_buffer: RingBuffer::new(32768), 47 | edge_buffer: RingBuffer::new(32768), 48 | last_edge: false, 49 | debug_filter: filters::HighPassIIR::new(44100.0, 300.0), // for visual flair, and also to remove DC offset 50 | 51 | envelope: VolumeEnvelopeState::new(), 52 | length_counter: LengthCounterState::new(), 53 | 54 | // Frequency Sweep 55 | sweep_enabled: false, 56 | sweep_period: 0, 57 | sweep_divider: 0, 58 | sweep_negate: false, 59 | sweep_shift: 0, 60 | sweep_reload: false, 61 | // Variance between Pulse 1 and Pulse 2 causes negation to work slightly differently 62 | sweep_ones_compliment: sweep_ones_compliment, 63 | 64 | duty: 0b0000_0001, 65 | sequence_counter: 0, 66 | period_initial: 0, 67 | period_current: 0, 68 | cpu_clock_rate: cpu_clock_rate, 69 | } 70 | } 71 | 72 | pub fn clock(&mut self) { 73 | if self.period_current == 0 { 74 | // Reset the period timer, and clock the waveform generator 75 | self.period_current = self.period_initial; 76 | 77 | // The sequence counter starts at zero, but counts downwards, resulting in an odd 78 | // lookup sequence of 0, 7, 6, 5, 4, 3, 2, 1 79 | if self.sequence_counter == 0 { 80 | self.sequence_counter = 7; 81 | self.last_edge = true; 82 | } else { 83 | self.sequence_counter -= 1; 84 | } 85 | } else { 86 | self.period_current -= 1; 87 | } 88 | } 89 | 90 | pub fn output(&self) -> i16 { 91 | if self.length_counter.length > 0 { 92 | let target_period = self.target_period(); 93 | if target_period > 0x7FF || self.period_initial < 8 { 94 | // Sweep unit mutes the channel, because the period is out of range 95 | return 0; 96 | } else { 97 | let mut sample = ((self.duty >> self.sequence_counter) & 0b1) as i16; 98 | sample *= self.envelope.current_volume() as i16; 99 | return sample; 100 | } 101 | } else { 102 | return 0; 103 | } 104 | } 105 | 106 | pub fn target_period(&self) -> u16 { 107 | let change_amount = self.period_initial >> self.sweep_shift; 108 | if self.sweep_negate { 109 | if self.sweep_ones_compliment { 110 | if self.sweep_shift == 0 || self.period_initial == 0 { 111 | // Special case: in one's compliment mode, this would overflow to 112 | // 0xFFFF, but that's not what real hardware appears to do. This solves 113 | // a muting bug with negate-mode sweep on Pulse 1 in some publishers 114 | // games. 115 | return 0; 116 | } 117 | return self.period_initial - change_amount - 1; 118 | } else { 119 | return self.period_initial - change_amount; 120 | } 121 | } else { 122 | return self.period_initial + change_amount; 123 | } 124 | } 125 | 126 | pub fn update_sweep(&mut self) { 127 | let target_period = self.target_period(); 128 | if self.sweep_divider == 0 && self.sweep_enabled && self.sweep_shift != 0 129 | && target_period <= 0x7FF && self.period_initial >= 8 { 130 | self.period_initial = target_period; 131 | } 132 | if self.sweep_divider == 0 || self.sweep_reload { 133 | self.sweep_divider = self.sweep_period; 134 | self.sweep_reload = false; 135 | } else { 136 | self.sweep_divider -= 1; 137 | } 138 | } 139 | } 140 | 141 | impl AudioChannelState for PulseChannelState { 142 | fn name(&self) -> String { 143 | return self.name.clone(); 144 | } 145 | 146 | fn chip(&self) -> String { 147 | return self.chip.clone(); 148 | } 149 | 150 | fn sample_buffer(&self) -> &RingBuffer { 151 | return &self.output_buffer; 152 | } 153 | 154 | fn edge_buffer(&self) -> &RingBuffer { 155 | return &self.edge_buffer; 156 | } 157 | 158 | fn record_current_output(&mut self) { 159 | self.debug_filter.consume(self.output() as f32); 160 | self.output_buffer.push((self.debug_filter.output() * -4.0) as i16); 161 | self.edge_buffer.push(self.last_edge as i16); 162 | self.last_edge = false; 163 | } 164 | 165 | fn min_sample(&self) -> i16 { 166 | return -60; 167 | } 168 | 169 | fn max_sample(&self) -> i16 { 170 | return 60; 171 | } 172 | 173 | fn muted(&self) -> bool { 174 | return self.debug_disable; 175 | } 176 | 177 | fn mute(&mut self) { 178 | self.debug_disable = true; 179 | } 180 | 181 | fn unmute(&mut self) { 182 | self.debug_disable = false; 183 | } 184 | 185 | fn playing(&self) -> bool { 186 | return 187 | (self.length_counter.length > 0) && 188 | (self.target_period() <= 0x7FF) && 189 | (self.period_initial > 8) && 190 | (self.envelope.current_volume() > 0); 191 | } 192 | 193 | fn rate(&self) -> PlaybackRate { 194 | let frequency = self.cpu_clock_rate as f32 / (16.0 * (self.period_initial as f32 + 1.0)); 195 | return PlaybackRate::FundamentalFrequency {frequency: frequency}; 196 | } 197 | 198 | fn volume(&self) -> Option { 199 | return Some(Volume::VolumeIndex{ index: self.envelope.current_volume() as usize, max: 15 }); 200 | } 201 | 202 | fn timbre(&self) -> Option { 203 | return match self.duty { 204 | 0b1000_0000 => Some(Timbre::DutyIndex{ index: 0, max: 3 }), 205 | 0b1100_0000 => Some(Timbre::DutyIndex{ index: 1, max: 3 }), 206 | 0b1111_0000 => Some(Timbre::DutyIndex{ index: 2, max: 3 }), 207 | 0b0011_1111 => Some(Timbre::DutyIndex{ index: 3, max: 3 }), 208 | _ => None 209 | } 210 | } 211 | } -------------------------------------------------------------------------------- /src/apu/ring_buffer.rs: -------------------------------------------------------------------------------- 1 | // Implements a rolling buffer for audio samples, with a fixed length and infinite operation. 2 | // Indices wrap around from the end of this buffer back to the beginning, so no memory allocation 3 | // is needed once it's been constructed. 4 | 5 | // Not intended to be generic, or particularly safe beyond rust's usual guarantees. 6 | 7 | pub struct RingBuffer { 8 | buffer: Vec, 9 | index: usize 10 | } 11 | 12 | impl RingBuffer { 13 | pub fn new(length: usize) -> RingBuffer { 14 | return RingBuffer { 15 | buffer: vec!(0i16; length), 16 | index: 0 17 | }; 18 | } 19 | 20 | pub fn push(&mut self, sample: i16) { 21 | self.buffer[self.index] = sample; 22 | self.index = (self.index + 1) % self.buffer.len(); 23 | } 24 | 25 | pub fn buffer(&self) -> &Vec { 26 | return &self.buffer; 27 | } 28 | 29 | pub fn index(&self) -> usize { 30 | return self.index; 31 | } 32 | 33 | pub fn reset(&mut self) { 34 | self.index = 0; 35 | } 36 | } -------------------------------------------------------------------------------- /src/apu/triangle.rs: -------------------------------------------------------------------------------- 1 | use super::length_counter::LengthCounterState; 2 | use super::audio_channel::AudioChannelState; 3 | use super::audio_channel::PlaybackRate; 4 | use super::audio_channel::Volume; 5 | use super::audio_channel::Timbre; 6 | use super::ring_buffer::RingBuffer; 7 | use super::filters; 8 | use super::filters::DspFilter; 9 | 10 | pub struct TriangleChannelState { 11 | pub name: String, 12 | pub chip: String, 13 | pub debug_disable: bool, 14 | pub output_buffer: RingBuffer, 15 | pub edge_buffer: RingBuffer, 16 | pub last_edge: bool, 17 | pub debug_filter: filters::HighPassIIR, 18 | pub length_counter: LengthCounterState, 19 | 20 | pub control_flag: bool, 21 | pub linear_reload_flag: bool, 22 | pub linear_counter_initial: u8, 23 | pub linear_counter_current: u8, 24 | 25 | pub sequence_counter: u8, 26 | pub period_initial: u16, 27 | pub period_current: u16, 28 | pub length: u8, 29 | 30 | pub cpu_clock_rate: u64, 31 | } 32 | 33 | impl TriangleChannelState { 34 | pub fn new(channel_name: &str, chip_name: &str, cpu_clock_rate: u64) -> TriangleChannelState { 35 | return TriangleChannelState { 36 | name: String::from(channel_name), 37 | chip: String::from(chip_name), 38 | debug_disable: false, 39 | output_buffer: RingBuffer::new(32768), 40 | last_edge: false, 41 | debug_filter: filters::HighPassIIR::new(44100.0, 300.0), 42 | edge_buffer: RingBuffer::new(32768), 43 | length_counter: LengthCounterState::new(), 44 | control_flag: false, 45 | linear_reload_flag: false, 46 | linear_counter_initial: 0, 47 | linear_counter_current: 0, 48 | 49 | sequence_counter: 0, 50 | period_initial: 0, 51 | period_current: 0, 52 | length: 0, 53 | 54 | cpu_clock_rate: cpu_clock_rate, 55 | } 56 | } 57 | 58 | pub fn update_linear_counter(&mut self) { 59 | if self.linear_reload_flag { 60 | self.linear_counter_current = self.linear_counter_initial; 61 | } else { 62 | if self.linear_counter_current > 0 { 63 | self.linear_counter_current -= 1; 64 | } 65 | } 66 | if !(self.control_flag) { 67 | self.linear_reload_flag = false; 68 | } 69 | } 70 | 71 | pub fn clock(&mut self) { 72 | if self.linear_counter_current != 0 && self.length_counter.length > 0 { 73 | if self.period_current == 0 { 74 | // Reset the period timer, and clock the waveform generator 75 | self.period_current = self.period_initial; 76 | 77 | // The sequence counter starts at zero, but counts downwards, resulting in an odd 78 | // lookup sequence of 0, 7, 6, 5, 4, 3, 2, 1 79 | if self.sequence_counter >= 31 { 80 | self.sequence_counter = 0; 81 | self.last_edge = true; 82 | } else { 83 | self.sequence_counter += 1; 84 | } 85 | } else { 86 | self.period_current -= 1; 87 | } 88 | } else { 89 | // When the triangle is disabled, treat every sample as the last edge. This prevents 90 | // holding onto the trailing end of the waveform in the debug display for too long 91 | self.last_edge = true; 92 | } 93 | } 94 | 95 | pub fn output(&self) -> i16 { 96 | if self.period_initial <= 2 { 97 | // This frequency is so high that the hardware mixer can't keep up, and effectively 98 | // receives 7.5. We'll just return 7 here (close enough). Some games use this 99 | // to silence the channel, and returning 7 emulates the resulting clicks and pops. 100 | return 7; 101 | } else { 102 | let triangle_sequence = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, 103 | 15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0]; 104 | return triangle_sequence[self.sequence_counter as usize]; 105 | } 106 | } 107 | } 108 | 109 | impl AudioChannelState for TriangleChannelState { 110 | fn name(&self) -> String { 111 | return self.name.clone(); 112 | } 113 | 114 | fn chip(&self) -> String { 115 | return self.chip.clone(); 116 | } 117 | 118 | fn sample_buffer(&self) -> &RingBuffer { 119 | return &self.output_buffer; 120 | } 121 | 122 | fn edge_buffer(&self) -> &RingBuffer { 123 | return &self.edge_buffer; 124 | } 125 | 126 | fn record_current_output(&mut self) { 127 | self.debug_filter.consume(self.output() as f32); 128 | self.output_buffer.push((self.debug_filter.output() * -4.0) as i16); 129 | self.edge_buffer.push(self.last_edge as i16); 130 | self.last_edge = false; 131 | } 132 | 133 | fn min_sample(&self) -> i16 { 134 | return -60; 135 | } 136 | 137 | fn max_sample(&self) -> i16 { 138 | return 60; 139 | } 140 | 141 | fn muted(&self) -> bool { 142 | return self.debug_disable; 143 | } 144 | 145 | fn mute(&mut self) { 146 | self.debug_disable = true; 147 | } 148 | 149 | fn unmute(&mut self) { 150 | self.debug_disable = false; 151 | } 152 | 153 | fn playing(&self) -> bool { 154 | return 155 | self.length_counter.length > 0 && 156 | self.linear_counter_current != 0 && 157 | self.period_initial > 2; 158 | } 159 | 160 | fn rate(&self) -> PlaybackRate { 161 | let frequency = self.cpu_clock_rate as f32 / (32.0 * (self.period_initial as f32 + 1.0)); 162 | return PlaybackRate::FundamentalFrequency {frequency: frequency}; 163 | } 164 | 165 | fn volume(&self) -> Option { 166 | return None; 167 | } 168 | 169 | fn timbre(&self) -> Option { 170 | return None; 171 | } 172 | 173 | fn amplitude(&self) -> f32 { 174 | if self.playing() { 175 | return 0.55; 176 | } 177 | return 0.0; 178 | } 179 | } -------------------------------------------------------------------------------- /src/apu/volume_envelope.rs: -------------------------------------------------------------------------------- 1 | pub struct VolumeEnvelopeState { 2 | // Volume Envelope 3 | pub volume_register: u8, 4 | pub decay: u8, 5 | pub divider: u8, 6 | pub enabled: bool, 7 | pub looping: bool, 8 | pub start_flag: bool, 9 | } 10 | 11 | impl VolumeEnvelopeState { 12 | pub fn new() -> VolumeEnvelopeState { 13 | return VolumeEnvelopeState { 14 | volume_register: 0, 15 | decay: 0, 16 | divider: 0, 17 | enabled: false, 18 | looping: false, 19 | start_flag: false, 20 | } 21 | } 22 | 23 | pub fn current_volume(&self) -> u8 { 24 | if self.enabled { 25 | return self.decay; 26 | } else { 27 | return self.volume_register; 28 | } 29 | } 30 | 31 | pub fn clock(&mut self) { 32 | if self.start_flag { 33 | self.decay = 15; 34 | self.start_flag = false; 35 | self.divider = self.volume_register; 36 | } else { 37 | // Clock the divider 38 | if self.divider == 0 { 39 | self.divider = self.volume_register; 40 | if self.decay > 0 { 41 | self.decay -= 1; 42 | } else { 43 | if self.looping { 44 | self.decay = 15; 45 | } 46 | } 47 | } else { 48 | self.divider = self.divider - 1; 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /src/cartridge.rs: -------------------------------------------------------------------------------- 1 | use mmc::mapper::*; 2 | use mmc::action53::Action53; 3 | use mmc::axrom::AxRom; 4 | use mmc::bnrom::BnRom; 5 | use mmc::cnrom::CnRom; 6 | use mmc::fme7::Fme7; 7 | use mmc::fds::FdsMapper; 8 | use mmc::gxrom::GxRom; 9 | use mmc::ines31::INes31; 10 | use mmc::mmc1::Mmc1; 11 | use mmc::mmc3::Mmc3; 12 | use mmc::mmc5::Mmc5; 13 | use mmc::n163::Namco163; 14 | use mmc::nrom::Nrom; 15 | use mmc::nsf::NsfMapper; 16 | use mmc::pxrom::PxRom; 17 | use mmc::rainbow::Rainbow; 18 | use mmc::uxrom::UxRom; 19 | use mmc::vrc6::Vrc6; 20 | use mmc::vrc7::Vrc7; 21 | 22 | use ines::INesCartridge; 23 | use nsf::NsfFile; 24 | use fds::FdsFile; 25 | 26 | use std::io::Read; 27 | 28 | fn mapper_from_ines(ines: INesCartridge) -> Result, String> { 29 | let mapper_number = ines.header.mapper_number(); 30 | 31 | let mapper: Box = match mapper_number { 32 | 0 => Box::new(Nrom::from_ines(ines)?), 33 | 1 => Box::new(Mmc1::from_ines(ines)?), 34 | 2 => Box::new(UxRom::from_ines(ines)?), 35 | 3 => Box::new(CnRom::from_ines(ines)?), 36 | 4 => Box::new(Mmc3::from_ines(ines)?), 37 | 5 => Box::new(Mmc5::from_ines(ines)?), 38 | 7 => Box::new(AxRom::from_ines(ines)?), 39 | 9 => Box::new(PxRom::from_ines(ines)?), 40 | 19 => Box::new(Namco163::from_ines(ines)?), 41 | 24 => Box::new(Vrc6::from_ines(ines)?), 42 | 26 => Box::new(Vrc6::from_ines(ines)?), 43 | 28 => Box::new(Action53::from_ines(ines)?), 44 | 31 => Box::new(INes31::from_ines(ines)?), 45 | 34 => Box::new(BnRom::from_ines(ines)?), 46 | 66 => Box::new(GxRom::from_ines(ines)?), 47 | 69 => Box::new(Fme7::from_ines(ines)?), 48 | 85 => Box::new(Vrc7::from_ines(ines)?), 49 | 682 => Box::new(Rainbow::from_ines(ines)?), 50 | _ => { 51 | return Err(format!("Unsupported iNES mapper: {}", ines.header.mapper_number())); 52 | } 53 | }; 54 | 55 | println!("Successfully loaded mapper: {}", mapper_number); 56 | 57 | return Ok(mapper); 58 | } 59 | 60 | pub fn mapper_from_reader(file_reader: &mut dyn Read) -> Result, String> { 61 | let mut entire_file = Vec::new(); 62 | match file_reader.read_to_end(&mut entire_file) { 63 | Ok(_) => {/* proceed normally */}, 64 | Err(e) => { 65 | return Err(format!("Failed to read any data at all, giving up.{}\n", e)); 66 | } 67 | } 68 | 69 | let mut errors = String::new(); 70 | match INesCartridge::from_reader(&mut entire_file.as_slice()) { 71 | Ok(ines) => {return mapper_from_ines(ines);}, 72 | Err(e) => {errors += format!("ines: {}\n", e).as_str()} 73 | } 74 | 75 | match NsfFile::from_reader(&mut entire_file.as_slice()) { 76 | Ok(nsf) => {return Ok(Box::new(NsfMapper::from_nsf(nsf)?));}, 77 | Err(e) => {errors += format!("nsf: {}\n", e).as_str()} 78 | } 79 | 80 | match FdsFile::from_reader(&mut entire_file.as_slice()) { 81 | Ok(nsf) => {return Ok(Box::new(FdsMapper::from_fds(nsf)?));}, 82 | Err(e) => {errors += format!("fds: {}\n", e).as_str()} 83 | } 84 | 85 | return Err(format!("Unable to open file as any known type, giving up.\n{}", errors)); 86 | } 87 | 88 | pub fn mapper_from_file(file_data: &[u8]) -> Result, String> { 89 | let mut file_reader = file_data; 90 | return mapper_from_reader(&mut file_reader); 91 | } -------------------------------------------------------------------------------- /src/cycle_cpu.rs: -------------------------------------------------------------------------------- 1 | // Documentation for this 6502 implementation came from many sources, but the following 2 | // two guides served as the primary inspiration: 3 | // http://www.llx.com/~nparker/a2/opcodes.html - For opcode decoding structure 4 | // http://nesdev.com/6502_cpu.txt - for information on cycle timings for each addressing mode 5 | 6 | use addressing; 7 | use memory::read_byte; 8 | use memory::write_byte; 9 | use nes::NesState; 10 | use opcodes; 11 | use unofficial_opcodes; 12 | 13 | #[derive(Copy, Clone)] 14 | pub struct Flags { 15 | pub carry: bool, 16 | pub zero: bool, 17 | pub decimal: bool, 18 | pub interrupts_disabled: bool, 19 | pub overflow: bool, 20 | pub negative: bool, 21 | 22 | // internal only 23 | pub last_nmi: bool, 24 | } 25 | 26 | #[derive(Copy, Clone)] 27 | pub struct Registers { 28 | pub a: u8, 29 | pub x: u8, 30 | pub y: u8, 31 | pub pc: u16, 32 | pub s: u8, 33 | pub flags: Flags, 34 | } 35 | 36 | impl Registers { 37 | pub fn new() -> Registers { 38 | return Registers { 39 | a: 0, 40 | x: 0, 41 | y: 0, 42 | pc: 0, 43 | s: 0, 44 | flags: Flags { 45 | carry: false, 46 | zero: false, 47 | interrupts_disabled: false, 48 | decimal: false, 49 | overflow: false, 50 | negative: false, 51 | last_nmi: false, 52 | } 53 | } 54 | } 55 | 56 | pub fn status_as_byte(&self, s_flag: bool) -> u8 { 57 | return (self.flags.carry as u8) + 58 | ((self.flags.zero as u8) << 1) + 59 | ((self.flags.interrupts_disabled as u8) << 2) + 60 | ((self.flags.decimal as u8) << 3) + 61 | ((s_flag as u8) << 4) + 62 | ((1u8 ) << 5) + // always set 63 | ((self.flags.overflow as u8) << 6) + 64 | ((self.flags.negative as u8) << 7) 65 | } 66 | 67 | pub fn set_status_from_byte(&mut self, data: u8) { 68 | self.flags.carry = data & (1 << 0) != 0; 69 | self.flags.zero = data & (1 << 1) != 0; 70 | self.flags.interrupts_disabled = data & (1 << 2) != 0; 71 | self.flags.decimal = data & (1 << 3) != 0; 72 | // bits 4 and 5, the s_flag, do not actually exist 73 | self.flags.overflow = data & (1 << 6) != 0; 74 | self.flags.negative = data & (1 << 7) != 0; 75 | } 76 | } 77 | 78 | pub struct CpuState { 79 | pub tick: u8, 80 | pub opcode: u8, 81 | pub data1: u8, 82 | pub data2: u8, 83 | pub temp_address: u16, 84 | pub service_routine_active: bool, 85 | pub nmi_requested: bool, 86 | pub irq_requested: bool, 87 | pub last_nmi: bool, 88 | pub upcoming_write: bool, 89 | 90 | pub oam_dma_active: bool, 91 | pub oam_dma_cycle: u16, 92 | pub oam_dma_address: u16, 93 | 94 | pub old_nmi_requested: bool, 95 | } 96 | 97 | impl CpuState { 98 | pub fn new() -> CpuState{ 99 | return CpuState { 100 | tick: 0, 101 | opcode: 0, 102 | data1: 0, 103 | data2: 0, 104 | temp_address: 0, 105 | service_routine_active: false, 106 | nmi_requested: false, 107 | last_nmi: false, 108 | irq_requested: false, 109 | oam_dma_active: false, 110 | oam_dma_cycle: 0, 111 | oam_dma_address: 0, 112 | upcoming_write: false, 113 | 114 | old_nmi_requested: false, 115 | } 116 | } 117 | } 118 | 119 | 120 | 121 | pub fn nmi_signal(nes: &NesState) -> bool { 122 | return ((nes.ppu.control & 0x80) & (nes.ppu.status & 0x80)) != 0; 123 | } 124 | 125 | pub fn irq_signal(nes: &NesState) -> bool { 126 | if nes.registers.flags.interrupts_disabled { 127 | return false; 128 | } else { 129 | return nes.apu.irq_signal() || nes.mapper.irq_flag(); 130 | } 131 | } 132 | 133 | pub fn poll_for_interrupts(nes: &mut NesState) { 134 | nes.cpu.old_nmi_requested = nes.cpu.nmi_requested; 135 | 136 | let current_nmi = nmi_signal(&nes); 137 | let last_nmi = nes.registers.flags.last_nmi; 138 | nes.registers.flags.last_nmi = current_nmi; 139 | if current_nmi && !last_nmi { 140 | nes.cpu.nmi_requested = true; 141 | } 142 | nes.cpu.irq_requested = irq_signal(&nes); 143 | } 144 | 145 | pub fn interrupt_requested(nes: &NesState) -> bool { 146 | return nes.cpu.old_nmi_requested || nes.cpu.irq_requested; 147 | } 148 | 149 | pub fn halt_cpu(nes: &mut NesState) { 150 | // HALT the CPU. It died, jim. 151 | if nes.cpu.tick < 10 { 152 | println!("STP opcode encountered: {}", nes.cpu.opcode); 153 | println!("Proceeding to lock up CPU. Goodbye, cruel world!"); 154 | } 155 | nes.cpu.tick = 10; 156 | } 157 | 158 | pub fn alu_block(nes: &mut NesState, addressing_mode_index: u8, opcode_index: u8) { 159 | let addressing_mode = match addressing_mode_index { 160 | // Zero Page Mode 161 | 0b000 => &addressing::INDEXED_INDIRECT_X, 162 | 0b001 => &addressing::ZERO_PAGE, 163 | 0b010 => &addressing::IMMEDIATE, 164 | 0b011 => &addressing::ABSOLUTE, 165 | 0b100 => &addressing::INDIRECT_INDEXED_Y, 166 | 0b101 => &addressing::ZERO_PAGE_INDEXED_X, 167 | 0b110 => &addressing::ABSOLUTE_INDEXED_Y, 168 | 0b111 => &addressing::ABSOLUTE_INDEXED_X, 169 | 170 | // Not implemented yet 171 | _ => &addressing::UNIMPLEMENTED, 172 | }; 173 | 174 | match opcode_index { 175 | 0b000 => {(addressing_mode.read)(nes, opcodes::ora)}, 176 | 0b001 => {(addressing_mode.read)(nes, opcodes::and)}, 177 | 0b010 => {(addressing_mode.read)(nes, opcodes::eor)}, 178 | 0b011 => {(addressing_mode.read)(nes, opcodes::adc)}, 179 | 0b100 => {(addressing_mode.write)(nes, opcodes::sta)}, 180 | 0b101 => {(addressing_mode.read)(nes, opcodes::lda)}, 181 | 0b110 => {(addressing_mode.read)(nes, opcodes::cmp)}, 182 | 0b111 => {(addressing_mode.read)(nes, opcodes::sbc)}, 183 | _ => () 184 | }; 185 | } 186 | 187 | pub fn rmw_block(nes: &mut NesState, addressing_mode_index: u8, opcode_index: u8) { 188 | // First, handle some block 10 opcodes that break the mold 189 | match nes.cpu.opcode { 190 | // Assorted NOPs 191 | 0x82 | 0xC2 | 0xE2 => (addressing::IMMEDIATE.read) (nes, opcodes::nop_read), 192 | 0x1A | 0x3A | 0x5A | 0x7A | 0xDA | 0xEA | 0xFA => addressing::implied(nes, opcodes::nop), 193 | // Certain opcodes may be vital to your success. THESE opcodes are not. 194 | 0x02 | 0x22 | 0x42 | 0x62 | 0x12 | 0x32 | 0x52 | 0x72 | 0x92 | 0xB2 | 0xD2 | 0xF2 => { 195 | halt_cpu(nes); 196 | }, 197 | 0xA2 => {(addressing::IMMEDIATE.read)(nes, opcodes::ldx)}, 198 | 0x8A => addressing::implied(nes, opcodes::txa), 199 | 0xAA => addressing::implied(nes, opcodes::tax), 200 | 0xCA => addressing::implied(nes, opcodes::dex), 201 | 0x9A => addressing::implied(nes, opcodes::txs), 202 | 0xBA => addressing::implied(nes, opcodes::tsx), 203 | 0x96 => {(addressing::ZERO_PAGE_INDEXED_Y.write)(nes, opcodes::stx)}, 204 | 0xB6 => {(addressing::ZERO_PAGE_INDEXED_Y.read)(nes, opcodes::ldx)}, 205 | 0xBE => {(addressing::ABSOLUTE_INDEXED_Y.read)(nes, opcodes::ldx)}, 206 | 0x9E => unofficial_opcodes::shx(nes), 207 | _ => { 208 | let addressing_mode = match addressing_mode_index { 209 | // Zero Page Mode 210 | 0b001 => &addressing::ZERO_PAGE, 211 | 0b010 => &addressing::ACCUMULATOR, // Note: masked for 8A, AA, CA, and EA above 212 | 0b011 => &addressing::ABSOLUTE, 213 | 0b101 => &addressing::ZERO_PAGE_INDEXED_X, 214 | 0b111 => &addressing::ABSOLUTE_INDEXED_X, 215 | 216 | // Not implemented yet 217 | _ => &addressing::UNIMPLEMENTED, 218 | }; 219 | 220 | match opcode_index { 221 | 0b000 => {(addressing_mode.modify)(nes, opcodes::asl)}, 222 | 0b001 => {(addressing_mode.modify)(nes, opcodes::rol)}, 223 | 0b010 => {(addressing_mode.modify)(nes, opcodes::lsr)}, 224 | 0b011 => {(addressing_mode.modify)(nes, opcodes::ror)}, 225 | 0b100 => {(addressing_mode.write)(nes, opcodes::stx)}, 226 | 0b101 => {(addressing_mode.read)(nes, opcodes::ldx)}, 227 | 0b110 => {(addressing_mode.modify)(nes, opcodes::dec)}, 228 | 0b111 => {(addressing_mode.modify)(nes, opcodes::inc)}, 229 | _ => () 230 | }; 231 | } 232 | }; 233 | } 234 | 235 | pub fn control_block(nes: &mut NesState) { 236 | // Branch instructions are of the form xxy10000 237 | if (nes.cpu.opcode & 0b1_1111) == 0b1_0000 { 238 | return opcodes::branch(nes); 239 | } 240 | 241 | // Everything else is pretty irregular, so we'll just match the whole opcode 242 | match nes.cpu.opcode { 243 | 0x00 => opcodes::brk(nes), 244 | 245 | // Various unofficial NOPs 246 | 0x80 => 247 | (addressing::IMMEDIATE.read) (nes, opcodes::nop_read), 248 | 0x04 | 0x44 | 0x64 => 249 | (addressing::ZERO_PAGE.read) (nes, opcodes::nop_read), 250 | 0x0C => 251 | (addressing::ABSOLUTE.read) (nes, opcodes::nop_read), 252 | 0x14 | 0x34 | 0x54 | 0x74 | 0xD4 | 0xF4 => 253 | (addressing::ZERO_PAGE_INDEXED_X.read) (nes, opcodes::nop_read), 254 | 0x1C | 0x3C | 0x5C | 0x7C | 0xDC | 0xFC => 255 | (addressing::ABSOLUTE_INDEXED_X.read) (nes, opcodes::nop_read), 256 | 257 | 0x9C => unofficial_opcodes::shy(nes), 258 | 259 | // Opcodes with similar addressing modes 260 | 0xA0 => (addressing::IMMEDIATE.read) (nes, opcodes::ldy), 261 | 0xC0 => (addressing::IMMEDIATE.read) (nes, opcodes::cpy), 262 | 0xE0 => (addressing::IMMEDIATE.read) (nes, opcodes::cpx), 263 | 0x24 => (addressing::ZERO_PAGE.read) (nes, opcodes::bit), 264 | 0x84 => (addressing::ZERO_PAGE.write) (nes, opcodes::sty), 265 | 0xA4 => (addressing::ZERO_PAGE.read) (nes, opcodes::ldy), 266 | 0xC4 => (addressing::ZERO_PAGE.read) (nes, opcodes::cpy), 267 | 0xE4 => (addressing::ZERO_PAGE.read) (nes, opcodes::cpx), 268 | 0x2C => (addressing::ABSOLUTE.read) (nes, opcodes::bit), 269 | 0x8C => (addressing::ABSOLUTE.write) (nes, opcodes::sty), 270 | 0xAC => (addressing::ABSOLUTE.read) (nes, opcodes::ldy), 271 | 0xCC => (addressing::ABSOLUTE.read) (nes, opcodes::cpy), 272 | 0xEC => (addressing::ABSOLUTE.read) (nes, opcodes::cpx), 273 | 0x94 => (addressing::ZERO_PAGE_INDEXED_X.write) (nes, opcodes::sty), 274 | 0xB4 => (addressing::ZERO_PAGE_INDEXED_X.read) (nes, opcodes::ldy), 275 | 0xBC => (addressing::ABSOLUTE_INDEXED_X.read) (nes, opcodes::ldy), 276 | 277 | 0x4C => opcodes::jmp_absolute(nes), 278 | 0x6C => opcodes::jmp_indirect(nes), 279 | 280 | 0x08 => opcodes::php(nes), 281 | 0x28 => opcodes::plp(nes), 282 | 0x48 => opcodes::pha(nes), 283 | 0x68 => opcodes::pla(nes), 284 | 285 | 0x20 => opcodes::jsr(nes), 286 | 0x40 => opcodes::rti(nes), 287 | 0x60 => opcodes::rts(nes), 288 | 289 | 0x88 => addressing::implied(nes, opcodes::dey), 290 | 0xA8 => addressing::implied(nes, opcodes::tay), 291 | 0xC8 => addressing::implied(nes, opcodes::iny), 292 | 0xE8 => addressing::implied(nes, opcodes::inx), 293 | 294 | 0x18 => addressing::implied(nes, opcodes::clc), 295 | 0x58 => addressing::implied(nes, opcodes::cli), 296 | 0xB8 => addressing::implied(nes, opcodes::clv), 297 | 0xD8 => addressing::implied(nes, opcodes::cld), 298 | 0x38 => addressing::implied(nes, opcodes::sec), 299 | 0x78 => addressing::implied(nes, opcodes::sei), 300 | 0xF8 => addressing::implied(nes, opcodes::sed), 301 | 0x98 => addressing::implied(nes, opcodes::tya), 302 | 303 | _ => { 304 | // Unimplemented, fall back on old behavior 305 | println!("Undefined (0x00) opcode: {:02X}", nes.cpu.opcode); 306 | nes.cpu.tick = 0; 307 | } 308 | }; 309 | } 310 | 311 | pub fn unofficial_block(nes: &mut NesState, addressing_mode_index: u8, opcode_index: u8) { 312 | // unofficial opcodes are surprisingly regular, but the following instructions break the 313 | // mold, mostly from the +0B block: 314 | match nes.cpu.opcode { 315 | 0x0B | 0x2B => {(addressing::IMMEDIATE.read)(nes, unofficial_opcodes::anc)}, 316 | 0x4B => {(addressing::IMMEDIATE.read)(nes, unofficial_opcodes::alr)}, 317 | 0x6B => {(addressing::IMMEDIATE.read)(nes, unofficial_opcodes::arr)}, 318 | 0x8B => {(addressing::IMMEDIATE.read)(nes, unofficial_opcodes::xaa)}, 319 | 0x93 => unofficial_opcodes::ahx_indirect_indexed_y(nes), 320 | 0x9B => unofficial_opcodes::tas(nes), 321 | 0x97 => {(addressing::ZERO_PAGE_INDEXED_Y.write)(nes, unofficial_opcodes::sax)}, 322 | 0x9F => unofficial_opcodes::ahx_absolute_indexed_y(nes), 323 | 0xB7 => {(addressing::ZERO_PAGE_INDEXED_Y.read)(nes, unofficial_opcodes::lax)}, 324 | 0xBB => {(addressing::ABSOLUTE_INDEXED_Y.read)(nes, unofficial_opcodes::las)}, 325 | 0xBF => {(addressing::ABSOLUTE_INDEXED_Y.read)(nes, unofficial_opcodes::lax)}, 326 | 0xCB => {(addressing::IMMEDIATE.read)(nes, unofficial_opcodes::axs)}, 327 | 0xEB => {(addressing::IMMEDIATE.read)(nes, opcodes::sbc)}, 328 | _ => { 329 | // The remaining opcodes all use the same addressing mode as the ALU block, and the wame 330 | // read / write / modify type as the corresponding RMW block. Opcodes are mostly a combination 331 | // of the two, with a few exceptions. 332 | 333 | let addressing_mode = match addressing_mode_index { 334 | // Zero Page Mode 335 | 0b000 => &addressing::INDEXED_INDIRECT_X, 336 | 0b001 => &addressing::ZERO_PAGE, 337 | 0b010 => &addressing::IMMEDIATE, 338 | 0b011 => &addressing::ABSOLUTE, 339 | 0b100 => &addressing::INDIRECT_INDEXED_Y, 340 | 0b101 => &addressing::ZERO_PAGE_INDEXED_X, 341 | 0b110 => &addressing::ABSOLUTE_INDEXED_Y, 342 | 0b111 => &addressing::ABSOLUTE_INDEXED_X, 343 | 344 | // Not implemented yet 345 | _ => &addressing::UNIMPLEMENTED, 346 | }; 347 | 348 | match opcode_index { 349 | 0b000 => {(addressing_mode.modify)(nes, unofficial_opcodes::slo)}, 350 | 0b001 => {(addressing_mode.modify)(nes, unofficial_opcodes::rla)}, 351 | 0b010 => {(addressing_mode.modify)(nes, unofficial_opcodes::sre)}, 352 | 0b011 => {(addressing_mode.modify)(nes, unofficial_opcodes::rra)}, 353 | 0b100 => {(addressing_mode.write )(nes, unofficial_opcodes::sax)}, 354 | 0b101 => {(addressing_mode.read )(nes, unofficial_opcodes::lax)}, 355 | 0b110 => {(addressing_mode.modify)(nes, unofficial_opcodes::dcp)}, 356 | 0b111 => {(addressing_mode.modify)(nes, unofficial_opcodes::isc)}, 357 | _ => () 358 | }; 359 | } 360 | } 361 | } 362 | 363 | pub fn advance_oam_dma(nes: &mut NesState) { 364 | if nes.cpu.oam_dma_cycle & 0b1 == 0 && nes.cpu.oam_dma_cycle <= 511 { 365 | let address = nes.cpu.oam_dma_address; 366 | let oam_byte = read_byte(nes, address); 367 | write_byte(nes, 0x2004, oam_byte); 368 | nes.cpu.oam_dma_address += 1; 369 | } 370 | 371 | if nes.cpu.oam_dma_cycle & 0b1 == 0 || nes.apu.dmc.rdy_line == false { 372 | nes.cpu.oam_dma_cycle += 1; 373 | } 374 | 375 | if nes.cpu.oam_dma_cycle > 513 { 376 | nes.cpu.oam_dma_active = false; 377 | } 378 | } 379 | 380 | pub fn run_one_clock(nes: &mut NesState) { 381 | if nes.cpu.oam_dma_active { 382 | advance_oam_dma(nes); 383 | return; 384 | } 385 | 386 | if nes.cpu.upcoming_write == false && nes.apu.dmc.rdy_line == true { 387 | // The DMC DMA is active during an upcoming READ cycle. PAUSE until the rdy_line 388 | // is no longer being asserted by the APU. 389 | return; 390 | } 391 | 392 | nes.cpu.tick += 1; 393 | 394 | // The ordering of these checks may seem a bit strange. The 6502 polls for interrupts 395 | // at the START of each cycle, not at the end. This means that whether an interrupt is 396 | // serviced is determined right before the last cycle of a given instruction, not after 397 | // the last cycle as one might expect. 398 | 399 | if nes.cpu.tick == 1 && interrupt_requested(&nes) { 400 | nes.cpu.service_routine_active = true; 401 | } 402 | 403 | poll_for_interrupts(nes); 404 | 405 | if nes.cpu.service_routine_active { 406 | return opcodes::service_interrupt(nes); 407 | } 408 | 409 | // Universal behavior for every opcode 410 | if nes.cpu.tick == 1 { 411 | // Fetch opcode from memory 412 | let pc = nes.registers.pc; 413 | nes.cpu.opcode = read_byte(nes, pc); 414 | nes.registers.pc = nes.registers.pc.wrapping_add(1); 415 | return; // all done 416 | } 417 | 418 | // Decode this opcode into its component parts 419 | let logic_block = nes.cpu.opcode & 0b0000_0011; 420 | let addressing_mode_index = (nes.cpu.opcode & 0b0001_1100) >> 2; 421 | let opcode_index = (nes.cpu.opcode & 0b1110_0000) >> 5; 422 | 423 | match logic_block { 424 | 0b00 => control_block(nes), 425 | 0b01 => alu_block(nes, addressing_mode_index, opcode_index), 426 | 0b10 => rmw_block(nes, addressing_mode_index, opcode_index), 427 | 0b11 => unofficial_block(nes, addressing_mode_index, opcode_index), 428 | _ => () 429 | } 430 | } -------------------------------------------------------------------------------- /src/fds.rs: -------------------------------------------------------------------------------- 1 | // FDS: an archival format for the Famicom Disk System, detailed here: 2 | // https://www.nesdev.org/wiki/FDS_file_format 3 | 4 | use std::io::Read; 5 | use std::error::Error; 6 | use std::fmt; 7 | 8 | #[derive(Copy, Clone)] 9 | pub struct FdsHeader { 10 | raw_bytes: [u8; 16] 11 | } 12 | 13 | const MSDOS_EOF: u8 = 0x1A; 14 | 15 | const FDS_MAGIC_F: usize = 0x000; 16 | const FDS_MAGIC_D: usize = 0x001; 17 | const FDS_MAGIC_S: usize = 0x002; 18 | const FDS_MAGIC_EOF: usize = 0x003; 19 | const FDS_DISK_SIDES: usize = 0x004; 20 | 21 | impl FdsHeader { 22 | pub fn from(raw_bytes: &[u8]) -> FdsHeader { 23 | let mut header = FdsHeader { 24 | raw_bytes: [0u8; 16], 25 | }; 26 | header.raw_bytes.copy_from_slice(&raw_bytes[0..16]); 27 | return header; 28 | } 29 | 30 | pub fn magic_header_valid(&self) -> bool { 31 | return 32 | self.raw_bytes[FDS_MAGIC_F] as char == 'F' && 33 | self.raw_bytes[FDS_MAGIC_D] as char == 'D' && 34 | self.raw_bytes[FDS_MAGIC_S] as char == 'S' && 35 | self.raw_bytes[FDS_MAGIC_EOF] == MSDOS_EOF; 36 | } 37 | 38 | pub fn num_disk_sides(&self) -> usize { 39 | return self.raw_bytes[FDS_DISK_SIDES] as usize; 40 | } 41 | } 42 | 43 | #[derive(Debug)] 44 | pub enum FdsError { 45 | InvalidHeader, 46 | ReadError{reason: String} 47 | } 48 | 49 | impl Error for FdsError {} 50 | 51 | impl fmt::Display for FdsError { 52 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 53 | match self { 54 | FdsError::InvalidHeader => {write!(f, "Invalid FDS Header")}, 55 | FdsError::ReadError{reason} => {write!(f, "Error reading disk image: {}", reason)} 56 | } 57 | } 58 | } 59 | 60 | impl From for FdsError { 61 | fn from(error: std::io::Error) -> Self { 62 | return FdsError::ReadError{reason: error.to_string()}; 63 | } 64 | } 65 | 66 | #[derive(Clone)] 67 | pub struct FdsFile { 68 | pub header: FdsHeader, 69 | pub disk_sides: Vec>, 70 | } 71 | 72 | impl FdsFile { 73 | pub fn from_reader(file_reader: &mut dyn Read) -> Result { 74 | // Read in the *whole* file at once. We need to try several different headers 75 | // because that's a thing, so we can't assume any particular one is present. 76 | let mut fds_data: Vec = Vec::new(); 77 | file_reader.read_to_end(&mut fds_data)?; 78 | 79 | let mut disk_sides: Vec> = Vec::new(); 80 | 81 | // First try the 16-byte header originating in fwNES 82 | let header = FdsHeader::from(&fds_data[0..16]); 83 | if header.magic_header_valid() { 84 | for i in 0 .. header.num_disk_sides() { 85 | let start = 16 + (i * 65500); 86 | let end = 16 + ((i+1) * 65500); 87 | if end > fds_data.len() { 88 | return Err(FdsError::ReadError{reason: "Unexpected end of file!".to_string()}); 89 | } 90 | let disk_side_bytes = Vec::from(&fds_data[start..end]); 91 | disk_sides.push(disk_side_bytes); 92 | } 93 | return Ok(FdsFile { 94 | header: header, 95 | disk_sides: disk_sides, 96 | }); 97 | } 98 | 99 | // Second, see if the first 15 bytes correspond to the start of info block 1. If they do, this is 100 | // likely a raw dump. Assume disk sides as a multiple of 65500 bytes and complain if we have anything else 101 | let verification_string = "\x01*NINTENDO-HVC*"; 102 | let candidate_string = std::str::from_utf8(&fds_data[0..15]).expect("invalid utf8-sequence"); 103 | if candidate_string.eq(verification_string) { 104 | for i in 0 .. fds_data.len() / 65500 { 105 | let start = i * 65500; 106 | let end = (i+1) * 65500; 107 | if end > fds_data.len() { 108 | return Err(FdsError::ReadError{reason: "Unexpected end of file!".to_string()}); 109 | } 110 | let disk_side_bytes = Vec::from(&fds_data[start..end]); 111 | disk_sides.push(disk_side_bytes); 112 | } 113 | return Ok(FdsFile { 114 | header: header, 115 | disk_sides: disk_sides, 116 | }); 117 | } 118 | 119 | return Err(FdsError::InvalidHeader); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/ines.rs: -------------------------------------------------------------------------------- 1 | // Parses and manages header fields and data blobs for iNES 1.0 and 2.0 2 | // Details here: https://wiki.nesdev.com/w/index.php/INES 3 | // And here: https://wiki.nesdev.com/w/index.php/NES_2.0#Default_Expansion_Device 4 | // iNES 2.0 is always preferred when detected. 5 | 6 | use std::io::Read; 7 | use std::error::Error; 8 | use std::fmt; 9 | 10 | use mmc::mapper::Mirroring; 11 | use memoryblock::MemoryBlock; 12 | use memoryblock::MemoryType; 13 | 14 | #[derive(Debug)] 15 | pub enum INesError { 16 | InvalidHeader, 17 | Unimplemented, 18 | ReadError{reason: String} 19 | } 20 | 21 | impl Error for INesError {} 22 | 23 | impl fmt::Display for INesError { 24 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 25 | match self { 26 | INesError::InvalidHeader => {write!(f, "Invalid iNES Header")}, 27 | INesError::Unimplemented => {write!(f, "Unimplemented (Lazy programmers!!1)")}, 28 | INesError::ReadError{reason} => {write!(f, "Error reading cartridge: {}", reason)} 29 | } 30 | } 31 | } 32 | 33 | impl From for INesError { 34 | fn from(error: std::io::Error) -> Self { 35 | return INesError::ReadError{reason: error.to_string()}; 36 | } 37 | } 38 | 39 | #[derive(Copy, Clone)] 40 | pub struct INesHeader { 41 | raw_bytes: [u8; 16] 42 | } 43 | 44 | // header byte constants 45 | const INES_MAGIC_N: usize = 0; 46 | const INES_MAGIC_E: usize = 1; 47 | const INES_MAGIC_S: usize = 2; 48 | const INES_MAGIC_EOF: usize = 3; 49 | const INES_PRG_ROM_LSB: usize = 4; 50 | const INES_CHR_ROM_LSB: usize = 5; 51 | const INES_FLAGS_6: usize = 6; 52 | const INES_FLAGS_7: usize = 7; 53 | 54 | // here the constants diverge depending on type 55 | const INES1_PRG_RAM_SIZE: usize = 8; 56 | //const INES1_TV_SYSTEM: usize = 9; 57 | //const INES1_FLAGS_10: usize = 10; 58 | 59 | const INES2_MAPPER_SUB_MSB: usize = 8; 60 | const INES2_PRG_CHR_MSB: usize = 9; 61 | const INES2_PRG_RAM: usize = 10; 62 | const INES2_CHR_RAM: usize = 11; 63 | //const INES2_CPU_PPU_TIMING: usize = 12; 64 | //const INES2_SYSTEM_TYPE: usize = 13; 65 | //const INES2_MISC_ROM_COUNT: usize = 14; 66 | //const INES2_DEFAULT_EXPANSION: usize = 15; 67 | 68 | impl INesHeader { 69 | pub fn from(raw_bytes: &[u8]) -> INesHeader { 70 | let mut header = INesHeader { 71 | raw_bytes: [0u8; 16], 72 | }; 73 | header.raw_bytes.copy_from_slice(&raw_bytes[0..16]); 74 | return header; 75 | } 76 | 77 | pub fn magic_header_valid(&self) -> bool { 78 | // Constant $4E $45 $53 $1A ("NES" followed by MS-DOS end-of-file) 79 | return 80 | self.raw_bytes[INES_MAGIC_N] as char == 'N' && 81 | self.raw_bytes[INES_MAGIC_E] as char == 'E' && 82 | self.raw_bytes[INES_MAGIC_S] as char == 'S' && 83 | self.raw_bytes[INES_MAGIC_EOF] == 0x1A; 84 | } 85 | 86 | fn ines1_extended_attributes_valid(&self) -> bool { 87 | // Or in other words, "DiskDude!" is missing: 88 | return self.raw_bytes[12] | self.raw_bytes[13] | self.raw_bytes[14] | self.raw_bytes[15] == 0; 89 | } 90 | 91 | pub fn version(&self) -> u8 { 92 | // A file is a NES 2.0 ROM image file if it begins with "NES" (same as iNES) and, 93 | // additionally, the byte at offset 7 has bit 2 clear and bit 3 set: 94 | if (self.raw_bytes[INES_FLAGS_7] & 0x0C) == 0x08 { 95 | return 2; 96 | } 97 | if self.magic_header_valid() { 98 | return 1; 99 | } 100 | return 0; 101 | } 102 | 103 | fn _prg_size_ines1(&self) -> usize { 104 | return self.raw_bytes[INES_PRG_ROM_LSB] as usize * 16 * 1024; 105 | } 106 | 107 | fn _prg_size_ines2(&self) -> usize { 108 | // https://wiki.nesdev.com/w/index.php/NES_2.0#PRG-ROM_Area 109 | let lsb = self.raw_bytes[INES_PRG_ROM_LSB]; 110 | let msb = self.raw_bytes[INES2_PRG_CHR_MSB] & 0b0000_1111; 111 | if msb == 0xF { 112 | // exponent-multiplier mode 113 | // ++++----------- Header byte 9 D0..D3 114 | // |||| ++++-++++- Header byte 4 115 | // D~BA98 7654 3210 116 | // -------------- 117 | // 1111 EEEE EEMM 118 | // |||| ||++- Multiplier, actual value is MM*2+1 (1,3,5,7) 119 | // ++++-++--- Exponent (2^E), 0-63 120 | 121 | let multiplier = ((lsb & 0b0000_0011) * 2 + 1) as usize; 122 | let exponent = ((lsb & 0b1111_1100) >> 2) as u32; 123 | let base: usize = 2; 124 | return base.pow(exponent) * multiplier; 125 | } else { 126 | // simple mode 127 | return ((msb as usize) << 8) + (lsb as usize) * 16 * 1024; 128 | } 129 | } 130 | 131 | pub fn prg_size(&self) -> usize { 132 | return match self.version() { 133 | 1 => self._prg_size_ines1(), 134 | 2 => self._prg_size_ines2(), 135 | _ => 0 136 | } 137 | } 138 | 139 | fn _chr_rom_size_ines1(&self) -> usize { 140 | let chr_size = self.raw_bytes[INES_CHR_ROM_LSB] as usize * 8 * 1024; 141 | return chr_size; 142 | } 143 | 144 | fn _chr_rom_size_ines2(&self) -> usize { 145 | // https://wiki.nesdev.com/w/index.php/NES_2.0#PRG-ROM_Area 146 | let lsb = self.raw_bytes[INES_CHR_ROM_LSB]; 147 | let msb = (self.raw_bytes[INES2_PRG_CHR_MSB] & 0b1111_0000) >> 4; 148 | if msb == 0xF { 149 | // exponent-multiplier mode 150 | // ++++----------- Header byte 9 D0..D3 151 | // |||| ++++-++++- Header byte 4 152 | // D~BA98 7654 3210 153 | // -------------- 154 | // 1111 EEEE EEMM 155 | // |||| ||++- Multiplier, actual value is MM*2+1 (1,3,5,7) 156 | // ++++-++--- Exponent (2^E), 0-63 157 | 158 | let multiplier = ((lsb & 0b0000_0011) * 2 + 1) as usize; 159 | let exponent = ((lsb & 0b1111_1100) >> 2) as u32; 160 | let base: usize = 2; 161 | return base.pow(exponent) * multiplier; 162 | } else { 163 | // simple mode 164 | return ((msb as usize) << 8) + (lsb as usize) * 8 * 1024; 165 | } 166 | } 167 | 168 | pub fn chr_rom_size(&self) -> usize { 169 | return match self.version() { 170 | 1 => self._chr_rom_size_ines1(), 171 | 2 => self._chr_rom_size_ines2(), 172 | _ => 0 173 | } 174 | } 175 | 176 | fn _chr_ram_size_ines1(&self) -> usize { 177 | if self.raw_bytes[INES_CHR_ROM_LSB] == 0 { 178 | return 8 * 1024; 179 | } 180 | return 0; 181 | } 182 | 183 | fn _chr_ram_size_ines2(&self) -> usize { 184 | let shift_count = self.raw_bytes[INES2_CHR_RAM] & 0b0000_1111; 185 | if shift_count == 0 { 186 | return 0; 187 | } 188 | return 64 << (shift_count as usize); 189 | } 190 | 191 | pub fn chr_ram_size(&self) -> usize { 192 | return match self.version() { 193 | 1 => self._chr_ram_size_ines1(), 194 | 2 => self._chr_ram_size_ines2(), 195 | _ => 0 196 | } 197 | } 198 | 199 | pub fn chr_sram_size(&self) -> usize { 200 | // Note: iNes2.0 calls this NVRAM, we're going with SRAM to match 201 | // RusticNES's conventions, and also user expectation 202 | if self.version() != 2 { 203 | return 0; 204 | } 205 | let shift_count = (self.raw_bytes[INES2_CHR_RAM] & 0b1111_0000) >> 4; 206 | if shift_count == 0 { 207 | return 0; 208 | } 209 | return 64 << (shift_count as usize); 210 | } 211 | 212 | // https://wiki.nesdev.com/w/index.php/INES#Flags_6 213 | 214 | pub fn mirroring(&self) -> Mirroring { 215 | if self.raw_bytes[INES_FLAGS_6] & 0b0000_1000 != 0 { 216 | return Mirroring::FourScreen; 217 | } 218 | if self.raw_bytes[INES_FLAGS_6] & 0b0000_0001 != 0 { 219 | return Mirroring::Vertical; 220 | } 221 | return Mirroring::Horizontal; 222 | } 223 | 224 | pub fn has_sram(&self) -> bool { 225 | return self.raw_bytes[INES_FLAGS_6] & 0b0000_0010 != 0; 226 | } 227 | 228 | fn _prg_ram_size_ines1(&self) -> usize { 229 | let has_sram = self.raw_bytes[INES_FLAGS_6] & 0b0000_0010 != 0; 230 | if has_sram { 231 | return 0; 232 | } 233 | if self.ines1_extended_attributes_valid() && self.raw_bytes[INES1_PRG_RAM_SIZE] != 0 { 234 | return (self.raw_bytes[INES1_PRG_RAM_SIZE] as usize) * 8 * 1024; 235 | } else { 236 | return 8 * 1024; 237 | } 238 | } 239 | 240 | fn _prg_ram_size_ines2(&self) -> usize { 241 | let shift_count = self.raw_bytes[INES2_PRG_RAM] & 0b0000_1111; 242 | if shift_count == 0 { 243 | return 0; 244 | } 245 | return 64 << (shift_count as usize); 246 | } 247 | 248 | pub fn prg_ram_size(&self) -> usize { 249 | return match self.version() { 250 | 1 => self._prg_ram_size_ines1(), 251 | 2 => self._prg_ram_size_ines2(), 252 | _ => 0 253 | } 254 | } 255 | 256 | fn _prg_sram_size_ines1(&self) -> usize { 257 | let has_sram = self.raw_bytes[INES_FLAGS_6] & 0b0000_0010 != 0; 258 | if !has_sram { 259 | return 0; 260 | } 261 | if self.ines1_extended_attributes_valid() && self.raw_bytes[INES1_PRG_RAM_SIZE] != 0 { 262 | return (self.raw_bytes[INES1_PRG_RAM_SIZE] as usize) * 8 * 1024; 263 | } else { 264 | return 8 * 1024; 265 | } 266 | } 267 | 268 | fn _prg_sram_size_ines2(&self) -> usize { 269 | let shift_count = (self.raw_bytes[INES2_PRG_RAM] & 0b1111_0000) >> 4; 270 | if shift_count == 0 { 271 | return 0; 272 | } 273 | return 64 << (shift_count as usize); 274 | } 275 | 276 | pub fn prg_sram_size(&self) -> usize { 277 | return match self.version() { 278 | 1 => self._prg_sram_size_ines1(), 279 | 2 => self._prg_sram_size_ines2(), 280 | _ => 0 281 | } 282 | } 283 | 284 | pub fn has_trainer(&self) -> bool { 285 | return self.raw_bytes[INES_FLAGS_6] & 0b0000_0100 != 0; 286 | } 287 | 288 | fn _mapper_ines1(&self) -> u16 { 289 | let lower_nybble = (self.raw_bytes[INES_FLAGS_6] & 0b1111_0000) >> 4; 290 | let upper_nybble = self.raw_bytes[INES_FLAGS_7] & 0b1111_0000; 291 | // DiskDude! check: are the padding bytes here all zero? 292 | // Documented here: https://wiki.nesdev.com/w/index.php/INES#Flags_10 293 | if self.ines1_extended_attributes_valid() { 294 | // Spec compliant path 295 | let mapper_number = lower_nybble + upper_nybble; 296 | return mapper_number as u16; 297 | } else { 298 | // DiskDude! path 299 | // We probably have a very old ROM and a dumper's 300 | // signature in the padding bytes from 7-15. Since byte 7 was a 301 | // later addition to the spec, in this instance we should not 302 | // trust its contents. 303 | let mapper_number = lower_nybble; 304 | return mapper_number as u16; 305 | } 306 | } 307 | 308 | fn _mapper_ines2(&self) -> u16 { 309 | let lower_nybble = ((self.raw_bytes[INES_FLAGS_6] & 0b1111_0000) >> 4) as u16; 310 | let middle_nybble = (self.raw_bytes[INES_FLAGS_7] & 0b1111_0000) as u16; 311 | let upper_nybble = ((self.raw_bytes[INES2_MAPPER_SUB_MSB] & 0b0000_1111) as u16) << 8; 312 | return upper_nybble | middle_nybble | lower_nybble; 313 | } 314 | 315 | pub fn mapper_number(&self) -> u16 { 316 | match self.version() { 317 | 1 => self._mapper_ines1(), 318 | 2 => self._mapper_ines2(), 319 | _ => 0 320 | } 321 | } 322 | 323 | pub fn submapper_number(&self) -> u8 { 324 | match self.version() { 325 | 1 => 0, 326 | 2 => (self.raw_bytes[INES2_MAPPER_SUB_MSB] & 0b1111_0000) >> 4, 327 | _ => 0 328 | } 329 | } 330 | } 331 | 332 | #[derive(Clone)] 333 | pub struct INesCartridge { 334 | // Internal strategy is to store each major chunk of the file as 335 | // raw data bytes, and then reinterpret these on the fly based 336 | // on the header bytes when accessed. 337 | pub header: INesHeader, 338 | pub trainer: Vec, 339 | pub prg: Vec, 340 | pub chr: Vec, 341 | pub misc_rom: Vec, 342 | } 343 | 344 | impl INesCartridge { 345 | pub fn from_reader(file_reader: &mut dyn Read) -> Result { 346 | let mut header_bytes = [0u8; 16]; 347 | file_reader.read_exact(&mut header_bytes)?; 348 | 349 | let header = INesHeader::from(&header_bytes); 350 | if !header.magic_header_valid() { 351 | return Err(INesError::InvalidHeader); 352 | } 353 | 354 | let trainer_size = if header.has_trainer() {512} else {0}; 355 | let mut trainer: Vec = Vec::new(); 356 | trainer.resize(trainer_size, 0); 357 | file_reader.read_exact(&mut trainer)?; 358 | 359 | let mut prg: Vec = Vec::new(); 360 | prg.resize(header.prg_size(), 0); 361 | file_reader.read_exact(&mut prg)?; 362 | if prg.len() == 0 { 363 | return Err(INesError::ReadError{reason: format!("PRG ROM size is {}. This file is invalid, or at the very least quite unusual. Aborting.", prg.len())}); 364 | } 365 | 366 | let mut chr: Vec = Vec::new(); 367 | chr.resize(header.chr_rom_size(), 0); 368 | file_reader.read_exact(&mut chr)?; 369 | println!("chr rom size: {}", chr.len()); 370 | 371 | // If there is any remaining data at this point, it becomes misc_rom and, 372 | // currently, has no other special handling 373 | let mut misc: Vec = Vec::new(); 374 | file_reader.read_to_end(&mut misc)?; 375 | println!("misc_size: {}", misc.len()); 376 | 377 | return Ok(INesCartridge { 378 | header: header, 379 | trainer: trainer, 380 | prg: prg, 381 | chr: chr, 382 | misc_rom: misc 383 | }); 384 | } 385 | 386 | pub fn prg_rom_block(&self) -> MemoryBlock { 387 | return MemoryBlock::new(&self.prg, MemoryType::Rom); 388 | } 389 | 390 | pub fn prg_ram_blocks(&self) -> Vec { 391 | let mut blocks: Vec = Vec::new(); 392 | if self.header.prg_ram_size() > 0 { 393 | let mut prg_ram: Vec = Vec::new(); 394 | prg_ram.resize(self.header.prg_ram_size(), 0); 395 | blocks.push(MemoryBlock::new(&prg_ram, MemoryType::Ram)); 396 | } 397 | if self.header.prg_sram_size() > 0 { 398 | let mut prg_sram: Vec = Vec::new(); 399 | prg_sram.resize(self.header.prg_sram_size(), 0); 400 | blocks.push(MemoryBlock::new(&prg_sram, MemoryType::NvRam)); 401 | } 402 | if blocks.len() == 0 { 403 | // Always include at least one entry in this list; in this case, a 404 | // single empty block. 405 | blocks.push(MemoryBlock::new(&Vec::new(), MemoryType::Rom)); 406 | } 407 | return blocks; 408 | } 409 | 410 | pub fn chr_blocks(&self) -> Vec { 411 | let mut blocks: Vec = Vec::new(); 412 | if self.chr.len() > 0 { 413 | blocks.push(MemoryBlock::new(&self.chr, MemoryType::Rom)); 414 | } 415 | if self.header.chr_ram_size() > 0 { 416 | let mut chr_ram: Vec = Vec::new(); 417 | chr_ram.resize(self.header.chr_ram_size(), 0); 418 | blocks.push(MemoryBlock::new(&chr_ram, MemoryType::Ram)); 419 | } 420 | if self.header.chr_sram_size() > 0 { 421 | let mut chr_sram: Vec = Vec::new(); 422 | chr_sram.resize(self.header.chr_sram_size(), 0); 423 | blocks.push(MemoryBlock::new(&chr_sram, MemoryType::NvRam)); 424 | } 425 | if blocks.len() == 0 { 426 | // Always include at least one entry in this list; in this case, a 427 | // single empty block. 428 | blocks.push(MemoryBlock::new(&Vec::new(), MemoryType::Rom)); 429 | } 430 | return blocks; 431 | } 432 | 433 | pub fn prg_ram_block(&self) -> Result { 434 | let blocks = self.prg_ram_blocks(); 435 | if blocks.len() != 1 { 436 | return Err(format!("Unsupported mixed PRG RAM types for mapper number {}", self.header.mapper_number())); 437 | } 438 | return Ok(blocks[0].clone()); 439 | } 440 | 441 | pub fn chr_block(&self) -> Result { 442 | let blocks = self.chr_blocks(); 443 | if blocks.len() != 1 { 444 | return Err(format!("Unsupported mixed CHR types for mapper number {}", self.header.mapper_number())); 445 | } 446 | return Ok(blocks[0].clone()); 447 | } 448 | } 449 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod addressing; 2 | pub mod apu; 3 | pub mod asm; 4 | pub mod cartridge; 5 | pub mod cycle_cpu; 6 | pub mod fds; 7 | pub mod tracked_events; 8 | pub mod ines; 9 | pub mod memory; 10 | pub mod memoryblock; 11 | pub mod mmc; 12 | pub mod nes; 13 | pub mod nsf; 14 | pub mod opcodes; 15 | pub mod opcode_info; 16 | pub mod palettes; 17 | pub mod ppu; 18 | pub mod unofficial_opcodes; -------------------------------------------------------------------------------- /src/memory.rs: -------------------------------------------------------------------------------- 1 | use nes::NesState; 2 | 3 | pub struct CpuMemory { 4 | pub iram_raw: Vec, 5 | 6 | pub recent_reads: Vec, 7 | pub recent_writes: Vec, 8 | pub open_bus: u8 9 | } 10 | 11 | impl CpuMemory { 12 | pub fn new() -> CpuMemory { 13 | return CpuMemory { 14 | iram_raw: vec!(0u8; 0x800), 15 | recent_reads: Vec::new(), 16 | recent_writes: Vec::new(), 17 | open_bus: 0, 18 | } 19 | } 20 | } 21 | 22 | pub fn debug_read_byte(nes: &NesState, address: u16) -> u8 { 23 | // Handle a few special cases for debug reads 24 | match address { 25 | 0x2000 ..= 0x3FFF => { 26 | let ppu_reg = address & 0x7; 27 | match ppu_reg { 28 | 7 => { 29 | let ppu_addr = nes.ppu.current_vram_address; 30 | // Note: does not simulate the data / palette fetch quirk. 31 | return nes.ppu.debug_read_byte(& *nes.mapper, ppu_addr); 32 | }, 33 | _ => {} 34 | } 35 | }, 36 | 0x4015 => { 37 | return nes.apu.debug_read_register(address); 38 | }, 39 | _ => {} 40 | } 41 | 42 | let mapped_byte = nes.mapper.debug_read_cpu(address).unwrap_or(nes.memory.open_bus); 43 | return _read_byte(nes, address, mapped_byte); 44 | } 45 | 46 | pub fn read_byte(nes: &mut NesState, address: u16) -> u8 { 47 | let mapped_byte = nes.mapper.read_cpu(address).unwrap_or(nes.memory.open_bus); 48 | 49 | // This is a live read, handle any side effects 50 | match address { 51 | 0x2000 ..= 0x3FFF => { 52 | let ppu_reg = address & 0x7; 53 | match ppu_reg { 54 | // PPUSTATUS 55 | 2 => { 56 | nes.ppu.write_toggle = false; 57 | nes.ppu.latch = (nes.ppu.status & 0xE0) + (nes.ppu.latch & 0x1F); 58 | nes.ppu.status = nes.ppu.status & 0x7F; // Clear VBlank bit 59 | nes.event_tracker.snoop_cpu_read(nes.registers.pc, address, nes.ppu.latch); 60 | return nes.ppu.latch; 61 | }, 62 | // OAMDATA 63 | 4 => { 64 | nes.ppu.latch = nes.ppu.oam[nes.ppu.oam_addr as usize]; 65 | nes.event_tracker.snoop_cpu_read(nes.registers.pc, address, nes.ppu.latch); 66 | }, 67 | // PPUDATA 68 | 7 => { 69 | let ppu_addr = nes.ppu.current_vram_address; 70 | nes.ppu.latch = nes.ppu.read_latched_byte(&mut *nes.mapper, ppu_addr); 71 | if nes.ppu.rendering_enabled() && 72 | (nes.ppu.current_scanline == 261 || 73 | nes.ppu.current_scanline <= 239) { 74 | // Glitchy increment, a fine y and a coarse x 75 | nes.ppu.increment_coarse_x(); 76 | nes.ppu.increment_fine_y(); 77 | } else { 78 | // Normal incrementing behavior based on PPUCTRL 79 | if nes.ppu.control & 0x04 == 0 { 80 | nes.ppu.current_vram_address += 1; 81 | } else { 82 | nes.ppu.current_vram_address += 32; 83 | } 84 | nes.ppu.current_vram_address &= 0b0111_1111_1111_1111; 85 | } 86 | // Perform a dummy access immediately, to simulte the behavior of the PPU 87 | // address lines changing, so the mapper can react accordingly 88 | let address = nes.ppu.current_vram_address; 89 | nes.mapper.access_ppu(address); 90 | nes.event_tracker.snoop_cpu_read(nes.registers.pc, address, nes.ppu.latch); 91 | }, 92 | _ => {} 93 | } 94 | }, 95 | 0x4015 => { 96 | let apu_byte = nes.apu.read_register(address); 97 | nes.event_tracker.snoop_cpu_read(nes.registers.pc, address, apu_byte); 98 | return apu_byte; 99 | }, 100 | 0x4016 => { 101 | if nes.input_latch { 102 | // strobe register is high, so copy input data to latch (probably bad if this 103 | // actually occurs here, but it matches what real hardware would do) 104 | nes.p1_data = nes.p1_input; 105 | } 106 | let result = 0x40 | (nes.p1_data & 0x1); 107 | // Standard Controllers set extra bits to 1, which affects controller detection routines 108 | nes.p1_data = (nes.p1_data >> 1) | 0x80; 109 | nes.event_tracker.snoop_cpu_read(nes.registers.pc, address, result); 110 | return result; 111 | }, 112 | 0x4017 => { 113 | if nes.input_latch { 114 | // strobe register is high, so copy input data to latch (probably bad if this 115 | // actually occurs here, but it matches what real hardware would do) 116 | nes.p2_data = nes.p2_input; 117 | } 118 | let result = 0x40 | (nes.p2_data & 0x1); 119 | // Standard Controllers set extra bits to 1, which affects controller detection routines 120 | nes.p2_data = (nes.p2_data >> 1) | 0x80; 121 | nes.event_tracker.snoop_cpu_read(nes.registers.pc, address, result); 122 | return result; 123 | }, 124 | _ => {} 125 | } 126 | 127 | let byte = _read_byte(nes, address, mapped_byte); 128 | nes.memory.open_bus = byte; 129 | nes.event_tracker.snoop_cpu_read(nes.registers.pc, address, byte); 130 | return byte; 131 | } 132 | 133 | fn _read_byte(nes: &NesState, address: u16, mapped_byte: u8) -> u8 { 134 | match address { 135 | 0x0000 ..= 0x1FFF => { 136 | return nes.memory.iram_raw[(address & 0x7FF) as usize]; 137 | }, 138 | 0x2000 ..= 0x3FFF => { 139 | // PPU 140 | let ppu_reg = address & 0x7; 141 | match ppu_reg { 142 | // PPUCTRL, PPUMASK, OAMADDR | PPUSCROLL | PPUADDR (Write Only) 143 | 0 | 1 | 3 | 5 | 6 => { 144 | return nes.ppu.latch; 145 | }, 146 | // PPUSTATUS 147 | 2 => { 148 | return (nes.ppu.status & 0xE0) + (nes.ppu.latch & 0x1F); 149 | }, 150 | // OAMDATA 151 | 4 => { 152 | return nes.ppu.oam[nes.ppu.oam_addr as usize]; 153 | }, 154 | // PPUDATA 155 | 7 => { 156 | return nes.ppu.latch; 157 | }, 158 | _ => return 0 159 | } 160 | }, 161 | 0x4011 => { 162 | return mapped_byte; 163 | }, 164 | 0x4016 => { 165 | let result = 0x40 | (nes.p1_data & 0x1); 166 | return result; 167 | }, 168 | 0x4017 => { 169 | let result = 0x40 | (nes.p2_data & 0x1); 170 | return result; 171 | }, 172 | 0x4020 ..= 0xFFFF => { 173 | return mapped_byte; 174 | }, 175 | _ => { 176 | return nes.memory.open_bus; 177 | } 178 | } 179 | } 180 | 181 | pub fn write_byte(nes: &mut NesState, address: u16, data: u8) { 182 | // Track every byte written, unconditionally 183 | // (filtering is done inside the tracker) 184 | nes.event_tracker.snoop_cpu_write(nes.registers.pc, address, data); 185 | 186 | // The mapper *always* sees the write. Even to RAM, and even to internal registers. 187 | // Most mappers ignore writes to addresses below 0x6000. Some (notably MMC5) do not. 188 | nes.mapper.write_cpu(address, data); 189 | match address { 190 | 0x0000 ..= 0x1FFF => nes.memory.iram_raw[(address & 0x7FF) as usize] = data, 191 | 0x2000 ..= 0x3FFF => { 192 | // PPU 193 | let ppu_reg = address & 0x7; 194 | nes.ppu.latch = data; 195 | match ppu_reg { 196 | // PPUCTRL 197 | 0 => { 198 | nes.ppu.control = data; 199 | // Shift the nametable select bits into the temporary vram address 200 | // yyy_nn_YYYYY_XXXXX 201 | nes.ppu.temporary_vram_address &= 0b111_00_11111_11111; 202 | nes.ppu.temporary_vram_address |= (data as u16 & 0b11) << 10; 203 | }, 204 | // PPU MASK 205 | 1 => { 206 | nes.ppu.mask = data; 207 | }, 208 | // PPUSTATUS is read-only 209 | // OAM ADDRESS 210 | 3 => { 211 | nes.ppu.oam_addr = data; 212 | }, 213 | // OAMDATA 214 | 4 => { 215 | nes.ppu.oam[nes.ppu.oam_addr as usize] = data; 216 | nes.ppu.oam_addr = nes.ppu.oam_addr.wrapping_add(1); 217 | }, 218 | // PPU SCROLL 219 | 5 => { 220 | if nes.ppu.write_toggle { 221 | // Set coarse Y and fine y into temporary address 222 | // yyy_nn_YYYYY_XXXXX 223 | nes.ppu.temporary_vram_address &= 0b000_11_00000_11111; 224 | nes.ppu.temporary_vram_address |= ((data as u16) & 0b1111_1000) << 2; 225 | nes.ppu.temporary_vram_address |= ((data as u16) & 0b111) << 12; 226 | 227 | nes.ppu.write_toggle = false; 228 | } else { 229 | // Set coarse X into temporary address 230 | // yyy_nn_YYYYY_XXXXX 231 | nes.ppu.temporary_vram_address &= 0b111_11_11111_00000; 232 | nes.ppu.temporary_vram_address |= (data as u16) >> 3; 233 | // Set fine X immediately 234 | nes.ppu.fine_x = data & 0b111; 235 | 236 | nes.ppu.write_toggle = true; 237 | } 238 | }, 239 | // PPU ADDR 240 | 6 => { 241 | if nes.ppu.write_toggle { 242 | nes.ppu.temporary_vram_address &= 0b0111_1111_0000_0000; 243 | nes.ppu.temporary_vram_address |= data as u16; 244 | // Apply the final vram address immediately 245 | nes.ppu.current_vram_address = nes.ppu.temporary_vram_address; 246 | nes.ppu.write_toggle = false; 247 | 248 | // Perform a dummy access immediately, to simulte the behavior of the PPU 249 | // address lines changing, so the mapper can react accordingly 250 | let address = nes.ppu.current_vram_address; 251 | nes.mapper.access_ppu(address); 252 | } else { 253 | nes.ppu.temporary_vram_address &= 0b0000_0000_1111_1111; 254 | // Note: This is missing bit 14 on purpose! This is cleared by the real PPU during 255 | // the write to PPU ADDR for reasons unknown. 256 | nes.ppu.temporary_vram_address |= ((data as u16) & 0b0011_1111) << 8; 257 | nes.ppu.write_toggle = true; 258 | } 259 | 260 | }, 261 | // PPUDATA 262 | 7 => { 263 | let ppu_addr = nes.ppu.current_vram_address; 264 | if nes.ppu.rendering_enabled() && 265 | (nes.ppu.current_scanline == 261 || 266 | nes.ppu.current_scanline <= 239) { 267 | // Glitchy increment, a fine y and a coarse x 268 | nes.ppu.increment_coarse_x(); 269 | nes.ppu.increment_fine_y(); 270 | } else { 271 | // Normal incrementing behavior based on PPUCTRL 272 | if nes.ppu.control & 0x04 == 0 { 273 | nes.ppu.current_vram_address += 1; 274 | } else { 275 | nes.ppu.current_vram_address += 32; 276 | } 277 | nes.ppu.current_vram_address &= 0b0111_1111_1111_1111; 278 | } 279 | nes.ppu.write_byte(&mut *nes.mapper, ppu_addr, data); 280 | 281 | // Perform a dummy access immediately, to simulte the behavior of the PPU 282 | // address lines changing, so the mapper can react accordingly 283 | let address = nes.ppu.current_vram_address; 284 | nes.mapper.access_ppu(address); 285 | }, 286 | _ => () 287 | } 288 | }, 289 | 0x4000 ..= 0x4013 => { 290 | nes.apu.write_register(address, data); 291 | }, 292 | 0x4014 => { 293 | // OAM DMA, for cheating just do this instantly and return 294 | // OR NOT! 295 | //let read_address = (data as u16) << 8; 296 | //for i in 0 .. 256 { 297 | // let byte = read_byte(nes, read_address + i); 298 | // nes.ppu.oam[i as usize] = byte; 299 | //} 300 | nes.cpu.oam_dma_address = (data as u16) << 8; 301 | nes.cpu.oam_dma_cycle = 0; 302 | nes.cpu.oam_dma_active = true; 303 | }, 304 | 0x4015 => { 305 | nes.apu.write_register(address, data); 306 | }, 307 | 0x4016 => { 308 | // Input latch 309 | nes.input_latch = data & 0x1 != 0; 310 | if nes.input_latch { 311 | nes.p1_data = nes.p1_input; 312 | nes.p2_data = nes.p2_input; 313 | } 314 | }, 315 | 0x4017 => { 316 | nes.apu.write_register(address, data); 317 | }, 318 | _ => () // Do nothing! 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /src/memoryblock.rs: -------------------------------------------------------------------------------- 1 | /// Represents one contiguous block of memory, typically residing on a single 2 | /// physical chip. Implementations have varying behavior, but provide one 3 | /// consistent guarantee: all memory access will return some value, possibly 4 | /// open bus. This helps the trait to correctly represent missing 5 | /// chips, wrapping behavior, mirroring, bank switching, etc. 6 | #[derive(Clone)] 7 | pub struct MemoryBlock { 8 | bytes: Vec, 9 | readonly: bool, 10 | volatile: bool 11 | } 12 | 13 | #[derive(PartialEq)] 14 | pub enum MemoryType { 15 | Rom, 16 | Ram, 17 | NvRam, 18 | } 19 | 20 | impl MemoryBlock { 21 | pub fn new(data: &[u8], memory_type: MemoryType) -> MemoryBlock { 22 | return MemoryBlock { 23 | bytes: data.to_vec(), 24 | readonly: memory_type == MemoryType::Rom, 25 | volatile: memory_type != MemoryType::NvRam, 26 | } 27 | } 28 | 29 | pub fn len(&self) -> usize { 30 | return self.bytes.len(); 31 | } 32 | 33 | pub fn is_volatile(&self) -> bool { 34 | return self.volatile; 35 | } 36 | 37 | pub fn is_readonly(&self) -> bool { 38 | return self.readonly; 39 | } 40 | 41 | pub fn bounded_read(&self, address: usize) -> Option { 42 | if address >= self.len() { 43 | return None; 44 | } 45 | return Some(self.bytes[address]); 46 | } 47 | 48 | pub fn bounded_write(&mut self, address: usize, data: u8) { 49 | if address >= self.len() || self.readonly { 50 | return; 51 | } 52 | self.bytes[address] = data; 53 | } 54 | 55 | pub fn wrapping_read(&self, address: usize) -> Option { 56 | if self.bytes.len() == 0 { 57 | return None; 58 | } 59 | return Some(self.bytes[address % self.len()]); 60 | } 61 | 62 | pub fn wrapping_write(&mut self, address: usize, data: u8) { 63 | if self.bytes.len() == 0 || self.readonly { 64 | return; 65 | } 66 | let len = self.len(); 67 | self.bytes[address % len] = data; 68 | } 69 | 70 | pub fn banked_read(&self, bank_size: usize, bank_index: usize, offset: usize) -> Option { 71 | let effective_address = (bank_size * bank_index) + (offset % bank_size); 72 | return self.wrapping_read(effective_address); 73 | } 74 | 75 | pub fn banked_write(&mut self, bank_size: usize, bank_index: usize, offset: usize, data: u8) { 76 | let effective_address = (bank_size * bank_index) + (offset % bank_size); 77 | self.wrapping_write(effective_address, data); 78 | } 79 | 80 | pub fn as_vec(&self) -> &Vec { 81 | return &self.bytes; 82 | } 83 | 84 | pub fn as_mut_vec(&mut self) -> &mut Vec { 85 | return &mut self.bytes; 86 | } 87 | } 88 | 89 | -------------------------------------------------------------------------------- /src/mmc/action53.rs: -------------------------------------------------------------------------------- 1 | // A very simple Mapper with no esoteric features or bank switching. 2 | // Reference capabilities: https://wiki.nesdev.com/w/index.php/NROM 3 | 4 | use ines::INesCartridge; 5 | use memoryblock::MemoryBlock; 6 | 7 | use mmc::mapper::*; 8 | use mmc::mirroring; 9 | 10 | pub struct Action53 { 11 | prg_rom: MemoryBlock, 12 | prg_ram: MemoryBlock, 13 | chr: MemoryBlock, 14 | vram: Vec, 15 | 16 | register_select: u8, 17 | mirroring_mode: u8, 18 | chr_ram_a13_a14: usize, 19 | prg_inner_bank: usize, 20 | prg_outer_bank: usize, 21 | prg_mode: u8, 22 | prg_outer_bank_size: usize, 23 | } 24 | 25 | impl Action53 { 26 | pub fn from_ines(ines: INesCartridge) -> Result { 27 | let prg_rom_block = ines.prg_rom_block(); 28 | let prg_ram_block = ines.prg_ram_block()?; 29 | let chr_block = ines.chr_block()?; 30 | 31 | return Ok(Action53 { 32 | prg_rom: prg_rom_block.clone(), 33 | prg_ram: prg_ram_block.clone(), 34 | chr: chr_block.clone(), 35 | vram: vec![0u8; 0x1000], 36 | register_select: 0, 37 | mirroring_mode: 0, 38 | chr_ram_a13_a14: 0, 39 | prg_inner_bank: 0xFF, 40 | prg_outer_bank: 0xFF, 41 | prg_mode: 0, 42 | prg_outer_bank_size: 0, 43 | }); 44 | } 45 | 46 | fn prg_address(&self, cpu_address: u16) -> usize { 47 | let cpu_a14 = ((cpu_address & 0b0100_0000_0000_0000) >> 14) as usize; 48 | let bits_a22_to_a14: usize = match self.prg_mode { 49 | 0 | 1 => match self.prg_outer_bank_size { 50 | 0 => (self.prg_outer_bank << 1) | cpu_a14, 51 | 1 => ((self.prg_outer_bank & 0b1111_1110) << 1) | ((self.prg_inner_bank & 0b0000_0001) << 1) | cpu_a14, 52 | 2 => ((self.prg_outer_bank & 0b1111_1100) << 1) | ((self.prg_inner_bank & 0b0000_0011) << 1) | cpu_a14, 53 | 3 => ((self.prg_outer_bank & 0b1111_1000) << 1) | ((self.prg_inner_bank & 0b0000_0111) << 1) | cpu_a14, 54 | _ => 0 // unreachable 55 | }, 56 | 2 => match cpu_a14 { 57 | // $8000 58 | 0 => self.prg_outer_bank << 1, 59 | // $C000 60 | 1 => { 61 | let outer_bitmask = (0b1_1111_1110 << self.prg_outer_bank_size) & 0b1_1111_1110; 62 | let inner_bitmask = 0b0_0000_1111 >> (3 - self.prg_outer_bank_size); 63 | ((self.prg_outer_bank << 1) & outer_bitmask) | (self.prg_inner_bank & inner_bitmask) 64 | }, 65 | _ => 0 // unreachable 66 | }, 67 | 3 => match cpu_a14 { 68 | // $8000 69 | 0 => { 70 | let outer_bitmask = (0b1_1111_1110 << self.prg_outer_bank_size) & 0b1_1111_1110; 71 | let inner_bitmask = 0b0_0000_1111 >> (3 - self.prg_outer_bank_size); 72 | ((self.prg_outer_bank << 1) & outer_bitmask) | (self.prg_inner_bank & inner_bitmask) 73 | } 74 | // $C000 75 | 1 => self.prg_outer_bank << 1 | 1, 76 | _ => 0 // unreachable 77 | }, 78 | _ => 0 // unreachable 79 | }; 80 | return (bits_a22_to_a14 << 14) | ((cpu_address & 0b0011_1111_1111_1111) as usize); 81 | } 82 | 83 | fn chr_address(&self, ppu_address: u16) -> usize { 84 | return (self.chr_ram_a13_a14 << 13) | ((ppu_address & 0b1_1111_1111_1111) as usize); 85 | } 86 | } 87 | 88 | impl Mapper for Action53 { 89 | fn mirroring(&self) -> Mirroring { 90 | match self.mirroring_mode { 91 | 0 => Mirroring::OneScreenLower, 92 | 1 => Mirroring::OneScreenUpper, 93 | 2 => Mirroring::Vertical, 94 | 3 => Mirroring::Horizontal, 95 | _ => Mirroring::Horizontal // unreachable 96 | } 97 | } 98 | 99 | fn debug_read_cpu(&self, address: u16) -> Option { 100 | match address { 101 | 0x6000 ..= 0x7FFF => {self.prg_ram.wrapping_read((address - 0x6000) as usize)}, 102 | 0x8000 ..= 0xFFFF => { 103 | self.prg_rom.wrapping_read(self.prg_address(address)) 104 | }, 105 | _ => None 106 | } 107 | } 108 | 109 | fn write_cpu(&mut self, address: u16, data: u8) { 110 | match address { 111 | 0x5000 ..= 0x5FFF => {self.register_select = data & 0x81;}, 112 | 0x6000 ..= 0x7FFF => {self.prg_ram.wrapping_write((address - 0x6000) as usize, data);}, 113 | 0x8000 ..= 0xFFFF => { 114 | match self.register_select { 115 | 0x00 => { 116 | if (self.mirroring_mode & 0b10) == 0 { 117 | let mirroring_mode_bit_0: u8 = (data & 0b0001_0000) >> 4; 118 | self.mirroring_mode = (self.mirroring_mode & 0b10) | mirroring_mode_bit_0; 119 | } 120 | self.chr_ram_a13_a14 = (data & 0b0000_0011) as usize; 121 | }, 122 | 0x01 => { 123 | if (self.mirroring_mode & 0b10) == 0 { 124 | let mirroring_mode_bit_0: u8 = (data & 0b0001_0000) >> 4; 125 | self.mirroring_mode = (self.mirroring_mode & 0b10) | mirroring_mode_bit_0; 126 | } 127 | self.prg_inner_bank = (data & 0b0000_1111) as usize; 128 | }, 129 | 0x80 => { 130 | self.mirroring_mode = data & 0b0000_0011; 131 | self.prg_mode = (data & 0b0000_1100) >> 2; 132 | self.prg_outer_bank_size = ((data & 0b0011_0000) >> 4) as usize; 133 | }, 134 | 0x81 => { 135 | self.prg_outer_bank = data as usize; 136 | }, 137 | _ => {/* never reached */} 138 | } 139 | } 140 | _ => {} 141 | } 142 | } 143 | 144 | fn debug_read_ppu(&self, address: u16) -> Option { 145 | match address { 146 | 0x0000 ..= 0x1FFF => {return self.chr.wrapping_read(self.chr_address(address))}, 147 | 0x2000 ..= 0x3FFF => return match self.mirroring() { 148 | Mirroring::Horizontal => Some(self.vram[mirroring::horizontal_mirroring(address) as usize]), 149 | Mirroring::Vertical => Some(self.vram[mirroring::vertical_mirroring(address) as usize]), 150 | Mirroring::OneScreenLower => Some(self.vram[mirroring::one_screen_lower(address) as usize]), 151 | Mirroring::OneScreenUpper => Some(self.vram[mirroring::one_screen_upper(address) as usize]), 152 | _ => None 153 | }, 154 | _ => return None 155 | } 156 | } 157 | 158 | fn write_ppu(&mut self, address: u16, data: u8) { 159 | match address { 160 | 0x0000 ..= 0x1FFF => {self.chr.wrapping_write(self.chr_address(address), data);}, 161 | 0x2000 ..= 0x3FFF => match self.mirroring() { 162 | Mirroring::Horizontal => self.vram[mirroring::horizontal_mirroring(address) as usize] = data, 163 | Mirroring::Vertical => self.vram[mirroring::vertical_mirroring(address) as usize] = data, 164 | Mirroring::OneScreenLower => self.vram[mirroring::one_screen_lower(address) as usize] = data, 165 | Mirroring::OneScreenUpper => self.vram[mirroring::one_screen_upper(address) as usize] = data, 166 | _ => {} 167 | }, 168 | _ => {} 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/mmc/axrom.rs: -------------------------------------------------------------------------------- 1 | // AxROM, bank switchable PRG ROM, 8kb CHR RAM, basic single-screen mirroring. 2 | // Reference capabilities: https://wiki.nesdev.com/w/index.php/AxROM 3 | 4 | use ines::INesCartridge; 5 | use memoryblock::MemoryBlock; 6 | 7 | use mmc::mapper::*; 8 | use mmc::mirroring; 9 | 10 | pub struct AxRom { 11 | pub prg_rom: MemoryBlock, 12 | pub chr: MemoryBlock, 13 | pub mirroring: Mirroring, 14 | pub prg_bank: usize, 15 | pub vram: Vec, 16 | } 17 | 18 | impl AxRom { 19 | pub fn from_ines(ines: INesCartridge) -> Result { 20 | let prg_rom_block = ines.prg_rom_block(); 21 | let chr_block = ines.chr_block()?; 22 | 23 | return Ok(AxRom { 24 | prg_rom: prg_rom_block.clone(), 25 | chr: chr_block.clone(), 26 | mirroring: Mirroring::OneScreenUpper, 27 | prg_bank: 0x07, 28 | vram: vec![0u8; 0x1000], 29 | }); 30 | } 31 | } 32 | 33 | impl Mapper for AxRom { 34 | fn mirroring(&self) -> Mirroring { 35 | return self.mirroring; 36 | } 37 | 38 | fn print_debug_status(&self) { 39 | println!("======= AxROM ======="); 40 | println!("PRG Bank: {}, Mirroring Mode: {}", self.prg_bank, mirroring_mode_name(self.mirroring)); 41 | println!("===================="); 42 | } 43 | 44 | fn debug_read_cpu(&self, address: u16) -> Option { 45 | match address { 46 | 0x8000 ..= 0xFFFF => {self.prg_rom.banked_read(0x8000, self.prg_bank, (address - 0x8000) as usize)}, 47 | _ => None 48 | } 49 | } 50 | 51 | fn write_cpu(&mut self, address: u16, data: u8) { 52 | match address { 53 | 0x8000 ..= 0xFFFF => { 54 | self.prg_bank = (data & 0x07) as usize; 55 | if data & 0x10 == 0 { 56 | self.mirroring = Mirroring::OneScreenLower; 57 | } else { 58 | self.mirroring = Mirroring::OneScreenUpper; 59 | } 60 | } 61 | _ => {} 62 | } 63 | } 64 | 65 | fn debug_read_ppu(&self, address: u16) -> Option { 66 | match address { 67 | 0x0000 ..= 0x1FFF => self.chr.wrapping_read(address as usize), 68 | 0x2000 ..= 0x3FFF => match self.mirroring { 69 | Mirroring::OneScreenLower => Some(self.vram[mirroring::one_screen_lower(address) as usize]), 70 | Mirroring::OneScreenUpper => Some(self.vram[mirroring::one_screen_upper(address) as usize]), 71 | _ => None 72 | }, 73 | _ => None 74 | } 75 | } 76 | 77 | fn write_ppu(&mut self, address: u16, data: u8) { 78 | match address { 79 | 0x0000 ..= 0x1FFF => self.chr.wrapping_write(address as usize, data), 80 | 0x2000 ..= 0x3FFF => match self.mirroring { 81 | Mirroring::OneScreenLower => self.vram[mirroring::one_screen_lower(address) as usize] = data, 82 | Mirroring::OneScreenUpper => self.vram[mirroring::one_screen_upper(address) as usize] = data, 83 | _ => {} 84 | }, 85 | _ => {} 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/mmc/bnrom.rs: -------------------------------------------------------------------------------- 1 | // BNROM, bank switchable PRG ROM, 8kb CHR RAM, solder-pad fixed horizontal or vertical mirroring. 2 | // Essentially an AxROM variant, though I'm choosing to keep all numbered mapper implementations 3 | // dependency free for my own sanity. 4 | // Reference capabilities: https://wiki.nesdev.com/w/index.php/BNROM 5 | 6 | use ines::INesCartridge; 7 | use memoryblock::MemoryBlock; 8 | 9 | use mmc::mapper::*; 10 | use mmc::mirroring; 11 | 12 | pub struct BnRom { 13 | pub prg_rom: MemoryBlock, 14 | pub chr: MemoryBlock, 15 | pub mirroring: Mirroring, 16 | pub prg_bank: usize, 17 | pub vram: Vec, 18 | } 19 | 20 | impl BnRom { 21 | pub fn from_ines(ines: INesCartridge) -> Result { 22 | let prg_rom_block = ines.prg_rom_block(); 23 | let chr_block = ines.chr_block()?; 24 | 25 | return Ok(BnRom { 26 | prg_rom: prg_rom_block.clone(), 27 | chr: chr_block.clone(), 28 | mirroring: ines.header.mirroring(), 29 | prg_bank: 0x07, 30 | vram: vec![0u8; 0x1000], 31 | }); 32 | } 33 | } 34 | 35 | impl Mapper for BnRom { 36 | fn mirroring(&self) -> Mirroring { 37 | return self.mirroring; 38 | } 39 | 40 | fn print_debug_status(&self) { 41 | println!("======= BNROM ======="); 42 | println!("PRG Bank: {}, Mirroring Mode: {}", self.prg_bank, mirroring_mode_name(self.mirroring)); 43 | println!("===================="); 44 | } 45 | 46 | fn debug_read_cpu(&self, address: u16) -> Option { 47 | match address { 48 | 0x8000 ..= 0xFFFF => {self.prg_rom.banked_read(0x8000, self.prg_bank, (address - 0x8000) as usize)}, 49 | _ => None 50 | } 51 | } 52 | 53 | fn write_cpu(&mut self, address: u16, data: u8) { 54 | match address { 55 | 0x8000 ..= 0xFFFF => {self.prg_bank = data as usize;} 56 | _ => {} 57 | } 58 | } 59 | 60 | fn debug_read_ppu(&self, address: u16) -> Option { 61 | match address { 62 | 0x0000 ..= 0x1FFF => self.chr.wrapping_read(address as usize), 63 | 0x2000 ..= 0x3FFF => match self.mirroring { 64 | Mirroring::Horizontal => Some(self.vram[mirroring::horizontal_mirroring(address) as usize]), 65 | Mirroring::Vertical => Some(self.vram[mirroring::vertical_mirroring(address) as usize]), 66 | _ => None 67 | }, 68 | _ => None 69 | } 70 | } 71 | 72 | fn write_ppu(&mut self, address: u16, data: u8) { 73 | match address { 74 | 0x0000 ..= 0x1FFF => {self.chr.wrapping_write(address as usize, data);}, 75 | 0x2000 ..= 0x3FFF => match self.mirroring { 76 | Mirroring::Horizontal => self.vram[mirroring::horizontal_mirroring(address) as usize] = data, 77 | Mirroring::Vertical => self.vram[mirroring::vertical_mirroring(address) as usize] = data, 78 | _ => {} 79 | }, 80 | _ => {} 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/mmc/cnrom.rs: -------------------------------------------------------------------------------- 1 | // CnROM, 16-32kb PRG ROM, up to 2048k CHR ROM 2 | // Reference capabilities: https://wiki.nesdev.com/w/index.php/INES_Mapper_003 3 | 4 | use ines::INesCartridge; 5 | use memoryblock::MemoryBlock; 6 | 7 | use mmc::mapper::*; 8 | use mmc::mirroring; 9 | 10 | pub struct CnRom { 11 | pub prg_rom: MemoryBlock, 12 | pub chr: MemoryBlock, 13 | pub mirroring: Mirroring, 14 | pub chr_bank: usize, 15 | pub vram: Vec, 16 | } 17 | 18 | impl CnRom { 19 | pub fn from_ines(ines: INesCartridge) -> Result { 20 | let prg_rom_block = ines.prg_rom_block(); 21 | let chr_block = ines.chr_block()?; 22 | 23 | return Ok(CnRom { 24 | prg_rom: prg_rom_block.clone(), 25 | chr: chr_block.clone(), 26 | mirroring: ines.header.mirroring(), 27 | chr_bank: 0x00, 28 | vram: vec![0u8; 0x1000], 29 | }); 30 | } 31 | } 32 | 33 | impl Mapper for CnRom { 34 | fn print_debug_status(&self) { 35 | println!("======= CnROM ======="); 36 | println!("CHR Bank: {}, Mirroring Mode: {}", self.chr_bank, mirroring_mode_name(self.mirroring)); 37 | println!("===================="); 38 | } 39 | 40 | fn mirroring(&self) -> Mirroring { 41 | return self.mirroring; 42 | } 43 | 44 | fn debug_read_cpu(&self, address: u16) -> Option { 45 | match address { 46 | 0x8000 ..= 0xFFFF => {self.prg_rom.wrapping_read((address - 0x8000) as usize)}, 47 | _ => None 48 | } 49 | } 50 | 51 | fn write_cpu(&mut self, address: u16, data: u8) { 52 | match address { 53 | 0x8000 ..= 0xFFFF => { 54 | self.chr_bank = data as usize; 55 | } 56 | _ => {} 57 | } 58 | } 59 | 60 | fn debug_read_ppu(&self, address: u16) -> Option { 61 | match address { 62 | 0x0000 ..= 0x1FFF => {self.chr.banked_read(0x2000, self.chr_bank, address as usize)}, 63 | 0x2000 ..= 0x3FFF => match self.mirroring { 64 | Mirroring::Horizontal => Some(self.vram[mirroring::horizontal_mirroring(address) as usize]), 65 | Mirroring::Vertical => Some(self.vram[mirroring::vertical_mirroring(address) as usize]), 66 | _ => None 67 | }, 68 | _ => None 69 | } 70 | } 71 | 72 | fn write_ppu(&mut self, address: u16, data: u8) { 73 | match address { 74 | 0x0000 ..= 0x1FFF => {self.chr.banked_write(0x2000, self.chr_bank, address as usize, data)}, 75 | 0x2000 ..= 0x3FFF => match self.mirroring { 76 | Mirroring::Horizontal => self.vram[mirroring::horizontal_mirroring(address) as usize] = data, 77 | Mirroring::Vertical => self.vram[mirroring::vertical_mirroring(address) as usize] = data, 78 | _ => {} 79 | }, 80 | _ => {} 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/mmc/gxrom.rs: -------------------------------------------------------------------------------- 1 | // GxRom, simple bank switchable 32kb PRG ROM and 8k CHR ROM 2 | // Reference capabilities: https://wiki.nesdev.com/w/index.php/GxROM 3 | 4 | use ines::INesCartridge; 5 | use memoryblock::MemoryBlock; 6 | 7 | use mmc::mapper::*; 8 | use mmc::mirroring; 9 | 10 | pub struct GxRom { 11 | pub prg_rom: MemoryBlock, 12 | pub chr: MemoryBlock, 13 | pub mirroring: Mirroring, 14 | pub prg_bank: usize, 15 | pub chr_bank: usize, 16 | pub vram: Vec, 17 | } 18 | 19 | impl GxRom { 20 | pub fn from_ines(ines: INesCartridge) -> Result { 21 | let prg_rom_block = ines.prg_rom_block(); 22 | let chr_block = ines.chr_block()?; 23 | 24 | return Ok(GxRom { 25 | prg_rom: prg_rom_block.clone(), 26 | chr: chr_block.clone(), 27 | mirroring: ines.header.mirroring(), 28 | prg_bank: 0x00, 29 | chr_bank: 0x00, 30 | vram: vec![0u8; 0x1000], 31 | }); 32 | } 33 | } 34 | 35 | impl Mapper for GxRom { 36 | fn print_debug_status(&self) { 37 | println!("======= GxROM ======="); 38 | println!("PRG Bank: {}, CHR Bank: {}, Mirroring Mode: {}", self.prg_bank, self.chr_bank, mirroring_mode_name(self.mirroring)); 39 | println!("===================="); 40 | } 41 | 42 | fn mirroring(&self) -> Mirroring { 43 | return self.mirroring; 44 | } 45 | 46 | fn debug_read_cpu(&self, address: u16) -> Option { 47 | match address { 48 | 0x8000 ..= 0xFFFF => {self.prg_rom.banked_read(0x8000, self.prg_bank, (address - 0x8000) as usize)}, 49 | _ => None 50 | } 51 | } 52 | 53 | fn write_cpu(&mut self, address: u16, data: u8) { 54 | match address { 55 | 0x8000 ..= 0xFFFF => { 56 | self.prg_bank = ((data & 0b0011_0000) >> 4) as usize; 57 | self.chr_bank = (data & 0b0000_0011) as usize; 58 | } 59 | _ => {} 60 | } 61 | } 62 | 63 | fn debug_read_ppu(&self, address: u16) -> Option { 64 | match address { 65 | 0x0000 ..= 0x1FFF => self.chr.banked_read(0x2000, self.chr_bank, address as usize), 66 | 0x2000 ..= 0x3FFF => match self.mirroring { 67 | Mirroring::Horizontal => Some(self.vram[mirroring::horizontal_mirroring(address) as usize]), 68 | Mirroring::Vertical => Some(self.vram[mirroring::vertical_mirroring(address) as usize]), 69 | _ => None 70 | }, 71 | _ => None 72 | } 73 | } 74 | 75 | fn write_ppu(&mut self, address: u16, data: u8) { 76 | match address { 77 | 0x0000 ..= 0x1FFF => self.chr.banked_write(0x2000, self.chr_bank, address as usize, data), 78 | 0x2000 ..= 0x3FFF => match self.mirroring { 79 | Mirroring::Horizontal => self.vram[mirroring::horizontal_mirroring(address) as usize] = data, 80 | Mirroring::Vertical => self.vram[mirroring::vertical_mirroring(address) as usize] = data, 81 | _ => {} 82 | }, 83 | _ => {} 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/mmc/ines31.rs: -------------------------------------------------------------------------------- 1 | // iNES Mapper 031 represents a mapper created to facilitate cartridge compilations 2 | // of NSF music. It implements a common subset of the features used by NSFs. 3 | // Reference capabilities: https://wiki.nesdev.com/w/index.php/INES_Mapper_031 4 | 5 | use ines::INesCartridge; 6 | use memoryblock::MemoryBlock; 7 | 8 | use mmc::mapper::*; 9 | use mmc::mirroring; 10 | 11 | pub struct INes31 { 12 | pub prg_rom: MemoryBlock, 13 | pub chr: MemoryBlock, 14 | pub mirroring: Mirroring, 15 | pub vram: Vec, 16 | pub prg_banks: Vec, 17 | } 18 | 19 | impl INes31 { 20 | pub fn from_ines(ines: INesCartridge) -> Result { 21 | let prg_rom_block = ines.prg_rom_block(); 22 | let chr_block = ines.chr_block()?; 23 | 24 | return Ok(INes31 { 25 | prg_rom: prg_rom_block.clone(), 26 | chr: chr_block.clone(), 27 | mirroring: ines.header.mirroring(), 28 | vram: vec![0u8; 0x1000], 29 | prg_banks: vec![255usize; 8], 30 | }) 31 | } 32 | } 33 | 34 | impl Mapper for INes31 { 35 | fn print_debug_status(&self) { 36 | println!("======= iNes 31 ======="); 37 | println!("Mirroring Mode: {}", mirroring_mode_name(self.mirroring)); 38 | println!("===================="); 39 | } 40 | 41 | fn mirroring(&self) -> Mirroring { 42 | return self.mirroring; 43 | } 44 | 45 | fn debug_read_cpu(&self, address: u16) -> Option { 46 | match address { 47 | 0x8000 ..= 0x8FFF => self.prg_rom.banked_read(0x1000, self.prg_banks[0], (address as usize) - 0x8000), 48 | 0x9000 ..= 0x9FFF => self.prg_rom.banked_read(0x1000, self.prg_banks[1], (address as usize) - 0x9000), 49 | 0xA000 ..= 0xAFFF => self.prg_rom.banked_read(0x1000, self.prg_banks[2], (address as usize) - 0xA000), 50 | 0xB000 ..= 0xBFFF => self.prg_rom.banked_read(0x1000, self.prg_banks[3], (address as usize) - 0xB000), 51 | 0xC000 ..= 0xCFFF => self.prg_rom.banked_read(0x1000, self.prg_banks[4], (address as usize) - 0xC000), 52 | 0xD000 ..= 0xDFFF => self.prg_rom.banked_read(0x1000, self.prg_banks[5], (address as usize) - 0xD000), 53 | 0xE000 ..= 0xEFFF => self.prg_rom.banked_read(0x1000, self.prg_banks[6], (address as usize) - 0xE000), 54 | 0xF000 ..= 0xFFFF => self.prg_rom.banked_read(0x1000, self.prg_banks[7], (address as usize) - 0xF000), 55 | _ => None 56 | } 57 | } 58 | 59 | fn write_cpu(&mut self, address: u16, data: u8) { 60 | match address { 61 | 0x5FF8 => {self.prg_banks[0] = data as usize}, 62 | 0x5FF9 => {self.prg_banks[1] = data as usize}, 63 | 0x5FFA => {self.prg_banks[2] = data as usize}, 64 | 0x5FFB => {self.prg_banks[3] = data as usize}, 65 | 0x5FFC => {self.prg_banks[4] = data as usize}, 66 | 0x5FFD => {self.prg_banks[5] = data as usize}, 67 | 0x5FFE => {self.prg_banks[6] = data as usize}, 68 | 0x5FFF => {self.prg_banks[7] = data as usize}, 69 | _ => {} 70 | } 71 | } 72 | 73 | fn debug_read_ppu(&self, address: u16) -> Option { 74 | match address { 75 | 0x0000 ..= 0x1FFF => self.chr.wrapping_read(address as usize), 76 | 0x2000 ..= 0x3FFF => match self.mirroring { 77 | Mirroring::Horizontal => Some(self.vram[mirroring::horizontal_mirroring(address) as usize]), 78 | Mirroring::Vertical => Some(self.vram[mirroring::vertical_mirroring(address) as usize]), 79 | _ => None 80 | }, 81 | _ => None 82 | } 83 | } 84 | 85 | fn write_ppu(&mut self, address: u16, data: u8) { 86 | match address { 87 | 0x0000 ..= 0x1FFF => self.chr.wrapping_write(address as usize, data), 88 | 0x2000 ..= 0x3FFF => match self.mirroring { 89 | Mirroring::Horizontal => self.vram[mirroring::horizontal_mirroring(address) as usize] = data, 90 | Mirroring::Vertical => self.vram[mirroring::vertical_mirroring(address) as usize] = data, 91 | _ => {} 92 | }, 93 | _ => {} 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/mmc/mapper.rs: -------------------------------------------------------------------------------- 1 | use apu::AudioChannelState; 2 | 3 | #[derive(Copy, Clone, PartialEq)] 4 | pub enum Mirroring { 5 | Horizontal, 6 | Vertical, 7 | OneScreenLower, 8 | OneScreenUpper, 9 | FourScreen, 10 | } 11 | 12 | pub fn mirroring_mode_name(mode: Mirroring) -> &'static str { 13 | match mode { 14 | Mirroring::Horizontal => "Horizontal", 15 | Mirroring::Vertical => "Vertical", 16 | Mirroring::OneScreenLower => "OneScreenLower", 17 | Mirroring::OneScreenUpper => "OneScreenUpper", 18 | Mirroring::FourScreen => "FourScreen" 19 | } 20 | } 21 | 22 | pub trait Mapper: Send { 23 | fn read_cpu(&mut self, address: u16) -> Option {return self.debug_read_cpu(address);} 24 | fn write_cpu(&mut self, address: u16, data: u8); 25 | fn access_ppu(&mut self, _address: u16) {} 26 | fn read_ppu(&mut self, address: u16) -> Option {return self.debug_read_ppu(address);} 27 | fn write_ppu(&mut self, address: u16, data: u8); 28 | fn debug_read_cpu(&self, address: u16) -> Option; 29 | fn debug_read_ppu(&self, address: u16) -> Option; 30 | fn print_debug_status(&self) {} 31 | fn mirroring(&self) -> Mirroring; 32 | fn has_sram(&self) -> bool {return false;} 33 | fn get_sram(&self) -> Vec {return vec![0u8; 0];} 34 | fn load_sram(&mut self, _: Vec) {} 35 | fn irq_flag(&self) -> bool {return false;} 36 | fn clock_cpu(&mut self) {} 37 | fn mix_expansion_audio(&self, nes_sample: f32) -> f32 {return nes_sample;} 38 | fn channels(&self) -> Vec<& dyn AudioChannelState> {return Vec::new();} 39 | fn channels_mut(&mut self) -> Vec<&mut dyn AudioChannelState> {return Vec::new();} 40 | fn record_expansion_audio_output(&mut self, _nes_sample: f32) {} 41 | fn nsf_set_track(&mut self, _track_index: u8) {} 42 | fn nsf_manual_mode(&mut self) {} 43 | fn audio_multiplexing(&mut self, _emulate: bool) {} 44 | fn needs_bios(&self) -> bool {return false;} 45 | fn load_bios(&mut self, _: Vec) {} 46 | fn switch_disk(&mut self, _: usize) {} 47 | } 48 | -------------------------------------------------------------------------------- /src/mmc/mirroring.rs: -------------------------------------------------------------------------------- 1 | // Set of helper functions to assist mappers with a few of the most 2 | // common mirroring modes. Less common mirroring modes and more complex 3 | // logic may still be implemented within individual mappers as needed. 4 | const NT_OFFSET: (u16, u16, u16, u16) = (0x000, 0x400, 0x800, 0xC00); 5 | 6 | pub fn horizontal_mirroring(read_address: u16) -> u16 { 7 | let nt_base = read_address & 0xFFF; 8 | let nt_address = read_address & 0x3FF; 9 | match nt_base { 10 | // Nametable 0 (top-left) 11 | 0x000 ..= 0x3FF => nt_address + NT_OFFSET.0, 12 | 0x400 ..= 0x7FF => nt_address + NT_OFFSET.0, 13 | 0x800 ..= 0xBFF => nt_address + NT_OFFSET.1, 14 | 0xC00 ..= 0xFFF => nt_address + NT_OFFSET.1, 15 | _ => return 0, // wat 16 | } 17 | } 18 | 19 | pub fn vertical_mirroring(read_address: u16) -> u16 { 20 | let nt_base = read_address & 0xFFF; 21 | let nt_address = read_address & 0x3FF; 22 | match nt_base { 23 | // Nametable 0 (top-left) 24 | 0x000 ..= 0x3FF => nt_address + NT_OFFSET.0, 25 | 0x400 ..= 0x7FF => nt_address + NT_OFFSET.1, 26 | 0x800 ..= 0xBFF => nt_address + NT_OFFSET.0, 27 | 0xC00 ..= 0xFFF => nt_address + NT_OFFSET.1, 28 | _ => return 0, // wat 29 | } 30 | } 31 | 32 | pub fn one_screen_lower(read_address: u16) -> u16 { 33 | let nt_base = read_address & 0xFFF; 34 | let nt_address = read_address & 0x3FF; 35 | match nt_base { 36 | // Nametable 0 (top-left) 37 | 0x000 ..= 0x3FF => nt_address + NT_OFFSET.0, 38 | 0x400 ..= 0x7FF => nt_address + NT_OFFSET.0, 39 | 0x800 ..= 0xBFF => nt_address + NT_OFFSET.0, 40 | 0xC00 ..= 0xFFF => nt_address + NT_OFFSET.0, 41 | _ => return 0, // wat 42 | } 43 | } 44 | 45 | pub fn one_screen_upper(read_address: u16) -> u16 { 46 | let nt_base = read_address & 0xFFF; 47 | let nt_address = read_address & 0x3FF; 48 | match nt_base { 49 | // Nametable 0 (top-left) 50 | 0x000 ..= 0x3FF => nt_address + NT_OFFSET.1, 51 | 0x400 ..= 0x7FF => nt_address + NT_OFFSET.1, 52 | 0x800 ..= 0xBFF => nt_address + NT_OFFSET.1, 53 | 0xC00 ..= 0xFFF => nt_address + NT_OFFSET.1, 54 | _ => return 0, // wat 55 | } 56 | } 57 | 58 | pub fn four_banks(read_address: u16) -> u16 { 59 | let nt_base = read_address & 0xFFF; 60 | let nt_address = read_address & 0x3FF; 61 | match nt_base { 62 | // Nametable 0 (top-left) 63 | 0x000 ..= 0x3FF => nt_address + NT_OFFSET.0, 64 | 0x400 ..= 0x7FF => nt_address + NT_OFFSET.1, 65 | 0x800 ..= 0xBFF => nt_address + NT_OFFSET.2, 66 | 0xC00 ..= 0xFFF => nt_address + NT_OFFSET.3, 67 | _ => return 0, // wat 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/mmc/mmc1.rs: -------------------------------------------------------------------------------- 1 | // Common mapper with bank switched PRG_ROM, CHR_ROM/RAM, and optional PRG RAM. 2 | // Reference capabilities: https://wiki.nesdev.com/w/index.php/MMC1 3 | 4 | use ines::INesCartridge; 5 | use memoryblock::MemoryBlock; 6 | 7 | use mmc::mapper::*; 8 | use mmc::mirroring; 9 | 10 | pub struct Mmc1 { 11 | pub prg_rom: MemoryBlock, 12 | pub prg_ram: MemoryBlock, 13 | pub chr: MemoryBlock, 14 | pub vram: Vec, 15 | 16 | pub shift_counter: u8, 17 | pub shift_data: u8, 18 | 19 | pub chr_bank_0: usize, 20 | pub chr_bank_1: usize, 21 | 22 | pub prg_bank: usize, 23 | pub prg_ram_enabled: bool, 24 | pub prg_ram_bank: usize, 25 | 26 | pub control: u8, 27 | 28 | pub mirroring: Mirroring, 29 | pub last_write: bool, 30 | } 31 | 32 | impl Mmc1 { 33 | pub fn from_ines(ines: INesCartridge) -> Result { 34 | let prg_rom_block = ines.prg_rom_block(); 35 | let prg_ram_block = ines.prg_ram_block()?; 36 | let chr_block = ines.chr_block()?; 37 | 38 | return Ok(Mmc1 { 39 | prg_rom: prg_rom_block.clone(), 40 | prg_ram: prg_ram_block.clone(), 41 | chr: chr_block.clone(), 42 | vram: vec![0u8; 0x1000], 43 | // Note: On real MMC1-based hardware, many of these values are random on startup, so 44 | // the defaults presented below are arbitrary. 45 | shift_counter: 0, 46 | shift_data: 0, 47 | chr_bank_0: 0, 48 | chr_bank_1: 0, 49 | prg_bank: 0x00, 50 | prg_ram_enabled: true, 51 | prg_ram_bank: 0, 52 | // Power-on in PRG mode 3, so the last bank is fixed and reset vectors are reliably available. 53 | // (Real hardware might not do this consistently?) 54 | control: 0x0C, 55 | mirroring: Mirroring::Vertical, 56 | last_write: false, 57 | }) 58 | } 59 | } 60 | 61 | impl Mapper for Mmc1 { 62 | fn print_debug_status(&self) { 63 | let prg_mode = (self.control >> 2) & 0x3; 64 | let chr_mode = (self.control & 0x10) >> 4; 65 | println!("======= MMC1 ======="); 66 | println!("PRG Mode: {} | CHR: Mode: {} | S.Count: {} | S.Data: {:02X}", 67 | prg_mode, chr_mode, self.shift_counter, self.shift_data); 68 | let last_bank = (self.prg_rom.len() / (16 * 1024)) as u16 - 1; 69 | println!("PRG: {} | CHR0: {} | CHR1: {} | PRG_LAST: {}", 70 | self.prg_bank, self.chr_bank_0, self.chr_bank_1, last_bank); 71 | println!("Mirroring Mode: {}", mirroring_mode_name(self.mirroring)); 72 | println!("===================="); 73 | } 74 | 75 | fn mirroring(&self) -> Mirroring { 76 | return self.mirroring; 77 | } 78 | 79 | fn read_cpu(&mut self, address: u16) -> Option { 80 | self.last_write = false; 81 | return self.debug_read_cpu(address); 82 | } 83 | 84 | fn debug_read_cpu(&self, address: u16) -> Option { 85 | match address { 86 | // PRG RAM 87 | 0x6000 ..= 0x7FFF => { 88 | self.prg_ram.banked_read(0x2000, self.prg_ram_bank, address as usize) 89 | }, 90 | // PRG ROM - First 16k Page 91 | 0x8000 ..= 0xBFFF => { 92 | let prg_rom_len = self.prg_rom.len(); 93 | if prg_rom_len > 0 { 94 | let prg_mode = (self.control >> 2) & 0x3; 95 | match prg_mode { 96 | 0 | 1 => { 97 | // 32kb PRG mode, use prg_bank ignoring bit 0 98 | let lower_half_bank = self.prg_bank & 0xFFFE; 99 | return self.prg_rom.banked_read(0x4000, lower_half_bank, (address - 0x8000) as usize) 100 | }, 101 | 2 => { 102 | // Fixed first bank, read that out here 103 | return self.prg_rom.banked_read(0x4000, 0, (address - 0x8000) as usize) 104 | }, 105 | 3 => { 106 | // Fixed last bank, read out the bank-switched first bank 107 | return self.prg_rom.banked_read(0x4000, self.prg_bank, (address - 0x8000) as usize) 108 | }, 109 | _ => return None, // Never called 110 | } 111 | } else { 112 | return None; 113 | } 114 | }, 115 | // PRG ROM - Last 16k Page 116 | 0xC000 ..= 0xFFFF => { 117 | let prg_rom_len = self.prg_rom.len(); 118 | if prg_rom_len > 0 { 119 | let prg_mode = (self.control >> 2) & 0x3; 120 | match prg_mode { 121 | 0 | 1 => { 122 | // 32kb PRG mode, use prg_bank and force-set bit 1 123 | let upper_half_bank = self.prg_bank | 0x0001; 124 | return self.prg_rom.banked_read(0x4000, upper_half_bank, (address - 0x8000) as usize) 125 | }, 126 | 2 => { 127 | // Fixed first bank, read out the bank-switched second bank 128 | return self.prg_rom.banked_read(0x4000, self.prg_bank, (address - 0x8000) as usize) 129 | }, 130 | 3 => { 131 | // Fixed last bank, read out the bank-switched *last* bank 132 | return self.prg_rom.banked_read(0x4000, 0xFF, (address - 0x8000) as usize) 133 | }, 134 | _ => return None, // Never called 135 | } 136 | } else { 137 | return None; 138 | } 139 | }, 140 | _ => return None 141 | } 142 | } 143 | 144 | fn write_cpu(&mut self, address: u16, data: u8) { 145 | match address { 146 | // PRG RAM 147 | 0x6000 ..= 0x7FFF => { 148 | if self.prg_ram_enabled { 149 | self.prg_ram.banked_write(0x2000, self.prg_ram_bank, address as usize, data); 150 | } 151 | }, 152 | // Control Registers 153 | 0x8000 ..= 0xFFFF => { 154 | if self.last_write { 155 | // Ignore this write! MMC1 ignores successive writes, and will clear this flag 156 | // on the next read cycle. 157 | return; 158 | } 159 | self.last_write = true; 160 | 161 | if data & 0x80 != 0 { 162 | // Shift / Control Reset! 163 | self.shift_counter = 0; 164 | // Upon reset, this sets the PRG ROM mode to 3, which fixes the last bank 165 | // to the upper PRG Page. This is the startup state of MMC1 variants. 166 | // https://wiki.nesdev.com/w/index.php/MMC1#Load_register_.28.248000-.24FFFF.29 167 | self.control = self.control | 0b0_1100; 168 | } else { 169 | self.shift_data = (self.shift_data >> 1) | ((data & 0b1) << 4); 170 | self.shift_counter += 1; 171 | if self.shift_counter == 5 { 172 | // Only the top 3 bits (13-15) matter for register selection, everything 173 | // else is mirrored due to incomplete decoding of the address. 174 | // https://wiki.nesdev.com/w/index.php/MMC1#Registers 175 | let register = address & 0b1110_0000_0000_0000; 176 | match register { 177 | 0x8000 ..= 0x9F00 => { 178 | self.control = self.shift_data; 179 | let nametable_mode = self.control & 0b0_0011; 180 | match nametable_mode { 181 | 0 => self.mirroring = Mirroring::OneScreenLower, 182 | 1 => self.mirroring = Mirroring::OneScreenUpper, 183 | 2 => self.mirroring = Mirroring::Vertical, 184 | 3 => self.mirroring = Mirroring::Horizontal, 185 | _ => println!("Bad mirroring mode!! {}", nametable_mode), 186 | } 187 | }, 188 | 0xA000 ..= 0xBF00 => { 189 | self.chr_bank_0 = self.shift_data as usize; 190 | self.prg_ram_bank = ((self.shift_data & 0b0_1100) >> 2) as usize; 191 | }, 192 | 0xC000 ..= 0xDF00 => { 193 | self.chr_bank_1 = self.shift_data as usize; 194 | self.prg_ram_bank = ((self.shift_data & 0b0_1100) >> 2) as usize; 195 | }, 196 | 0xE000 ..= 0xFF00 => { 197 | // The 5th bit disables RAM, so invert it here to decide when 198 | // RAM should be enabled. 199 | // TODO: This is ignored on certain MMC variants! 200 | self.prg_ram_enabled = self.shift_data & 0b1_0000 == 0; 201 | self.prg_bank = (self.shift_data & 0b0_1111) as usize; 202 | }, 203 | _ => () 204 | } 205 | self.shift_counter = 0; 206 | self.shift_data = 0; 207 | } 208 | } 209 | } 210 | _ => {} 211 | } 212 | } 213 | 214 | fn debug_read_ppu(&self, address: u16) -> Option { 215 | match address { 216 | // CHR Bank 0 217 | 0x0000 ..= 0x0FFF => { 218 | if self.control & 0x10 == 0 { 219 | // 8kb CHR mode, bit 0 is treated as cleared 220 | let lower_half_bank = self.chr_bank_0 & 0xFFFE; 221 | return self.chr.banked_read(0x1000, lower_half_bank, address as usize) 222 | } else { 223 | // 4kb CHR mode 224 | return self.chr.banked_read(0x1000, self.chr_bank_0 , address as usize) 225 | } 226 | }, 227 | // CHR Bank 1 228 | 0x1000 ..= 0x1FFF => { 229 | if self.control & 0x10 == 0 { 230 | // 8kb CHR mode, bit 0 is treated as set 231 | let upper_half_bank = self.chr_bank_0 | 0x0001; 232 | return self.chr.banked_read(0x1000, upper_half_bank, address as usize) 233 | } else { 234 | // 4kb CHR mode 235 | return self.chr.banked_read(0x1000, self.chr_bank_1 , address as usize) 236 | } 237 | }, 238 | 0x2000 ..= 0x3FFF => return match self.mirroring { 239 | Mirroring::Horizontal => Some(self.vram[mirroring::horizontal_mirroring(address) as usize]), 240 | Mirroring::Vertical => Some(self.vram[mirroring::vertical_mirroring(address) as usize]), 241 | Mirroring::OneScreenLower => Some(self.vram[mirroring::one_screen_lower(address) as usize]), 242 | Mirroring::OneScreenUpper => Some(self.vram[mirroring::one_screen_upper(address) as usize]), 243 | _ => None 244 | }, 245 | _ => return None 246 | } 247 | } 248 | 249 | fn write_ppu(&mut self, address: u16, data: u8) { 250 | match address { 251 | // CHR Bank 0 252 | 0x0000 ..= 0x0FFF => { 253 | if self.control & 0x10 == 0 { 254 | // 8kb CHR mode, bit 0 is ignored 255 | let lower_half_bank = self.chr_bank_0 & 0xFFFE; 256 | self.chr.banked_write(0x1000, lower_half_bank, address as usize, data) 257 | } else { 258 | // 4kb CHR mode 259 | self.chr.banked_write(0x1000, self.chr_bank_0, address as usize, data) 260 | } 261 | }, 262 | // CHR Bank 1 263 | 0x1000 ..= 0x1FFF => { 264 | if self.control & 0x10 == 0 { 265 | // 8kb CHR mode, use chr_bank_0 with bit 1 set 266 | let upper_half_bank = self.chr_bank_0 | 0x0001; 267 | self.chr.banked_write(0x1000, upper_half_bank, address as usize, data) 268 | } else { 269 | // 4kb CHR mode 270 | self.chr.banked_write(0x1000, self.chr_bank_1, address as usize, data) 271 | } 272 | }, 273 | 0x2000 ..= 0x3FFF => match self.mirroring { 274 | Mirroring::Horizontal => self.vram[mirroring::horizontal_mirroring(address) as usize] = data, 275 | Mirroring::Vertical => self.vram[mirroring::vertical_mirroring(address) as usize] = data, 276 | Mirroring::OneScreenLower => self.vram[mirroring::one_screen_lower(address) as usize] = data, 277 | Mirroring::OneScreenUpper => self.vram[mirroring::one_screen_upper(address) as usize] = data, 278 | _ => {} 279 | }, 280 | _ => {} 281 | } 282 | } 283 | 284 | fn has_sram(&self) -> bool { 285 | return true; 286 | } 287 | 288 | fn get_sram(&self) -> Vec { 289 | return self.prg_ram.as_vec().clone(); 290 | } 291 | 292 | fn load_sram(&mut self, sram_data: Vec) { 293 | *self.prg_ram.as_mut_vec() = sram_data; 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /src/mmc/mmc3.rs: -------------------------------------------------------------------------------- 1 | // Advanced mapper with bank-switched PRG ROM and CHR ROM, and a scanline counter feeding into IRQ 2 | // Reference capabilities: https://wiki.nesdev.com/w/index.php/MMC3 3 | 4 | use ines::INesCartridge; 5 | use memoryblock::MemoryBlock; 6 | 7 | use mmc::mapper::*; 8 | use mmc::mirroring; 9 | 10 | pub struct Mmc3 { 11 | pub prg_rom: MemoryBlock, 12 | pub prg_ram: MemoryBlock, 13 | pub chr: MemoryBlock, 14 | pub vram: Vec, 15 | 16 | pub chr2_bank_0: usize, 17 | pub chr2_bank_1: usize, 18 | pub chr1_bank_2: usize, 19 | pub chr1_bank_3: usize, 20 | pub chr1_bank_4: usize, 21 | pub chr1_bank_5: usize, 22 | 23 | pub prg_bank_6: usize, 24 | pub prg_bank_7: usize, 25 | 26 | pub switch_chr_banks: bool, 27 | pub switch_prg_banks: bool, 28 | 29 | pub bank_select: u8, 30 | 31 | pub irq_counter: u8, 32 | pub irq_reload: u8, 33 | pub irq_reload_requested: bool, 34 | pub irq_enabled: bool, 35 | pub irq_flag: bool, 36 | 37 | pub last_a12: u8, 38 | pub filtered_a12: u8, 39 | pub low_a12_counter: u8, 40 | 41 | // Debug 42 | pub last_chr_read: u16, 43 | 44 | pub mirroring: Mirroring, 45 | } 46 | 47 | impl Mmc3 { 48 | pub fn from_ines(ines: INesCartridge) -> Result { 49 | let prg_rom_block = ines.prg_rom_block(); 50 | let prg_ram_block = ines.prg_ram_block()?; 51 | let chr_block = ines.chr_block()?; 52 | 53 | return Ok(Mmc3 { 54 | prg_rom: prg_rom_block.clone(), 55 | prg_ram: prg_ram_block.clone(), 56 | chr: chr_block.clone(), 57 | vram: vec![0u8; 0x2000], 58 | // Note: On real MMC3-based hardware, many of these values are random on startup, so 59 | // the defaults presented below are arbitrary. 60 | chr2_bank_0: 0, 61 | chr2_bank_1: 0, 62 | chr1_bank_2: 0, 63 | chr1_bank_3: 0, 64 | chr1_bank_4: 0, 65 | chr1_bank_5: 0, 66 | 67 | prg_bank_6: 0, 68 | prg_bank_7: 0, 69 | 70 | switch_chr_banks: false, 71 | switch_prg_banks: false, 72 | 73 | bank_select: 0, 74 | 75 | irq_counter: 0, 76 | irq_reload: 0, 77 | irq_reload_requested: false, 78 | irq_enabled: false, 79 | irq_flag: false, 80 | 81 | last_a12: 0, 82 | filtered_a12: 0, 83 | last_chr_read: 0, 84 | low_a12_counter: 0, 85 | 86 | mirroring: ines.header.mirroring(), 87 | }) 88 | } 89 | 90 | fn snoop_ppu_a12(&mut self, address: u16) { 91 | self.last_chr_read = address; 92 | let current_a12 = ((address & 0b0001_0000_0000_0000) >> 12) as u8; 93 | 94 | let last_filtered_a12 = self.filtered_a12; 95 | 96 | if current_a12 == 1 { 97 | self.filtered_a12 = 1; 98 | self.low_a12_counter = 0; 99 | } 100 | 101 | let filtered_a12_rising_edge = (self.filtered_a12 == 1) && (last_filtered_a12 == 0); 102 | if filtered_a12_rising_edge { 103 | self.clock_irq_counter(); 104 | } 105 | 106 | // Caching this value so the M2 counter can see it 107 | self.last_a12 = current_a12; 108 | } 109 | 110 | fn snoop_cpu_m2(&mut self) { 111 | if self.low_a12_counter < 255 && self.last_a12 == 0 { 112 | self.low_a12_counter += 1; 113 | } 114 | if self.low_a12_counter >= 3 { 115 | self.filtered_a12 = 0; 116 | } 117 | } 118 | 119 | fn clock_irq_counter(&mut self) { 120 | if self.irq_counter == 0 || self.irq_reload_requested { 121 | self.irq_counter = self.irq_reload; 122 | self.irq_reload_requested = false; 123 | } else { 124 | self.irq_counter -= 1; 125 | } 126 | if self.irq_counter == 0 && self.irq_enabled { 127 | self.irq_flag = true; 128 | } 129 | } 130 | 131 | fn _read_ppu(&self, address: u16) -> Option { 132 | match address { 133 | // CHR 134 | 0x0000 ..= 0x1FFF => { 135 | if self.switch_chr_banks { 136 | match address { 137 | 0x0000 ..= 0x03FF => self.chr.banked_read(0x400, self.chr1_bank_2, address as usize - 0x000), 138 | 0x0400 ..= 0x07FF => self.chr.banked_read(0x400, self.chr1_bank_3, address as usize - 0x400), 139 | 0x0800 ..= 0x0BFF => self.chr.banked_read(0x400, self.chr1_bank_4, address as usize - 0x800), 140 | 0x0C00 ..= 0x0FFF => self.chr.banked_read(0x400, self.chr1_bank_5, address as usize - 0xC00), 141 | 0x1000 ..= 0x17FF => self.chr.banked_read(0x800, self.chr2_bank_0 >> 1, address as usize - 0x1000), 142 | 0x1800 ..= 0x1FFF => self.chr.banked_read(0x800, self.chr2_bank_1 >> 1, address as usize - 0x1800), 143 | _ => None, 144 | } 145 | } else { 146 | match address { 147 | 0x0000 ..= 0x07FF => self.chr.banked_read(0x800, self.chr2_bank_0 >> 1, address as usize - 0x000), 148 | 0x0800 ..= 0x0FFF => self.chr.banked_read(0x800, self.chr2_bank_1 >> 1, address as usize - 0x800), 149 | 0x1000 ..= 0x13FF => self.chr.banked_read(0x400, self.chr1_bank_2, address as usize - 0x1000), 150 | 0x1400 ..= 0x17FF => self.chr.banked_read(0x400, self.chr1_bank_3, address as usize - 0x1400), 151 | 0x1800 ..= 0x1BFF => self.chr.banked_read(0x400, self.chr1_bank_4, address as usize - 0x1800), 152 | 0x1C00 ..= 0x1FFF => self.chr.banked_read(0x400, self.chr1_bank_5, address as usize - 0x1C00), 153 | _ => None, 154 | } 155 | } 156 | }, 157 | 0x2000 ..= 0x3FFF => match self.mirroring { 158 | Mirroring::Horizontal => Some(self.vram[mirroring::horizontal_mirroring(address) as usize]), 159 | Mirroring::Vertical => Some(self.vram[mirroring::vertical_mirroring(address) as usize]), 160 | Mirroring::FourScreen => Some(self.vram[mirroring::four_banks(address) as usize]), 161 | _ => None 162 | }, 163 | _ => None 164 | } 165 | } 166 | } 167 | 168 | impl Mapper for Mmc3 { 169 | fn print_debug_status(&self) { 170 | println!("======= MMC3 ======="); 171 | println!("IRQ: Current: {}, Reload: {}", self.irq_counter, self.irq_reload); 172 | println!("Last A12: {}, Last CHR Read: 0x{:04X}", self.last_a12, self.last_chr_read); 173 | println!("Mirroring Mode: {}", mirroring_mode_name(self.mirroring)); 174 | println!("===================="); 175 | } 176 | 177 | fn mirroring(&self) -> Mirroring { 178 | return self.mirroring; 179 | } 180 | 181 | fn irq_flag(&self) -> bool { 182 | return self.irq_flag; 183 | } 184 | 185 | fn clock_cpu(&mut self) { 186 | self.snoop_cpu_m2(); 187 | } 188 | 189 | fn debug_read_cpu(&self, address: u16) -> Option { 190 | match address { 191 | // PRG RAM 192 | 0x6000 ..= 0x7FFF => { 193 | self.prg_ram.wrapping_read(address as usize - 0x6000) 194 | }, 195 | // PRG ROM 196 | 0x8000 ..= 0xFFFF => { 197 | if self.switch_prg_banks { 198 | match address { 199 | 0x8000 ..= 0x9FFF => self.prg_rom.banked_read(0x2000, 0xFE, address as usize - 0x8000), 200 | 0xA000 ..= 0xBFFF => self.prg_rom.banked_read(0x2000, self.prg_bank_7, address as usize - 0xA000), 201 | 0xC000 ..= 0xDFFF => self.prg_rom.banked_read(0x2000, self.prg_bank_6, address as usize - 0xC000), 202 | 0xE000 ..= 0xFFFF => self.prg_rom.banked_read(0x2000, 0xFF, address as usize - 0xE000), 203 | _ => None, 204 | } 205 | } else { 206 | match address { 207 | 0x8000 ..= 0x9FFF => self.prg_rom.banked_read(0x2000, self.prg_bank_6, address as usize - 0x8000), 208 | 0xA000 ..= 0xBFFF => self.prg_rom.banked_read(0x2000, self.prg_bank_7, address as usize - 0xA000), 209 | 0xC000 ..= 0xDFFF => self.prg_rom.banked_read(0x2000, 0xFE, address as usize - 0xC000), 210 | 0xE000 ..= 0xFFFF => self.prg_rom.banked_read(0x2000, 0xFF, address as usize - 0xE000), 211 | _ => None, 212 | } 213 | } 214 | }, 215 | _ => None 216 | } 217 | } 218 | 219 | fn write_cpu(&mut self, address: u16, data: u8) { 220 | match address { 221 | // PRG RAM 222 | 0x6000 ..= 0x7FFF => { 223 | // Note: Intentionally omitting PRG RAM protection feature, since this 224 | // retains compatability with assumptions about iNES mapper 004 225 | self.prg_ram.wrapping_write(address as usize - 0x6000, data) 226 | }, 227 | // Registers 228 | 0x8000 ..= 0xFFFF => { 229 | if address & 0b1 == 0 { 230 | // Even Registers 231 | match address { 232 | 0x8000 ..= 0x9FFF => { 233 | // Bank Select 234 | self.bank_select = data & 0b0000_0111; 235 | self.switch_prg_banks = (data & 0b0100_0000) != 0; 236 | self.switch_chr_banks = (data & 0b1000_0000) != 0; 237 | }, 238 | 0xA000 ..= 0xBFFF => { 239 | if self.mirroring != Mirroring::FourScreen { 240 | if data & 0b1 == 0 { 241 | self.mirroring = Mirroring::Vertical; 242 | } else { 243 | self.mirroring = Mirroring::Horizontal; 244 | } 245 | } 246 | }, 247 | 0xC000 ..= 0xDFFF => { 248 | self.irq_reload = data; 249 | }, 250 | 0xE000 ..= 0xFFFF => { 251 | self.irq_enabled = false; 252 | self.irq_flag = false; 253 | } 254 | 255 | _ => (), 256 | } 257 | } else { 258 | // Odd Registers 259 | match address { 260 | 0x8000 ..= 0x9FFF => { 261 | // Bank Data 262 | match self.bank_select { 263 | 0 => self.chr2_bank_0 = (data & 0b1111_1110) as usize, 264 | 1 => self.chr2_bank_1 = (data & 0b1111_1110) as usize, 265 | 2 => self.chr1_bank_2 = data as usize, 266 | 3 => self.chr1_bank_3 = data as usize, 267 | 4 => self.chr1_bank_4 = data as usize, 268 | 5 => self.chr1_bank_5 = data as usize, 269 | 6 => self.prg_bank_6 = (data & 0b0011_1111) as usize, 270 | 7 => self.prg_bank_7 = (data & 0b0011_1111) as usize, 271 | _ => (), 272 | } 273 | }, 274 | 0xA000 ..= 0xBFFF => { 275 | // PRG RAM Protect 276 | // Intentionally not emulated, for compatability with iNES mapper 004 277 | }, 278 | 0xC000 ..= 0xDFFF => { 279 | self.irq_reload_requested = true; 280 | }, 281 | 0xE000 ..= 0xFFFF => { 282 | self.irq_enabled = true; 283 | } 284 | _ => (), 285 | } 286 | } 287 | }, 288 | _ => (), 289 | } 290 | } 291 | 292 | fn read_ppu(&mut self, address: u16) -> Option { 293 | self.snoop_ppu_a12(address); 294 | return self._read_ppu(address); 295 | } 296 | 297 | fn access_ppu(&mut self, address: u16) { 298 | self.snoop_ppu_a12(address); 299 | } 300 | 301 | fn debug_read_ppu(&self, address: u16) -> Option { 302 | return self._read_ppu(address); 303 | } 304 | 305 | fn write_ppu(&mut self, address: u16, data: u8) { 306 | self.snoop_ppu_a12(address); 307 | match address { 308 | // CHR RAM (if enabled) 309 | 0x0000 ..= 0x1FFF => { 310 | self.last_chr_read = address; 311 | let current_a12 = ((address & 0b0001_0000_0000_0000) >> 12) as u8; 312 | if current_a12 == 1 && self.last_a12 == 0 { 313 | if self.irq_counter == 0 || self.irq_reload_requested { 314 | self.irq_counter = self.irq_reload; 315 | self.irq_reload_requested = false; 316 | } else { 317 | self.irq_counter -= 1; 318 | } 319 | if self.irq_counter == 0 && self.irq_enabled { 320 | self.irq_flag = true; 321 | } 322 | } 323 | self.last_a12 = current_a12; 324 | if self.switch_chr_banks { 325 | match address { 326 | 0x0000 ..= 0x03FF => self.chr.banked_write(0x400, self.chr1_bank_2, address as usize - 0x000, data), 327 | 0x0400 ..= 0x07FF => self.chr.banked_write(0x400, self.chr1_bank_3, address as usize - 0x400, data), 328 | 0x0800 ..= 0x0BFF => self.chr.banked_write(0x400, self.chr1_bank_4, address as usize - 0x800, data), 329 | 0x0C00 ..= 0x0FFF => self.chr.banked_write(0x400, self.chr1_bank_5, address as usize - 0xC00, data), 330 | 0x1000 ..= 0x17FF => self.chr.banked_write(0x800, self.chr2_bank_0 >> 1, address as usize - 0x1000, data), 331 | 0x1800 ..= 0x1FFF => self.chr.banked_write(0x800, self.chr2_bank_1 >> 1, address as usize - 0x1800, data), 332 | _ => {}, 333 | } 334 | } else { 335 | match address { 336 | 0x0000 ..= 0x07FF => self.chr.banked_write(0x800, self.chr2_bank_0 >> 1, address as usize - 0x000, data), 337 | 0x0800 ..= 0x0FFF => self.chr.banked_write(0x800, self.chr2_bank_1 >> 1, address as usize - 0x800, data), 338 | 0x1000 ..= 0x13FF => self.chr.banked_write(0x400, self.chr1_bank_2, address as usize - 0x1000, data), 339 | 0x1400 ..= 0x17FF => self.chr.banked_write(0x400, self.chr1_bank_3, address as usize - 0x1400, data), 340 | 0x1800 ..= 0x1BFF => self.chr.banked_write(0x400, self.chr1_bank_4, address as usize - 0x1800, data), 341 | 0x1C00 ..= 0x1FFF => self.chr.banked_write(0x400, self.chr1_bank_5, address as usize - 0x1C00, data), 342 | _ => {}, 343 | } 344 | } 345 | }, 346 | 0x2000 ..= 0x3FFF => match self.mirroring { 347 | Mirroring::Horizontal => self.vram[mirroring::horizontal_mirroring(address) as usize] = data, 348 | Mirroring::Vertical => self.vram[mirroring::vertical_mirroring(address) as usize] = data, 349 | Mirroring::FourScreen => self.vram[mirroring::four_banks(address) as usize] = data, 350 | _ => {} 351 | }, 352 | _ => (), 353 | } 354 | } 355 | 356 | fn has_sram(&self) -> bool { 357 | return true; 358 | } 359 | 360 | fn get_sram(&self) -> Vec { 361 | return self.prg_ram.as_vec().clone(); 362 | } 363 | 364 | fn load_sram(&mut self, sram_data: Vec) { 365 | *self.prg_ram.as_mut_vec() = sram_data; 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /src/mmc/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod mapper; 2 | pub mod mirroring; 3 | 4 | pub mod action53; 5 | pub mod axrom; 6 | pub mod bnrom; 7 | pub mod cnrom; 8 | pub mod fds; 9 | pub mod fme7; 10 | pub mod gxrom; 11 | pub mod ines31; 12 | pub mod mmc1; 13 | pub mod mmc3; 14 | pub mod mmc5; 15 | pub mod n163; 16 | pub mod none; 17 | pub mod nrom; 18 | pub mod nsf; 19 | pub mod pxrom; 20 | pub mod rainbow; 21 | pub mod uxrom; 22 | pub mod vrc6; 23 | pub mod vrc7; 24 | -------------------------------------------------------------------------------- /src/mmc/none.rs: -------------------------------------------------------------------------------- 1 | // A dummy mapper with no loaded data. Useful for initializing an NesState 2 | // with no actual cartridge loaded. 3 | 4 | use mmc::mapper::*; 5 | 6 | pub struct NoneMapper { 7 | } 8 | 9 | impl NoneMapper { 10 | pub fn new() -> NoneMapper { 11 | return NoneMapper { 12 | } 13 | } 14 | } 15 | 16 | impl Mapper for NoneMapper { 17 | fn mirroring(&self) -> Mirroring { 18 | return Mirroring::Horizontal; 19 | } 20 | 21 | fn debug_read_cpu(&self, _: u16) -> Option { 22 | return None; 23 | } 24 | 25 | fn debug_read_ppu(&self, _: u16) -> Option { 26 | return None; 27 | } 28 | 29 | fn write_cpu(&mut self, _: u16, _: u8) { 30 | //Do nothing 31 | } 32 | 33 | fn write_ppu(&mut self, _: u16, _: u8) { 34 | //Do nothing 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/mmc/nrom.rs: -------------------------------------------------------------------------------- 1 | // A very simple Mapper with no esoteric features or bank switching. 2 | // Reference capabilities: https://wiki.nesdev.com/w/index.php/NROM 3 | 4 | use ines::INesCartridge; 5 | use memoryblock::MemoryBlock; 6 | 7 | use mmc::mapper::*; 8 | use mmc::mirroring; 9 | 10 | pub struct Nrom { 11 | prg_rom: MemoryBlock, 12 | prg_ram: MemoryBlock, 13 | chr: MemoryBlock, 14 | 15 | mirroring: Mirroring, 16 | vram: Vec, 17 | } 18 | 19 | impl Nrom { 20 | pub fn from_ines(ines: INesCartridge) -> Result { 21 | let prg_rom_block = ines.prg_rom_block(); 22 | let prg_ram_block = ines.prg_ram_block()?; 23 | let chr_block = ines.chr_block()?; 24 | 25 | println!("NROM Mirroring Mode: {}", mirroring_mode_name(ines.header.mirroring())); 26 | 27 | return Ok(Nrom { 28 | prg_rom: prg_rom_block.clone(), 29 | prg_ram: prg_ram_block.clone(), 30 | chr: chr_block.clone(), 31 | mirroring: ines.header.mirroring(), 32 | vram: vec![0u8; 0x1000], 33 | }); 34 | } 35 | } 36 | 37 | impl Mapper for Nrom { 38 | fn print_debug_status(&self) { 39 | println!("======= NROM ======="); 40 | println!("Mirroring Mode: {}", mirroring_mode_name(self.mirroring)); 41 | println!("===================="); 42 | } 43 | 44 | fn mirroring(&self) -> Mirroring { 45 | return self.mirroring; 46 | } 47 | 48 | fn debug_read_cpu(&self, address: u16) -> Option { 49 | match address { 50 | 0x6000 ..= 0x7FFF => {self.prg_ram.wrapping_read((address - 0x6000) as usize)}, 51 | 0x8000 ..= 0xFFFF => {self.prg_rom.wrapping_read((address - 0x8000) as usize)}, 52 | _ => None 53 | } 54 | } 55 | 56 | fn write_cpu(&mut self, address: u16, data: u8) { 57 | match address { 58 | 0x6000 ..= 0x7FFF => {self.prg_ram.wrapping_write((address - 0x6000) as usize, data);}, 59 | _ => {} 60 | } 61 | } 62 | 63 | fn debug_read_ppu(&self, address: u16) -> Option { 64 | match address { 65 | 0x0000 ..= 0x1FFF => return self.chr.wrapping_read(address as usize), 66 | 0x2000 ..= 0x3FFF => return match self.mirroring { 67 | Mirroring::Horizontal => Some(self.vram[mirroring::horizontal_mirroring(address) as usize]), 68 | Mirroring::Vertical => Some(self.vram[mirroring::vertical_mirroring(address) as usize]), 69 | // Note: no licensed NROM boards support four-screen mirroring, but it is possible 70 | // to build a board that does. Since iNes allows this, some homebrew requires it, and 71 | // so we support it in the interest of compatibility. 72 | Mirroring::FourScreen => Some(self.vram[mirroring::four_banks(address) as usize]), 73 | _ => None 74 | }, 75 | _ => return None 76 | } 77 | } 78 | 79 | fn write_ppu(&mut self, address: u16, data: u8) { 80 | match address { 81 | 0x0000 ..= 0x1FFF => {self.chr.wrapping_write(address as usize, data);}, 82 | 0x2000 ..= 0x3FFF => match self.mirroring { 83 | Mirroring::Horizontal => self.vram[mirroring::horizontal_mirroring(address) as usize] = data, 84 | Mirroring::Vertical => self.vram[mirroring::vertical_mirroring(address) as usize] = data, 85 | Mirroring::FourScreen => self.vram[mirroring::four_banks(address) as usize] = data, 86 | _ => {} 87 | }, 88 | _ => {} 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/mmc/pxrom.rs: -------------------------------------------------------------------------------- 1 | // MMC2, a somewhat advanced bank switcher with extended CHR memory 2 | // https://wiki.nesdev.com/w/index.php/MMC2 3 | 4 | use ines::INesCartridge; 5 | use memoryblock::MemoryBlock; 6 | 7 | use mmc::mapper::*; 8 | use mmc::mirroring; 9 | 10 | pub struct PxRom { 11 | pub prg_rom: MemoryBlock, 12 | pub prg_ram: MemoryBlock, 13 | pub chr: MemoryBlock, 14 | pub mirroring: Mirroring, 15 | pub chr_0_latch: u8, 16 | pub chr_0_fd_bank: usize, 17 | pub chr_0_fe_bank: usize, 18 | pub chr_1_latch: u8, 19 | pub chr_1_fd_bank: usize, 20 | pub chr_1_fe_bank: usize, 21 | pub prg_bank: usize, 22 | pub vram: Vec, 23 | } 24 | 25 | impl PxRom { 26 | pub fn from_ines(ines: INesCartridge) -> Result { 27 | let prg_rom_block = ines.prg_rom_block(); 28 | let prg_ram_block = ines.prg_ram_block()?; 29 | let chr_block = ines.chr_block()?; 30 | 31 | return Ok(PxRom { 32 | prg_rom: prg_rom_block.clone(), 33 | prg_ram: prg_ram_block.clone(), 34 | chr: chr_block.clone(), 35 | mirroring: Mirroring::Vertical, 36 | chr_0_latch: 0, 37 | chr_0_fd_bank: 0, 38 | chr_0_fe_bank: 0, 39 | chr_1_latch: 0, 40 | chr_1_fd_bank: 0, 41 | chr_1_fe_bank: 0, 42 | prg_bank: 0, 43 | vram: vec![0u8; 0x1000], 44 | }) 45 | } 46 | } 47 | 48 | impl Mapper for PxRom { 49 | fn print_debug_status(&self) { 50 | println!("======= PxROM ======="); 51 | println!("PRG Bank: {}, ", self.prg_bank); 52 | println!("CHR0 0xFD Bank: {}. CHR0 0xFE Bank: {}", self.chr_0_fd_bank, self.chr_0_fe_bank); 53 | println!("CHR1 0xFD Bank: {}. CHR1 0xFE Bank: {}", self.chr_1_fd_bank, self.chr_1_fe_bank); 54 | println!("Mirroring Mode: {}", mirroring_mode_name(self.mirroring)); 55 | println!("===================="); 56 | } 57 | 58 | fn mirroring(&self) -> Mirroring { 59 | return self.mirroring; 60 | } 61 | 62 | fn debug_read_cpu(&self, address: u16) -> Option { 63 | match address { 64 | 0x6000 ..= 0x7FFF => self.prg_ram.wrapping_read((address - 0x6000) as usize), 65 | 0x8000 ..= 0x9FFF => self.prg_rom.banked_read(0x2000, self.prg_bank, address as usize - 0x8000), 66 | 0xA000 ..= 0xBFFF => self.prg_rom.banked_read(0x2000, 0xFD, address as usize - 0xA000), 67 | 0xC000 ..= 0xDFFF => self.prg_rom.banked_read(0x2000, 0xFE, address as usize - 0xC000), 68 | 0xE000 ..= 0xFFFF => self.prg_rom.banked_read(0x2000, 0xFF, address as usize - 0xE000), 69 | _ => None 70 | } 71 | } 72 | 73 | fn write_cpu(&mut self, address: u16, data: u8) { 74 | match address { 75 | 0x6000 ..= 0x7FFF => self.prg_ram.wrapping_write(address as usize, data), 76 | 0xA000 ..= 0xAFFF => { self.prg_bank = (data & 0b0000_1111) as usize; }, 77 | 0xB000 ..= 0xBFFF => { self.chr_0_fd_bank = (data & 0b0001_1111) as usize; }, 78 | 0xC000 ..= 0xCFFF => { self.chr_0_fe_bank = (data & 0b0001_1111) as usize; }, 79 | 0xD000 ..= 0xDFFF => { self.chr_1_fd_bank = (data & 0b0001_1111) as usize; }, 80 | 0xE000 ..= 0xEFFF => { self.chr_1_fe_bank = (data & 0b0001_1111) as usize; }, 81 | 0xF000 ..= 0xFFFF => { 82 | if data & 0b1 == 0 { 83 | self.mirroring = Mirroring::Vertical; 84 | } else { 85 | self.mirroring = Mirroring::Horizontal; 86 | } 87 | }, 88 | _ => {} 89 | } 90 | } 91 | 92 | fn read_ppu(&mut self, address: u16) -> Option { 93 | match address { 94 | 0x0FD8 => {self.chr_0_latch = 0;}, 95 | 0x0FE8 => {self.chr_0_latch = 1;}, 96 | 0x1FD8 ..= 0x1FDF => {self.chr_1_latch = 0;}, 97 | 0x1FE8 ..= 0x1FEF => {self.chr_1_latch = 1;}, 98 | _ => {} 99 | } 100 | return self.debug_read_ppu(address); 101 | } 102 | 103 | fn debug_read_ppu(&self, address: u16) -> Option { 104 | match address { 105 | 0x0000 ..= 0x0FFF => { 106 | let chr_bank = match self.chr_0_latch { 107 | 0 => self.chr_0_fd_bank, 108 | 1 => self.chr_0_fe_bank, 109 | _ => 0 110 | }; 111 | self.chr.banked_read(0x1000, chr_bank, address as usize - 0x0000) 112 | }, 113 | 0x1000 ..= 0x1FFF => { 114 | let chr_bank = match self.chr_1_latch { 115 | 0 => self.chr_1_fd_bank, 116 | 1 => self.chr_1_fe_bank, 117 | _ => 0 118 | }; 119 | self.chr.banked_read(0x1000, chr_bank, address as usize - 0x0000) 120 | }, 121 | 0x2000 ..= 0x3FFF => match self.mirroring { 122 | Mirroring::Horizontal => Some(self.vram[mirroring::horizontal_mirroring(address) as usize]), 123 | Mirroring::Vertical => Some(self.vram[mirroring::vertical_mirroring(address) as usize]), 124 | _ => None 125 | }, 126 | _ => None 127 | } 128 | } 129 | 130 | fn write_ppu(&mut self, address: u16, data: u8) { 131 | match address { 132 | 0x2000 ..= 0x3FFF => match self.mirroring { 133 | Mirroring::Horizontal => self.vram[mirroring::horizontal_mirroring(address) as usize] = data, 134 | Mirroring::Vertical => self.vram[mirroring::vertical_mirroring(address) as usize] = data, 135 | _ => {} 136 | }, 137 | _ => {} 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/mmc/uxrom.rs: -------------------------------------------------------------------------------- 1 | // UxROM, simple bank switchable PRG ROM with the last page fixed 2 | // Reference capabilities: https://wiki.nesdev.com/w/index.php/UxROM 3 | 4 | use ines::INesCartridge; 5 | use memoryblock::MemoryBlock; 6 | 7 | use mmc::mapper::*; 8 | use mmc::mirroring; 9 | 10 | pub struct UxRom { 11 | pub prg_rom: MemoryBlock, 12 | pub chr: MemoryBlock, 13 | pub mirroring: Mirroring, 14 | pub prg_bank: usize, 15 | pub vram: Vec, 16 | } 17 | 18 | impl UxRom { 19 | pub fn from_ines(ines: INesCartridge) -> Result { 20 | let prg_rom_block = ines.prg_rom_block(); 21 | let chr_block = ines.chr_block()?; 22 | 23 | return Ok(UxRom { 24 | prg_rom: prg_rom_block.clone(), 25 | chr: chr_block.clone(), 26 | mirroring: ines.header.mirroring(), 27 | prg_bank: 0x00, 28 | vram: vec![0u8; 0x1000], 29 | }) 30 | } 31 | } 32 | 33 | impl Mapper for UxRom { 34 | fn print_debug_status(&self) { 35 | println!("======= UxROM ======="); 36 | println!("PRG Bank: {}, ", self.prg_bank); 37 | println!("Mirroring Mode: {}", mirroring_mode_name(self.mirroring)); 38 | println!("===================="); 39 | } 40 | 41 | fn mirroring(&self) -> Mirroring { 42 | return self.mirroring; 43 | } 44 | 45 | fn debug_read_cpu(&self, address: u16) -> Option { 46 | match address { 47 | 0x8000 ..= 0xBFFF => self.prg_rom.banked_read(0x4000, self.prg_bank, address as usize - 0x8000), 48 | 0xC000 ..= 0xFFFF => self.prg_rom.banked_read(0x4000, 0xFF, address as usize - 0xC000), 49 | _ => None 50 | } 51 | } 52 | 53 | fn write_cpu(&mut self, address: u16, data: u8) { 54 | match address { 55 | 0x8000 ..= 0xFFFF => { 56 | self.prg_bank = data as usize; 57 | } 58 | _ => {} 59 | } 60 | } 61 | 62 | fn debug_read_ppu(&self, address: u16) -> Option { 63 | match address { 64 | 0x0000 ..= 0x1FFF => self.chr.wrapping_read(address as usize), 65 | 0x2000 ..= 0x3FFF => match self.mirroring { 66 | Mirroring::Horizontal => Some(self.vram[mirroring::horizontal_mirroring(address) as usize]), 67 | Mirroring::Vertical => Some(self.vram[mirroring::vertical_mirroring(address) as usize]), 68 | _ => None 69 | }, 70 | _ => None 71 | } 72 | } 73 | 74 | fn write_ppu(&mut self, address: u16, data: u8) { 75 | match address { 76 | 0x0000 ..= 0x1FFF => self.chr.wrapping_write(address as usize, data), 77 | 0x2000 ..= 0x3FFF => match self.mirroring { 78 | Mirroring::Horizontal => self.vram[mirroring::horizontal_mirroring(address) as usize] = data, 79 | Mirroring::Vertical => self.vram[mirroring::vertical_mirroring(address) as usize] = data, 80 | _ => {} 81 | }, 82 | _ => {} 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/nes.rs: -------------------------------------------------------------------------------- 1 | use apu::ApuState; 2 | use cartridge; 3 | use cycle_cpu; 4 | use cycle_cpu::CpuState; 5 | use cycle_cpu::Registers; 6 | use memory; 7 | use memory::CpuMemory; 8 | use ppu::PpuState; 9 | use mmc::mapper::Mapper; 10 | use tracked_events::EventTracker; 11 | 12 | pub struct NesState { 13 | pub apu: ApuState, 14 | pub cpu: CpuState, 15 | pub memory: CpuMemory, 16 | pub ppu: PpuState, 17 | pub registers: Registers, 18 | pub master_clock: u64, 19 | pub p1_input: u8, 20 | pub p1_data: u8, 21 | pub p2_input: u8, 22 | pub p2_data: u8, 23 | pub input_latch: bool, 24 | pub mapper: Box, 25 | pub last_frame: u32, 26 | pub event_tracker: EventTracker, 27 | } 28 | 29 | impl NesState { 30 | pub fn new(m: Box) -> NesState { 31 | return NesState { 32 | apu: ApuState::new(), 33 | cpu: CpuState::new(), 34 | memory: CpuMemory::new(), 35 | ppu: PpuState::new(), 36 | registers: Registers::new(), 37 | master_clock: 0, 38 | p1_input: 0, 39 | p1_data: 0, 40 | p2_input: 0, 41 | p2_data: 0, 42 | input_latch: false, 43 | mapper: m, 44 | last_frame: 0, 45 | event_tracker: EventTracker::new(), 46 | } 47 | } 48 | 49 | #[deprecated(since="0.2.0", note="please use `::new(mapper)` instead")] 50 | pub fn from_rom(cart_data: &[u8]) -> Result { 51 | let maybe_mapper = cartridge::mapper_from_file(cart_data); 52 | match maybe_mapper { 53 | Ok(mapper) => { 54 | let mut nes = NesState::new(mapper); 55 | nes.power_on(); 56 | return Ok(nes); 57 | }, 58 | Err(why) => { 59 | return Err(why); 60 | } 61 | } 62 | } 63 | 64 | pub fn power_on(&mut self) { 65 | // Initialize CPU register state for power-up sequence 66 | self.registers.a = 0; 67 | self.registers.y = 0; 68 | self.registers.x = 0; 69 | self.registers.s = 0xFD; 70 | 71 | self.registers.set_status_from_byte(0x34); 72 | 73 | // Initialize I/O and Audio registers to known startup values 74 | for i in 0x4000 .. (0x400F + 1) { 75 | memory::write_byte(self, i, 0); 76 | } 77 | memory::write_byte(self, 0x4015, 0); 78 | memory::write_byte(self, 0x4017, 0); 79 | 80 | let pc_low = memory::read_byte(self, 0xFFFC); 81 | let pc_high = memory::read_byte(self, 0xFFFD); 82 | self.registers.pc = pc_low as u16 + ((pc_high as u16) << 8); 83 | 84 | // Clock the APU 10 times (this subtly affects the first IRQ's timing and frame counter operation) 85 | for _ in 0 .. 10 { 86 | self.apu.clock_apu(&mut *self.mapper); 87 | } 88 | } 89 | 90 | pub fn reset(&mut self) { 91 | self.registers.s = self.registers.s.wrapping_sub(3); 92 | self.registers.flags.interrupts_disabled = true; 93 | 94 | // Silence the APU 95 | memory::write_byte(self, 0x4015, 0); 96 | 97 | let pc_low = memory::read_byte(self, 0xFFFC); 98 | let pc_high = memory::read_byte(self, 0xFFFD); 99 | self.registers.pc = pc_low as u16 + ((pc_high as u16) << 8); 100 | } 101 | 102 | pub fn cycle(&mut self) { 103 | cycle_cpu::run_one_clock(self); 104 | self.master_clock = self.master_clock + 12; 105 | // Three PPU clocks per every 1 CPU clock 106 | self.ppu.clock(&mut *self.mapper); 107 | self.ppu.clock(&mut *self.mapper); 108 | self.ppu.clock(&mut *self.mapper); 109 | self.event_tracker.current_scanline = self.ppu.current_scanline; 110 | self.event_tracker.current_cycle = self.ppu.current_scanline_cycle; 111 | self.apu.clock_apu(&mut *self.mapper); 112 | self.mapper.clock_cpu(); 113 | } 114 | 115 | pub fn step(&mut self) { 116 | // Always run at least one cycle 117 | self.cycle(); 118 | let mut i = 0; 119 | // Continue until either we loop back around to cycle 0 (a new instruction) 120 | // or this instruction has failed to reset (encountered a STP or an opcode bug) 121 | while self.cpu.tick >= 1 && i < 10 { 122 | self.cycle(); 123 | i += 1; 124 | } 125 | if self.ppu.current_frame != self.last_frame { 126 | self.event_tracker.swap_buffers(); 127 | self.last_frame = self.ppu.current_frame; 128 | } 129 | } 130 | 131 | pub fn run_until_hblank(&mut self) { 132 | let old_scanline = self.ppu.current_scanline; 133 | while old_scanline == self.ppu.current_scanline { 134 | self.step(); 135 | } 136 | } 137 | 138 | pub fn run_until_vblank(&mut self) { 139 | while self.ppu.current_scanline == 242 { 140 | self.step(); 141 | } 142 | while self.ppu.current_scanline != 242 { 143 | self.step(); 144 | } 145 | } 146 | 147 | pub fn nudge_ppu_alignment(&mut self) { 148 | // Give the PPU a swift kick: 149 | self.ppu.clock(&mut *self.mapper); 150 | self.event_tracker.current_scanline = self.ppu.current_scanline; 151 | self.event_tracker.current_cycle = self.ppu.current_scanline_cycle; 152 | } 153 | 154 | pub fn sram(&self) -> Vec { 155 | return self.mapper.get_sram(); 156 | } 157 | 158 | pub fn set_sram(&mut self, sram_data: Vec) { 159 | if sram_data.len() != self.mapper.get_sram().len() { 160 | println!("SRAM size mismatch, expected {} bytes but file is {} bytes!", self.mapper.get_sram().len(), sram_data.len()); 161 | } else { 162 | self.mapper.load_sram(sram_data); 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/nsf.rs: -------------------------------------------------------------------------------- 1 | // NSF, a container format for NES music data and the 6502 code 2 | // necessary to play it back. Often used to house ripped music engines 3 | // from commercial games, but sees additional popularity for modern 4 | // chiptune artists and occasionally indie games. 5 | // https://wiki.nesdev.com/w/index.php/NSF 6 | 7 | // RusticNES is an NES emulator, so it will be attempting to mimick 8 | // the limitations of a hardware player. Some advanced NSF files, 9 | // especially those with many expansion chips or a fast playback rate, 10 | // may not perform correctly in RusticNES, just as they would fail in most 11 | // hardware NSF player implementations. This is a feature, not a bug. 12 | 13 | use std::io::Read; 14 | use std::error::Error; 15 | use std::fmt; 16 | 17 | #[derive(Copy, Clone)] 18 | pub struct NsfHeader { 19 | raw_bytes: [u8; 0x80] 20 | } 21 | 22 | const MSDOS_EOF: u8 = 0x1A; 23 | 24 | const NSF_MAGIC_N: usize = 0x000; 25 | const NSF_MAGIC_E: usize = 0x001; 26 | const NSF_MAGIC_S: usize = 0x002; 27 | const NSF_MAGIC_M: usize = 0x003; 28 | const NSF_MAGIC_EOF: usize = 0x004; 29 | const NSF_VERSION: usize = 0x005; 30 | const NSF_TOTAL_SONGS: usize = 0x006; 31 | const NSF_STARTING_SONG: usize = 0x007; 32 | const NSF_LOAD_ADDR: usize = 0x008; 33 | const NSF_INIT_ADDR: usize = 0x00A; 34 | const NSF_PLAY_ADDR: usize = 0x00C; 35 | const NSF_SONG_NAME: usize = 0x00E; 36 | const NSF_ARTIST_NAME: usize = 0x02E; 37 | const NSF_COPYRIGHT_HOLDER: usize = 0x04E; 38 | const NSF_NTSC_PLAY_SPEED: usize = 0x06E; 39 | const NSF_BANK_INIT: usize = 0x070; 40 | const NSF_PAL_PLAY_SPEED: usize = 0x078; 41 | //const NSF_NTSC_PAL_SELECTION: usize = 0x07A; 42 | const NSF_EXPANSION_CHIPS: usize = 0x07B; 43 | //const NSF2_FLAGS: usize = 0x07C; 44 | const NSF_PRG_LENGTH: usize = 0x07D; 45 | 46 | impl NsfHeader { 47 | pub fn from(raw_bytes: &[u8]) -> NsfHeader { 48 | let mut header = NsfHeader { 49 | raw_bytes: [0u8; 0x80], 50 | }; 51 | header.raw_bytes.copy_from_slice(&raw_bytes[0..0x80]); 52 | return header; 53 | } 54 | 55 | pub fn magic_header_valid(&self) -> bool { 56 | return 57 | self.raw_bytes[NSF_MAGIC_N] as char == 'N' && 58 | self.raw_bytes[NSF_MAGIC_E] as char == 'E' && 59 | self.raw_bytes[NSF_MAGIC_S] as char == 'S' && 60 | self.raw_bytes[NSF_MAGIC_M] as char == 'M' && 61 | self.raw_bytes[NSF_MAGIC_EOF] == MSDOS_EOF; 62 | } 63 | 64 | pub fn version_number(&self) -> u8 { 65 | return self.raw_bytes[NSF_VERSION]; 66 | } 67 | 68 | pub fn total_songs(&self) -> u8 { 69 | return self.raw_bytes[NSF_TOTAL_SONGS]; 70 | } 71 | 72 | pub fn starting_song(&self) -> u8 { 73 | return self.raw_bytes[NSF_STARTING_SONG]; 74 | } 75 | 76 | pub fn _word(&self, offset: usize) -> u16 { 77 | let addr_low = self.raw_bytes[offset + 0] as u16; 78 | let addr_high = (self.raw_bytes[offset + 1] as u16) << 8; 79 | return addr_low + addr_high; 80 | } 81 | 82 | pub fn load_address(&self) -> u16 { 83 | return self._word(NSF_LOAD_ADDR); 84 | } 85 | 86 | pub fn init_address(&self) -> u16 { 87 | return self._word(NSF_INIT_ADDR); 88 | } 89 | 90 | pub fn play_address(&self) -> u16 { 91 | return self._word(NSF_PLAY_ADDR); 92 | } 93 | 94 | /* strings are complicated, let's skip them for now */ 95 | 96 | pub fn ntsc_playback_speed(&self) -> u16 { 97 | return self._word(NSF_NTSC_PLAY_SPEED); 98 | } 99 | 100 | pub fn pal_playback_speed(&self) -> u16 { 101 | return self._word(NSF_PAL_PLAY_SPEED); 102 | } 103 | 104 | pub fn initial_banks(&self) -> Vec { 105 | return vec![ 106 | self.raw_bytes[NSF_BANK_INIT + 0] as usize, 107 | self.raw_bytes[NSF_BANK_INIT + 1] as usize, 108 | self.raw_bytes[NSF_BANK_INIT + 2] as usize, 109 | self.raw_bytes[NSF_BANK_INIT + 3] as usize, 110 | self.raw_bytes[NSF_BANK_INIT + 4] as usize, 111 | self.raw_bytes[NSF_BANK_INIT + 5] as usize, 112 | self.raw_bytes[NSF_BANK_INIT + 6] as usize, 113 | self.raw_bytes[NSF_BANK_INIT + 7] as usize, 114 | ]; 115 | } 116 | 117 | pub fn is_bank_switched(&self) -> bool { 118 | return 119 | (self.raw_bytes[NSF_BANK_INIT + 0] != 0) || 120 | (self.raw_bytes[NSF_BANK_INIT + 1] != 0) || 121 | (self.raw_bytes[NSF_BANK_INIT + 2] != 0) || 122 | (self.raw_bytes[NSF_BANK_INIT + 3] != 0) || 123 | (self.raw_bytes[NSF_BANK_INIT + 4] != 0) || 124 | (self.raw_bytes[NSF_BANK_INIT + 5] != 0) || 125 | (self.raw_bytes[NSF_BANK_INIT + 6] != 0) || 126 | (self.raw_bytes[NSF_BANK_INIT + 7] != 0); 127 | } 128 | 129 | pub fn program_length(&self) -> usize { 130 | let addr_low = self.raw_bytes[NSF_PRG_LENGTH + 0] as usize; 131 | let addr_mid = (self.raw_bytes[NSF_PRG_LENGTH + 1] as usize) << 8; 132 | let addr_high = (self.raw_bytes[NSF_PRG_LENGTH + 2] as usize) << 16; 133 | return addr_low + addr_mid + addr_high; 134 | } 135 | 136 | pub fn vrc6(&self) -> bool { 137 | return (self.raw_bytes[NSF_EXPANSION_CHIPS] & 0b0000_0001) != 0; 138 | } 139 | 140 | pub fn vrc7(&self) -> bool { 141 | return (self.raw_bytes[NSF_EXPANSION_CHIPS] & 0b0000_0010) != 0; 142 | } 143 | 144 | pub fn fds(&self) -> bool { 145 | return (self.raw_bytes[NSF_EXPANSION_CHIPS] & 0b0000_0100) != 0; 146 | } 147 | 148 | pub fn mmc5(&self) -> bool { 149 | return (self.raw_bytes[NSF_EXPANSION_CHIPS] & 0b0000_1000) != 0; 150 | } 151 | 152 | pub fn n163(&self) -> bool { 153 | return (self.raw_bytes[NSF_EXPANSION_CHIPS] & 0b0001_0000) != 0; 154 | } 155 | 156 | pub fn s5b(&self) -> bool { 157 | return (self.raw_bytes[NSF_EXPANSION_CHIPS] & 0b0010_0000) != 0; 158 | } 159 | 160 | pub fn song_name(&self) -> Vec { 161 | return self.raw_bytes[NSF_SONG_NAME ..= (NSF_SONG_NAME + 32)].to_vec(); 162 | } 163 | 164 | pub fn artist_name(&self) -> Vec { 165 | return self.raw_bytes[NSF_ARTIST_NAME ..= (NSF_ARTIST_NAME + 32)].to_vec(); 166 | } 167 | 168 | pub fn copyright_holder(&self) -> Vec { 169 | return self.raw_bytes[NSF_COPYRIGHT_HOLDER ..= (NSF_COPYRIGHT_HOLDER + 32)].to_vec(); 170 | } 171 | } 172 | 173 | #[derive(Debug)] 174 | pub enum NsfError { 175 | InvalidHeader, 176 | Unimplemented, 177 | ReadError{reason: String} 178 | } 179 | 180 | impl Error for NsfError {} 181 | 182 | impl fmt::Display for NsfError { 183 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 184 | match self { 185 | NsfError::InvalidHeader => {write!(f, "Invalid NSF Header")}, 186 | NsfError::Unimplemented => {write!(f, "Unimplemented (Lazy programmers!!1)")}, 187 | NsfError::ReadError{reason} => {write!(f, "Error reading cartridge: {}", reason)} 188 | } 189 | } 190 | } 191 | 192 | impl From for NsfError { 193 | fn from(error: std::io::Error) -> Self { 194 | return NsfError::ReadError{reason: error.to_string()}; 195 | } 196 | } 197 | 198 | #[derive(Clone)] 199 | pub struct NsfFile { 200 | // Internal strategy is to store each major chunk of the file as 201 | // raw data bytes, and then reinterpret these on the fly based 202 | // on the header bytes when accessed. 203 | pub header: NsfHeader, 204 | pub prg: Vec, 205 | pub metadata: Vec, 206 | } 207 | 208 | impl NsfFile { 209 | pub fn from_reader(file_reader: &mut dyn Read) -> Result { 210 | let mut header_bytes = [0u8; 0x80]; 211 | file_reader.read_exact(&mut header_bytes)?; 212 | 213 | let header = NsfHeader::from(&header_bytes); 214 | if !header.magic_header_valid() { 215 | return Err(NsfError::InvalidHeader); 216 | } 217 | 218 | let mut prg: Vec = Vec::new(); 219 | let mut metadata: Vec = Vec::new(); 220 | if header.program_length() == 0 { 221 | // There is no explicit length, so consider the entire rest of the file 222 | // to be program data 223 | file_reader.read_to_end(&mut prg)?; 224 | } else { 225 | // The size specifies only the program data area 226 | prg.resize(header.program_length(), 0); 227 | file_reader.read_exact(&mut prg)?; 228 | // Everything else is "metadata" which for NSF2 might be parsed out separately. 229 | // It should not be considered part of the rom image. 230 | file_reader.read_to_end(&mut metadata)?; 231 | } 232 | 233 | if header.is_bank_switched() { 234 | // Pad the beginning of this data with zero bytes up to the load address 235 | let padding_bytes = (header.load_address() & 0x0FFF) as usize; 236 | let mut rom_image = Vec::new(); 237 | rom_image.resize(padding_bytes, 0); 238 | rom_image.extend(prg); 239 | // If the final length at this point is not a multiple of 4k, the size of one PRG bank, 240 | // then we now additionally extend it to fill out the last bank to this boundary 241 | if rom_image.len() % 0x1000 != 0 { 242 | let alignment_shortage = 0x1000 - (rom_image.len() % 0x1000); 243 | let aligned_size = rom_image.len() + alignment_shortage; 244 | rom_image.resize(aligned_size, 0); 245 | } 246 | prg = rom_image; 247 | } 248 | 249 | return Ok(NsfFile { 250 | header: header, 251 | prg: prg, 252 | metadata: metadata 253 | }); 254 | } 255 | } 256 | 257 | -------------------------------------------------------------------------------- /src/opcode_info.rs: -------------------------------------------------------------------------------- 1 | pub fn alu_block(addressing_mode_index: u8, opcode_index: u8) -> (&'static str, &'static str) { 2 | let addressing_mode = match addressing_mode_index { 3 | // Zero Page Mode 4 | 0b000 => "(d, x)", 5 | 0b001 => "d", 6 | 0b010 => "#i", 7 | 0b011 => "a", 8 | 0b100 => "(d), y", 9 | 0b101 => "d, x", 10 | 0b110 => "a, y", 11 | 0b111 => "a, x", 12 | 13 | // Not implemented yet 14 | _ => "???", 15 | }; 16 | 17 | let opcode_name = match opcode_index { 18 | 0b000 => "ORA", 19 | 0b001 => "AND", 20 | 0b010 => "EOR", 21 | 0b011 => "ADC", 22 | 0b100 => "STA", 23 | 0b101 => "LDA", 24 | 0b110 => "CMP", 25 | 0b111 => "SBC", 26 | _ => "???" 27 | }; 28 | 29 | return (opcode_name, addressing_mode); 30 | } 31 | 32 | pub fn rmw_block(opcode: u8, addressing_mode_index: u8, opcode_index: u8) -> (&'static str, &'static str) { 33 | // First, handle some block 10 opcodes that break the mold 34 | return match opcode { 35 | // Assorted NOPs 36 | 0x82 | 0xC2 | 0xE2 => ("NOP", "#i"), 37 | 0x1A | 0x3A | 0x5A | 0x7A | 0xDA | 0xEA | 0xFA => ("NOP", ""), 38 | // Certain opcodes may be vital to your success. THESE opcodes are not. 39 | 0x02 | 0x22 | 0x42 | 0x62 | 0x12 | 0x32 | 0x52 | 0x72 | 0x92 | 0xB2 | 0xD2 | 0xF2 => ("STP", ""), 40 | 0xA2 => ("LDX", "#i"), 41 | 0x8A => ("TXA", ""), 42 | 0xAA => ("TAX", ""), 43 | 0xCA => ("DEX", ""), 44 | 0x9A => ("TXS", ""), 45 | 0xBA => ("TSX", ""), 46 | 0x96 => ("STX", "d, y"), 47 | 0xB6 => ("LDX", "d, y"), 48 | 0xBE => ("LDX", "a, y"), 49 | _ => { 50 | let addressing_mode = match addressing_mode_index { 51 | // Zero Page Mode 52 | 0b001 => "d", 53 | 0b010 => "", // Note: masked for 8A, AA, CA, and EA above 54 | 0b011 => "a", 55 | 0b101 => "d, x", 56 | 0b111 => "a, x", 57 | 58 | // Not implemented yet 59 | _ => "???", 60 | }; 61 | 62 | let opcode_name = match opcode_index { 63 | 0b000 => "ASL", 64 | 0b001 => "ROL", 65 | 0b010 => "LSR", 66 | 0b011 => "ROR", 67 | 0b100 => "STX", 68 | 0b101 => "LDX", 69 | 0b110 => "DEC", 70 | 0b111 => "INC", 71 | _ => "???" 72 | }; 73 | 74 | return (opcode_name, addressing_mode); 75 | } 76 | }; 77 | } 78 | 79 | pub fn control_block(opcode: u8) -> (&'static str, &'static str) { 80 | // Everything is pretty irregular, so we'll just match the whole opcode 81 | return match opcode { 82 | 0x10 => ("BPL", ""), 83 | 0x30 => ("BMI", ""), 84 | 0x50 => ("BVC", ""), 85 | 0x70 => ("BVS", ""), 86 | 0x90 => ("BCC", ""), 87 | 0xB0 => ("BCS", ""), 88 | 0xD0 => ("BNE", ""), 89 | 0xF0 => ("BEQ", ""), 90 | 91 | 0x00 => ("BRK", ""), 92 | 0x80 => ("NOP", "#i"), 93 | 94 | // Opcodes with similar addressing modes 95 | 0xA0 => ("LDY", "#i"), 96 | 0xC0 => ("CPY", "#i"), 97 | 0xE0 => ("CPX", "#i"), 98 | 0x24 => ("BIT", "d"), 99 | 0x84 => ("STY", "d"), 100 | 0xA4 => ("LDY", "d"), 101 | 0xC4 => ("CPY", "d"), 102 | 0xE4 => ("CPX", "d"), 103 | 0x2C => ("BIT", "a"), 104 | 0x8C => ("STY", "a"), 105 | 0xAC => ("LDY", "a"), 106 | 0xCC => ("CPY", "a"), 107 | 0xEC => ("CPX", "a"), 108 | 0x94 => ("STY", "d, x"), 109 | 0xB4 => ("LDY", "d, x"), 110 | 0xBC => ("LDY", "a, x"), 111 | 112 | 0x4C => ("JMP", "a"), 113 | 0x6C => ("JMP", "(a)"), 114 | 115 | 0x08 => ("PHP", ""), 116 | 0x28 => ("PLP", ""), 117 | 0x48 => ("PHA", ""), 118 | 0x68 => ("PLA", ""), 119 | 120 | 0x20 => ("JSR", ""), 121 | 0x40 => ("RTI", ""), 122 | 0x60 => ("RTS", ""), 123 | 124 | 0x88 => ("DEY", ""), 125 | 0xA8 => ("TAY", ""), 126 | 0xC8 => ("INY", ""), 127 | 0xE8 => ("INX", ""), 128 | 129 | 0x18 => ("CLC", ""), 130 | 0x58 => ("CLI", ""), 131 | 0xB8 => ("CLV", ""), 132 | 0xD8 => ("CLD", ""), 133 | 0x38 => ("SEC", ""), 134 | 0x78 => ("SEI", ""), 135 | 0xF8 => ("SED", ""), 136 | 0x98 => ("TYA", ""), 137 | 138 | _ => ("???", "???") 139 | }; 140 | } 141 | 142 | pub fn addressing_bytes(addressing_mode: &str) -> u8 { 143 | return match addressing_mode { 144 | "#i" | "d" | "(d, x)" | "(d), y" | "d, x" => 1, 145 | "a" | "a, x" | "a, y" | "(a)" => 2, 146 | _ => 0 147 | } 148 | } 149 | 150 | pub fn disassemble_instruction(opcode: u8, _: u8, _: u8) -> (String, u8) { 151 | let logic_block = opcode & 0b0000_0011; 152 | let addressing_mode_index = (opcode & 0b0001_1100) >> 2; 153 | let opcode_index = (opcode & 0b1110_0000) >> 5; 154 | 155 | let (opcode_name, addressing_mode) = match logic_block { 156 | 0b00 => control_block(opcode), 157 | 0b01 => alu_block(addressing_mode_index, opcode_index), 158 | 0b10 => rmw_block(opcode, addressing_mode_index, opcode_index), 159 | _ => ("???", "") 160 | }; 161 | 162 | let instruction = format!("{} {}", opcode_name, addressing_mode); 163 | let data_bytes = addressing_bytes(addressing_mode); 164 | return (instruction, data_bytes); 165 | } -------------------------------------------------------------------------------- /src/palettes.rs: -------------------------------------------------------------------------------- 1 | // Palette generated by http://bisqwit.iki.fi/utils/nespalette.php 2 | 3 | pub const NTSC_PAL: [u8; 64 * 8 * 3] = [ 4 | 0x52, 0x52, 0x52, 5 | 0x01, 0x1a, 0x51, 6 | 0x0f, 0x0f, 0x65, 7 | 0x23, 0x06, 0x63, 8 | 0x36, 0x03, 0x4b, 9 | 0x40, 0x04, 0x26, 10 | 0x3f, 0x09, 0x04, 11 | 0x32, 0x13, 0x00, 12 | 0x1f, 0x20, 0x00, 13 | 0x0b, 0x2a, 0x00, 14 | 0x00, 0x2f, 0x00, 15 | 0x00, 0x2e, 0x0a, 16 | 0x00, 0x26, 0x2d, 17 | 0x00, 0x00, 0x00, 18 | 0x00, 0x00, 0x00, 19 | 0x00, 0x00, 0x00, 20 | 0xa0, 0xa0, 0xa0, 21 | 0x1e, 0x4a, 0x9d, 22 | 0x38, 0x37, 0xbc, 23 | 0x58, 0x28, 0xb8, 24 | 0x75, 0x21, 0x94, 25 | 0x84, 0x23, 0x5c, 26 | 0x82, 0x2e, 0x24, 27 | 0x6f, 0x3f, 0x00, 28 | 0x51, 0x52, 0x00, 29 | 0x31, 0x63, 0x00, 30 | 0x1a, 0x6b, 0x05, 31 | 0x0e, 0x69, 0x2e, 32 | 0x10, 0x5c, 0x68, 33 | 0x00, 0x00, 0x00, 34 | 0x00, 0x00, 0x00, 35 | 0x00, 0x00, 0x00, 36 | 0xfe, 0xff, 0xff, 37 | 0x69, 0x9e, 0xfc, 38 | 0x89, 0x87, 0xff, 39 | 0xae, 0x76, 0xff, 40 | 0xce, 0x6d, 0xf1, 41 | 0xe0, 0x70, 0xb2, 42 | 0xde, 0x7c, 0x70, 43 | 0xc8, 0x91, 0x3e, 44 | 0xa6, 0xa7, 0x25, 45 | 0x81, 0xba, 0x28, 46 | 0x63, 0xc4, 0x46, 47 | 0x54, 0xc1, 0x7d, 48 | 0x56, 0xb3, 0xc0, 49 | 0x3c, 0x3c, 0x3c, 50 | 0x00, 0x00, 0x00, 51 | 0x00, 0x00, 0x00, 52 | 0xfe, 0xff, 0xff, 53 | 0xbe, 0xd6, 0xfd, 54 | 0xcc, 0xcc, 0xff, 55 | 0xdd, 0xc4, 0xff, 56 | 0xea, 0xc0, 0xf9, 57 | 0xf2, 0xc1, 0xdf, 58 | 0xf1, 0xc7, 0xc2, 59 | 0xe8, 0xd0, 0xaa, 60 | 0xd9, 0xda, 0x9d, 61 | 0xc9, 0xe2, 0x9e, 62 | 0xbc, 0xe6, 0xae, 63 | 0xb4, 0xe5, 0xc7, 64 | 0xb5, 0xdf, 0xe4, 65 | 0xa9, 0xa9, 0xa9, 66 | 0x00, 0x00, 0x00, 67 | 0x00, 0x00, 0x00, 68 | 0x4b, 0x34, 0x32, 69 | 0x00, 0x07, 0x2e, 70 | 0x0b, 0x01, 0x41, 71 | 0x1d, 0x00, 0x42, 72 | 0x30, 0x00, 0x31, 73 | 0x3b, 0x00, 0x16, 74 | 0x3d, 0x03, 0x00, 75 | 0x2f, 0x09, 0x00, 76 | 0x1c, 0x10, 0x00, 77 | 0x09, 0x16, 0x00, 78 | 0x00, 0x18, 0x00, 79 | 0x00, 0x15, 0x00, 80 | 0x00, 0x0d, 0x11, 81 | 0x00, 0x00, 0x00, 82 | 0x00, 0x00, 0x00, 83 | 0x00, 0x00, 0x00, 84 | 0x94, 0x71, 0x6c, 85 | 0x17, 0x27, 0x67, 86 | 0x30, 0x1c, 0x84, 87 | 0x4f, 0x13, 0x85, 88 | 0x6b, 0x11, 0x6b, 89 | 0x7c, 0x15, 0x41, 90 | 0x7e, 0x21, 0x16, 91 | 0x6a, 0x2c, 0x00, 92 | 0x4c, 0x38, 0x00, 93 | 0x2d, 0x42, 0x00, 94 | 0x16, 0x45, 0x00, 95 | 0x0a, 0x40, 0x10, 96 | 0x08, 0x32, 0x39, 97 | 0x00, 0x00, 0x00, 98 | 0x00, 0x00, 0x00, 99 | 0x00, 0x00, 0x00, 100 | 0xee, 0xbc, 0xb5, 101 | 0x5d, 0x67, 0xb0, 102 | 0x7c, 0x59, 0xcf, 103 | 0xa0, 0x4f, 0xd1, 104 | 0xc0, 0x4b, 0xb4, 105 | 0xd3, 0x51, 0x84, 106 | 0xd6, 0x60, 0x51, 107 | 0xbf, 0x6d, 0x25, 108 | 0x9d, 0x7b, 0x0f, 109 | 0x79, 0x87, 0x0e, 110 | 0x5c, 0x8a, 0x22, 111 | 0x4b, 0x84, 0x49, 112 | 0x49, 0x75, 0x7b, 113 | 0x36, 0x23, 0x21, 114 | 0x00, 0x00, 0x00, 115 | 0x00, 0x00, 0x00, 116 | 0xee, 0xbc, 0xb5, 117 | 0xaf, 0x98, 0xb3, 118 | 0xbd, 0x91, 0xc0, 119 | 0xcd, 0x8d, 0xc0, 120 | 0xdb, 0x8b, 0xb5, 121 | 0xe3, 0x8e, 0xa1, 122 | 0xe4, 0x94, 0x8a, 123 | 0xda, 0x9a, 0x75, 124 | 0xcc, 0xa1, 0x69, 125 | 0xbc, 0xa5, 0x69, 126 | 0xaf, 0xa7, 0x74, 127 | 0xa7, 0xa4, 0x86, 128 | 0xa6, 0x9e, 0x9d, 129 | 0x9d, 0x78, 0x73, 130 | 0x00, 0x00, 0x00, 131 | 0x00, 0x00, 0x00, 132 | 0x2e, 0x45, 0x27, 133 | 0x00, 0x13, 0x2c, 134 | 0x00, 0x07, 0x3a, 135 | 0x0a, 0x00, 0x35, 136 | 0x15, 0x00, 0x20, 137 | 0x1f, 0x00, 0x08, 138 | 0x21, 0x03, 0x00, 139 | 0x19, 0x0d, 0x00, 140 | 0x0c, 0x19, 0x00, 141 | 0x01, 0x25, 0x00, 142 | 0x00, 0x2c, 0x00, 143 | 0x00, 0x28, 0x00, 144 | 0x00, 0x1f, 0x13, 145 | 0x00, 0x00, 0x00, 146 | 0x00, 0x00, 0x00, 147 | 0x00, 0x00, 0x00, 148 | 0x67, 0x8b, 0x5c, 149 | 0x08, 0x3d, 0x64, 150 | 0x18, 0x2a, 0x79, 151 | 0x2d, 0x1a, 0x72, 152 | 0x40, 0x12, 0x51, 153 | 0x50, 0x16, 0x29, 154 | 0x52, 0x22, 0x04, 155 | 0x46, 0x34, 0x00, 156 | 0x31, 0x48, 0x00, 157 | 0x1c, 0x59, 0x00, 158 | 0x0c, 0x64, 0x00, 159 | 0x02, 0x5e, 0x13, 160 | 0x01, 0x50, 0x3d, 161 | 0x00, 0x00, 0x00, 162 | 0x00, 0x00, 0x00, 163 | 0x00, 0x00, 0x00, 164 | 0xae, 0xe1, 0x9d, 165 | 0x3c, 0x89, 0xa6, 166 | 0x52, 0x72, 0xbd, 167 | 0x6c, 0x5f, 0xb6, 168 | 0x81, 0x54, 0x92, 169 | 0x94, 0x5a, 0x64, 170 | 0x96, 0x68, 0x34, 171 | 0x89, 0x7e, 0x12, 172 | 0x71, 0x95, 0x04, 173 | 0x56, 0xa9, 0x08, 174 | 0x42, 0xb6, 0x21, 175 | 0x33, 0xaf, 0x48, 176 | 0x31, 0x9f, 0x7a, 177 | 0x1e, 0x31, 0x18, 178 | 0x00, 0x00, 0x00, 179 | 0x00, 0x00, 0x00, 180 | 0xae, 0xe1, 0x9d, 181 | 0x7d, 0xbb, 0xa1, 182 | 0x86, 0xb1, 0xaa, 183 | 0x92, 0xa9, 0xa7, 184 | 0x9b, 0xa4, 0x99, 185 | 0xa3, 0xa7, 0x85, 186 | 0xa4, 0xad, 0x70, 187 | 0x9e, 0xb7, 0x5f, 188 | 0x94, 0xc1, 0x56, 189 | 0x89, 0xca, 0x59, 190 | 0x7f, 0xcf, 0x66, 191 | 0x78, 0xcc, 0x79, 192 | 0x77, 0xc5, 0x8f, 193 | 0x6e, 0x93, 0x62, 194 | 0x00, 0x00, 0x00, 195 | 0x00, 0x00, 0x00, 196 | 0x2d, 0x2e, 0x1e, 197 | 0x00, 0x04, 0x21, 198 | 0x00, 0x00, 0x2f, 199 | 0x0a, 0x00, 0x2d, 200 | 0x15, 0x00, 0x1d, 201 | 0x1f, 0x00, 0x06, 202 | 0x20, 0x00, 0x00, 203 | 0x18, 0x05, 0x00, 204 | 0x0b, 0x0c, 0x00, 205 | 0x00, 0x13, 0x00, 206 | 0x00, 0x16, 0x00, 207 | 0x00, 0x13, 0x00, 208 | 0x00, 0x0b, 0x0c, 209 | 0x00, 0x00, 0x00, 210 | 0x00, 0x00, 0x00, 211 | 0x00, 0x00, 0x00, 212 | 0x66, 0x66, 0x4d, 213 | 0x07, 0x23, 0x52, 214 | 0x18, 0x17, 0x67, 215 | 0x2c, 0x0e, 0x64, 216 | 0x3f, 0x09, 0x4c, 217 | 0x4f, 0x0d, 0x25, 218 | 0x51, 0x18, 0x02, 219 | 0x44, 0x24, 0x00, 220 | 0x30, 0x31, 0x00, 221 | 0x1b, 0x3c, 0x00, 222 | 0x0b, 0x42, 0x00, 223 | 0x01, 0x3c, 0x09, 224 | 0x00, 0x2f, 0x2f, 225 | 0x00, 0x00, 0x00, 226 | 0x00, 0x00, 0x00, 227 | 0x00, 0x00, 0x00, 228 | 0xac, 0xac, 0x89, 229 | 0x3b, 0x5f, 0x8e, 230 | 0x51, 0x50, 0xa5, 231 | 0x6a, 0x44, 0xa2, 232 | 0x80, 0x3e, 0x87, 233 | 0x93, 0x44, 0x5a, 234 | 0x95, 0x52, 0x2c, 235 | 0x86, 0x60, 0x0d, 236 | 0x6e, 0x70, 0x01, 237 | 0x55, 0x7d, 0x02, 238 | 0x40, 0x83, 0x12, 239 | 0x31, 0x7d, 0x36, 240 | 0x2f, 0x6e, 0x66, 241 | 0x1d, 0x1d, 0x11, 242 | 0x00, 0x00, 0x00, 243 | 0x00, 0x00, 0x00, 244 | 0xac, 0xac, 0x89, 245 | 0x7b, 0x8b, 0x8b, 246 | 0x85, 0x85, 0x94, 247 | 0x90, 0x7f, 0x93, 248 | 0x9a, 0x7d, 0x88, 249 | 0xa1, 0x7f, 0x75, 250 | 0xa2, 0x85, 0x60, 251 | 0x9c, 0x8c, 0x51, 252 | 0x92, 0x93, 0x48, 253 | 0x86, 0x98, 0x49, 254 | 0x7d, 0x9b, 0x53, 255 | 0x76, 0x98, 0x65, 256 | 0x75, 0x92, 0x7a, 257 | 0x6c, 0x6c, 0x53, 258 | 0x00, 0x00, 0x00, 259 | 0x00, 0x00, 0x00, 260 | 0x37, 0x37, 0x59, 261 | 0x00, 0x0f, 0x53, 262 | 0x09, 0x08, 0x68, 263 | 0x17, 0x00, 0x63, 264 | 0x24, 0x00, 0x4b, 265 | 0x2a, 0x00, 0x28, 266 | 0x26, 0x00, 0x07, 267 | 0x1a, 0x03, 0x00, 268 | 0x09, 0x0a, 0x00, 269 | 0x00, 0x13, 0x00, 270 | 0x00, 0x1a, 0x00, 271 | 0x00, 0x1b, 0x0e, 272 | 0x00, 0x17, 0x31, 273 | 0x00, 0x00, 0x00, 274 | 0x00, 0x00, 0x01, 275 | 0x00, 0x00, 0x01, 276 | 0x75, 0x75, 0xab, 277 | 0x13, 0x37, 0xa1, 278 | 0x2c, 0x2a, 0xc0, 279 | 0x43, 0x1b, 0xb8, 280 | 0x57, 0x13, 0x94, 281 | 0x61, 0x12, 0x5f, 282 | 0x5b, 0x17, 0x2a, 283 | 0x48, 0x21, 0x06, 284 | 0x2c, 0x2d, 0x00, 285 | 0x17, 0x3d, 0x00, 286 | 0x08, 0x48, 0x0d, 287 | 0x03, 0x49, 0x37, 288 | 0x06, 0x43, 0x6e, 289 | 0x00, 0x00, 0x01, 290 | 0x00, 0x00, 0x01, 291 | 0x00, 0x00, 0x01, 292 | 0xc2, 0xc1, 0xff, 293 | 0x50, 0x7b, 0xff, 294 | 0x6e, 0x6c, 0xff, 295 | 0x89, 0x5a, 0xff, 296 | 0xa0, 0x4f, 0xf5, 297 | 0xab, 0x4d, 0xba, 298 | 0xa5, 0x54, 0x7b, 299 | 0x8f, 0x61, 0x4a, 300 | 0x6f, 0x6f, 0x30, 301 | 0x55, 0x83, 0x36, 302 | 0x40, 0x8e, 0x55, 303 | 0x38, 0x90, 0x8a, 304 | 0x3d, 0x89, 0xca, 305 | 0x25, 0x25, 0x42, 306 | 0x00, 0x00, 0x01, 307 | 0x00, 0x00, 0x01, 308 | 0xc2, 0xc1, 0xff, 309 | 0x91, 0xa4, 0xff, 310 | 0x9f, 0x9d, 0xff, 311 | 0xaa, 0x95, 0xff, 312 | 0xb4, 0x90, 0xff, 313 | 0xb9, 0x8f, 0xea, 314 | 0xb6, 0x92, 0xcf, 315 | 0xad, 0x98, 0xb8, 316 | 0x9f, 0x9f, 0xab, 317 | 0x93, 0xa7, 0xae, 318 | 0x8a, 0xac, 0xbd, 319 | 0x86, 0xad, 0xd6, 320 | 0x88, 0xaa, 0xf1, 321 | 0x7d, 0x7c, 0xb4, 322 | 0x00, 0x00, 0x01, 323 | 0x00, 0x00, 0x01, 324 | 0x32, 0x27, 0x35, 325 | 0x00, 0x04, 0x2f, 326 | 0x05, 0x00, 0x43, 327 | 0x12, 0x00, 0x41, 328 | 0x1e, 0x00, 0x30, 329 | 0x25, 0x00, 0x17, 330 | 0x24, 0x00, 0x01, 331 | 0x18, 0x00, 0x00, 332 | 0x08, 0x06, 0x00, 333 | 0x00, 0x0c, 0x00, 334 | 0x00, 0x0f, 0x00, 335 | 0x00, 0x0e, 0x00, 336 | 0x00, 0x0a, 0x12, 337 | 0x00, 0x00, 0x00, 338 | 0x00, 0x00, 0x00, 339 | 0x00, 0x00, 0x00, 340 | 0x6d, 0x5b, 0x73, 341 | 0x0d, 0x21, 0x69, 342 | 0x25, 0x16, 0x86, 343 | 0x3b, 0x0d, 0x84, 344 | 0x4f, 0x09, 0x6a, 345 | 0x5a, 0x0a, 0x43, 346 | 0x58, 0x10, 0x1b, 347 | 0x45, 0x1a, 0x00, 348 | 0x2a, 0x26, 0x00, 349 | 0x15, 0x30, 0x00, 350 | 0x06, 0x36, 0x00, 351 | 0x00, 0x34, 0x14, 352 | 0x01, 0x2c, 0x3b, 353 | 0x00, 0x00, 0x00, 354 | 0x00, 0x00, 0x00, 355 | 0x00, 0x00, 0x00, 356 | 0xb6, 0x9d, 0xbe, 357 | 0x45, 0x5a, 0xb4, 358 | 0x63, 0x4c, 0xd4, 359 | 0x7d, 0x40, 0xd1, 360 | 0x94, 0x3b, 0xb5, 361 | 0xa0, 0x3c, 0x88, 362 | 0x9f, 0x45, 0x59, 363 | 0x89, 0x51, 0x2c, 364 | 0x69, 0x5f, 0x15, 365 | 0x50, 0x6c, 0x16, 366 | 0x3c, 0x72, 0x2b, 367 | 0x31, 0x70, 0x51, 368 | 0x33, 0x67, 0x7f, 369 | 0x21, 0x18, 0x24, 370 | 0x00, 0x00, 0x00, 371 | 0x00, 0x00, 0x00, 372 | 0xb6, 0x9d, 0xbe, 373 | 0x85, 0x81, 0xba, 374 | 0x93, 0x7a, 0xc7, 375 | 0x9e, 0x75, 0xc6, 376 | 0xa8, 0x72, 0xba, 377 | 0xad, 0x73, 0xa8, 378 | 0xac, 0x77, 0x93, 379 | 0xa3, 0x7d, 0x7e, 380 | 0x95, 0x83, 0x71, 381 | 0x8a, 0x88, 0x72, 382 | 0x81, 0x8b, 0x7d, 383 | 0x7c, 0x8a, 0x8f, 384 | 0x7c, 0x86, 0xa3, 385 | 0x73, 0x62, 0x7a, 386 | 0x00, 0x00, 0x00, 387 | 0x00, 0x00, 0x00, 388 | 0x24, 0x2f, 0x30, 389 | 0x00, 0x0a, 0x2f, 390 | 0x00, 0x03, 0x3d, 391 | 0x07, 0x00, 0x39, 392 | 0x12, 0x00, 0x23, 393 | 0x19, 0x00, 0x0c, 394 | 0x18, 0x00, 0x00, 395 | 0x10, 0x01, 0x00, 396 | 0x05, 0x08, 0x00, 397 | 0x00, 0x11, 0x00, 398 | 0x00, 0x17, 0x00, 399 | 0x00, 0x16, 0x01, 400 | 0x00, 0x11, 0x17, 401 | 0x00, 0x00, 0x00, 402 | 0x00, 0x00, 0x00, 403 | 0x00, 0x00, 0x00, 404 | 0x57, 0x68, 0x6a, 405 | 0x04, 0x2d, 0x68, 406 | 0x14, 0x20, 0x7e, 407 | 0x28, 0x12, 0x77, 408 | 0x3b, 0x0a, 0x56, 409 | 0x45, 0x0b, 0x30, 410 | 0x44, 0x12, 0x0c, 411 | 0x37, 0x1d, 0x00, 412 | 0x23, 0x29, 0x00, 413 | 0x0f, 0x39, 0x00, 414 | 0x02, 0x44, 0x01, 415 | 0x00, 0x42, 0x1c, 416 | 0x00, 0x39, 0x43, 417 | 0x00, 0x00, 0x00, 418 | 0x00, 0x00, 0x00, 419 | 0x00, 0x00, 0x00, 420 | 0x97, 0xaf, 0xb2, 421 | 0x33, 0x6b, 0xb0, 422 | 0x48, 0x5c, 0xc8, 423 | 0x61, 0x4a, 0xc0, 424 | 0x77, 0x3f, 0x9b, 425 | 0x83, 0x41, 0x70, 426 | 0x81, 0x4a, 0x43, 427 | 0x72, 0x58, 0x21, 428 | 0x5b, 0x67, 0x11, 429 | 0x42, 0x7a, 0x16, 430 | 0x2f, 0x86, 0x31, 431 | 0x25, 0x84, 0x57, 432 | 0x27, 0x7a, 0x86, 433 | 0x16, 0x1e, 0x20, 434 | 0x00, 0x00, 0x00, 435 | 0x00, 0x00, 0x00, 436 | 0x97, 0xaf, 0xb2, 437 | 0x6b, 0x92, 0xb1, 438 | 0x75, 0x8c, 0xbb, 439 | 0x80, 0x83, 0xb8, 440 | 0x89, 0x7f, 0xa9, 441 | 0x8e, 0x7f, 0x96, 442 | 0x8e, 0x83, 0x82, 443 | 0x88, 0x8a, 0x72, 444 | 0x7e, 0x91, 0x69, 445 | 0x73, 0x99, 0x6c, 446 | 0x6a, 0x9e, 0x7a, 447 | 0x65, 0x9d, 0x8b, 448 | 0x65, 0x99, 0xa0, 449 | 0x5d, 0x6e, 0x71, 450 | 0x00, 0x00, 0x00, 451 | 0x00, 0x00, 0x00, 452 | 0x25, 0x25, 0x25, 453 | 0x00, 0x03, 0x23, 454 | 0x00, 0x00, 0x31, 455 | 0x07, 0x00, 0x2f, 456 | 0x12, 0x00, 0x20, 457 | 0x19, 0x00, 0x09, 458 | 0x18, 0x00, 0x00, 459 | 0x10, 0x00, 0x00, 460 | 0x05, 0x05, 0x00, 461 | 0x00, 0x0b, 0x00, 462 | 0x00, 0x0f, 0x00, 463 | 0x00, 0x0e, 0x00, 464 | 0x00, 0x09, 0x0d, 465 | 0x00, 0x00, 0x00, 466 | 0x00, 0x00, 0x00, 467 | 0x00, 0x00, 0x00, 468 | 0x58, 0x58, 0x58, 469 | 0x05, 0x1f, 0x56, 470 | 0x14, 0x13, 0x6b, 471 | 0x28, 0x0a, 0x68, 472 | 0x3b, 0x06, 0x50, 473 | 0x45, 0x07, 0x2b, 474 | 0x44, 0x0e, 0x08, 475 | 0x37, 0x18, 0x00, 476 | 0x24, 0x24, 0x00, 477 | 0x10, 0x2f, 0x00, 478 | 0x02, 0x35, 0x00, 479 | 0x00, 0x33, 0x0e, 480 | 0x00, 0x2b, 0x32, 481 | 0x00, 0x00, 0x00, 482 | 0x00, 0x00, 0x00, 483 | 0x00, 0x00, 0x00, 484 | 0x98, 0x98, 0x98, 485 | 0x33, 0x56, 0x96, 486 | 0x48, 0x47, 0xac, 487 | 0x61, 0x3c, 0xaa, 488 | 0x77, 0x36, 0x8f, 489 | 0x83, 0x38, 0x64, 490 | 0x81, 0x40, 0x38, 491 | 0x73, 0x4e, 0x18, 492 | 0x5c, 0x5d, 0x09, 493 | 0x43, 0x69, 0x0a, 494 | 0x30, 0x70, 0x1d, 495 | 0x26, 0x6e, 0x40, 496 | 0x27, 0x64, 0x6d, 497 | 0x16, 0x16, 0x16, 498 | 0x00, 0x00, 0x00, 499 | 0x00, 0x00, 0x00, 500 | 0x98, 0x98, 0x98, 501 | 0x6c, 0x7c, 0x97, 502 | 0x76, 0x75, 0xa0, 503 | 0x81, 0x70, 0x9f, 504 | 0x8a, 0x6d, 0x94, 505 | 0x8f, 0x6e, 0x82, 506 | 0x8e, 0x72, 0x6e, 507 | 0x88, 0x78, 0x5e, 508 | 0x7e, 0x7f, 0x56, 509 | 0x73, 0x84, 0x57, 510 | 0x6a, 0x87, 0x61, 511 | 0x65, 0x86, 0x72, 512 | 0x66, 0x82, 0x86, 513 | 0x5e, 0x5e, 0x5e, 514 | 0x00, 0x00, 0x00, 515 | 0x00, 0x00, 0x00]; 516 | -------------------------------------------------------------------------------- /src/tracked_events.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Copy)] 2 | pub enum EventType { 3 | NullEvent, 4 | CpuRead{program_counter: u16, address: u16, data: u8}, 5 | CpuWrite{program_counter: u16, address: u16, data: u8}, 6 | CpuExecute{program_counter: u16, data: u8}, 7 | } 8 | 9 | #[derive(Clone, Copy)] 10 | pub struct TrackedEvent { 11 | pub scanline: u16, 12 | pub cycle: u16, 13 | pub event_type: EventType, 14 | } 15 | 16 | pub struct EventTracker { 17 | pub tracked_events_a: Vec, 18 | pub size_a: usize, 19 | pub tracked_events_b: Vec, 20 | pub size_b: usize, 21 | pub a_active: bool, 22 | pub current_scanline: u16, 23 | pub current_cycle: u16, 24 | pub cpu_snoop_list: Vec, 25 | } 26 | 27 | const CPU_READ: u8 = 0b0000_0001; 28 | const CPU_WRITE: u8 = 0b0000_0010; 29 | const CPU_EXECUTE: u8 = 0b0000_0100; 30 | 31 | impl EventTracker { 32 | pub fn new() -> EventTracker { 33 | let mut default_cpu_snoops = vec![0u8; 0x10000]; 34 | 35 | // PPU registers 36 | default_cpu_snoops[0x2000] = CPU_WRITE; 37 | default_cpu_snoops[0x2001] = CPU_WRITE; 38 | default_cpu_snoops[0x2002] = CPU_WRITE | CPU_READ; 39 | default_cpu_snoops[0x2003] = CPU_WRITE; 40 | default_cpu_snoops[0x2004] = CPU_WRITE | CPU_READ; 41 | default_cpu_snoops[0x2005] = CPU_WRITE; 42 | default_cpu_snoops[0x2006] = CPU_WRITE; 43 | default_cpu_snoops[0x2007] = CPU_WRITE | CPU_READ; 44 | 45 | // APU registers 46 | // Pulse 1 47 | default_cpu_snoops[0x4000] = CPU_WRITE; 48 | default_cpu_snoops[0x4001] = CPU_WRITE; 49 | default_cpu_snoops[0x4002] = CPU_WRITE; 50 | default_cpu_snoops[0x4003] = CPU_WRITE; 51 | 52 | // Pulse 2 53 | default_cpu_snoops[0x4004] = CPU_WRITE; 54 | default_cpu_snoops[0x4005] = CPU_WRITE; 55 | default_cpu_snoops[0x4006] = CPU_WRITE; 56 | default_cpu_snoops[0x4007] = CPU_WRITE; 57 | 58 | // Triangle 59 | default_cpu_snoops[0x4008] = CPU_WRITE; 60 | default_cpu_snoops[0x400A] = CPU_WRITE; 61 | default_cpu_snoops[0x400B] = CPU_WRITE; 62 | 63 | // Noise 64 | default_cpu_snoops[0x400C] = CPU_WRITE; 65 | default_cpu_snoops[0x400E] = CPU_WRITE; 66 | default_cpu_snoops[0x400F] = CPU_WRITE; 67 | 68 | // DMC 69 | default_cpu_snoops[0x4010] = CPU_WRITE; 70 | default_cpu_snoops[0x4011] = CPU_WRITE; 71 | default_cpu_snoops[0x4012] = CPU_WRITE; 72 | default_cpu_snoops[0x4013] = CPU_WRITE; 73 | 74 | // Misc 75 | default_cpu_snoops[0x4014] = CPU_WRITE; 76 | default_cpu_snoops[0x4015] = CPU_WRITE | CPU_READ; 77 | default_cpu_snoops[0x4017] = CPU_WRITE; 78 | 79 | 80 | 81 | return EventTracker { 82 | // Way, way more events than we could *possibly* need, just to be safe 83 | // Manually indexed, and never resized, to avoid allocations at runtime 84 | tracked_events_a: vec![TrackedEvent{scanline: 0xFFFF, cycle: 0xFFFF, event_type: EventType::NullEvent}; 262*341], 85 | size_a: 0, 86 | tracked_events_b: vec![TrackedEvent{scanline: 0xFFFF, cycle: 0xFFFF, event_type: EventType::NullEvent}; 262*341], 87 | size_b: 0, 88 | a_active: true, 89 | current_scanline: 0, 90 | current_cycle: 0, 91 | cpu_snoop_list: default_cpu_snoops, 92 | } 93 | } 94 | 95 | pub fn track(&mut self, event: TrackedEvent) { 96 | match self.a_active { 97 | true => { 98 | self.tracked_events_a[self.size_a] = event; 99 | self.size_a += 1; 100 | }, 101 | false => { 102 | self.tracked_events_b[self.size_b] = event; 103 | self.size_b += 1; 104 | } 105 | } 106 | } 107 | 108 | pub fn swap_buffers(&mut self) { 109 | match self.a_active { 110 | true => { 111 | self.size_b = 0; 112 | self.a_active = false; 113 | }, 114 | false => { 115 | self.size_a = 0; 116 | self.a_active = true; 117 | } 118 | } 119 | } 120 | 121 | pub fn events_this_frame(&self) -> &[TrackedEvent] { 122 | match self.a_active { 123 | true => &self.tracked_events_a[..self.size_a], 124 | false => &self.tracked_events_b[..self.size_b], 125 | } 126 | } 127 | 128 | pub fn events_last_frame(&self) -> &[TrackedEvent] { 129 | match self.a_active { 130 | true => &self.tracked_events_b[..self.size_b], 131 | false => &self.tracked_events_a[..self.size_a], 132 | } 133 | } 134 | 135 | pub fn snoop_cpu_read(&mut self, program_counter: u16, address: u16, data: u8) { 136 | if (self.cpu_snoop_list[address as usize] & CPU_READ) != 0 { 137 | self.track(TrackedEvent{ 138 | scanline: self.current_scanline, 139 | cycle: self.current_cycle, 140 | event_type: EventType::CpuRead{ 141 | program_counter: program_counter, 142 | address: address, 143 | data: data, 144 | } 145 | }); 146 | } 147 | } 148 | 149 | pub fn snoop_cpu_write(&mut self, program_counter: u16, address: u16, data: u8) { 150 | if (self.cpu_snoop_list[address as usize] & CPU_WRITE) != 0 { 151 | self.track(TrackedEvent{ 152 | scanline: self.current_scanline, 153 | cycle: self.current_cycle, 154 | event_type: EventType::CpuWrite{ 155 | program_counter: program_counter, 156 | address: address, 157 | data: data, 158 | } 159 | }); 160 | } 161 | } 162 | 163 | pub fn snoop_cpu_execute(&mut self, program_counter: u16, data: u8) { 164 | if (self.cpu_snoop_list[program_counter as usize] & CPU_EXECUTE) != 0 { 165 | self.track(TrackedEvent{ 166 | scanline: self.current_scanline, 167 | cycle: self.current_cycle, 168 | event_type: EventType::CpuExecute{ 169 | program_counter: program_counter, 170 | data: data, 171 | } 172 | }); 173 | } 174 | } 175 | } -------------------------------------------------------------------------------- /src/unofficial_opcodes.rs: -------------------------------------------------------------------------------- 1 | use addressing; 2 | use cycle_cpu::Registers; 3 | use opcodes; 4 | use nes::NesState; 5 | use memory::read_byte; 6 | use memory::write_byte; 7 | 8 | // Note: Opcode names follow the undefined opcodes tabke here: 9 | // https://wiki.nesdev.com/w/index.php/CPU_unofficial_opcodes 10 | 11 | // Shift left and inclusive OR A 12 | pub fn slo(registers: &mut Registers, data: u8) -> u8 { 13 | let result = opcodes::asl(registers, data); 14 | opcodes::ora(registers, result); 15 | return result; 16 | } 17 | 18 | // Rotate left, then AND A 19 | pub fn rla(registers: &mut Registers, data: u8) -> u8 { 20 | let result = opcodes::rol(registers, data); 21 | opcodes::and(registers, result); 22 | return result; 23 | } 24 | 25 | // Shift right, then Exclisive OR A 26 | pub fn sre(registers: &mut Registers, data: u8) -> u8 { 27 | let result = opcodes::lsr(registers, data); 28 | opcodes::eor(registers, result); 29 | return result; 30 | } 31 | 32 | // Rotate right, then ADC result with A 33 | pub fn rra(registers: &mut Registers, data: u8) -> u8 { 34 | let result = opcodes::ror(registers, data); 35 | opcodes::adc(registers, result); 36 | return result; 37 | } 38 | 39 | pub fn sax(registers: &mut Registers) -> u8 { 40 | let result = registers.a & registers.x; 41 | return result; 42 | } 43 | 44 | pub fn lax(registers: &mut Registers, data: u8) { 45 | opcodes::lda(registers, data); 46 | registers.x = data; 47 | } 48 | 49 | // Decrement and compare 50 | pub fn dcp(registers: &mut Registers, data: u8) -> u8 { 51 | let result = opcodes::dec(registers, data); 52 | opcodes::cmp(registers, result); 53 | return result; 54 | } 55 | 56 | // Increment and subtract w/ carry 57 | pub fn isc(registers: &mut Registers, data: u8) -> u8 { 58 | let result = opcodes::inc(registers, data); 59 | opcodes::sbc(registers, result); 60 | return result; 61 | } 62 | 63 | // Many of the following opcodes are unstable, and therefore not part of official tests. 64 | // Hardware results may depend on the alignment of the planets, and whether or not the code 65 | // is being run on an odd-numbered thursday in a month that ends with R. 66 | 67 | // The following opcodes perform an &H, which requires the address byte to be available at a certain 68 | // point. These are weird enough to break the opcode structure, and so get custom functions. 69 | 70 | // UNSTABLE. Performs A & X & H, where H is usually the high byte of the target address + 1. 71 | pub fn ahx_indirect_indexed_y(nes: &mut NesState) { 72 | match nes.cpu.tick { 73 | 2 => addressing::read_data1(nes), 74 | 3 => { 75 | // Read low byte of indirect address 76 | let pointer_low = nes.cpu.data1 as u16; 77 | nes.cpu.temp_address = read_byte(nes, pointer_low) as u16; 78 | nes.cpu.data1 = nes.cpu.data1.wrapping_add(1); 79 | }, 80 | 4 => { 81 | // Read high byte of indirect address 82 | let pointer_high = nes.cpu.data1 as u16; 83 | nes.cpu.temp_address = ((read_byte(nes, pointer_high) as u16) << 8) | nes.cpu.temp_address; 84 | }, 85 | 5 => { 86 | // Add Y to LOW BYTE of the effective address 87 | let low_byte = (nes.cpu.temp_address & 0xFF) + (nes.registers.y as u16); 88 | nes.cpu.temp_address = (nes.cpu.temp_address & 0xFF00) | (low_byte & 0xFF); 89 | let temp_address = nes.cpu.temp_address; 90 | // Dummy read from the new address before it is fixed 91 | let _ = read_byte(nes, temp_address); 92 | // Save the result of our weird modification here: 93 | nes.cpu.data1 = nes.registers.a & nes.registers.x & (nes.cpu.temp_address.wrapping_add(0x100) >> 8) as u8; 94 | if low_byte > 0xFF { 95 | // Fix the high byte of the address by adding 1 to it 96 | //nes.cpu.temp_address = nes.cpu.temp_address.wrapping_add(0x100); 97 | nes.cpu.temp_address = (nes.cpu.temp_address & 0x00FF) | ((nes.cpu.data1 as u16) << 8); 98 | } 99 | }, 100 | 6 => { 101 | let data = nes.cpu.data1; 102 | let address = nes.cpu.temp_address; 103 | write_byte(nes, address, data); 104 | nes.cpu.tick = 0; 105 | }, 106 | _ => {} 107 | } 108 | } 109 | 110 | pub fn ahx_absolute_indexed_y(nes: &mut NesState) { 111 | match nes.cpu.tick { 112 | 2 => addressing::read_address_low(nes), 113 | 3 => addressing::read_address_high(nes), 114 | 4 => { 115 | // Add Y to LOW BYTE of the effective address 116 | let low_byte = (nes.cpu.temp_address & 0xFF) + (nes.registers.y as u16); 117 | nes.cpu.temp_address = (nes.cpu.temp_address & 0xFF00) | (low_byte & 0xFF); 118 | let temp_address = nes.cpu.temp_address; 119 | // Dummy read from the new address before it is fixed 120 | let _ = read_byte(nes, temp_address); 121 | // Save the result of our weird modification here: 122 | nes.cpu.data1 = nes.registers.a & nes.registers.x & (nes.cpu.temp_address.wrapping_add(0x100) >> 8) as u8; 123 | if low_byte > 0xFF { 124 | // Fix the high byte of the address by adding 1 to it 125 | nes.cpu.temp_address = (nes.cpu.temp_address & 0x00FF) | ((nes.cpu.data1 as u16) << 8); 126 | } 127 | }, 128 | 5 => { 129 | let data = nes.cpu.data1; 130 | let address = nes.cpu.temp_address; 131 | write_byte(nes, address, data); 132 | nes.cpu.tick = 0; 133 | }, 134 | _ => {} 135 | } 136 | } 137 | 138 | pub fn tas(nes: &mut NesState) { 139 | match nes.cpu.tick { 140 | 2 => addressing::read_address_low(nes), 141 | 3 => addressing::read_address_high(nes), 142 | 4 => { 143 | // Add X to LOW BYTE of the effective address 144 | // Accuracy note: technically this occurs in cycle 4, but as it has no effect on emulation, I've 145 | // moved it to the beginning of cycle 5, as it makes the early escape detection more straightforward. 146 | let low_byte = (nes.cpu.temp_address & 0xFF) + (nes.registers.y as u16); 147 | nes.cpu.temp_address = (nes.cpu.temp_address & 0xFF00) | (low_byte & 0xFF); 148 | let temp_address = nes.cpu.temp_address; 149 | // Dummy read from the new address before it is fixed 150 | let _ = read_byte(nes, temp_address); 151 | 152 | // First, set S = A & X 153 | nes.registers.s = nes.registers.a & nes.registers.x; 154 | 155 | // Now, AND that result with the high byte + 1, and save it for later writing 156 | nes.cpu.data1 = nes.registers.s & (nes.cpu.temp_address.wrapping_add(0x100) >> 8) as u8; 157 | 158 | if low_byte > 0xFF { 159 | // Use our calculated value as the high byte, effectively corrupting the page boundary 160 | // crossing logic. Where will we write? It's a mystery! 161 | nes.cpu.temp_address = (nes.cpu.temp_address & 0x00FF) | ((nes.cpu.data1 as u16) << 8); 162 | } 163 | }, 164 | 5 => { 165 | let data = nes.cpu.data1; 166 | let address = nes.cpu.temp_address; 167 | write_byte(nes, address, data); 168 | nes.cpu.tick = 0; 169 | }, 170 | _ => {} 171 | } 172 | } 173 | 174 | // Increment and subtract w/ carry 175 | pub fn las(registers: &mut Registers, data: u8) { 176 | let result = registers.s & data; 177 | registers.a = result; 178 | registers.x = result; 179 | registers.s = result; 180 | } 181 | 182 | // AND with carry 183 | pub fn anc(registers: &mut Registers, data: u8) { 184 | opcodes::and(registers, data); 185 | registers.flags.carry = (registers.a & 0b1000_0000) != 0; 186 | } 187 | 188 | // AND with #imm, then LSR 189 | pub fn alr(registers: &mut Registers, data: u8) { 190 | opcodes::and(registers, data); 191 | let result = registers.a; 192 | registers.a = opcodes::lsr(registers, result); 193 | } 194 | 195 | // AND with #imm, then ROR 196 | pub fn arr(registers: &mut Registers, data: u8) { 197 | opcodes::and(registers, data); 198 | let result = registers.a; 199 | registers.a = opcodes::ror(registers, result); 200 | // Carry and Overflow are set weirdly: 201 | registers.flags.carry = (registers.a & 0b0100_0000) != 0; 202 | registers.flags.overflow = (((registers.a & 0b0100_0000) >> 6) ^ ((registers.a & 0b0010_0000) >> 5)) != 0; 203 | } 204 | 205 | // "Magic" value is a complete guess. I don't know if the NES's decimal unit actually 206 | // exists and is stubbed out; I'm assuming here that it is NOT, so setting magic to 207 | // 0x00. The true effect of this instruction varies *by console* and the instruction 208 | // should not be used for any purpose. 209 | // http://visual6502.org/wiki/index.php?title=6502_Opcode_8B_%28XAA,_ANE%29 210 | pub fn xaa(registers: &mut Registers, data: u8) { 211 | // A = (A | magic) & X & imm 212 | let magic = 0x00; 213 | registers.a = (registers.a | magic) & registers.x & data; 214 | registers.flags.zero = registers.a == 0; 215 | registers.flags.negative = registers.a & 0x80 != 0; 216 | } 217 | 218 | pub fn axs(registers: &mut Registers, data: u8) { 219 | let initial = registers.a & registers.x; 220 | // CMP with #imm, but store value in x: 221 | registers.flags.carry = initial >= data; 222 | registers.x = initial.wrapping_sub(data); 223 | registers.flags.zero = registers.x == 0; 224 | registers.flags.negative = registers.x & 0x80 != 0; 225 | } 226 | 227 | pub fn shx(nes: &mut NesState) { 228 | match nes.cpu.tick { 229 | 2 => addressing::read_address_low(nes), 230 | 3 => addressing::read_address_high(nes), 231 | 4 => { 232 | // Add X to LOW BYTE of the effective address 233 | let low_byte = (nes.cpu.temp_address & 0xFF) + (nes.registers.y as u16); 234 | nes.cpu.temp_address = (nes.cpu.temp_address & 0xFF00) | (low_byte & 0xFF); 235 | let temp_address = nes.cpu.temp_address; 236 | // Dummy read from the new address before it is fixed 237 | let _ = read_byte(nes, temp_address); 238 | // Save the result of our weird modification here: 239 | nes.cpu.data1 = nes.registers.x & (nes.cpu.temp_address.wrapping_add(0x100) >> 8) as u8; 240 | if low_byte > 0xFF { 241 | // Fix the high byte of the address by adding 1 to it 242 | nes.cpu.temp_address = (nes.cpu.temp_address & 0x00FF) | ((nes.cpu.data1 as u16) << 8); 243 | } 244 | }, 245 | 5 => { 246 | let data = nes.cpu.data1; 247 | let address = nes.cpu.temp_address; 248 | write_byte(nes, address, data); 249 | nes.cpu.tick = 0; 250 | }, 251 | _ => {} 252 | } 253 | } 254 | 255 | pub fn shy(nes: &mut NesState) { 256 | match nes.cpu.tick { 257 | 2 => addressing::read_address_low(nes), 258 | 3 => addressing::read_address_high(nes), 259 | 4 => { 260 | // Add X to LOW BYTE of the effective address 261 | let low_byte = (nes.cpu.temp_address & 0xFF) + (nes.registers.x as u16); 262 | nes.cpu.temp_address = (nes.cpu.temp_address & 0xFF00) | (low_byte & 0xFF); 263 | let temp_address = nes.cpu.temp_address; 264 | // Dummy read from the new address before it is fixed 265 | let _ = read_byte(nes, temp_address); 266 | // Save the result of our weird modification here: 267 | nes.cpu.data1 = nes.registers.y & (nes.cpu.temp_address.wrapping_add(0x100) >> 8) as u8; 268 | if low_byte > 0xFF { 269 | // Fix the high byte of the address by adding 1 to it 270 | nes.cpu.temp_address = (nes.cpu.temp_address & 0x00FF) | ((nes.cpu.data1 as u16) << 8); 271 | } 272 | }, 273 | 5 => { 274 | let data = nes.cpu.data1; 275 | let address = nes.cpu.temp_address; 276 | write_byte(nes, address, data); 277 | nes.cpu.tick = 0; 278 | }, 279 | _ => {} 280 | } 281 | } --------------------------------------------------------------------------------