├── .gitignore ├── Cargo.toml ├── README.md ├── pics ├── contra.png ├── dk.png ├── excitebike.png ├── final_fantasy.png ├── kirby.png ├── metroid.png ├── smb.png ├── smb3.png ├── zelda.png └── zelda_dungeon.png └── src ├── apu ├── dmc.rs ├── envelope.rs ├── mod.rs ├── noise.rs ├── serialize.rs ├── square.rs └── triangle.rs ├── audio.rs ├── cartridge ├── cnrom.rs ├── mmc1.rs ├── mmc3.rs ├── mod.rs ├── nrom.rs ├── serialize.rs └── uxrom.rs ├── cpu ├── addressing_modes.rs ├── mod.rs ├── opcodes.rs ├── serialize.rs └── utility.rs ├── input.rs ├── main.rs ├── ppu ├── cpu_registers.rs ├── memory.rs ├── mod.rs ├── rendering.rs └── serialize.rs ├── screen.rs └── state.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | roms 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nestur" 3 | version = "0.1.0" 4 | authors = ["Theron "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | #sdl2 = { version = "0.33", features = ["bundled", "static-link"] } 9 | sdl2 = { version = "0.35" } 10 | serde = { version = "1.0.104", features = ["derive"] } 11 | serde_json = "1.0" 12 | cpuprofiler = "0.0.3" 13 | 14 | [profile.release] 15 | debug = true 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nestur 2 | 3 | Nestur is an NES emulator. There are plenty of full-featured emulators out there; this is primarily an educational project but it is usable. There may still be many bugs, but I'm probably not aware of them so please submit issues. 4 | - no use of `unsafe` 5 | - NTSC timing 6 | - supports mappers 0-4 which cover ~85% of [games](http://tuxnes.sourceforge.net/nesmapper.txt) 7 | 8 | 9 | 10 | The code aims to follow the explanations from the [NES dev wiki](https://wiki.nesdev.com/w/index.php/NES_reference_guide) where possible, especially in the PPU, and the comments quote from it often. Thanks to everyone who contributes to that wiki/forum, and to Michael Fogleman's [NES](https://github.com/fogleman/nes) and Scott Ferguson's [Fergulator](https://github.com/scottferg/Fergulator) for getting me unstuck at several points. 11 | 12 | ## Controls 13 | ``` 14 | Button | Key 15 | ___________________ 16 | | A | D | 17 | | B | F | 18 | | Start | Enter | 19 | | Select | R-Shift| 20 | | Up | Up | 21 | | Down | Down | 22 | | Left | Left | 23 | | Right | Right | 24 | ------------------- 25 | 26 | F2: reset console 27 | F5: save game state 28 | F9: load most recent save state 29 | ``` 30 | If the game is called `mygame.nes`, the save state files will be called `mygame-#.dat`. To load any previous save state, drag and drop a `.dat` file onto the window. 31 | 32 | ## Use 33 | 34 | Double-click or run the executable from a terminal by itself to launch with instructions. Then click Ok and drag a (iNES/`.nes`) ROM file onto the window. Or, drag and drop a ROM file onto the executable to run it directly, or use the path to the ROM file as the first argument to the terminal command. 35 | 36 | If the game uses battery-backed RAM (if it can save data when the console is turned off), a save file like `rom_filename.sav` will be created in the same folder as the ROM when the program is exited. When Nestur is run again, it will look for a file matching the ROM name, with a `.sav` extension instead of `.nes`. 37 | 38 | ## Compilation 39 | 40 | 1. Install [Rust](https://www.rust-lang.org/tools/install) 41 | 2. Have a C compiler 42 | - Linux: `sudo apt install build-essential` 43 | - Mac: [XCode](https://apps.apple.com/us/app/xcode/id497799835) 44 | - Windows: install the [Visual Studio Build Tools](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=16) (or [Visual Studio](https://docs.microsoft.com/en-us/cpp/build/vscpp-step-0-installation?view=vs-2019) with the "Desktop development with C++" workload). 45 | 3. Install CMake 46 | - Linux: `sudo apt install cmake` 47 | - Mac: install [Homebrew](https://brew.sh/) and run `brew install cmake` 48 | - [Windows](https://cmake.org/download/) 49 | 4. `cd nestur/ && cargo build --release` (be sure to build/run with the release flag or it will run very slowly) 50 | 5. The `nestur` executable or `nestur.exe` will be in `nestur/target/release`. 51 | 52 | ## To do 53 | 54 | - support other controllers? 55 | 56 | - more mappers? 57 | 58 | - better save file organization? 59 | 60 | ## Known problem games 61 | 62 | - None currently, please report any issues 63 | 64 | 65 | Please also check out [Cloaker](https://github.com/spieglt/cloaker) and [Flying Carpet](https://github.com/spieglt/flyingcarpet)! 66 | -------------------------------------------------------------------------------- /pics/contra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spieglt/nestur/4bdb1d39340493fe7556a9fb22e584dd85bdbdf1/pics/contra.png -------------------------------------------------------------------------------- /pics/dk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spieglt/nestur/4bdb1d39340493fe7556a9fb22e584dd85bdbdf1/pics/dk.png -------------------------------------------------------------------------------- /pics/excitebike.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spieglt/nestur/4bdb1d39340493fe7556a9fb22e584dd85bdbdf1/pics/excitebike.png -------------------------------------------------------------------------------- /pics/final_fantasy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spieglt/nestur/4bdb1d39340493fe7556a9fb22e584dd85bdbdf1/pics/final_fantasy.png -------------------------------------------------------------------------------- /pics/kirby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spieglt/nestur/4bdb1d39340493fe7556a9fb22e584dd85bdbdf1/pics/kirby.png -------------------------------------------------------------------------------- /pics/metroid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spieglt/nestur/4bdb1d39340493fe7556a9fb22e584dd85bdbdf1/pics/metroid.png -------------------------------------------------------------------------------- /pics/smb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spieglt/nestur/4bdb1d39340493fe7556a9fb22e584dd85bdbdf1/pics/smb.png -------------------------------------------------------------------------------- /pics/smb3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spieglt/nestur/4bdb1d39340493fe7556a9fb22e584dd85bdbdf1/pics/smb3.png -------------------------------------------------------------------------------- /pics/zelda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spieglt/nestur/4bdb1d39340493fe7556a9fb22e584dd85bdbdf1/pics/zelda.png -------------------------------------------------------------------------------- /pics/zelda_dungeon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spieglt/nestur/4bdb1d39340493fe7556a9fb22e584dd85bdbdf1/pics/zelda_dungeon.png -------------------------------------------------------------------------------- /src/apu/dmc.rs: -------------------------------------------------------------------------------- 1 | // number of CPU cycles between sample output level being adjusted 2 | pub const SAMPLE_RATES: [u16; 16] = [428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54]; 3 | 4 | #[derive(serde::Serialize, serde::Deserialize, Clone)] 5 | pub struct DMC { 6 | pub sample: u16, // "output value" that goes to the mixer 7 | pub enabled: bool, 8 | irq_enabled: bool, 9 | pub interrupt: bool, 10 | loop_flag: bool, 11 | pub cpu_stall: bool, 12 | rate_index: usize, 13 | cpu_cycles_left: u16, 14 | 15 | // Memory reader 16 | sample_byte: u8, // passed in every APU clock cycle, need to think of a better way to read CPU from APU 17 | sample_buffer: Option, // buffer that the output unit draws into its shift register, wrapped in Option to denote 'emptiness' 18 | pub sample_address: usize, // start of sample in memory 19 | pub sample_length: usize, // number of bytes starting from sample_address that constitute the sample. each byte has 8 bits that can raise or lower the output level, at a speed determined by rate_index 20 | pub current_address: usize, // address of the next byte of the sample to play 21 | pub bytes_remaining: usize, // bytes left in the sample 22 | 23 | // Output unit 24 | shift_register: u8, 25 | bits_remaining: usize, 26 | } 27 | 28 | impl DMC { 29 | pub fn new() -> Self { 30 | DMC { 31 | sample: 0, 32 | enabled: false, 33 | irq_enabled: false, 34 | interrupt: false, 35 | loop_flag: false, 36 | cpu_stall: false, 37 | rate_index: 0, 38 | cpu_cycles_left: 0, 39 | sample_byte: 0, 40 | sample_buffer: None, 41 | sample_address: 0, 42 | sample_length: 0, 43 | current_address: 0, 44 | bytes_remaining: 0, 45 | shift_register: 0, 46 | bits_remaining: 0, 47 | } 48 | } 49 | 50 | pub fn clock(&mut self, sample_byte: u8) { 51 | if self.enabled { 52 | self.clock_memory_reader(sample_byte); 53 | self.clock_output_unit(); 54 | } 55 | } 56 | 57 | fn clock_memory_reader(&mut self, sample_byte: u8) { 58 | // When a sample is (re)started, the current address is set to the sample address, and bytes remaining is set to the sample length. 59 | if self.bytes_remaining == 0 && self.loop_flag { 60 | self.current_address = self.sample_address; 61 | self.bytes_remaining = self.sample_length; 62 | } 63 | // Any time the sample buffer is in an empty state and bytes remaining is not zero (including just after a write to $4015 that enables the channel, 64 | // regardless of where that write occurs relative to the bit counter mentioned below), the following occur: 65 | if self.sample_buffer.is_none() && self.bytes_remaining != 0 { 66 | // The CPU is stalled for up to 4 CPU cycles to allow the longest possible write (the return address and write after an IRQ) to finish. 67 | // If OAM DMA is in progress, it is paused for two cycles. The sample fetch always occurs on an even CPU cycle due to its alignment with the APU. 68 | self.cpu_stall = true; 69 | // The sample buffer is filled with the next sample byte read from the current address, subject to whatever mapping hardware is present. 70 | self.sample_buffer = Some(sample_byte); 71 | // The address is incremented; if it exceeds $FFFF, it is wrapped around to $8000. 72 | if self.current_address == 0xFFFF { 73 | self.current_address = 0x8000 74 | } else { 75 | self.current_address += 1; 76 | } 77 | // The bytes remaining counter is decremented; if it becomes zero and the loop flag is set, the sample is restarted (see above); 78 | // otherwise, if the bytes remaining counter becomes zero and the IRQ enabled flag is set, the interrupt flag is set. 79 | self.bytes_remaining -= 1; 80 | } else if self.sample_buffer.is_none() && self.irq_enabled { 81 | self.interrupt = true; 82 | } 83 | } 84 | 85 | fn clock_output_unit(&mut self) { 86 | // When the timer outputs a clock, the following actions occur in order: 87 | // If the silence flag is clear, the output level changes based on bit 0 of the shift register. 88 | // If the bit is 1, add 2; otherwise, subtract 2. But if adding or subtracting 2 would cause the output level to leave the 0-127 range, 89 | // leave the output level unchanged. This means subtract 2 only if the current level is at least 2, or add 2 only if the current level is at most 125. 90 | // The right shift register is clocked. 91 | // As stated above, the bits-remaining counter is decremented. If it becomes zero, a new output cycle is started. 92 | if self.cpu_cycles_left > 0 { 93 | self.cpu_cycles_left -= 2; 94 | } 95 | if self.cpu_cycles_left == 0 { 96 | self.cpu_cycles_left = SAMPLE_RATES[self.rate_index]; 97 | if self.enabled { 98 | match self.shift_register & 1 { 99 | 0 => if self.sample >= 2 { self.sample -= 2}, 100 | 1 => if self.sample <= 125 { self.sample += 2 }, 101 | _ => panic!("uh oh! magical bits!"), 102 | } 103 | } else { 104 | self.sample = 0; 105 | } 106 | self.shift_register >>= 1; 107 | if self.bits_remaining > 0 { 108 | self.bits_remaining -= 1; 109 | } 110 | // When an output cycle ends, a new cycle is started as follows: 111 | // The bits-remaining counter is loaded with 8. 112 | // If the sample buffer is empty, then the silence flag is set; otherwise, the silence flag is cleared and the sample buffer is emptied into the shift register. 113 | if self.bits_remaining == 0 { 114 | self.bits_remaining = 8; 115 | match self.sample_buffer { 116 | Some(s) => { 117 | self.enabled = true; 118 | self.shift_register = s; 119 | self.sample_buffer = None; 120 | }, 121 | None => self.enabled = false, 122 | } 123 | } 124 | } 125 | } 126 | 127 | pub fn write_control(&mut self, value: u8) { 128 | // $4010 IL--.RRRR Flags and Rate (write) 129 | self.irq_enabled = value & 0b1000_0000 != 0; 130 | if !self.irq_enabled { 131 | self.interrupt = false; 132 | } 133 | self.loop_flag = value & 0b0100_0000 != 0; 134 | self.rate_index = value as usize & 0b0000_1111; 135 | } 136 | 137 | pub fn direct_load(&mut self, value: u8) { 138 | // $4011 -DDD.DDDD Direct load (write) 139 | self.sample = value as u16 & 0b0111_1111; 140 | } 141 | 142 | pub fn write_sample_address(&mut self, value: u8) { 143 | // $4012 AAAA.AAAA Sample address (write) 144 | // bits 7-0 AAAA.AAAA Sample address = %11AAAAAA.AA000000 = $C000 + (A * 64) 145 | self.sample_address = ((value as usize) << 6) + 0xC000; 146 | } 147 | 148 | pub fn write_sample_length(&mut self, value: u8) { 149 | // $4013 LLLL.LLLL Sample length (write) 150 | // bits 7-0 LLLL.LLLL Sample length = %LLLL.LLLL0001 = (L * 16) + 1 bytes 151 | self.sample_length = ((value as usize) << 4) + 1; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/apu/envelope.rs: -------------------------------------------------------------------------------- 1 | #[derive(serde::Serialize, serde::Deserialize, Clone)] 2 | pub struct Envelope { 3 | pub period: u16, // constant volume/envelope period 4 | divider: u16, 5 | pub decay_counter: u16, // remainder of envelope divider 6 | pub start: bool, // restarts envelope 7 | pub length_counter_halt: bool, // also the envelope loop flag 8 | } 9 | 10 | impl Envelope { 11 | pub fn new() -> Self { 12 | Envelope { 13 | period: 0, 14 | divider: 0, 15 | decay_counter: 0, 16 | start: false, 17 | length_counter_halt: false, 18 | } 19 | } 20 | 21 | pub fn clock(&mut self) { 22 | // When clocked by the frame counter, one of two actions occurs: 23 | // if the start flag is clear, the divider is clocked, 24 | if !self.start { 25 | self.clock_divider(); 26 | } else { 27 | self.start = false; // otherwise the start flag is cleared, 28 | self.decay_counter = 15; // the decay level counter is loaded with 15, 29 | self.divider = self.period; // and the divider's period is immediately reloaded 30 | } 31 | } 32 | 33 | fn clock_divider(&mut self) { 34 | // When the divider is clocked while at 0, it is loaded with V and clocks the decay level counter. 35 | if self.divider == 0 { 36 | self.divider = self.period; 37 | // Then one of two actions occurs: If the counter is non-zero, it is decremented, 38 | if self.decay_counter != 0 { 39 | self.decay_counter -= 1; 40 | } else if self.length_counter_halt { 41 | // otherwise if the loop flag is set, the decay level counter is loaded with 15. 42 | self.decay_counter = 15; 43 | } 44 | } else { 45 | self.divider -= 1; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/apu/mod.rs: -------------------------------------------------------------------------------- 1 | mod noise; 2 | mod square; 3 | mod triangle; 4 | mod dmc; 5 | mod envelope; 6 | pub mod serialize; 7 | 8 | use noise::Noise; 9 | use square::Square; 10 | use triangle::Triangle; 11 | use dmc::DMC; 12 | 13 | // APU clock ticks every other CPU cycle. 14 | // Frame counter only ticks every 3728.5 APU ticks, and in audio frames of 4 or 5. 15 | // Length counter controls note durations. 16 | 17 | const FRAME_COUNTER_STEPS: [usize; 5] = [3728, 7456, 11185, 14914, 18640]; 18 | const LENGTH_COUNTER_TABLE: [u8; 32] = [ 19 | 10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, 20 | 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30, 21 | ]; 22 | 23 | #[derive(serde::Serialize, serde::Deserialize, Clone)] 24 | pub struct Apu { 25 | square1: Square, 26 | square2: Square, 27 | triangle: Triangle, 28 | noise: Noise, 29 | pub dmc: DMC, 30 | 31 | square_table: Vec, 32 | tnd_table: Vec, 33 | 34 | frame_sequence: u8, 35 | frame_counter: u8, 36 | interrupt_inhibit: bool, 37 | frame_interrupt: bool, 38 | cycle: usize, 39 | pub trigger_irq: bool, 40 | } 41 | 42 | impl Apu { 43 | pub fn new() -> Self { 44 | let square_table = (0..31).map(|x| 95.52/((8128.0 / x as f32) + 100.0)).collect(); 45 | let tnd_table = (0..203).map(|x| 163.67/((24329.0 / x as f32) + 100.0)).collect(); 46 | Apu { 47 | square1: Square::new(true), 48 | square2: Square::new(false), 49 | triangle: Triangle::new(), 50 | noise: Noise::new(), 51 | dmc: DMC::new(), 52 | 53 | square_table: square_table, 54 | tnd_table: tnd_table, 55 | 56 | frame_sequence: 0, 57 | frame_counter: 0, 58 | interrupt_inhibit: false, 59 | frame_interrupt: false, 60 | cycle: 0, 61 | trigger_irq: false, 62 | } 63 | } 64 | 65 | pub fn clock(&mut self, sample_byte: u8) -> f32 { 66 | // Clock each channel 67 | self.square1.clock(); 68 | self.square2.clock(); 69 | self.triangle.clock(); 70 | self.triangle.clock(); // hacky. clocking triangle twice because it runs every CPU cycle 71 | self.noise.clock(); 72 | self.dmc.clock(sample_byte); 73 | 74 | // Step frame counter if necessary 75 | if FRAME_COUNTER_STEPS.contains(&self.cycle) { 76 | self.clock_frame_counter(); 77 | } 78 | self.cycle += 1; 79 | if (self.frame_sequence == 4 && self.cycle == 14915) || self.cycle == 18641 { 80 | self.cycle = 0; 81 | } 82 | 83 | // Send all samples to buffer, let the SDL2 audio callback take what it needs 84 | self.mix() 85 | } 86 | 87 | fn mix(&self) -> f32 { 88 | let square_out = self.square_table[(self.square1.sample + self.square2.sample) as usize]; 89 | let tnd_out = self.tnd_table[((3*self.triangle.sample)+(2*self.noise.sample) + self.dmc.sample) as usize]; 90 | square_out + tnd_out 91 | } 92 | 93 | pub fn write_reg(&mut self, address: usize, value: u8) { 94 | match address { 95 | 0x4000 => self.square1.write_duty(value), 96 | 0x4001 => self.square1.write_sweep(value), 97 | 0x4002 => self.square1.write_timer_low(value), 98 | 0x4003 => self.square1.write_timer_high(value), 99 | 0x4004 => self.square2.write_duty(value), 100 | 0x4005 => self.square2.write_sweep(value), 101 | 0x4006 => self.square2.write_timer_low(value), 102 | 0x4007 => self.square2.write_timer_high(value), 103 | 0x4008 => self.triangle.write_counter(value), 104 | 0x4009 => (), 105 | 0x400A => self.triangle.write_timer_low(value), 106 | 0x400B => self.triangle.write_timer_high(value), 107 | 0x400C => self.noise.write_envelope(value), 108 | 0x400D => (), 109 | 0x400E => self.noise.write_loop_noise(value), 110 | 0x400F => self.noise.write_length_counter(value), 111 | 0x4010 => self.dmc.write_control(value), 112 | 0x4011 => self.dmc.direct_load(value), 113 | 0x4012 => self.dmc.write_sample_address(value), 114 | 0x4013 => self.dmc.write_sample_length(value), 115 | 0x4014 => (), 116 | 0x4015 => self.write_control(value), 117 | 0x4016 => (), 118 | 0x4017 => self.write_frame_counter(value), 119 | _ => panic!("bad address written: 0x{:X}", address), 120 | } 121 | } 122 | 123 | // mode 0: mode 1: function 124 | // --------- ----------- ----------------------------- 125 | // - - - f - - - - - IRQ (if bit 6 is clear) 126 | // - l - l - l - - l Length counter and sweep 127 | // e e e e e e e - e Envelope and linear counter 128 | fn clock_frame_counter(&mut self) { 129 | if !(self.frame_sequence == 5 && self.frame_counter == 3) { 130 | // step envelopes 131 | self.square1.envelope.clock(); 132 | self.square2.envelope.clock(); 133 | self.triangle.clock_linear_counter(); 134 | self.noise.envelope.clock(); 135 | } 136 | if (self.frame_counter == 1) 137 | || (self.frame_sequence == 4 && self.frame_counter == 3) 138 | || (self.frame_sequence == 5 && self.frame_counter == 4) 139 | { 140 | // step length counters and sweep units 141 | self.square1.clock_sweep(); 142 | self.square2.clock_sweep(); 143 | self.square1.clock_length_counter(); 144 | self.square2.clock_length_counter(); 145 | self.triangle.clock_length_counter(); 146 | self.noise.clock_length_counter(); 147 | } 148 | if self.frame_sequence == 4 && self.frame_counter == 3 && !self.interrupt_inhibit { 149 | self.trigger_irq = true; 150 | } 151 | // advance counter 152 | self.frame_counter = self.frame_counter.wrapping_add(1); 153 | if self.frame_counter == self.frame_sequence { 154 | self.frame_counter = 0; 155 | } 156 | } 157 | 158 | // CPU writes to $4015 159 | fn write_control(&mut self, value: u8) { 160 | // Writing to this register clears the DMC interrupt flag. 161 | self.dmc.interrupt = false; 162 | // Writing a zero to any of the channel enable bits will silence that channel and immediately set its length counter to 0. 163 | if value & (1<<0) != 0 { 164 | self.square1.enabled = true; 165 | } else { 166 | self.square1.enabled = false; 167 | self.square1.length_counter = 0; 168 | } 169 | if value & (1<<1) != 0 { 170 | self.square2.enabled = true; 171 | } else { 172 | self.square2.enabled = false; 173 | self.square2.length_counter = 0; 174 | } 175 | if value & (1<<2) != 0 { 176 | self.triangle.enabled = true; 177 | } else { 178 | self.triangle.enabled = false; 179 | self.triangle.length_counter = 0; 180 | } 181 | if value & (1<<3) != 0 { 182 | self.noise.enabled = true; 183 | } else { 184 | self.noise.enabled = false; 185 | self.noise.length_counter = 0; 186 | } 187 | if value & (1<<4) != 0 { 188 | self.dmc.enabled = true; 189 | // If the DMC bit is set, the DMC sample will be restarted only if its bytes remaining is 0. 190 | // If there are bits remaining in the 1-byte sample buffer, these will finish playing before the next sample is fetched. 191 | if self.dmc.bytes_remaining == 0 { 192 | self.dmc.current_address = self.dmc.sample_address; 193 | self.dmc.bytes_remaining = self.dmc.sample_length; 194 | } 195 | } else { 196 | self.dmc.enabled = false; 197 | // If the DMC bit is clear, the DMC bytes remaining will be set to 0 and the DMC will silence when it empties. 198 | self.dmc.bytes_remaining = 0; 199 | } 200 | } 201 | 202 | // CPU reads from $4015 203 | pub fn read_status(&mut self) -> u8 { 204 | // IF-D NT21: DMC interrupt (I), frame interrupt (F), DMC active (D), length counter > 0 (N/T/2/1) 205 | let mut val = 0; 206 | // N/T/2/1 will read as 1 if the corresponding length counter is greater than 0. For the triangle channel, the status of the linear counter is irrelevant. 207 | if self.square1.length_counter != 0 { 208 | val |= 1<<0; 209 | } 210 | if self.square2.length_counter != 0 { 211 | val |= 1<<1; 212 | } 213 | if self.triangle.length_counter != 0 { 214 | val |= 1<<2; 215 | } 216 | if self.noise.length_counter != 0 { 217 | val |= 1<<3; 218 | } 219 | // D will read as 1 if the DMC bytes remaining is more than 0. 220 | if self.dmc.bytes_remaining != 0 { 221 | val |= 1<<4; 222 | } 223 | if self.frame_interrupt { 224 | val |= 1<<6; 225 | } 226 | if self.dmc.interrupt { 227 | val |= 1<<7; 228 | } 229 | // Reading this register clears the frame interrupt flag (but not the DMC interrupt flag). 230 | self.frame_interrupt = false; 231 | // TODO: If an interrupt flag was set at the same moment of the read, it will read back as 1 but it will not be cleared. 232 | val 233 | } 234 | 235 | // $4017 236 | fn write_frame_counter(&mut self, value: u8) { 237 | // 0 selects 4-step sequence, 1 selects 5-step sequence 238 | self.frame_sequence = if value & (1<<7) == 0 { 4 } else { 5 }; 239 | // If set, the frame interrupt flag is cleared, otherwise it is unaffected. 240 | if value & (1<<6) != 0 { 241 | self.interrupt_inhibit = false; 242 | } 243 | // If the mode flag is set, then both "quarter frame" and "half frame" signals are also generated. 244 | if self.frame_sequence == 5 { 245 | // Clock envelopes, length counters, and sweep units 246 | self.square1.envelope.clock(); 247 | self.square1.clock_sweep(); 248 | self.square1.clock_length_counter(); 249 | self.square2.envelope.clock(); 250 | self.square2.clock_sweep(); 251 | self.square2.clock_length_counter(); 252 | self.triangle.clock_linear_counter(); 253 | self.triangle.clock_length_counter(); 254 | self.noise.envelope.clock(); 255 | self.noise.clock_length_counter(); 256 | } 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/apu/noise.rs: -------------------------------------------------------------------------------- 1 | use super::envelope::Envelope; 2 | 3 | const NOISE_TABLE: [u16; 16] = [4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068]; 4 | 5 | // $400E M---.PPPP Mode and period (write) 6 | // bit 7 M--- ---- Mode flag 7 | #[derive(serde::Serialize, serde::Deserialize, Clone)] 8 | pub struct Noise { 9 | pub sample: u16, // output value that gets sent to the mixer 10 | pub enabled: bool, 11 | constant_volume_flag: bool, 12 | mode: bool, // also called loop noise, bit 7 of $400E 13 | timer: u16, 14 | timer_period: u16, 15 | pub length_counter: u8, 16 | linear_feedback_sr: u16, 17 | pub envelope: Envelope, 18 | } 19 | 20 | impl Noise { 21 | pub fn new() -> Self { 22 | Noise { 23 | sample: 0, 24 | enabled: false, 25 | constant_volume_flag: false, 26 | mode: false, 27 | timer: 0, 28 | timer_period: 0, 29 | length_counter: 0, 30 | linear_feedback_sr: 1, // On power-up, the shift register is loaded with the value 1. 31 | envelope: Envelope::new(), 32 | } 33 | } 34 | 35 | pub fn clock(&mut self) { 36 | if self.timer == 0 { 37 | self.clock_linear_counter(); 38 | } else { 39 | self.timer -= 1; 40 | } 41 | // The mixer receives the current envelope volume except when 42 | // Bit 0 of the shift register is set, or the length counter is zero 43 | self.sample = if self.linear_feedback_sr & 1 == 1 || self.length_counter == 0 { 44 | 0 45 | } else if self.constant_volume_flag { 46 | self.envelope.period 47 | } else { 48 | self.envelope.decay_counter 49 | }; 50 | } 51 | 52 | pub fn clock_linear_counter(&mut self) { 53 | // When the timer clocks the shift register, the following actions occur in order: 54 | // Feedback is calculated as the exclusive-OR of bit 0 55 | let bit0 = self.linear_feedback_sr & (1 << 0); 56 | // and one other bit: bit 6 if Mode flag is set, otherwise bit 1. 57 | let bit_num = if self.mode { 6 } else { 1 }; 58 | let other_bit = (self.linear_feedback_sr & (1 << bit_num)) >> bit_num; 59 | let feedback = bit0 ^ other_bit; 60 | // The shift register is shifted right by one bit. 61 | self.linear_feedback_sr >>= 1; 62 | // Bit 14, the leftmost bit, is set to the feedback calculated earlier. 63 | self.linear_feedback_sr |= feedback << 14; 64 | } 65 | 66 | pub fn clock_length_counter(&mut self) { 67 | if !(self.length_counter == 0 || self.envelope.length_counter_halt) { 68 | self.length_counter -= 1; 69 | } 70 | } 71 | 72 | // $400C 73 | pub fn write_envelope(&mut self, value: u8) { 74 | self.envelope.length_counter_halt = (value >> 5) & 1 == 1; 75 | self.constant_volume_flag = (value >> 4) & 1 == 1; 76 | self.envelope.period = value as u16 & 0b1111; 77 | } 78 | 79 | // $400E 80 | pub fn write_loop_noise(&mut self, value: u8) { 81 | self.mode = value >> 7 == 1; 82 | self.timer_period = NOISE_TABLE[(value & 0b1111) as usize]; 83 | } 84 | 85 | // $400F 86 | pub fn write_length_counter(&mut self, value: u8) { 87 | self.length_counter = value >> 3; 88 | self.envelope.start = true; 89 | } 90 | } 91 | 92 | // When the timer clocks the shift register, the following actions occur in order: 93 | 94 | // 1. Feedback is calculated as the exclusive-OR of bit 0 and one other bit: bit 6 if Mode flag is set, otherwise bit 1. 95 | // 2. The shift register is shifted right by one bit. 96 | // 3. Bit 14, the leftmost bit, is set to the feedback calculated earlier. 97 | 98 | // This results in a pseudo-random bit sequence, 32767 steps long when Mode flag is clear, 99 | // and randomly 93 or 31 steps long otherwise. (The particular 31- or 93-step sequence depends 100 | // on where in the 32767-step sequence the shift register was when Mode flag was set). 101 | -------------------------------------------------------------------------------- /src/apu/serialize.rs: -------------------------------------------------------------------------------- 1 | pub type ApuData = super::Apu; 2 | 3 | impl super::Apu{ 4 | pub fn save_state(&self) -> ApuData { 5 | self.clone() 6 | } 7 | 8 | pub fn load_state(&mut self, data: ApuData) { 9 | self.square1 = data.square1; 10 | self.square2 = data.square2; 11 | self.triangle = data.triangle; 12 | self.noise = data.noise; 13 | self.dmc = data.dmc; 14 | self.square_table = data.square_table; 15 | self.tnd_table = data.tnd_table; 16 | self.frame_sequence = data.frame_sequence; 17 | self.frame_counter = data.frame_counter; 18 | self.interrupt_inhibit = data.interrupt_inhibit; 19 | self.frame_interrupt = data.frame_interrupt; 20 | self.cycle = data.cycle; 21 | self.trigger_irq = data.trigger_irq; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/apu/square.rs: -------------------------------------------------------------------------------- 1 | use super::envelope::Envelope; 2 | 3 | const DUTY_CYCLE_SEQUENCES: [[u8; 8]; 4] = [ 4 | [0, 1, 0, 0, 0, 0, 0, 0], 5 | [0, 1, 1, 0, 0, 0, 0, 0], 6 | [0, 1, 1, 1, 1, 0, 0, 0], 7 | [1, 0, 0, 1, 1, 1, 1, 1], 8 | ]; 9 | 10 | #[derive(serde::Serialize, serde::Deserialize, Clone)] 11 | pub struct Square { 12 | pub sample: u16, // output value that gets sent to the mixer 13 | pub enabled: bool, 14 | constant_volume_flag: bool, // (0: use volume from envelope; 1: use constant volume) 15 | first_channel: bool, // hack to detect timing difference in clock_sweep() 16 | 17 | timer: u16, 18 | timer_period: u16, 19 | duty_cycle: [u8; 8], // "sequencer", set to one of the lines in DUTY_CYCLE_SEQUENCES 20 | duty_counter: usize, // current index within the duty_cycle 21 | pub length_counter: u8, 22 | 23 | target_period: u16, 24 | sweep_period: u16, 25 | sweep_counter: u16, 26 | shift_count: u8, 27 | sweep_enabled: bool, 28 | sweep_negate: bool, 29 | sweep_reload: bool, 30 | 31 | pub envelope: Envelope, 32 | } 33 | 34 | impl Square { 35 | pub fn new(first_channel: bool) -> Self { 36 | Square { 37 | sample: 0, 38 | enabled: false, 39 | constant_volume_flag: false, 40 | first_channel: first_channel, 41 | timer: 0, 42 | timer_period: 0, 43 | duty_cycle: DUTY_CYCLE_SEQUENCES[0], 44 | duty_counter: 0, 45 | length_counter: 0, 46 | target_period: 0, 47 | sweep_period: 0, 48 | sweep_counter: 0, 49 | shift_count: 0, 50 | sweep_enabled: false, 51 | sweep_negate: false, 52 | sweep_reload: false, 53 | envelope: Envelope::new(), 54 | } 55 | } 56 | 57 | pub fn clock(&mut self) { 58 | // The sequencer is clocked by an 11-bit timer. Given the timer value t = HHHLLLLLLLL formed by timer high and timer low, this timer is updated every APU cycle 59 | // (i.e., every second CPU cycle), and counts t, t-1, ..., 0, t, t-1, ..., clocking the waveform generator when it goes from 0 to t. 60 | if self.timer == 0 { 61 | self.timer = self.timer_period; 62 | self.duty_counter = (self.duty_counter + 1) % 8; 63 | } else { 64 | self.timer -= 1; 65 | } 66 | // Update volume for this channel 67 | // The mixer receives the current envelope volume except when 68 | self.sample = if self.duty_cycle[self.duty_counter] == 0 // the sequencer output is zero, or 69 | || self.timer_period > 0x7FF // overflow from the sweep unit's adder is silencing the channel, 70 | || self.length_counter == 0 // the length counter is zero, or 71 | || self.timer_period < 8 // the timer has a value less than eight. 72 | { 73 | 0 74 | } else if self.constant_volume_flag { 75 | self.envelope.period 76 | } else { 77 | self.envelope.decay_counter 78 | }; 79 | } 80 | 81 | pub fn clock_length_counter(&mut self) { 82 | if !(self.length_counter == 0 || self.envelope.length_counter_halt) { 83 | self.length_counter -= 1; 84 | } 85 | } 86 | 87 | pub fn clock_sweep(&mut self) { 88 | self.calculate_target_period(); 89 | // When the frame counter sends a half-frame clock (at 120 or 96 Hz), two things happen. 90 | // If the divider's counter is zero, the sweep is enabled, and the sweep unit is not muting the channel: The pulse's period is adjusted. 91 | if self.sweep_counter == 0 && self.sweep_enabled && !(self.timer_period < 8 || self.target_period > 0x7FF) { 92 | self.timer_period = self.target_period; 93 | } 94 | // If the divider's counter is zero or the reload flag is true: The counter is set to P and the reload flag is cleared. Otherwise, the counter is decremented. 95 | if self.sweep_counter == 0 || self.sweep_reload { 96 | self.sweep_counter = self.sweep_period; 97 | self.sweep_reload = false; 98 | if self.sweep_enabled { self.timer_period = self.target_period; } // This fixes the DK walking sound. Why? Not reflected in documentation. 99 | } else { 100 | self.sweep_counter -= 1; 101 | } 102 | } 103 | 104 | // Whenever the current period changes for any reason, whether by $400x writes or by sweep, the target period also changes. 105 | pub fn calculate_target_period(&mut self) { 106 | // The sweep unit continuously calculates each channel's target period in this way: 107 | // A barrel shifter shifts the channel's 11-bit raw timer period right by the shift count, producing the change amount. 108 | let change = self.timer_period >> self.shift_count; 109 | // If the negate flag is true, the change amount is made negative. 110 | // The target period is the sum of the current period and the change amount. 111 | if self.sweep_negate { 112 | self.target_period = self.timer_period - change; 113 | // The two pulse channels have their adders' carry inputs wired differently, 114 | // which produces different results when each channel's change amount is made negative: 115 | // Pulse 1 adds the ones' complement (-c - 1). Making 20 negative produces a change amount of -21. 116 | // Pulse 2 adds the two's complement (-c). Making 20 negative produces a change amount of -20. 117 | if self.first_channel && self.target_period >= 1 { 118 | self.target_period -= 1; 119 | } 120 | } else { 121 | self.target_period = self.timer_period + change; 122 | } 123 | } 124 | 125 | // $4000/$4004 126 | pub fn write_duty(&mut self, value: u8) { 127 | // The duty cycle is changed (see table below), but the sequencer's current position isn't affected. 128 | self.duty_cycle = DUTY_CYCLE_SEQUENCES[(value >> 6) as usize]; 129 | self.envelope.length_counter_halt = value & (1<<5) != 0; 130 | self.constant_volume_flag = value & (1<<4) != 0; 131 | self.envelope.period = value as u16 & 0b1111; 132 | } 133 | 134 | // $4001/$4005 135 | pub fn write_sweep(&mut self, value: u8) { 136 | self.sweep_enabled = value >> 7 == 1; 137 | self.sweep_period = ((value as u16 >> 4) & 0b111) + 1; 138 | self.sweep_negate = value & 0b1000 != 0; 139 | self.shift_count = value & 0b111; 140 | self.sweep_reload = true; 141 | } 142 | 143 | // $4002/$4006 144 | pub fn write_timer_low(&mut self, value: u8) { 145 | self.timer_period &= 0b00000111_00000000; // mask off everything but high 3 bits of 11-bit timer 146 | self.timer_period |= value as u16; // apply low 8 bits 147 | self.calculate_target_period(); 148 | } 149 | 150 | // $4003/$4007 151 | pub fn write_timer_high(&mut self, value: u8) { 152 | // LLLL.Lttt Pulse channel 1 length counter load and timer (write) 153 | // TODO: thought the below meant that the length counter was only set if the channel was enabled, but apparently not as not having it fixes start game noise in DK. 154 | // When the enabled bit is cleared (via $4015), the length counter is forced to 0 and cannot be changed until enabled is set again (the length counter's previous value is lost). 155 | if self.enabled { 156 | self.length_counter = super::LENGTH_COUNTER_TABLE[value as usize >> 3]; 157 | } 158 | let timer_high = value as u16 & 0b0000_0111; 159 | self.timer_period &= 0b11111000_11111111; // mask off high 3 bits of 11-bit timer 160 | self.timer_period |= timer_high << 8; // apply high timer bits in their place 161 | self.calculate_target_period(); 162 | // The sequencer is immediately restarted at the first value of the current sequence. The envelope is also restarted. The period divider is not reset. 163 | self.duty_counter = 0; 164 | self.envelope.start = true; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/apu/triangle.rs: -------------------------------------------------------------------------------- 1 | const WAVEFORM: [u16; 32] = [ 2 | 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 3 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 4 | ]; 5 | 6 | #[derive(serde::Serialize, serde::Deserialize, Clone)] 7 | pub struct Triangle { 8 | pub sample: u16, 9 | pub enabled: bool, 10 | 11 | timer: u16, 12 | timer_period: u16, 13 | waveform_counter: usize, 14 | pub length_counter: u8, 15 | length_counter_halt: bool, // (this bit is also the linear counter's control flag) 16 | 17 | linear_counter: u8, 18 | counter_reload_value: u8, 19 | linear_counter_reload: bool, 20 | } 21 | 22 | impl Triangle { 23 | pub fn new() -> Self { 24 | Triangle { 25 | sample: 0, 26 | enabled: false, 27 | timer: 0, 28 | timer_period: 0, 29 | waveform_counter: 0, 30 | length_counter: 0, 31 | length_counter_halt: false, 32 | linear_counter: 0, 33 | counter_reload_value: 0, 34 | linear_counter_reload: false, 35 | } 36 | } 37 | 38 | pub fn clock(&mut self) { 39 | if self.timer == 0 { 40 | self.timer = self.timer_period; 41 | // The sequencer is clocked by the timer as long as both the linear counter and the length counter are nonzero. 42 | if self.linear_counter != 0 && self.length_counter != 0 { 43 | self.waveform_counter = (self.waveform_counter + 1) % 32; 44 | } 45 | } else { 46 | self.timer -= 1; 47 | } 48 | self.sample = WAVEFORM[self.waveform_counter]; 49 | } 50 | 51 | pub fn clock_linear_counter(&mut self) { 52 | // When the frame counter generates a linear counter clock, the following actions occur in order: 53 | // If the linear counter reload flag is set, the linear counter is reloaded with the counter reload value, 54 | if self.linear_counter_reload { 55 | self.linear_counter = self.counter_reload_value; 56 | } else if self.linear_counter != 0 { // otherwise if the linear counter is non-zero, it is decremented. 57 | self.linear_counter -= 1; 58 | } 59 | // If the control flag is clear, the linear counter reload flag is cleared. 60 | if !self.length_counter_halt { 61 | self.linear_counter_reload = false; 62 | } 63 | } 64 | 65 | pub fn clock_length_counter(&mut self) { 66 | if !(self.length_counter == 0 || self.length_counter_halt) { 67 | self.length_counter -= 1; 68 | } 69 | } 70 | 71 | // $4008 72 | pub fn write_counter(&mut self, value: u8) { 73 | self.length_counter_halt = value >> 7 != 0; 74 | self.counter_reload_value = (value << 1) >> 1; 75 | } 76 | 77 | // $400A 78 | pub fn write_timer_low(&mut self, value: u8) { 79 | self.timer_period &= 0b00000111_00000000; 80 | self.timer_period |= value as u16; 81 | } 82 | 83 | // $400B 84 | pub fn write_timer_high(&mut self, value: u8) { 85 | if self.enabled { 86 | self.length_counter = super::LENGTH_COUNTER_TABLE[value as usize >> 3]; 87 | } 88 | self.timer_period &= 0b00000000_11111111; 89 | let timer_high = value & 0b0000_0111; 90 | self.timer_period |= (timer_high as u16) << 8; 91 | self.linear_counter_reload = true; 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/audio.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | use sdl2::Sdl; 3 | use sdl2::audio::{AudioCallback, AudioSpecDesired}; 4 | use std::f32::consts::PI; 5 | 6 | const APU_SAMPLE_RATE: f32 = 894_886.5; 7 | const SDL_SAMPLE_RATE: i32 = 44_100; 8 | // Video runs at 60Hz, so console is clocked by doing enough work to create one frame of video, then sending the video and audio to their respective SDL 9 | // devices and then sleeping. So the audio device is set to play 44,100 samples per second, and grab them in 60 intervals over the course of that second. 10 | const SAMPLES_PER_FRAME: u16 = SDL_SAMPLE_RATE as u16/60; 11 | 12 | 13 | pub struct ApuSampler { 14 | // This buffer receives all of the raw audio produced by the APU. 15 | // The callback will take what it needs when it needs it and truncate the buffer for smooth audio output. 16 | buffer: Arc>>, 17 | sample_ratio: f32, 18 | 19 | prev_input_90_hz: f32, 20 | prev_output_90_hz: f32, 21 | gamma_90_hz: f32, 22 | 23 | prev_input_440_hz: f32, 24 | prev_output_440_hz: f32, 25 | gamma_440_hz: f32, 26 | 27 | prev_input_14_khz: f32, 28 | prev_output_14_khz: f32, 29 | gamma_14_khz: f32, 30 | } 31 | 32 | impl ApuSampler { 33 | fn high_pass_90_hz(&self, sample: f32) -> f32 { 34 | // y[i] := α × y[i−1] + α × (x[i] − x[i−1]) 35 | (self.gamma_90_hz * self.prev_output_90_hz) + (sample - self.prev_input_90_hz) 36 | } 37 | 38 | fn high_pass_440_hz(&self, sample: f32) -> f32 { 39 | (self.gamma_440_hz * self.prev_output_440_hz) + (sample - self.prev_input_440_hz) 40 | } 41 | 42 | fn low_pass_14_khz(&self, sample: f32) -> f32 { 43 | ((1. - self.gamma_14_khz) * self.prev_output_14_khz) + (self.gamma_14_khz * sample) 44 | } 45 | 46 | } 47 | 48 | impl AudioCallback for ApuSampler { 49 | type Channel = f32; 50 | 51 | fn callback(&mut self, out: &mut [f32]) { 52 | 53 | let mut b = self.buffer.lock().unwrap(); 54 | // if we have data in the buffer 55 | if b.len() > 0 { 56 | // copy samples at the appropriate interval from the raw APU buffer to the output device 57 | for (i, x) in out.iter_mut().enumerate() { 58 | let sample_idx = ((i as f32) * self.sample_ratio) as usize; 59 | if sample_idx < b.len() { 60 | let sample = b[sample_idx]; 61 | 62 | let filtered_90_hz = self.high_pass_90_hz(sample); 63 | self.prev_input_90_hz = sample; 64 | self.prev_output_90_hz = filtered_90_hz; 65 | 66 | let filtered_440_hz = self.high_pass_440_hz(filtered_90_hz); 67 | self.prev_input_440_hz = filtered_90_hz; 68 | self.prev_output_440_hz = filtered_440_hz; 69 | 70 | let filtered_14_khz = self.low_pass_14_khz(filtered_440_hz); 71 | self.prev_input_14_khz = filtered_440_hz; 72 | self.prev_output_14_khz = filtered_14_khz; 73 | *x = filtered_14_khz; 74 | } 75 | } 76 | let l = b.len(); 77 | let target = (SAMPLES_PER_FRAME as f32 * self.sample_ratio) as usize; 78 | if l > target { 79 | *b = b.split_off(target); 80 | } 81 | } else { 82 | println!("buffer empty!"); // happens when the callback fires twice between video frames 83 | } 84 | } 85 | } 86 | 87 | pub fn initialize(sdl_context: &Sdl, buffer: Arc>>) 88 | -> Result, String> 89 | { 90 | let audio_subsystem = sdl_context.audio()?; 91 | let desired_spec = AudioSpecDesired { 92 | freq: Some(SDL_SAMPLE_RATE), 93 | channels: Some(1), // mono 94 | samples: Some(SAMPLES_PER_FRAME) 95 | }; 96 | audio_subsystem.open_playback(None, &desired_spec, |_spec| { 97 | // println!("{:?}", _spec); 98 | ApuSampler{ 99 | buffer, 100 | sample_ratio: APU_SAMPLE_RATE / (SDL_SAMPLE_RATE as f32), 101 | prev_input_90_hz: 0., 102 | prev_output_90_hz: 0., 103 | gamma_90_hz: high_pass_coefficient(90.), 104 | prev_input_440_hz: 0., 105 | prev_output_440_hz: 0., 106 | gamma_440_hz: high_pass_coefficient(440.), 107 | prev_input_14_khz: 0., 108 | prev_output_14_khz: 0., 109 | gamma_14_khz: low_pass_coefficient(14_000.), 110 | } 111 | }) 112 | } 113 | 114 | fn low_pass_coefficient(cutoff_freq: f32) -> f32 { 115 | (2.*PI*cutoff_freq/SDL_SAMPLE_RATE as f32) / ((2.*PI*cutoff_freq/SDL_SAMPLE_RATE as f32) + 1.) 116 | } 117 | 118 | fn high_pass_coefficient(cutoff_freq: f32) -> f32 { 119 | 1. / ((2.*PI*cutoff_freq/SDL_SAMPLE_RATE as f32) + 1.) 120 | } 121 | 122 | /* 123 | 124 | https://en.wikipedia.org/wiki/High-pass_filter 125 | https://en.wikipedia.org/wiki/Low-pass_filter 126 | 127 | low pass filter: 128 | y = (1 - gamma) * y + gamma * x 129 | 130 | high pass filter: 131 | y[i] := gamma * y[i−1] + gamma * (x[i] − x[i−1]) 132 | 133 | */ 134 | -------------------------------------------------------------------------------- /src/cartridge/cnrom.rs: -------------------------------------------------------------------------------- 1 | use super::{Cartridge, Mapper, Mirror, serialize::*}; 2 | 3 | pub struct Cnrom { 4 | cart: Cartridge, 5 | chr_bank_select: usize, 6 | } 7 | 8 | impl Cnrom { 9 | pub fn new(cart: Cartridge) -> Self { 10 | Cnrom{ 11 | cart: cart, 12 | chr_bank_select: 0, 13 | } 14 | } 15 | } 16 | 17 | impl Mapper for Cnrom { 18 | fn read(&self, address: usize) -> u8 { 19 | let pl = self.cart.prg_rom.len(); 20 | let addr = address % 0x4000; 21 | match address { 22 | 0x0000..=0x1FFF => self.cart.chr_rom[self.chr_bank_select][address], 23 | 0x8000..=0xBFFF => self.cart.prg_rom[0][addr], 24 | 0xC000..=0xFFFF => self.cart.prg_rom[pl-1][addr], 25 | _ => {println!("bad address read from CNROM mapper: 0x{:X}", address); 0}, 26 | } 27 | } 28 | 29 | fn write(&mut self, address: usize, value: u8) { 30 | match address { 31 | 0x8000..=0xFFFF => self.chr_bank_select = (value & 0b11) as usize, 32 | _ => println!("bad address written to CNROM mapper: 0x{:X}", address), 33 | } 34 | } 35 | 36 | fn get_mirroring(&self) -> Mirror { 37 | self.cart.mirroring 38 | } 39 | 40 | fn load_battery_backed_ram(&mut self) {} 41 | fn save_battery_backed_ram(&self) {} 42 | fn clock(&mut self) {} 43 | fn check_irq(&mut self) -> bool {false} 44 | 45 | fn save_state(&self) -> MapperData { 46 | MapperData::Cnrom( 47 | CnromData { 48 | cart: self.cart.clone(), 49 | chr_bank_select: self.chr_bank_select, 50 | } 51 | ) 52 | } 53 | 54 | fn load_state(&mut self, mapper_data: MapperData) { 55 | if let MapperData::Cnrom(cnrom_data) = mapper_data { 56 | self.cart = cnrom_data.cart; 57 | self.chr_bank_select = cnrom_data.chr_bank_select; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/cartridge/mmc1.rs: -------------------------------------------------------------------------------- 1 | use super::{Cartridge, Mapper, Mirror, serialize::*}; 2 | 3 | use std::fs::File; 4 | use std::io::{Read, Write}; 5 | use std::path::Path; 6 | 7 | pub struct Mmc1 { 8 | cart: Cartridge, 9 | step: u8, 10 | shift_register: u8, 11 | mirroring: Mirror, 12 | control: u8, 13 | 14 | prg_ram_bank: Vec, // CPU $6000-$7FFF 15 | prg_ram_enabled: bool, 16 | 17 | prg_bank_mode: u8, 18 | prg_bank_select: usize, // selects among the PRG-RAM chunks in the cartridge 19 | 20 | chr_ram_bank: Vec, // used if cartridge doesn't have any CHR-ROM, 8KB, $0000-$1FFF 21 | chr_low_bank: usize, // PPU $0000-$0FFF 22 | chr_high_bank: usize, // PPU $1000-$1FFF 23 | chr_bank_mode: bool, // false: switch 8 KB at a time; true: switch two separate 4 KB banks 24 | } 25 | 26 | impl Mmc1 { 27 | pub fn new(cart: Cartridge) -> Self { 28 | let m = cart.mirroring; 29 | let mut mmc1 = Mmc1 { 30 | cart: cart, 31 | step: 0, 32 | shift_register: 0, 33 | mirroring: m, 34 | control: 0, 35 | prg_ram_bank: vec![0; 0x2000], 36 | prg_ram_enabled: false, 37 | prg_bank_mode: 3, 38 | prg_bank_select: 0, 39 | chr_ram_bank: vec![0; 0x2000], 40 | chr_low_bank: 0, 41 | chr_high_bank: 0, 42 | chr_bank_mode: false, 43 | }; 44 | mmc1.load_battery_backed_ram(); 45 | mmc1 46 | } 47 | 48 | fn write_serial_port(&mut self, address: usize, value: u8) { 49 | // if reset flag is on, reset 50 | if value & 0b1000_0000 != 0 { 51 | self.step = 0; 52 | self.shift_register = 0; 53 | // locking PRG ROM at $C000-$FFFF to the last bank 54 | // self.prg_high_bank = self.cart.prg_rom_size - 1; 55 | self.write_control(self.control | 0xC) 56 | } else { 57 | // otherwise perform normal write 58 | self.shift_register >>= 1; 59 | self.shift_register |= (value & 1) << 7; 60 | if self.step == 4 { 61 | // shift register values will be in top 5 bits, so cut it down to size before moving on to where it's used 62 | self.shift_register >>= 3; 63 | match address { 64 | 0x8000..=0x9FFF => self.write_control(self.shift_register), 65 | 0xA000..=0xBFFF => self.write_chr_bank_low(self.shift_register), 66 | 0xC000..=0xDFFF => self.write_chr_bank_high(self.shift_register), 67 | 0xE000..=0xFFFF => self.write_prg_bank(self.shift_register), 68 | _ => panic!("bad address write to MMC1: 0x{:X}", address), 69 | } 70 | self.step = 0; 71 | self.shift_register = 0; 72 | } else { 73 | self.step += 1; 74 | } 75 | } 76 | } 77 | 78 | fn write_control(&mut self, value: u8) { 79 | self.control = value; 80 | self.mirroring = match value & 0b11 { 81 | 0 => Mirror::LowBank, 82 | 1 => Mirror::HighBank, 83 | 2 => Mirror::Vertical, 84 | 3 => Mirror::Horizontal, 85 | _ => panic!("invalid mirroring value"), 86 | }; 87 | self.prg_bank_mode = (value >> 2) & 0b11; 88 | self.chr_bank_mode = if value & (1<<4) == 0 {false} else {true}; 89 | } 90 | 91 | fn write_chr_bank_low(&mut self, value: u8) { 92 | if self.chr_bank_mode { // 4 KB mode 93 | self.chr_low_bank = value as usize; 94 | } else { // 8 KB mode 95 | let v = value & (0xFF - 1); // turn off low bit 96 | self.chr_low_bank = v as usize; 97 | self.chr_high_bank = (v + 1) as usize; 98 | } 99 | } 100 | 101 | fn write_chr_bank_high(&mut self, value: u8) { 102 | if self.chr_bank_mode { // 4 KB mode only, ignored in 8 KB mode 103 | self.chr_high_bank = value as usize; 104 | } 105 | } 106 | 107 | fn write_prg_bank(&mut self, value: u8) { 108 | self.prg_bank_select = (value & 0b1111) as usize; 109 | self.prg_ram_enabled = value & 0b10000 != 0; 110 | } 111 | } 112 | 113 | impl Mapper for Mmc1 { 114 | fn read(&self, address: usize) -> u8 { 115 | match address { 116 | 0x0000..=0x1FFF => { 117 | if self.cart.chr_rom_size == 0 { 118 | self.chr_ram_bank[address] 119 | } else { 120 | let offset = address % 0x1000; 121 | if self.chr_bank_mode { 122 | // if 4K bank mode, $0000-$0FFF will be a 4K-indexed section of some CHR-ROM chunk referred to by chr_low_bank 123 | // and $1000-$1FFF will be the one referred to by chr_high_bank 124 | let bank = match address { 125 | 0x0000..=0x0FFF => self.chr_low_bank, 126 | 0x1000..=0x1FFF => self.chr_high_bank, 127 | _ => panic!("bad address read from MMC1: 0x{:X}", address), 128 | }; 129 | let chunk_num = bank / 2; 130 | let chunk_half = if bank % 2 == 0 {0x0} else {0x1000}; 131 | self.cart.chr_rom[chunk_num][chunk_half + offset] 132 | } else { 133 | // if we're in 8K bank mode, the whole $0000-$1FFF region will be the 8K range referred to by chr_low_bank 134 | self.cart.chr_rom[self.chr_low_bank][address] 135 | } 136 | } 137 | }, 138 | 0x6000..=0x7FFF => self.prg_ram_bank[address % 0x2000], 139 | 0x8000..=0xBFFF => { 140 | match self.prg_bank_mode { 141 | 0 | 1 => { // switch 32 KB at $8000, ignoring low bit of bank number 142 | let low_bank = self.prg_bank_select & (0xFF - 1); 143 | self.cart.prg_rom[low_bank][address % 0x4000] 144 | }, 145 | 2 => self.cart.prg_rom[0][address % 0x4000], 146 | 3 => self.cart.prg_rom[self.prg_bank_select][address % 0x4000], 147 | _ => panic!("invalid PRG bank mode"), 148 | } 149 | }, 150 | 0xC000..=0xFFFF => { 151 | match self.prg_bank_mode { 152 | 0 | 1 => { // switch 32 KB at $8000, ignoring low bit of bank number 153 | let high_bank = (self.prg_bank_select & (0xFF - 1)) + 1; 154 | self.cart.prg_rom[high_bank][address % 0x4000] 155 | }, 156 | 2 => self.cart.prg_rom[self.prg_bank_select][address % 0x4000], 157 | 3 => self.cart.prg_rom[self.cart.prg_rom_size - 1][address % 0x4000], 158 | _ => panic!("invalid PRG bank mode"), 159 | } 160 | }, 161 | _ => panic!("invalid address passed to MMC1: 0x{:X}", address), 162 | } 163 | } 164 | 165 | fn write(&mut self, address: usize, value: u8) { 166 | match address { 167 | 0x0000..=0x1FFF => { // if we don't have CHR-ROM, write to CHR-RAM 168 | if self.cart.chr_rom_size == 0 { 169 | self.chr_ram_bank[address] = value; 170 | } 171 | }, 172 | 0x6000..=0x7FFF => self.prg_ram_bank[address % 0x2000] = value, 173 | 0x8000..=0xFFFF => self.write_serial_port(address, value), 174 | _ => panic!("bad address write to MMC1: 0x{:X}", address), 175 | } 176 | } 177 | 178 | fn get_mirroring(&self) -> Mirror { 179 | self.mirroring 180 | } 181 | 182 | fn load_battery_backed_ram(&mut self) { 183 | if self.cart.battery_backed_ram { 184 | let p = Path::new(&self.cart.filename).parent().unwrap(); 185 | let stem = Path::new(&self.cart.filename).file_stem().unwrap(); 186 | let mut save_file = p.join(stem); 187 | save_file.set_extension("sav"); 188 | if Path::new(&save_file).exists() { 189 | let mut f = File::open(save_file.clone()).expect("save file exists but could not open it"); 190 | let mut battery_backed_ram_data = vec![]; 191 | f.read_to_end(&mut battery_backed_ram_data).expect("error reading save file"); 192 | println!("loading battery-backed RAM from file: {:?}", save_file); 193 | self.prg_ram_bank = battery_backed_ram_data; 194 | } 195 | } 196 | } 197 | 198 | fn save_battery_backed_ram(&self) { 199 | if self.cart.battery_backed_ram { 200 | let p = Path::new(&self.cart.filename).parent().unwrap(); 201 | let stem = Path::new(&self.cart.filename).file_stem().unwrap(); 202 | let mut save_file = p.join(stem); 203 | save_file.set_extension("sav"); 204 | println!("saving battery-backed RAM to file: {:?}", save_file); 205 | let mut f = File::create(&save_file) 206 | .expect("could not create output file for battery-backed RAM"); 207 | f.write_all(&self.prg_ram_bank).expect("could not write battery-backed RAM to file"); 208 | } 209 | } 210 | 211 | fn clock(&mut self) {} 212 | fn check_irq(&mut self) -> bool {false} 213 | 214 | fn save_state(&self) -> MapperData { 215 | MapperData::Mmc1( 216 | Mmc1Data { 217 | cart: self.cart.clone(), 218 | step: self.step, 219 | shift_register: self.shift_register, 220 | mirroring: self.mirroring, 221 | control: self.control, 222 | prg_ram_bank: self.prg_ram_bank.clone(), 223 | prg_ram_enabled: self.prg_ram_enabled, 224 | prg_bank_mode: self.prg_bank_mode, 225 | prg_bank_select: self.prg_bank_select, 226 | chr_ram_bank: self.chr_ram_bank.clone(), 227 | chr_low_bank: self.chr_low_bank, 228 | chr_high_bank: self.chr_high_bank, 229 | chr_bank_mode: self.chr_bank_mode, 230 | } 231 | ) 232 | } 233 | 234 | fn load_state(&mut self, mapper_data: MapperData) { 235 | if let MapperData::Mmc1(mmc1_data) = mapper_data { 236 | self.cart = mmc1_data.cart; 237 | self.step = mmc1_data.step; 238 | self.shift_register = mmc1_data.shift_register; 239 | self.mirroring = mmc1_data.mirroring; 240 | self.control = mmc1_data.control; 241 | self.prg_ram_bank = mmc1_data.prg_ram_bank; 242 | self.prg_ram_enabled = mmc1_data.prg_ram_enabled; 243 | self.prg_bank_mode = mmc1_data.prg_bank_mode; 244 | self.prg_bank_select = mmc1_data.prg_bank_select; 245 | self.chr_ram_bank = mmc1_data.chr_ram_bank; 246 | self.chr_low_bank = mmc1_data.chr_low_bank; 247 | self.chr_high_bank = mmc1_data.chr_high_bank; 248 | self.chr_bank_mode = mmc1_data.chr_bank_mode; 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/cartridge/mmc3.rs: -------------------------------------------------------------------------------- 1 | use super::{Cartridge, Mapper, Mirror, serialize::*}; 2 | 3 | pub struct Mmc3 { 4 | cart: Cartridge, 5 | mirroring: Mirror, 6 | 7 | bank_registers: Vec, 8 | next_bank: u8, 9 | 10 | irq_latch: u8, 11 | irq_counter: u8, 12 | irq_enable: bool, 13 | trigger_irq: bool, // signal to send to CPU 14 | reload_counter: bool, 15 | irq_delay: u8, 16 | 17 | prg_ram_bank: Vec, // CPU $6000-$7FFF 18 | // 0: $8000-$9FFF swappable, $C000-$DFFF fixed to second-last bank 19 | // 1: $C000-$DFFF swappable, $8000-$9FFF fixed to second-last bank 20 | prg_rom_bank_mode: bool, 21 | // 0: two 2 KB banks at $0000-$0FFF, four 1 KB banks at $1000-$1FFF 22 | // 1: two 2 KB banks at $1000-$1FFF, four 1 KB banks at $0000-$0FFF 23 | 24 | chr_rom_bank_mode: bool, 25 | chr_ram_bank: Vec, // used if cartridge doesn't have any CHR-ROM, 8KB, $0000-$1FFF 26 | } 27 | 28 | impl Mmc3 { 29 | pub fn new(cart: Cartridge) -> Self { 30 | let m = cart.mirroring; 31 | Mmc3{ 32 | cart: cart, 33 | mirroring: m, 34 | bank_registers: vec![0, 0, 0, 0, 0, 0, 0, 0], 35 | next_bank: 0, 36 | irq_latch: 0, 37 | irq_counter: 0, 38 | irq_enable: false, 39 | trigger_irq: false, 40 | reload_counter: false, 41 | irq_delay: 0, 42 | prg_ram_bank: vec![0; 0x2000], 43 | prg_rom_bank_mode: false, 44 | chr_rom_bank_mode: false, 45 | chr_ram_bank: vec![0; 0x2000], 46 | } 47 | } 48 | 49 | fn bank_select(&mut self, value: u8) { 50 | self.next_bank = value & 0b111; 51 | // ?? = value & (1<<5); // Nothing on the MMC3, see MMC6 52 | self.prg_rom_bank_mode = value & (1<<6) != 0; 53 | self.chr_rom_bank_mode = value & (1<<7) != 0; 54 | } 55 | 56 | fn bank_data(&mut self, value: u8) { 57 | // R6 and R7 will ignore the top two bits, as the MMC3 has only 6 PRG ROM address lines. 58 | // R0 and R1 ignore the bottom bit, as the value written still counts banks in 1KB units but odd numbered banks can't be selected. 59 | self.bank_registers[self.next_bank as usize] = match self.next_bank { 60 | 0 | 1 => value & 0b1111_1110, 61 | 6 | 7 => value & 0b0011_1111, 62 | _ => value, 63 | } as usize; 64 | } 65 | 66 | fn prg_ram_protect(&mut self) {} 67 | } 68 | 69 | impl Mapper for Mmc3 { 70 | fn read(&self, address: usize) -> u8 { 71 | let val = match address { 72 | 0x0000..=0x1FFF => { // reading from CHR-ROM 73 | let offset_1k = address % 0x400; 74 | let offset_2k = address % 0x800; 75 | let bank_reg_num = match self.chr_rom_bank_mode { 76 | true => { 77 | match address { 78 | 0x0000..=0x03FF => 2, 79 | 0x0400..=0x07FF => 3, 80 | 0x0800..=0x0BFF => 4, 81 | 0x0C00..=0x0FFF => 5, 82 | 0x1000..=0x17FF => 0, 83 | 0x1800..=0x1FFF => 1, 84 | _ => panic!("oh no"), 85 | } 86 | }, 87 | false => { 88 | match address { 89 | 0x0000..=0x07FF => 0, 90 | 0x0800..=0x0FFF => 1, 91 | 0x1000..=0x13FF => 2, 92 | 0x1400..=0x17FF => 3, 93 | 0x1800..=0x1BFF => 4, 94 | 0x1C00..=0x1FFF => 5, 95 | _ => panic!("oh no"), 96 | } 97 | }, 98 | }; 99 | let bank_num = self.bank_registers[bank_reg_num]; 100 | let chunk_num = bank_num / 8; 101 | let chunk_eighth = (bank_num % 8) * 0x400; 102 | if bank_reg_num == 0 || bank_reg_num == 1 { // dealing with 2K banks of 8K chunks 103 | self.cart.chr_rom[chunk_num][chunk_eighth + offset_2k] 104 | } else { // dealing with 1K banks of 8K chunks 105 | self.cart.chr_rom[chunk_num][chunk_eighth + offset_1k] 106 | } 107 | }, 108 | 109 | 0x6000..=0x7FFF => self.prg_ram_bank[address % 0x2000], // PRG-RAM 110 | 111 | 0x8000..=0xFFFF => { // reading from PRG ROM, dealing with 8K banks of 16K chunks 112 | let offset_8k = address % 0x2000; 113 | let num_banks = self.cart.prg_rom_size * 2; 114 | let bank_num = match self.prg_rom_bank_mode { 115 | true => { 116 | match address { 117 | 0x8000..=0x9FFF => num_banks - 2, 118 | 0xA000..=0xBFFF => self.bank_registers[7], 119 | 0xC000..=0xDFFF => self.bank_registers[6], 120 | 0xE000..=0xFFFF => num_banks - 1, 121 | _ => panic!("oh no"), 122 | } 123 | }, 124 | false => { 125 | match address { 126 | 0x8000..=0x9FFF => self.bank_registers[6], 127 | 0xA000..=0xBFFF => self.bank_registers[7], 128 | 0xC000..=0xDFFF => num_banks - 2, 129 | 0xE000..=0xFFFF => num_banks - 1, 130 | _ => panic!("oh no"), 131 | } 132 | }, 133 | }; 134 | let chunk_num = bank_num / 2; 135 | let chunk_half = (bank_num % 2) * 0x2000; 136 | self.cart.prg_rom[chunk_num][chunk_half + offset_8k] 137 | 138 | }, 139 | _ => { 140 | println!("bad address read from MMC3: 0x{:X}", address); 141 | 0 142 | }, 143 | }; 144 | val 145 | } 146 | 147 | fn write(&mut self, address: usize, value: u8) { 148 | if (0..=0x1FFF).contains(&address) { 149 | if self.cart.chr_rom_size == 0 { 150 | self.chr_ram_bank[address] = value; 151 | } 152 | return 153 | } 154 | match address % 2 == 0 { 155 | true => { // even 156 | match address { 157 | 0x6000..=0x7FFF => self.prg_ram_bank[address % 0x2000] = value, // PRG-RAM 158 | 0x8000..=0x9FFF => self.bank_select(value), 159 | 0xA000..=0xBFFF => self.mirroring = if value & 1 == 0 {Mirror::Vertical} else {Mirror::Horizontal}, 160 | 0xC000..=0xDFFF => self.irq_latch = value, 161 | 0xE000..=0xFFFF => {self.irq_enable = false; self.trigger_irq = false}, // Writing any value to this register will disable MMC3 interrupts AND acknowledge any pending interrupts. 162 | _ => println!("bad address written to MMC3: 0x{:X}", address), 163 | } 164 | }, 165 | false => { // odd 166 | match address { 167 | 0x6000..=0x7FFF => self.prg_ram_bank[address % 0x2000] = value, // PRG-RAM 168 | 0x8000..=0x9FFF => self.bank_data(value), 169 | 0xA000..=0xBFFF => self.prg_ram_protect(), 170 | 0xC000..=0xDFFF => self.reload_counter = true, // Writing any value to this register reloads the MMC3 IRQ counter at the NEXT rising edge of the PPU address, presumably at PPU cycle 260 of the current scanline. 171 | 0xE000..=0xFFFF => self.irq_enable = true, 172 | _ => println!("bad address written to MMC3: 0x{:X}", address), 173 | } 174 | }, 175 | } 176 | } 177 | 178 | fn get_mirroring(&self) -> Mirror { 179 | if self.cart.four_screen_vram { 180 | Mirror::FourScreen 181 | } else { 182 | self.mirroring 183 | } 184 | } 185 | 186 | fn load_battery_backed_ram(&mut self) {} 187 | fn save_battery_backed_ram(&self) {} 188 | 189 | // This function is called by the PPU when the A12 address line changes. 190 | // It's supposed to only be called when A12 goes from 0 to 1, but that doesn't work 191 | // for my emulator for some reason. 192 | fn clock(&mut self) { 193 | if self.reload_counter { 194 | self.irq_counter = self.irq_latch; 195 | self.reload_counter = false; 196 | } 197 | if self.irq_counter == 0 { 198 | self.irq_counter = self.irq_latch; 199 | } else { 200 | self.irq_counter -= 1; 201 | } 202 | if self.irq_counter == 0 && self.irq_enable { 203 | self.trigger_irq = true; 204 | } 205 | } 206 | 207 | // This function is called by the CPU every step (which takes more than one CPU clock cycle). 208 | // I think I'm supposed to be tracking IRQ delays by the PPU, not letting an IRQ fire if 209 | // there was one within the last 15 PPU cycles, but that didn't work and this does. 210 | fn check_irq(&mut self) -> bool { 211 | if self.trigger_irq { 212 | self.trigger_irq = false; 213 | if self.irq_delay == 0 { 214 | self.irq_delay = 5; 215 | } 216 | } 217 | if self.irq_delay > 0 { 218 | self.irq_delay -= 1; 219 | if self.irq_delay == 0 { 220 | return true; 221 | } 222 | } 223 | false 224 | } 225 | 226 | fn save_state(&self) -> MapperData { 227 | MapperData::Mmc3( 228 | Mmc3Data { 229 | cart: self.cart.clone(), 230 | mirroring: self.mirroring, 231 | bank_registers: self.bank_registers.clone(), 232 | next_bank: self.next_bank, 233 | irq_latch: self.irq_latch, 234 | irq_counter: self.irq_counter, 235 | irq_enable: self.irq_enable, 236 | trigger_irq: self.trigger_irq, 237 | reload_counter: self.reload_counter, 238 | irq_delay: self.irq_delay, 239 | prg_ram_bank: self.prg_ram_bank.clone(), 240 | prg_rom_bank_mode: self.prg_rom_bank_mode, 241 | chr_rom_bank_mode: self.chr_rom_bank_mode, 242 | chr_ram_bank: self.chr_ram_bank.clone(), 243 | } 244 | ) 245 | } 246 | 247 | fn load_state(&mut self, mapper_data: MapperData) { 248 | if let MapperData::Mmc3(mmc3_data) = mapper_data { 249 | self.cart = mmc3_data.cart; 250 | self.mirroring = mmc3_data.mirroring; 251 | self.bank_registers = mmc3_data.bank_registers; 252 | self.next_bank = mmc3_data.next_bank; 253 | self.irq_latch = mmc3_data.irq_latch; 254 | self.irq_counter = mmc3_data.irq_counter; 255 | self.irq_enable = mmc3_data.irq_enable; 256 | self.trigger_irq = mmc3_data.trigger_irq; 257 | self.reload_counter = mmc3_data.reload_counter; 258 | self.irq_delay = mmc3_data.irq_delay; 259 | self.prg_ram_bank = mmc3_data.prg_ram_bank; 260 | self.prg_rom_bank_mode = mmc3_data.prg_rom_bank_mode; 261 | self.chr_rom_bank_mode = mmc3_data.chr_rom_bank_mode; 262 | self.chr_ram_bank = mmc3_data.chr_ram_bank; 263 | } 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/cartridge/mod.rs: -------------------------------------------------------------------------------- 1 | mod nrom; 2 | mod mmc1; 3 | mod uxrom; 4 | mod cnrom; 5 | mod mmc3; 6 | pub mod serialize; 7 | 8 | use nrom::Nrom; 9 | use mmc1::Mmc1; 10 | use uxrom::Uxrom; 11 | use cnrom::Cnrom; 12 | use mmc3::Mmc3; 13 | 14 | use std::cell::RefCell; 15 | use std::fs::File; 16 | use std::io::Read; 17 | use std::rc::Rc; 18 | 19 | pub trait Mapper { 20 | fn read(&self, address: usize) -> u8; 21 | fn write(&mut self, address: usize, value: u8); 22 | fn get_mirroring(&self) -> Mirror; 23 | fn load_battery_backed_ram(&mut self); 24 | fn save_battery_backed_ram(&self); 25 | fn clock(&mut self); 26 | fn check_irq(&mut self) -> bool; 27 | fn save_state(&self) -> serialize::MapperData; 28 | fn load_state(&mut self, mapper_data: serialize::MapperData); 29 | } 30 | 31 | #[derive(Copy, Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] 32 | pub enum Mirror { 33 | LowBank, 34 | HighBank, 35 | Horizontal, 36 | Vertical, 37 | FourScreen, 38 | } 39 | 40 | pub fn get_mapper(filename: String) -> Rc> { 41 | let cart = Cartridge::new(filename); 42 | let num = cart.mapper_num; 43 | match num { 44 | 0 => Rc::new(RefCell::new(Nrom::new(cart))), 45 | 1 => Rc::new(RefCell::new(Mmc1::new(cart))), 46 | 2 => Rc::new(RefCell::new(Uxrom::new(cart))), 47 | 3 => Rc::new(RefCell::new(Cnrom::new(cart))), 48 | 4 => Rc::new(RefCell::new(Mmc3::new(cart))), 49 | _ => panic!("unimplemented mapper: {}", num), 50 | } 51 | } 52 | 53 | #[derive(Clone, serde::Serialize, serde::Deserialize)] 54 | pub struct Cartridge { 55 | filename: String, 56 | prg_rom_size: usize, 57 | chr_rom_size: usize, 58 | pub mirroring: Mirror, // 0 horizontal, 1 vertical 59 | battery_backed_ram: bool, // 1: Cartridge contains battery-backed PRG RAM ($6000-7FFF) or other persistent memory 60 | trainer_present: bool, // 1: 512-byte trainer at $7000-$71FF (stored before PRG data) 61 | four_screen_vram: bool, // 1: Ignore mirroring control or above mirroring bit; instead provide four-screen VRAM 62 | // TODO: other iNES header flags 63 | 64 | pub prg_rom: Vec>, // 16 KiB chunks for CPU 65 | pub chr_rom: Vec>, // 8 KiB chunks for PPU 66 | 67 | all_data: Vec, 68 | mapper_num: u8, 69 | } 70 | 71 | impl Cartridge { 72 | pub fn new(filename: String) -> Self { 73 | let mut f = std::fs::File::open(&filename).expect("could not open {}"); 74 | let mut data = vec![]; 75 | f.read_to_end(&mut data).unwrap(); 76 | assert!(data[0..4] == [0x4E, 0x45, 0x53, 0x1A], "signature mismatch, not an iNES file"); 77 | let mapper_num = ((data[7] >> 4) << 4) + (data[6] >> 4); 78 | let mut cart = Cartridge { 79 | filename: filename.to_string(), 80 | prg_rom_size: data[4] as usize, 81 | chr_rom_size: data[5] as usize, 82 | mirroring: if data[6] & (1 << 0) == 0 {Mirror::Horizontal} else {Mirror::Vertical}, 83 | battery_backed_ram: data[6] & (1 << 1) != 0, 84 | trainer_present: data[6] & (1 << 2) != 0, 85 | four_screen_vram: data[6] & (1 << 3) != 0, 86 | prg_rom: Vec::new(), 87 | chr_rom: Vec::new(), 88 | all_data: data, 89 | mapper_num: mapper_num, 90 | }; 91 | cart.fill(); 92 | cart 93 | } 94 | 95 | fn fill(&mut self) { 96 | let prg_chunk_size: usize = 1<<14; 97 | let chr_chunk_size: usize = 1<<13; 98 | let prg_offset: usize = 0x10 + if self.trainer_present { 0x200 } else { 0 }; // header plus trainer if present 99 | let chr_offset: usize = prg_offset + (self.prg_rom_size * prg_chunk_size); // chr comes after prg 100 | // fill vecs with chunks 101 | for i in 0..self.prg_rom_size { 102 | let offset = prg_offset + (i * prg_chunk_size); 103 | let chunk = self.all_data[offset..(offset + prg_chunk_size)].to_vec(); 104 | self.prg_rom.push(chunk.clone()); 105 | }; 106 | for i in 0..self.chr_rom_size { 107 | let offset = chr_offset + (i * chr_chunk_size); 108 | let chunk = self.all_data[offset..offset + chr_chunk_size].to_vec(); 109 | self.chr_rom.push(chunk); 110 | }; 111 | self.all_data.clear(); 112 | } 113 | 114 | } 115 | 116 | pub fn check_signature(filename: &str) -> Result<(), String> { 117 | let mut f = File::open(filename).map_err(|e| e.to_string())?; 118 | let mut data = [0; 4]; 119 | f.read_exact(&mut data).map_err(|e| e.to_string())?; 120 | if data == [0x4E, 0x45, 0x53, 0x1A] { 121 | Ok(()) 122 | } else { 123 | Err("file signature mismatch: not a valid iNES file".to_string()) 124 | } 125 | } 126 | 127 | /* 128 | The mappings above are the fixed addresses from which the PPU uses to fetch data during rendering. The actual device that the PPU fetches data from, however, may be configured by the cartridge. 129 | $0000-1FFF is normally mapped by the cartridge to a CHR-ROM or CHR-RAM, often with a bank switching mechanism. 130 | $2000-2FFF is normally mapped to the 2kB NES internal VRAM, providing 2 nametables with a mirroring configuration controlled by the cartridge, but it can be partly or fully remapped to RAM on the cartridge, allowing up to 4 simultaneous nametables. 131 | $3000-3EFF is usually a mirror of the 2kB region from $2000-2EFF. The PPU does not render from this address range, so this space has negligible utility. 132 | $3F00-3FFF is not configurable, always mapped to the internal palette control. 133 | */ 134 | -------------------------------------------------------------------------------- /src/cartridge/nrom.rs: -------------------------------------------------------------------------------- 1 | use super::{Cartridge, Mapper, Mirror, serialize::*}; 2 | 3 | pub struct Nrom { 4 | cart: Cartridge, 5 | chr_ram: Vec, 6 | } 7 | 8 | impl Nrom { 9 | pub fn new(cart: Cartridge) -> Self { 10 | Nrom{ 11 | cart: cart, 12 | chr_ram: vec![0; 0x2000], 13 | } 14 | } 15 | } 16 | 17 | impl Mapper for Nrom { 18 | fn read(&self, address: usize) -> u8 { 19 | let addr = address % 0x4000; 20 | match address { 21 | 0x0000..=0x1FFF => { 22 | if self.cart.chr_rom_size > 0 { 23 | self.cart.chr_rom[0][address] 24 | } else { 25 | self.chr_ram[address] 26 | } 27 | }, 28 | 0x8000..=0xBFFF => { 29 | self.cart.prg_rom[0][addr] 30 | }, 31 | 0xC000..=0xFFFF => { 32 | self.cart.prg_rom[self.cart.prg_rom_size - 1][addr] 33 | }, 34 | _ => {println!("bad address read from NROM mapper: 0x{:X}", address); 0}, 35 | } 36 | } 37 | 38 | fn write(&mut self, address: usize, value: u8) { 39 | match address { 40 | 0x0000..=0x1FFF => { 41 | // ROM isn't written to 42 | if self.cart.chr_rom_size == 0 { 43 | self.chr_ram[address] = value; 44 | } 45 | }, 46 | 0x8000..=0xBFFF => (), 47 | 0xC000..=0xFFFF => (), 48 | _ => println!("bad address written to NROM mapper: 0x{:X}", address), 49 | } 50 | } 51 | 52 | fn get_mirroring(&self) -> Mirror { 53 | self.cart.mirroring 54 | } 55 | 56 | fn load_battery_backed_ram(&mut self) {} 57 | fn save_battery_backed_ram(&self) {} 58 | fn clock(&mut self) {} 59 | fn check_irq(&mut self) -> bool {false} 60 | 61 | fn save_state(&self) -> MapperData { 62 | MapperData::Nrom( 63 | NromData { 64 | cart: self.cart.clone(), 65 | chr_ram: self.chr_ram.clone(), 66 | } 67 | ) 68 | } 69 | 70 | fn load_state(&mut self, mapper_data: MapperData) { 71 | if let MapperData::Nrom(nrom_data) = mapper_data { 72 | self.cart = nrom_data.cart; 73 | self.chr_ram = nrom_data.chr_ram; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/cartridge/serialize.rs: -------------------------------------------------------------------------------- 1 | use super::{Cartridge, Mirror}; 2 | 3 | #[derive(serde::Serialize, serde::Deserialize)] 4 | pub enum MapperData { 5 | Nrom(NromData), 6 | Mmc1(Mmc1Data), 7 | Uxrom(UxromData), 8 | Cnrom(CnromData), 9 | Mmc3(Mmc3Data), 10 | } 11 | 12 | 13 | #[derive(serde::Serialize, serde::Deserialize)] 14 | pub struct NromData { 15 | pub cart: Cartridge, 16 | pub chr_ram: Vec, 17 | } 18 | 19 | #[derive(serde::Serialize, serde::Deserialize)] 20 | pub struct Mmc1Data { 21 | pub cart: Cartridge, 22 | pub step: u8, 23 | pub shift_register: u8, 24 | pub mirroring: Mirror, 25 | pub control: u8, 26 | pub prg_ram_bank: Vec, 27 | pub prg_ram_enabled: bool, 28 | pub prg_bank_mode: u8, 29 | pub prg_bank_select: usize, 30 | pub chr_ram_bank: Vec, 31 | pub chr_low_bank: usize, 32 | pub chr_high_bank: usize, 33 | pub chr_bank_mode: bool, 34 | } 35 | 36 | #[derive(serde::Serialize, serde::Deserialize)] 37 | pub struct UxromData { 38 | pub cart: Cartridge, 39 | pub chr_ram: Vec, 40 | pub bank_select: usize, 41 | } 42 | 43 | #[derive(serde::Serialize, serde::Deserialize)] 44 | pub struct CnromData { 45 | pub cart: Cartridge, 46 | pub chr_bank_select: usize, 47 | } 48 | 49 | #[derive(serde::Serialize, serde::Deserialize)] 50 | pub struct Mmc3Data { 51 | pub cart: Cartridge, 52 | pub mirroring: Mirror, 53 | pub bank_registers: Vec, 54 | pub next_bank: u8, 55 | pub irq_latch: u8, 56 | pub irq_counter: u8, 57 | pub irq_enable: bool, 58 | pub trigger_irq: bool, 59 | pub reload_counter: bool, 60 | pub irq_delay: u8, 61 | pub prg_ram_bank: Vec, 62 | pub prg_rom_bank_mode: bool, 63 | pub chr_rom_bank_mode: bool, 64 | pub chr_ram_bank: Vec, 65 | } 66 | -------------------------------------------------------------------------------- /src/cartridge/uxrom.rs: -------------------------------------------------------------------------------- 1 | use super::{Cartridge, Mapper, Mirror, serialize::*}; 2 | 3 | pub struct Uxrom { 4 | cart: Cartridge, 5 | chr_ram: Vec, 6 | bank_select: usize, 7 | } 8 | 9 | impl Uxrom { 10 | pub fn new(cart: Cartridge) -> Self { 11 | Uxrom{ 12 | cart: cart, 13 | chr_ram: vec![0; 0x2000], 14 | bank_select: 0, 15 | } 16 | } 17 | } 18 | 19 | impl Mapper for Uxrom { 20 | fn read(&self, address: usize) -> u8 { 21 | match address { 22 | 0x0000..=0x1FFF => { 23 | if self.cart.chr_rom_size > 0 { 24 | self.cart.chr_rom[0][address] 25 | } else { 26 | self.chr_ram[address] 27 | } 28 | }, 29 | 0x8000..=0xBFFF => self.cart.prg_rom[self.bank_select][address % 0x4000], 30 | 0xC000..=0xFFFF => self.cart.prg_rom[self.cart.prg_rom.len()-1][address % 0x4000], 31 | _ => {println!("bad address read from UxROM mapper: 0x{:X}", address); 0}, 32 | } 33 | } 34 | 35 | fn write(&mut self, address: usize, value: u8) { 36 | match address { 37 | 0x0000..=0x1FFF => { 38 | if self.cart.chr_rom_size == 0 { 39 | self.chr_ram[address] = value; 40 | } 41 | }, 42 | 0x8000..=0xFFFF => self.bank_select = value as usize, 43 | _ => println!("bad address written to UxROM mapper: 0x{:X}", address), 44 | } 45 | } 46 | 47 | fn get_mirroring(&self) -> Mirror { 48 | self.cart.mirroring 49 | } 50 | 51 | fn load_battery_backed_ram(&mut self) {} 52 | fn save_battery_backed_ram(&self) {} 53 | fn clock(&mut self) {} 54 | fn check_irq(&mut self) -> bool {false} 55 | 56 | fn save_state(&self) -> MapperData { 57 | MapperData::Uxrom( 58 | UxromData { 59 | cart: self.cart.clone(), 60 | chr_ram: self.chr_ram.clone(), 61 | bank_select: self.bank_select, 62 | } 63 | ) 64 | } 65 | 66 | fn load_state(&mut self, mapper_data: MapperData) { 67 | if let MapperData::Uxrom(uxrom_data) = mapper_data { 68 | self.cart = uxrom_data.cart; 69 | self.chr_ram = uxrom_data.chr_ram; 70 | self.bank_select = uxrom_data.bank_select; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/cpu/addressing_modes.rs: -------------------------------------------------------------------------------- 1 | impl super::Cpu { 2 | 3 | pub fn absolute(&mut self) -> usize { 4 | self.clock += 4; 5 | ::from( 6 | ((self.read(self.pc + 2) as usize) << 8) + // high byte, little endian 7 | (self.read(self.pc + 1)) as usize // low byte 8 | ) 9 | } 10 | 11 | pub fn absolute_x(&mut self) -> usize { 12 | let current_opcode = self.read(self.pc); 13 | let old_address = self.absolute(); 14 | let new_address = old_address + self.x as usize; 15 | match current_opcode { 16 | 0x1C | 0x1D | 0x3C | 0x3D | 0x5C | 0x5D | 0x7C | 0x7D | 0xBC | 0xBD | 0xDC | 0xDD | 0xFC | 0xFD 17 | => self.address_page_cross(old_address, new_address), 18 | 0x1E | 0x1F | 0x3E | 0x3F | 0x5E | 0x5F | 0x7E | 0x7F | 0x9D | 0xC3 | 0xC7 | 0xCF | 0xD3 | 0xD7 | 0xDB | 0xDE | 0xDF | 0xFE | 0xFF 19 | => self.clock += 1, 20 | _ => panic!("illegal opcode using abs x: {:02x}", current_opcode), 21 | } 22 | new_address 23 | } 24 | 25 | pub fn absolute_y(&mut self) -> usize { 26 | let current_opcode = self.pc; 27 | let old_address = self.absolute() as u16; // coerce to u16 for wrapping addition 28 | let new_address = old_address.wrapping_add(self.y as u16); 29 | let old_address = old_address as usize; // coerce back 30 | let new_address = new_address as usize; 31 | if current_opcode == 0x99 { 32 | self.clock += 1; 33 | } else { 34 | self.address_page_cross(old_address, new_address); 35 | } 36 | new_address 37 | } 38 | 39 | pub fn accumulator(&mut self) -> usize { 40 | self.clock += 2; 41 | 0 42 | } 43 | 44 | pub fn immediate(&mut self) -> usize { 45 | self.clock += 2; 46 | self.pc + 1 47 | } 48 | 49 | pub fn implied(&mut self) -> usize { 50 | self.clock += 2; 51 | 0 52 | } 53 | 54 | pub fn indexed_indirect(&mut self) -> usize { 55 | self.clock += 6; 56 | let operand = self.read(self.pc + 1); 57 | let zp_low_addr = operand.wrapping_add(self.x); 58 | let zp_high_addr = zp_low_addr.wrapping_add(1); // take account of zero page wraparound 59 | let zp_low_byte = self.read(zp_low_addr as usize); 60 | let zp_high_byte = self.read(zp_high_addr as usize); 61 | ((zp_high_byte as usize) << 8) + zp_low_byte as usize 62 | } 63 | 64 | pub fn indirect(&mut self) -> usize { 65 | let operand_address = ((self.read(self.pc + 2) as usize) << 8) 66 | + (self.read(self.pc + 1) as usize); 67 | let low_byte = self.read(operand_address) as usize; 68 | // BUG TIME! from https://wiki.nesdev.com/w/index.php/Errata 69 | // "JMP ($xxyy), or JMP indirect, does not advance pages if the lower eight bits 70 | // of the specified address is $FF; the upper eight bits are fetched from $xx00, 71 | // 255 bytes earlier, instead of the expected following byte." 72 | let high_byte = if operand_address & 0xFF == 0xFF { 73 | (self.read(operand_address as usize - 0xFF) as usize) << 8 74 | } else { 75 | (self.read(operand_address as usize + 1) as usize) << 8 76 | }; 77 | let real_address = high_byte + low_byte; 78 | self.clock += 5; 79 | real_address 80 | } 81 | 82 | pub fn indirect_indexed(&mut self) -> usize { 83 | let operand = self.read(self.pc + 1); 84 | let zp_low_addr = operand; 85 | let zp_high_addr = operand.wrapping_add(1); 86 | let zp_low_byte = self.read(zp_low_addr as usize); 87 | let zp_high_byte = self.read(zp_high_addr as usize); 88 | let old_address = ((zp_high_byte as u16) << 8) + zp_low_byte as u16; 89 | let new_address = old_address.wrapping_add(self.y as u16); 90 | if self.pc == 0xF1 { 91 | self.clock += 1; 92 | } else { 93 | self.address_page_cross(old_address as usize, new_address as usize); 94 | } 95 | self.clock += 5; 96 | new_address as usize 97 | } 98 | 99 | pub fn relative(&mut self) -> usize { 100 | self.clock += 2; 101 | self.pc + 1 102 | } 103 | 104 | pub fn zero_page(&mut self) -> usize { 105 | let operand = self.read(self.pc + 1); 106 | self.clock += 3; 107 | operand as usize 108 | } 109 | 110 | pub fn zero_page_x(&mut self) -> usize { 111 | let operand = self.read(self.pc + 1); 112 | self.clock += 4; 113 | operand.wrapping_add(self.x) as usize 114 | } 115 | 116 | pub fn zero_page_y(&mut self) -> usize { 117 | let operand = self.read(self.pc + 1); 118 | self.clock += 4; 119 | operand.wrapping_add(self.y) as usize 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /src/cpu/mod.rs: -------------------------------------------------------------------------------- 1 | mod addressing_modes; 2 | mod opcodes; 3 | mod utility; 4 | pub mod serialize; 5 | 6 | use std::cell::RefCell; 7 | use std::rc::Rc; 8 | use serde::{Serialize, Deserialize}; 9 | use crate::cartridge::Mapper; 10 | 11 | // RAM locations 12 | const STACK_OFFSET: usize = 0x100; 13 | const NMI_VECTOR: usize = 0xFFFA; 14 | const RESET_VECTOR: usize = 0xFFFC; 15 | const IRQ_VECTOR: usize = 0xFFFE; 16 | 17 | // status register flags 18 | const CARRY_FLAG: u8 = 1 << 0; 19 | const ZERO_FLAG: u8 = 1 << 1; 20 | const INTERRUPT_DISABLE_FLAG: u8 = 1 << 2; 21 | const DECIMAL_FLAG: u8 = 1 << 3; 22 | // bits 4 and 5 are unused except when status register is copied to stack 23 | const OVERFLOW_FLAG: u8 = 1 << 6; 24 | const NEGATIVE_FLAG: u8 = 1 << 7; 25 | 26 | #[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq)] 27 | pub enum Mode { 28 | ABS, ABX, ABY, ACC, 29 | IMM, IMP, IDX, IND, 30 | INY, REL, ZPG, ZPX, 31 | ZPY, 32 | } 33 | 34 | type AddressingFunction = fn(&mut Cpu) -> usize; 35 | 36 | impl Mode { 37 | fn get(&self) -> (AddressingFunction, usize) { // usize is number of bytes the instruction takes, used for debug printing 38 | match self { 39 | Mode::ABS => (Cpu::absolute, 3), 40 | Mode::ABX => (Cpu::absolute_x, 3), 41 | Mode::ABY => (Cpu::absolute_y, 3), 42 | Mode::ACC => (Cpu::accumulator, 1), 43 | Mode::IMM => (Cpu::immediate, 2), 44 | Mode::IMP => (Cpu::implied, 1), 45 | Mode::IDX => (Cpu::indexed_indirect, 2), 46 | Mode::IND => (Cpu::indirect, 3), 47 | Mode::INY => (Cpu::indirect_indexed, 2), 48 | Mode::REL => (Cpu::relative, 2), 49 | Mode::ZPG => (Cpu::zero_page, 2), 50 | Mode::ZPX => (Cpu::zero_page_x, 2), 51 | Mode::ZPY => (Cpu::zero_page_y, 2), 52 | } 53 | } 54 | } 55 | 56 | pub struct Cpu { 57 | mem: Vec, // CPU's RAM, $0000-$1FFF 58 | a: u8, // accumulator 59 | x: u8, // general purpose 60 | y: u8, // general purpose 61 | pc: usize, // 16-bit program counter 62 | s: u8, // stack pointer 63 | p: u8, // status 64 | 65 | clock: u64, // number of ticks in current cycle 66 | delay: usize, // for skipping cycles during OAM DMA 67 | before_clock: u64, 68 | 69 | pub mapper: Rc>, // cartridge data 70 | pub ppu: super::Ppu, 71 | pub apu: super::Apu, 72 | 73 | // controller 74 | pub strobe: u8, // signals to the controller that button inputs should be read 75 | pub button_states: u8, // Player 1 controller 76 | button_number: u8, // counter that scans the bits of the input register serially 77 | 78 | opcode_table: Vec, // function table 79 | mode_table: Vec, // address mode table 80 | } 81 | 82 | impl Cpu { 83 | pub fn new(mapper: Rc>, ppu: super::Ppu, apu: super::Apu) -> Self { 84 | let mut cpu = Cpu{ 85 | mem: vec![0; 0x2000], 86 | a: 0, x: 0, y: 0, 87 | pc: 0, 88 | s: 0xFD, 89 | p: 0x24, 90 | clock: 0, 91 | delay: 0, 92 | before_clock: 0, 93 | mapper: mapper, 94 | ppu: ppu, 95 | apu: apu, 96 | strobe: 0, 97 | button_states: 0, 98 | button_number: 0, 99 | opcode_table: vec![ 100 | // 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 101 | /*00*/ Cpu::brk, Cpu::ora, Cpu::bad, Cpu::slo, Cpu::nop, Cpu::ora, Cpu::asl, Cpu::slo, Cpu::php, Cpu::ora, Cpu::asl, Cpu::nop, Cpu::nop, Cpu::ora, Cpu::asl, Cpu::slo, /*00*/ 102 | /*10*/ Cpu::bpl, Cpu::ora, Cpu::bad, Cpu::slo, Cpu::nop, Cpu::ora, Cpu::asl, Cpu::slo, Cpu::clc, Cpu::ora, Cpu::nop, Cpu::slo, Cpu::nop, Cpu::ora, Cpu::asl, Cpu::slo, /*10*/ 103 | /*20*/ Cpu::jsr, Cpu::and, Cpu::bad, Cpu::rla, Cpu::bit, Cpu::and, Cpu::rol, Cpu::rla, Cpu::plp, Cpu::and, Cpu::rol, Cpu::nop, Cpu::bit, Cpu::and, Cpu::rol, Cpu::rla, /*20*/ 104 | /*30*/ Cpu::bmi, Cpu::and, Cpu::bad, Cpu::rla, Cpu::nop, Cpu::and, Cpu::rol, Cpu::rla, Cpu::sec, Cpu::and, Cpu::nop, Cpu::rla, Cpu::nop, Cpu::and, Cpu::rol, Cpu::rla, /*30*/ 105 | /*40*/ Cpu::rti, Cpu::eor, Cpu::bad, Cpu::sre, Cpu::nop, Cpu::eor, Cpu::lsr, Cpu::sre, Cpu::pha, Cpu::eor, Cpu::lsr, Cpu::nop, Cpu::jmp, Cpu::eor, Cpu::lsr, Cpu::sre, /*40*/ 106 | /*50*/ Cpu::bvc, Cpu::eor, Cpu::bad, Cpu::sre, Cpu::nop, Cpu::eor, Cpu::lsr, Cpu::sre, Cpu::cli, Cpu::eor, Cpu::nop, Cpu::sre, Cpu::nop, Cpu::eor, Cpu::lsr, Cpu::sre, /*50*/ 107 | /*60*/ Cpu::rts, Cpu::adc, Cpu::bad, Cpu::rra, Cpu::nop, Cpu::adc, Cpu::ror, Cpu::rra, Cpu::pla, Cpu::adc, Cpu::ror, Cpu::nop, Cpu::jmp, Cpu::adc, Cpu::ror, Cpu::rra, /*60*/ 108 | /*70*/ Cpu::bvs, Cpu::adc, Cpu::bad, Cpu::rra, Cpu::nop, Cpu::adc, Cpu::ror, Cpu::rra, Cpu::sei, Cpu::adc, Cpu::nop, Cpu::rra, Cpu::nop, Cpu::adc, Cpu::ror, Cpu::rra, /*70*/ 109 | /*80*/ Cpu::nop, Cpu::sta, Cpu::nop, Cpu::sax, Cpu::sty, Cpu::sta, Cpu::stx, Cpu::sax, Cpu::dey, Cpu::nop, Cpu::txa, Cpu::nop, Cpu::sty, Cpu::sta, Cpu::stx, Cpu::sax, /*80*/ 110 | /*90*/ Cpu::bcc, Cpu::sta, Cpu::bad, Cpu::nop, Cpu::sty, Cpu::sta, Cpu::stx, Cpu::sax, Cpu::tya, Cpu::sta, Cpu::txs, Cpu::nop, Cpu::nop, Cpu::sta, Cpu::nop, Cpu::nop, /*90*/ 111 | /*A0*/ Cpu::ldy, Cpu::lda, Cpu::ldx, Cpu::lax, Cpu::ldy, Cpu::lda, Cpu::ldx, Cpu::lax, Cpu::tay, Cpu::lda, Cpu::tax, Cpu::nop, Cpu::ldy, Cpu::lda, Cpu::ldx, Cpu::lax, /*A0*/ 112 | /*B0*/ Cpu::bcs, Cpu::lda, Cpu::bad, Cpu::lax, Cpu::ldy, Cpu::lda, Cpu::ldx, Cpu::lax, Cpu::clv, Cpu::lda, Cpu::tsx, Cpu::nop, Cpu::ldy, Cpu::lda, Cpu::ldx, Cpu::lax, /*B0*/ 113 | /*C0*/ Cpu::cpy, Cpu::cmp, Cpu::nop, Cpu::dcp, Cpu::cpy, Cpu::cmp, Cpu::dec, Cpu::dcp, Cpu::iny, Cpu::cmp, Cpu::dex, Cpu::nop, Cpu::cpy, Cpu::cmp, Cpu::dec, Cpu::dcp, /*C0*/ 114 | /*D0*/ Cpu::bne, Cpu::cmp, Cpu::bad, Cpu::dcp, Cpu::nop, Cpu::cmp, Cpu::dec, Cpu::dcp, Cpu::cld, Cpu::cmp, Cpu::nop, Cpu::dcp, Cpu::nop, Cpu::cmp, Cpu::dec, Cpu::dcp, /*D0*/ 115 | /*E0*/ Cpu::cpx, Cpu::sbc, Cpu::nop, Cpu::isc, Cpu::cpx, Cpu::sbc, Cpu::inc, Cpu::isc, Cpu::inx, Cpu::sbc, Cpu::nop, Cpu::sbc, Cpu::cpx, Cpu::sbc, Cpu::inc, Cpu::isc, /*E0*/ 116 | /*F0*/ Cpu::beq, Cpu::sbc, Cpu::bad, Cpu::isc, Cpu::nop, Cpu::sbc, Cpu::inc, Cpu::isc, Cpu::sed, Cpu::sbc, Cpu::nop, Cpu::isc, Cpu::nop, Cpu::sbc, Cpu::inc, Cpu::isc, /*F0*/ 117 | ], 118 | mode_table: vec![ 119 | // 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 120 | /*00*/ Mode::IMP, Mode::IDX, Mode::IMP, Mode::IDX, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::IMP, Mode::IMM, Mode::ACC, Mode::IMM, Mode::ABS, Mode::ABS, Mode::ABS, Mode::ABS, /*00*/ 121 | /*10*/ Mode::REL, Mode::INY, Mode::IMP, Mode::INY, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::IMP, Mode::ABY, Mode::IMP, Mode::ABY, Mode::ABX, Mode::ABX, Mode::ABX, Mode::ABX, /*10*/ 122 | /*20*/ Mode::ABS, Mode::IDX, Mode::IMP, Mode::IDX, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::IMP, Mode::IMM, Mode::ACC, Mode::IMM, Mode::ABS, Mode::ABS, Mode::ABS, Mode::ABS, /*20*/ 123 | /*30*/ Mode::REL, Mode::INY, Mode::IMP, Mode::INY, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::IMP, Mode::ABY, Mode::IMP, Mode::ABY, Mode::ABX, Mode::ABX, Mode::ABX, Mode::ABX, /*30*/ 124 | /*40*/ Mode::IMP, Mode::IDX, Mode::IMP, Mode::IDX, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::IMP, Mode::IMM, Mode::ACC, Mode::IMM, Mode::ABS, Mode::ABS, Mode::ABS, Mode::ABS, /*40*/ 125 | /*50*/ Mode::REL, Mode::INY, Mode::IMP, Mode::INY, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::IMP, Mode::ABY, Mode::IMP, Mode::ABY, Mode::ABX, Mode::ABX, Mode::ABX, Mode::ABX, /*50*/ 126 | /*60*/ Mode::IMP, Mode::IDX, Mode::IMP, Mode::IDX, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::IMP, Mode::IMM, Mode::ACC, Mode::IMM, Mode::IND, Mode::ABS, Mode::ABS, Mode::ABS, /*60*/ 127 | /*70*/ Mode::REL, Mode::INY, Mode::IMP, Mode::INY, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::IMP, Mode::ABY, Mode::IMP, Mode::ABY, Mode::ABX, Mode::ABX, Mode::ABX, Mode::ABX, /*70*/ 128 | /*80*/ Mode::IMM, Mode::IDX, Mode::IMM, Mode::IDX, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::IMP, Mode::IMM, Mode::IMP, Mode::IMM, Mode::ABS, Mode::ABS, Mode::ABS, Mode::ABS, /*80*/ 129 | /*90*/ Mode::REL, Mode::INY, Mode::IMP, Mode::INY, Mode::ZPX, Mode::ZPX, Mode::ZPY, Mode::ZPY, Mode::IMP, Mode::ABY, Mode::IMP, Mode::ABY, Mode::ABX, Mode::ABX, Mode::ABY, Mode::ABY, /*90*/ 130 | /*A0*/ Mode::IMM, Mode::IDX, Mode::IMM, Mode::IDX, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::IMP, Mode::IMM, Mode::IMP, Mode::IMM, Mode::ABS, Mode::ABS, Mode::ABS, Mode::ABS, /*A0*/ 131 | /*B0*/ Mode::REL, Mode::INY, Mode::IMP, Mode::INY, Mode::ZPX, Mode::ZPX, Mode::ZPY, Mode::ZPY, Mode::IMP, Mode::ABY, Mode::IMP, Mode::ABY, Mode::ABX, Mode::ABX, Mode::ABY, Mode::ABY, /*B0*/ 132 | /*C0*/ Mode::IMM, Mode::IDX, Mode::IMM, Mode::IDX, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::IMP, Mode::IMM, Mode::IMP, Mode::IMM, Mode::ABS, Mode::ABS, Mode::ABS, Mode::ABS, /*C0*/ 133 | /*D0*/ Mode::REL, Mode::INY, Mode::IMP, Mode::INY, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::IMP, Mode::ABY, Mode::IMP, Mode::ABY, Mode::ABX, Mode::ABX, Mode::ABX, Mode::ABX, /*D0*/ 134 | /*E0*/ Mode::IMM, Mode::IDX, Mode::IMM, Mode::IDX, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::ZPG, Mode::IMP, Mode::IMM, Mode::IMP, Mode::IMM, Mode::ABS, Mode::ABS, Mode::ABS, Mode::ABS, /*E0*/ 135 | /*F0*/ Mode::REL, Mode::INY, Mode::IMP, Mode::INY, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::ZPX, Mode::IMP, Mode::ABY, Mode::IMP, Mode::ABY, Mode::ABX, Mode::ABX, Mode::ABX, Mode::ABX, /*F0*/ 136 | ], 137 | }; 138 | cpu.pc = ((cpu.read(RESET_VECTOR + 1) as usize) << 8) + cpu.read(RESET_VECTOR) as usize; 139 | cpu 140 | } 141 | 142 | pub fn step(&mut self) -> u64 { 143 | 144 | // The CPU is stalled for up to 4 CPU cycles to allow the longest possible write (the return address and write after an IRQ) to finish. 145 | // If OAM DMA is in progress, it is paused for two cycles. The sample fetch always occurs on an even CPU cycle due to its alignment with the APU. 146 | // Specific delay cases: 147 | // 4 cycles if it falls on a CPU read cycle. 148 | // 3 cycles if it falls on a single CPU write cycle (or the second write of a double CPU write). 149 | // 4 cycles if it falls on the first write of a double CPU write cycle.[4] 150 | // 2 cycles if it occurs during an OAM DMA, or on the $4014 write cycle that triggers the OAM DMA. 151 | // 1 cycle if it occurs on the second-last OAM DMA cycle. 152 | // 3 cycles if it occurs on the last OAM DMA cycle. 153 | if self.apu.dmc.cpu_stall { 154 | self.delay = 3; // TODO: not correct 155 | self.apu.dmc.cpu_stall = false; 156 | } 157 | 158 | // skip cycles from OAM DMA if necessary 159 | if self.delay > 0 { 160 | self.delay -= 1; 161 | return 1; 162 | } 163 | 164 | // back up clock so we know how many cycles we complete 165 | let clock = self.clock; 166 | self.before_clock = self.clock; 167 | 168 | // interrupts happen after clock is backed up because they advance it 169 | self.handle_interrupts(); 170 | 171 | // read program counter 172 | let opcode = ::from(self.read(self.pc)); 173 | 174 | // get addressing mode 175 | let mode = self.mode_table[opcode].clone(); 176 | let (address_func, _num_bytes) = mode.get(); 177 | let address = address_func(self); 178 | // self._debug(_num_bytes, opcode); 179 | // advance program counter according to how many bytes that instruction operated on 180 | self.advance_pc(mode); 181 | // look up instruction in table and execute 182 | self.opcode_table[opcode](self, address, mode); 183 | 184 | // return how many cycles it took 185 | self.clock - clock 186 | } 187 | 188 | // memory interface 189 | pub fn read(&mut self, address: usize) -> u8 { 190 | let val = match address { 191 | 0x0000..=0x1FFF => self.mem[address % 0x0800], 192 | 0x2000..=0x3FFF => self.read_ppu_reg(address % 8), 193 | 0x4014 => self.read_ppu_reg(8), 194 | 0x4015 => self.apu.read_status(), 195 | 0x4016 => self.read_controller(), 196 | 0x4000..=0x4017 => 0, // can't read from these APU registers 197 | 0x4018..=0x401F => 0, // APU and I/O functionality that is normally disabled. See CPU Test Mode. 198 | 0x4020..=0xFFFF => self.mapper.borrow().read(address), 199 | _ => panic!("invalid read from 0x{:02x}", address), 200 | }; 201 | val 202 | } 203 | 204 | // memory interface 205 | fn write(&mut self, address: usize, val: u8) { 206 | match address { 207 | 0x0000..=0x1FFF => self.mem[address % 0x0800] = val, 208 | 0x2000..=0x3FFF => self.write_ppu_reg(address % 8, val), 209 | 0x4014 => self.write_ppu_reg(8, val), 210 | 0x4016 => self.write_controller(val), 211 | 0x4000..=0x4017 => self.apu.write_reg(address, val), 212 | 0x4018..=0x401F => (), // APU and I/O functionality that is normally disabled. See CPU Test Mode. 213 | 0x4020..=0xFFFF => self.mapper.borrow_mut().write(address, val), 214 | _ => panic!("invalid write to {:02x}", address), 215 | } 216 | } 217 | 218 | fn read_controller(&mut self) -> u8 { 219 | let bit = match self.button_number < 8 { 220 | true => (self.button_states & (1< 1, 222 | }; 223 | if self.strobe & 1 != 0 { 224 | self.button_number = 0; 225 | } else { 226 | self.button_number += 1; 227 | } 228 | bit | 0x40 229 | } 230 | 231 | fn write_controller(&mut self, val: u8) { 232 | self.strobe = val; 233 | if self.strobe & 1 != 0 { 234 | self.button_number = 0; 235 | } 236 | } 237 | 238 | fn read_ppu_reg(&mut self, reg_num: usize) -> u8 { 239 | match reg_num { 240 | 2 => self.ppu.read_status(), 241 | 4 => self.ppu.read_oam_data(), 242 | 7 => self.ppu.read_data(), 243 | _ => self.ppu.recent_bits, 244 | } 245 | } 246 | 247 | fn write_ppu_reg(&mut self, reg_num: usize, val: u8) { 248 | self.ppu.recent_bits = val; 249 | match reg_num { 250 | 0 => self.ppu.write_controller(val), 251 | 1 => self.ppu.write_mask(val), 252 | 3 => self.ppu.write_oam_address(val as usize), 253 | 4 => self.ppu.write_oam_data(val), 254 | 5 => self.ppu.write_scroll(val), 255 | 6 => self.ppu.write_address(val), 256 | 7 => self.ppu.write_data(val), 257 | 8 => { 258 | let page = (val as usize) << 8; 259 | let mut data = vec![]; 260 | for i in 0..=255 { 261 | data.push(self.read(page + i)); 262 | } 263 | self.ppu.write_oam_dma(data); 264 | let is_odd = self.clock % 2 != 0; 265 | self.delay = 513 + if is_odd {1} else {0}; 266 | }, 267 | _ => panic!("wrote to bad ppu reg: {}", reg_num), 268 | } 269 | } 270 | 271 | fn handle_interrupts(&mut self) { 272 | // handle interrupts from ppu 273 | if self.ppu.trigger_nmi { 274 | self.nmi(); 275 | } 276 | self.ppu.trigger_nmi = false; 277 | // and apu 278 | if self.apu.trigger_irq && (self.p & INTERRUPT_DISABLE_FLAG == 0) { 279 | self.irq(); 280 | } 281 | self.apu.trigger_irq = false; 282 | // and mapper MMC3 283 | if self.mapper.borrow_mut().check_irq() && (self.p & INTERRUPT_DISABLE_FLAG == 0) { 284 | self.irq(); 285 | } 286 | // TODO: should checks for APU and MMC3 IRQs be combined and acknowledged together? 287 | 288 | // At any time, if the interrupt flag is set, the CPU's IRQ line is continuously asserted until the interrupt flag is cleared. 289 | // The processor will continue on from where it was stalled. 290 | if self.apu.dmc.interrupt { 291 | self.irq(); 292 | } 293 | } 294 | 295 | fn _debug(&mut self, num_bytes: usize, opcode: usize) { 296 | let pc = self.pc; 297 | let operands = match num_bytes { 298 | 1 => " ".to_string(), 299 | 2 => format!("{:02X} ", self.read(pc + 1)), 300 | 3 => format!("{:02X} {:02X}", self.read(pc + 1), self.read(pc+2)), 301 | _ => "error".to_string(), 302 | }; 303 | println!("{:04X} {:02X} {} {} A:{:02X} X:{:02X} Y:{:02X} P:{:02X} SP:{:02X}", 304 | pc, self.read(pc), operands, _OPCODE_DISPLAY_NAMES[opcode], 305 | self.a, self.x, self.y, self.p, self.s, 306 | ); 307 | } 308 | 309 | pub fn _memory_at(&mut self, address: usize, amount: usize) -> Vec { 310 | let mut ret = vec![]; 311 | for i in 0..amount { 312 | ret.push(self.read(address+i)); 313 | } 314 | ret 315 | } 316 | } 317 | 318 | /* 319 | Address range Size Device 320 | $0000-$07FF $0800 2KB internal RAM 321 | $0800-$0FFF $0800 ]---+ 322 | $1000-$17FF $0800 |---- Mirrors of $0000-$07FF 323 | $1800-$1FFF $0800 ]---+ 324 | $2000-$2007 $0008 NES PPU registers 325 | $2008-$3FFF $1FF8 Mirrors of $2000-2007 (repeats every 8 bytes) 326 | $4000-$4017 $0018 NES APU and I/O registers 327 | $4018-$401F $0008 APU and I/O functionality that is normally disabled. See CPU Test Mode. 328 | $4020-$FFFF $BFE0 Cartridge space: PRG ROM, PRG RAM, and mapper registers (See Note) 329 | */ 330 | 331 | // For debug output 332 | const _OPCODE_DISPLAY_NAMES: [&str; 256] = [ 333 | "BRK", "ORA", "BAD", "SLO", "NOP", "ORA", "ASL", "SLO", 334 | "PHP", "ORA", "ASL", "ANC", "NOP", "ORA", "ASL", "SLO", 335 | "BPL", "ORA", "BAD", "SLO", "NOP", "ORA", "ASL", "SLO", 336 | "CLC", "ORA", "NOP", "SLO", "NOP", "ORA", "ASL", "SLO", 337 | "JSR", "AND", "BAD", "RLA", "BIT", "AND", "ROL", "RLA", 338 | "PLP", "AND", "ROL", "ANC", "BIT", "AND", "ROL", "RLA", 339 | "BMI", "AND", "BAD", "RLA", "NOP", "AND", "ROL", "RLA", 340 | "SEC", "AND", "NOP", "RLA", "NOP", "AND", "ROL", "RLA", 341 | "RTI", "EOR", "BAD", "SRE", "NOP", "EOR", "LSR", "SRE", 342 | "PHA", "EOR", "LSR", "ALR", "JMP", "EOR", "LSR", "SRE", 343 | "BVC", "EOR", "BAD", "SRE", "NOP", "EOR", "LSR", "SRE", 344 | "CLI", "EOR", "NOP", "SRE", "NOP", "EOR", "LSR", "SRE", 345 | "RTS", "ADC", "BAD", "RRA", "NOP", "ADC", "ROR", "RRA", 346 | "PLA", "ADC", "ROR", "ARR", "JMP", "ADC", "ROR", "RRA", 347 | "BVS", "ADC", "BAD", "RRA", "NOP", "ADC", "ROR", "RRA", 348 | "SEI", "ADC", "NOP", "RRA", "NOP", "ADC", "ROR", "RRA", 349 | "NOP", "STA", "NOP", "SAX", "STY", "STA", "STX", "SAX", 350 | "DEY", "NOP", "TXA", "XAA", "STY", "STA", "STX", "SAX", 351 | "BCC", "STA", "BAD", "AHX", "STY", "STA", "STX", "SAX", 352 | "TYA", "STA", "TXS", "TAS", "SHY", "STA", "SHX", "AHX", 353 | "LDY", "LDA", "LDX", "LAX", "LDY", "LDA", "LDX", "LAX", 354 | "TAY", "LDA", "TAX", "LAX", "LDY", "LDA", "LDX", "LAX", 355 | "BCS", "LDA", "BAD", "LAX", "LDY", "LDA", "LDX", "LAX", 356 | "CLV", "LDA", "TSX", "LAS", "LDY", "LDA", "LDX", "LAX", 357 | "CPY", "CMP", "NOP", "DCP", "CPY", "CMP", "DEC", "DCP", 358 | "INY", "CMP", "DEX", "AXS", "CPY", "CMP", "DEC", "DCP", 359 | "BNE", "CMP", "BAD", "DCP", "NOP", "CMP", "DEC", "DCP", 360 | "CLD", "CMP", "NOP", "DCP", "NOP", "CMP", "DEC", "DCP", 361 | "CPX", "SBC", "NOP", "ISC", "CPX", "SBC", "INC", "ISC", 362 | "INY", "SBC", "NOP", "SBC", "CPX", "SBC", "INC", "ISC", 363 | "BEQ", "SBC", "BAD", "ISC", "NOP", "SBC", "INC", "ISC", 364 | "SED", "SBC", "NOP", "ISC", "NOP", "SBC", "INC", "ISC", 365 | ]; 366 | -------------------------------------------------------------------------------- /src/cpu/opcodes.rs: -------------------------------------------------------------------------------- 1 | use super::{CARRY_FLAG, DECIMAL_FLAG, INTERRUPT_DISABLE_FLAG, IRQ_VECTOR, NEGATIVE_FLAG, NMI_VECTOR, OVERFLOW_FLAG, ZERO_FLAG, Mode}; 2 | 3 | // TODO: check unofficial opcodes for page crosses 4 | 5 | impl super::Cpu { 6 | 7 | pub fn adc(&mut self, _address: usize, _mode: Mode) { 8 | let byte = self.read(_address); 9 | let carry_bit = if self.p & CARRY_FLAG == 0 {0} else {1}; 10 | let mut new_val = self.a.wrapping_add(byte); // add the byte at the _address to accum 11 | new_val = new_val.wrapping_add(carry_bit); // add carry flag to accumulator 12 | // set carry flag if we wrapped around and added something 13 | if new_val <= self.a && (byte != 0 || carry_bit != 0) { 14 | self.p |= CARRY_FLAG; 15 | } else { 16 | self.p &= 0xFF - CARRY_FLAG; 17 | } 18 | self.set_zero_flag(new_val); 19 | self.set_negative_flag(new_val); 20 | // signed 8-bit overflow can only happen if both signs were positive but result was negative, or if both signs were negative and the result was positive 21 | // sign is positive if num & 0x80 == 0, negative if num & 0x80 != 0 22 | // ((sum & 0x80 != 0) && (acc & 0x80 == 0) && (operand & 0x80 == 0)) || ((sum & 0x80 == 0) && (acc & 0x80 != 0) && (operand & 0x80 != 0)) 23 | // simplifies to the below, thanks http://www.righto.com/2012/12/the-6502-overflow-flag-explained.html 24 | if (byte ^ new_val) & (self.a ^ new_val) & 0x80 != 0 { 25 | self.p |= OVERFLOW_FLAG; 26 | } else { 27 | self.p &= 0xFF - OVERFLOW_FLAG; 28 | } 29 | self.a = new_val; // actually change the accumulator 30 | } 31 | 32 | pub fn and(&mut self, _address: usize, _mode: Mode) { 33 | self.a &= self.read(_address); 34 | self.set_zero_flag(self.a); 35 | self.set_negative_flag(self.a); 36 | } 37 | 38 | pub fn asl(&mut self, _address: usize, _mode: Mode) { 39 | let mut val = match _mode { 40 | Mode::ACC => self.a, 41 | _ => { 42 | self.clock += 2; 43 | self.read(_address) 44 | }, 45 | }; 46 | // put top bit in carry flag 47 | if val & (1<<7) != 0 { 48 | self.p |= CARRY_FLAG; 49 | } else { 50 | self.p &= 0xFF - CARRY_FLAG; 51 | } 52 | val <<= 1; 53 | match _mode { 54 | Mode::ACC => self.a = val, 55 | _ => self.write(_address, val), 56 | }; 57 | self.set_zero_flag(val); 58 | self.set_negative_flag(val); 59 | } 60 | 61 | pub fn bcc(&mut self, _address: usize, _mode: Mode) { 62 | let byte = self.read(_address); 63 | if self.p & CARRY_FLAG == 0 { 64 | self.branch(byte); 65 | } 66 | } 67 | 68 | pub fn bcs(&mut self, _address: usize, _mode: Mode) { 69 | let byte = self.read(_address); 70 | if self.p & CARRY_FLAG != 0 { 71 | self.branch(byte); 72 | } 73 | } 74 | 75 | pub fn beq(&mut self, _address: usize, _mode: Mode) { 76 | let byte = self.read(_address); 77 | if self.p & ZERO_FLAG != 0 { 78 | self.branch(byte); 79 | } 80 | } 81 | 82 | pub fn bit(&mut self, _address: usize, _mode: Mode) { 83 | let byte = self.read(_address); 84 | let tested = byte & self.a; 85 | self.set_zero_flag(tested); 86 | let bit6 = byte & (1 << 6); 87 | if bit6 != 0 { 88 | self.p |= OVERFLOW_FLAG; 89 | } else { 90 | self.p &= 0xFF - OVERFLOW_FLAG; 91 | } 92 | let bit7 = byte & (1 << 7); 93 | if bit7 != 0 { 94 | self.p |= NEGATIVE_FLAG; 95 | } else { 96 | self.p &= 0xFF - NEGATIVE_FLAG; 97 | } 98 | } 99 | 100 | pub fn bmi(&mut self, _address: usize, _mode: Mode) { 101 | let byte = self.read(_address); 102 | if self.p & NEGATIVE_FLAG != 0 { 103 | self.branch(byte); 104 | } 105 | } 106 | 107 | pub fn bne(&mut self, _address: usize, _mode: Mode) { 108 | let byte = self.read(_address); 109 | if self.p & ZERO_FLAG == 0 { 110 | self.branch(byte); 111 | } 112 | } 113 | 114 | pub fn bpl(&mut self, _address: usize, _mode: Mode) { 115 | let byte = self.read(_address); 116 | if self.p & NEGATIVE_FLAG == 0 { 117 | self.branch(byte); 118 | } 119 | } 120 | 121 | pub fn brk(&mut self, _address: usize, _mode: Mode) { 122 | // instr_test-v5/rom_singles/15-brk.nes and instr_test-v5/rom_singles/16-special.nes: 123 | // using self.pc + 1 in these next two lines allows these tests to pass. 124 | // I'm not sure why that's necessary as implied addressing mode is only supposed to consume 1 byte, 125 | // but the error message from 16-special.nes said "BRK should push address BRK + 2" 126 | 127 | // Aha! From http://nesdev.com/the%20%27B%27%20flag%20&%20BRK%20instruction.txt: 128 | // Regardless of what ANY 6502 documentation says, BRK is a 2 byte opcode. The 129 | // first is #$00, and the second is a padding byte. This explains why interrupt 130 | // routines called by BRK always return 2 bytes after the actual BRK opcode, 131 | // and not just 1. 132 | 133 | self.push(((self.pc + 1) >> 8) as u8); // push high byte 134 | self.push(((self.pc + 1) & 0xFF) as u8); // push low byte 135 | self.push(self.p | 0b00110000); // push status register with break bits set 136 | self.p |= INTERRUPT_DISABLE_FLAG; // set interrupt disable flag 137 | self.pc = ((self.read(IRQ_VECTOR + 1) as usize) << 8) // set program counter to IRQ/BRK vector, taking high byte 138 | + (self.read(IRQ_VECTOR) as usize); // and low byte 139 | self.clock += 5; // total of 7 cycles, 2 come from implied() 140 | } 141 | 142 | pub fn bvc(&mut self, _address: usize, _mode: Mode) { 143 | let byte = self.read(_address); 144 | if self.p & OVERFLOW_FLAG == 0 { 145 | self.branch(byte); 146 | } 147 | } 148 | 149 | pub fn bvs(&mut self, _address: usize, _mode: Mode) { 150 | let byte = self.read(_address); 151 | if self.p & OVERFLOW_FLAG != 0 { 152 | self.branch(byte); 153 | } 154 | } 155 | 156 | pub fn clc(&mut self, _address: usize, _mode: Mode) { 157 | self.p &= 0xFF - CARRY_FLAG; 158 | } 159 | 160 | pub fn cld(&mut self, _address: usize, _mode: Mode) { 161 | self.p &= 0xFF - DECIMAL_FLAG; 162 | } 163 | 164 | pub fn cli(&mut self, _address: usize, _mode: Mode) { 165 | self.p &= 0xFF - INTERRUPT_DISABLE_FLAG; 166 | } 167 | 168 | pub fn clv(&mut self, _address: usize, _mode: Mode) { 169 | self.p &= 0xFF - OVERFLOW_FLAG; 170 | } 171 | 172 | pub fn cmp(&mut self, _address: usize, _mode: Mode) { 173 | let byte = self.read(_address); 174 | self.compare(self.a, byte); 175 | } 176 | 177 | pub fn cpx(&mut self, _address: usize, _mode: Mode) { 178 | let byte = self.read(_address); 179 | self.compare(self.x, byte); 180 | } 181 | 182 | pub fn cpy(&mut self, _address: usize, _mode: Mode) { 183 | let byte = self.read(_address); 184 | self.compare(self.y, byte); 185 | } 186 | 187 | pub fn dcp(&mut self, _address: usize, _mode: Mode) { 188 | // unofficial 189 | let val = self.read(_address).wrapping_sub(1); 190 | self.write(_address, val); 191 | self.compare(self.a, val); 192 | } 193 | 194 | pub fn dec(&mut self, _address: usize, _mode: Mode) { 195 | let val = self.read(_address).wrapping_sub(1); 196 | self.write(_address, val); 197 | self.set_zero_flag(val); 198 | self.set_negative_flag(val); 199 | self.clock += 2; // extra cycles for all addressing modes of this instruction 200 | } 201 | 202 | pub fn dex(&mut self, _address: usize, _mode: Mode) { 203 | self.x = self.x.wrapping_sub(1); 204 | self.set_zero_flag(self.x); 205 | self.set_negative_flag(self.x); 206 | } 207 | 208 | pub fn dey(&mut self, _address: usize, _mode: Mode) { 209 | self.y = self.y.wrapping_sub(1); 210 | self.set_zero_flag(self.y); 211 | self.set_negative_flag(self.y); 212 | } 213 | 214 | pub fn eor(&mut self, _address: usize, _mode: Mode) { 215 | self.a ^= self.read(_address); 216 | self.set_negative_flag(self.a); 217 | self.set_zero_flag(self.a); 218 | } 219 | 220 | pub fn inc(&mut self, _address: usize, _mode: Mode) { 221 | let val = self.read(_address).wrapping_add(1); 222 | self.write(_address, val); 223 | self.set_zero_flag(val); 224 | self.set_negative_flag(val); 225 | self.clock += 2; // extra cycles for all addressing modes of this instruction 226 | } 227 | 228 | pub fn isc(&mut self, _address: usize, _mode: Mode) { 229 | // unofficial 230 | self.inc(_address, _mode); 231 | self.sbc(_address, _mode); 232 | } 233 | 234 | pub fn inx(&mut self, _address: usize, _mode: Mode) { 235 | self.x = self.x.wrapping_add(1); 236 | self.set_zero_flag(self.x); 237 | self.set_negative_flag(self.x); 238 | } 239 | 240 | pub fn iny(&mut self, _address: usize, _mode: Mode) { 241 | self.y = self.y.wrapping_add(1); 242 | self.set_zero_flag(self.y); 243 | self.set_negative_flag(self.y); 244 | } 245 | 246 | pub fn jmp(&mut self, _address: usize, _mode: Mode) { 247 | if _mode == Mode::ABS { 248 | self.clock -= 1; // this only takes 3.. 249 | } 250 | self.pc = _address; 251 | } 252 | 253 | pub fn jsr(&mut self, _address: usize, _mode: Mode) { 254 | // call to absolute already advances program counter by 3 255 | self.clock += 2; 256 | let minus1 = self.pc - 1; // so m1 is the last _byte of the jsr instruction. second _byte of the operand. 257 | self.push((minus1 >> 8) as u8); 258 | self.push((minus1 & 0xFF) as u8); 259 | self.pc = _address; 260 | } 261 | 262 | pub fn lax(&mut self, _address: usize, _mode: Mode) { 263 | // unofficial opcode that sets both X and accumulator 264 | // TODO: check cycle count? https://wiki.nesdev.com/w/index.php/Programming_with_unofficial_opcodes 265 | let byte = self.read(_address); 266 | self.a = byte; 267 | self.x = byte; 268 | self.set_zero_flag(byte); 269 | self.set_negative_flag(byte); 270 | } 271 | 272 | pub fn lda(&mut self, _address: usize, _mode: Mode) { 273 | let byte = self.read(_address); 274 | self.a = byte; 275 | self.set_zero_flag(byte); 276 | self.set_negative_flag(byte); 277 | } 278 | 279 | pub fn ldx(&mut self, _address: usize, _mode: Mode) { 280 | let byte = self.read(_address); 281 | self.x = byte; 282 | self.set_zero_flag(byte); 283 | self.set_negative_flag(byte); 284 | } 285 | 286 | pub fn ldy(&mut self, _address: usize, _mode: Mode) { 287 | let byte = self.read(_address); 288 | self.y = byte; 289 | self.set_zero_flag(byte); 290 | self.set_negative_flag(byte); 291 | } 292 | 293 | pub fn lsr(&mut self, _address: usize, _mode: Mode) { 294 | let mut val = match _mode { 295 | Mode::ACC => self.a, 296 | _ => { 297 | self.clock += 2; 298 | self.read(_address) 299 | }, 300 | }; 301 | if val & 0x1 == 0x1 { 302 | self.p |= CARRY_FLAG; 303 | } else { 304 | self.p &= 0xFF - CARRY_FLAG; 305 | } 306 | val >>= 1; 307 | match _mode { 308 | Mode::ACC => self.a = val, 309 | _ => self.write(_address, val), 310 | }; 311 | self.set_zero_flag(val); 312 | self.set_negative_flag(val); 313 | } 314 | 315 | pub fn nop(&mut self, _address: usize, _mode: Mode) { 316 | } 317 | 318 | pub fn ora(&mut self, _address: usize, _mode: Mode) { 319 | self.a |= self.read(_address); 320 | self.set_zero_flag(self.a); 321 | self.set_negative_flag(self.a); 322 | } 323 | 324 | pub fn pha(&mut self, _address: usize, _mode: Mode) { 325 | self.clock += 1; 326 | self.push(self.a); 327 | } 328 | 329 | pub fn php(&mut self, _address: usize, _mode: Mode) { 330 | self.clock += 1; 331 | self.push(self.p | 0b00110000); 332 | } 333 | 334 | pub fn pla(&mut self, _address: usize, _mode: Mode) { 335 | self.clock += 2; 336 | self.a = self.pop(); 337 | self.set_zero_flag(self.a); 338 | self.set_negative_flag(self.a); 339 | } 340 | 341 | pub fn plp(&mut self, _address: usize, _mode: Mode) { 342 | self.clock += 2; 343 | self.p = self.pop(); 344 | // TODO: figure out exactly what's supposed to happen here 345 | // let status = self.pop(); 346 | // // for each bit in the popped status, if it's 1, 347 | // // set that bit of self.p to 1. if it's 0, set that 348 | // // bit of self.p to 0. 349 | // for i in 0..=7 { 350 | // if i == 4 || i == 5 { 351 | // continue; // ignore B flags 352 | // } 353 | // let bit = if status & (1 << i) == 0 {0} else {1}; 354 | // if bit != 0 { 355 | // self.p |= 1 << i; 356 | // } else { 357 | // self.p &= 0xFF - (1 << i); 358 | // } 359 | // } 360 | // self.p |= 1 << 5; // turn on bit 5 361 | // self.p &= 0xFF - (1 << 4); // and turn off bit 4 because god knows why 362 | } 363 | 364 | pub fn rla(&mut self, _address: usize, _mode: Mode) { 365 | // unofficial 366 | self.rol(_address, _mode); 367 | self.and(_address, _mode); 368 | } 369 | 370 | pub fn rol(&mut self, _address: usize, _mode: Mode) { 371 | let mut val = match _mode { 372 | Mode::ACC => self.a, 373 | _ => { 374 | self.clock += 2; 375 | self.read(_address) 376 | }, 377 | }; 378 | let carry_flag_bit = if self.p & CARRY_FLAG != 0 {1} else {0}; 379 | let new_cfb = if val & 0x80 != 0 {1} else {0}; 380 | val <<= 1; 381 | val += carry_flag_bit; 382 | match _mode { 383 | Mode::ACC => self.a = val, 384 | _ => self.write(_address, val), 385 | }; 386 | if new_cfb != 0 { self.p |= CARRY_FLAG; } 387 | else { self.p &= 0xFF - CARRY_FLAG; } 388 | self.set_zero_flag(val); 389 | self.set_negative_flag(val); 390 | } 391 | 392 | pub fn ror(&mut self, _address: usize, _mode: Mode) { 393 | let mut val = match _mode { 394 | Mode::ACC => self.a, 395 | _ => { 396 | self.clock += 2; // extra cycles 397 | self.read(_address) 398 | } 399 | }; 400 | let cfb = if self.p & CARRY_FLAG != 0 {1} else {0}; 401 | let new_cfb = val & 0x1; 402 | val >>= 1; 403 | val += cfb * 0x80; 404 | if new_cfb != 0 { self.p |= CARRY_FLAG; } 405 | else { self.p &= 0xFF - CARRY_FLAG; } 406 | match _mode { 407 | Mode::ACC => self.a = val, 408 | _ => self.write(_address, val), 409 | }; 410 | self.set_zero_flag(val); 411 | self.set_negative_flag(val); 412 | } 413 | 414 | pub fn rra(&mut self, _address: usize, _mode: Mode) { 415 | // unofficial 416 | self.ror(_address, _mode); 417 | self.adc(_address, _mode); 418 | } 419 | 420 | pub fn rti(&mut self, _address: usize, _mode: Mode) { 421 | self.plp(_address, _mode); // pull and set status reg (2 clock cycles) 422 | self.pc = self.pop() as usize; // low byte 423 | self.pc += (self.pop() as usize) << 8; // high byte 424 | self.clock += 2; // +2 from implied 425 | } 426 | 427 | pub fn rts(&mut self, _address: usize, _mode: Mode) { 428 | self.pc = self.pop() as usize; 429 | self.pc += ((self.pop() as usize) << 8) + 1; 430 | self.clock += 4; 431 | } 432 | 433 | pub fn sax(&mut self, _address: usize, _mode: Mode) { 434 | // unofficial combo of stx and sta 435 | self.write(_address, self.a & self.x); 436 | } 437 | 438 | pub fn sbc(&mut self, _address: usize, _mode: Mode) { 439 | let byte = self.read(_address); 440 | let carry_bit = if self.p & CARRY_FLAG == 0 {1} else {0}; 441 | let mut new_val = self.a.wrapping_sub(byte); 442 | new_val = new_val.wrapping_sub(carry_bit); 443 | // if overflow occurs and we subtracted something, CLEAR the carry bit 444 | if new_val >= self.a && (byte != 0 || carry_bit != 0) { 445 | self.p &= 0xFF - CARRY_FLAG; 446 | } else { 447 | self.p |= CARRY_FLAG; 448 | } 449 | self.set_zero_flag(new_val); 450 | self.set_negative_flag(new_val); 451 | // if acc is positive, mem is negative, and result is negative 452 | // or if acc is negative, mem is positive, and result is positive 453 | let acc = self.a & 0x80 == 0; 454 | let mem = byte & 0x80 == 0; 455 | let res = new_val & 0x80 == 0; 456 | // if sign is wrong, SET overflow flag 457 | if (acc && !mem && !res) || (!acc && mem && res) { 458 | self.p |= OVERFLOW_FLAG; 459 | } else { 460 | self.p &= 0xFF - OVERFLOW_FLAG; 461 | } 462 | self.a = new_val; // actually change the accumulator 463 | } 464 | 465 | pub fn sec(&mut self, _address: usize, _mode: Mode) { 466 | self.p |= CARRY_FLAG; 467 | } 468 | 469 | pub fn sed(&mut self, _address: usize, _mode: Mode) { 470 | self.p |= DECIMAL_FLAG; // don't think this is necessary since the NES's 6502 doesn't have decimal _mode but whatever 471 | } 472 | 473 | pub fn sei(&mut self, _address: usize, _mode: Mode) { 474 | self.p |= INTERRUPT_DISABLE_FLAG; 475 | } 476 | 477 | pub fn slo(&mut self, _address: usize, _mode: Mode) { 478 | self.asl(_address, _mode); 479 | self.ora(_address, _mode); 480 | // can get away with ignoring that asl handles accumulator addressing mode because slo doesn't handle accumulator addressing mode. 481 | } 482 | 483 | pub fn sre(&mut self, _address: usize, _mode: Mode) { 484 | // unofficial 485 | self.lsr(_address, _mode); 486 | self.eor(_address, _mode); 487 | } 488 | 489 | pub fn sta(&mut self, _address: usize, _mode: Mode) { 490 | // PPU Test 17 491 | // STA, $2000,Y **must** issue a dummy read to 2007 492 | if _address == 0x2007 && _mode == Mode::ABY && self.y == 7 { 493 | self.read(0x2007); 494 | } 495 | 496 | if _mode == Mode::INY { 497 | self.clock = self.before_clock + 6; // Special 498 | } else if _mode == Mode::ABY { 499 | self.clock = self.before_clock + 5; // Special 500 | } 501 | self.write(_address, self.a); 502 | } 503 | 504 | pub fn stx(&mut self, _address: usize, _mode: Mode) { 505 | self.write(_address, self.x); 506 | } 507 | 508 | pub fn sty(&mut self, _address: usize, _mode: Mode) { 509 | self.write(_address, self.y); 510 | } 511 | 512 | pub fn tax(&mut self, _address: usize, _mode: Mode) { 513 | self.x = self.a; 514 | self.set_zero_flag(self.x); 515 | self.set_negative_flag(self.x); 516 | } 517 | 518 | pub fn tay(&mut self, _address: usize, _mode: Mode) { 519 | self.y = self.a; 520 | self.set_zero_flag(self.y); 521 | self.set_negative_flag(self.y); 522 | } 523 | 524 | pub fn tsx(&mut self, _address: usize, _mode: Mode) { 525 | self.x = self.s; 526 | self.set_zero_flag(self.x); 527 | self.set_negative_flag(self.x); 528 | } 529 | 530 | pub fn txa(&mut self, _address: usize, _mode: Mode) { 531 | self.a = self.x; 532 | self.set_zero_flag(self.a); 533 | self.set_negative_flag(self.a); 534 | } 535 | 536 | pub fn txs(&mut self, _address: usize, _mode: Mode) { 537 | self.s = self.x; 538 | } 539 | 540 | pub fn tya(&mut self, _address: usize, _mode: Mode) { 541 | self.a = self.y; 542 | self.set_zero_flag(self.a); 543 | self.set_negative_flag(self.a); 544 | } 545 | 546 | pub fn bad(&mut self, _address: usize, _mode: Mode) { 547 | panic!("illegal opcode: 0x{:02X}", self.read(self.pc)); // this won't be the illegal opcode because the PC somehow hasn't been updated yet 548 | } 549 | 550 | // Interrupts 551 | pub fn nmi(&mut self) { 552 | self.push((self.pc >> 8) as u8); // push high byte 553 | self.push((self.pc & 0xFF) as u8); // push low byte 554 | self.push(self.p | 0b00110000); // push status register with break bits set 555 | self.p |= INTERRUPT_DISABLE_FLAG; // set interrupt disable flag 556 | self.pc = ((self.read(NMI_VECTOR + 1) as usize) << 8) // set program counter to NMI vector, taking high byte 557 | + (self.read(NMI_VECTOR) as usize); // and low byte 558 | self.clock += 7; 559 | } 560 | 561 | pub fn irq(&mut self) { 562 | self.push((self.pc >> 8) as u8); // push high byte 563 | self.push((self.pc & 0xFF) as u8); // push low byte 564 | self.push(self.p & 0b11001111); // push status register with break bits cleared 565 | self.p |= INTERRUPT_DISABLE_FLAG; // set interrupt disable flag 566 | self.pc = ((self.read(IRQ_VECTOR + 1) as usize) << 8) // set program counter to IRQ/BRK vector, taking high byte 567 | + (self.read(IRQ_VECTOR) as usize); // and low byte 568 | self.clock += 7; 569 | } 570 | 571 | } 572 | -------------------------------------------------------------------------------- /src/cpu/serialize.rs: -------------------------------------------------------------------------------- 1 | use super::Mode; 2 | 3 | use serde::{Serialize, Deserialize}; 4 | 5 | #[derive(Serialize, Deserialize, Debug)] 6 | pub struct CpuData { 7 | mem: Vec, 8 | a: u8, 9 | x: u8, 10 | y: u8, 11 | pc: usize, 12 | s: u8, 13 | p: u8, 14 | clock: u64, 15 | delay: usize, 16 | strobe: u8, 17 | button_states: u8, 18 | button_number: u8, 19 | mode_table: Vec, 20 | } 21 | 22 | impl super::Cpu { 23 | pub fn save_state(&self) -> CpuData { 24 | CpuData{ 25 | mem: self.mem.clone(), 26 | a: self.a, 27 | x: self.x, 28 | y: self.y, 29 | pc: self.pc, 30 | s: self.s, 31 | p: self.p, 32 | clock: self.clock, 33 | delay: self.delay, 34 | strobe: self.strobe, 35 | button_states: self.button_states, 36 | button_number: self.button_number, 37 | mode_table: self.mode_table.clone(), 38 | } 39 | } 40 | 41 | pub fn load_state(&mut self, data: CpuData) { 42 | self.mem = data.mem; 43 | self.a = data.a; 44 | self.x = data.x; 45 | self.y = data.y; 46 | self.pc = data.pc; 47 | self.s = data.s; 48 | self.p = data.p; 49 | self.clock = data.clock; 50 | self.delay = data.delay; 51 | self.strobe = data.strobe; 52 | self.button_states = data.button_states; 53 | self.button_number = data.button_number; 54 | self.mode_table = data.mode_table; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/cpu/utility.rs: -------------------------------------------------------------------------------- 1 | use super::{CARRY_FLAG, NEGATIVE_FLAG, STACK_OFFSET, ZERO_FLAG, Mode}; 2 | 3 | impl super::Cpu { 4 | 5 | pub fn advance_pc(&mut self, mode: Mode) { 6 | self.pc += match mode { 7 | Mode::ABS => 3, 8 | Mode::ABX => 3, 9 | Mode::ABY => 3, 10 | Mode::ACC => 1, 11 | Mode::IMM => 2, 12 | Mode::IMP => 1, 13 | Mode::IDX => 2, 14 | Mode::IND => 3, 15 | Mode::INY => 2, 16 | Mode::REL => 2, 17 | Mode::ZPG => 2, 18 | Mode::ZPX => 2, 19 | Mode::ZPY => 2, 20 | } 21 | } 22 | 23 | pub fn add_offset_to_pc(&mut self, offset: i8) { 24 | match offset >= 0 { 25 | true => { 26 | let decoded_offset = offset as usize; 27 | self.pc += decoded_offset; 28 | }, 29 | false => { 30 | // instr_test-v5/rom_singles/11-stack.nes: 31 | // letting decoded_offset be (-offset) as usize was allowing for overflow if offset was -128/0b10000000 32 | let decoded_offset = (-offset) as u8; 33 | self.pc -= decoded_offset as usize; 34 | }, 35 | } 36 | } 37 | 38 | pub fn address_page_cross(&mut self, old_address: usize, new_address: usize) { 39 | if old_address >> 8 != new_address >> 8 { 40 | self.clock += 1; 41 | } 42 | } 43 | 44 | pub fn branch_page_cross(&mut self, old_address: usize, new_address: usize) { 45 | if old_address >> 8 != new_address >> 8 { 46 | self.clock += 1; 47 | } 48 | } 49 | 50 | pub fn branch(&mut self, unsigned_offset: u8) { 51 | let offset = unsigned_offset as i8; 52 | self.clock += 1; 53 | let old_addr = self.pc; 54 | self.add_offset_to_pc(offset); 55 | let new_addr = self.pc; 56 | self.branch_page_cross(old_addr, new_addr); 57 | } 58 | 59 | pub fn compare(&mut self, reg: u8, byte: u8) { 60 | if reg >= byte { 61 | self.p |= CARRY_FLAG; 62 | } else { 63 | self.p &= 0xFF - CARRY_FLAG; 64 | } 65 | self.set_zero_flag(if reg == byte {0} else {1}); 66 | let diff = reg.wrapping_sub(byte); 67 | self.set_negative_flag(diff); 68 | } 69 | 70 | pub fn pop(&mut self) -> u8 { 71 | self.s = self.s.wrapping_add(1); 72 | let byte = self.read(STACK_OFFSET + self.s as usize); 73 | byte 74 | } 75 | 76 | pub fn push(&mut self, byte: u8) { 77 | self.write(STACK_OFFSET + self.s as usize, byte); 78 | self.s = self.s.wrapping_sub(1); 79 | } 80 | 81 | pub fn set_negative_flag(&mut self, num: u8) { 82 | if num & 0x80 == 0x80 { 83 | self.p |= NEGATIVE_FLAG; 84 | } else { 85 | self.p &= 0xFF - NEGATIVE_FLAG; 86 | } 87 | } 88 | 89 | pub fn set_zero_flag(&mut self, num: u8) { 90 | if num == 0 { 91 | self.p |= ZERO_FLAG; 92 | } else { 93 | self.p &= 0xFF - ZERO_FLAG; 94 | } 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/input.rs: -------------------------------------------------------------------------------- 1 | use sdl2::keyboard::Scancode; 2 | use std::collections::HashSet; 3 | 4 | pub fn poll_buttons(strobe: &u8, event_pump: &sdl2::EventPump) -> Option { 5 | if *strobe & 1 == 1 { 6 | let mut button_states = 0; 7 | let pressed_keys: HashSet = event_pump.keyboard_state().pressed_scancodes().collect(); 8 | for key in pressed_keys.iter() { 9 | match key { 10 | Scancode::D => button_states |= 1 << 0, // A 11 | Scancode::F => button_states |= 1 << 1, // B 12 | Scancode::RShift => button_states |= 1 << 2, // Select 13 | Scancode::Return => button_states |= 1 << 3, // Start 14 | Scancode::Up => button_states |= 1 << 4, // Up 15 | Scancode::Down => button_states |= 1 << 5, // Down 16 | Scancode::Left => button_states |= 1 << 6, // Left 17 | Scancode::Right => button_states |= 1 << 7, // Right 18 | _ => (), 19 | } 20 | } 21 | Some(button_states) 22 | } else { 23 | None 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod cpu; 2 | mod ppu; 3 | mod apu; 4 | mod cartridge; 5 | mod input; 6 | mod screen; 7 | mod audio; 8 | mod state; 9 | 10 | use cpu::Cpu; 11 | use ppu::Ppu; 12 | use apu::Apu; 13 | use cartridge::{check_signature, get_mapper}; 14 | use input::poll_buttons; 15 | use screen::{init_window, draw_pixel, draw_to_window}; 16 | use state::{save_state, load_state, find_next_filename, find_last_save_state}; 17 | 18 | use std::path::{Path, PathBuf}; 19 | use std::sync::{Arc, Mutex}; 20 | use std::time::{Instant, Duration}; 21 | 22 | use sdl2::Sdl; 23 | use sdl2::render::{Canvas, Texture}; 24 | use sdl2::keyboard::Keycode; 25 | use sdl2::EventPump; 26 | use sdl2::event::Event; 27 | use sdl2::pixels::PixelFormatEnum; 28 | use sdl2::video::Window; 29 | use sdl2::messagebox::*; 30 | 31 | // use cpuprofiler::PROFILER; 32 | 33 | enum GameExitMode { 34 | QuitApplication, 35 | NewGame(String), 36 | Reset, 37 | Nothing, 38 | } 39 | 40 | fn main() -> Result<(), String> { 41 | // Set up screen 42 | let sdl_context = sdl2::init()?; 43 | let mut event_pump = sdl_context.event_pump()?; 44 | let (mut canvas, texture_creator) = init_window(&sdl_context).expect("Could not create window"); 45 | let mut texture = texture_creator.create_texture_streaming( 46 | PixelFormatEnum::RGB24, 256 as u32, 240 as u32) 47 | .map_err(|e| e.to_string())?; 48 | let mut screen_buffer = vec![0; 256 * 240 * 3]; // contains raw RGB data for the screen 49 | 50 | let argv = std::env::args().collect::>(); 51 | let mut filename = if argv.len() > 1 { 52 | argv[1].to_string() 53 | } else { 54 | show_simple_message_box( 55 | MessageBoxFlag::INFORMATION, "Welcome to Nestur!", INSTRUCTIONS, canvas.window() 56 | ).map_err(|e| e.to_string())?; 57 | let name; 58 | 'waiting: loop { 59 | for event in event_pump.poll_iter() { 60 | match event { 61 | Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. } 62 | => return Ok(()), 63 | Event::DropFile{ filename: f, .. } => { 64 | match check_signature(&f) { 65 | Ok(()) => { 66 | name = f; 67 | break 'waiting; 68 | }, 69 | Err(e) => println!("{}", e), 70 | } 71 | }, 72 | _ => (), 73 | } 74 | } 75 | std::thread::sleep(Duration::from_millis(100)); 76 | } 77 | name 78 | }; 79 | loop { 80 | let res = run_game(&sdl_context, &mut event_pump, &mut screen_buffer, &mut canvas, &mut texture, &filename); 81 | match res { 82 | Ok(Some(GameExitMode::Reset)) => (), 83 | Ok(Some(GameExitMode::NewGame(next_file))) => filename = next_file, 84 | Ok(None) | Ok(Some(GameExitMode::QuitApplication)) => return Ok(()), 85 | Err(e) => return Err(e), 86 | Ok(Some(GameExitMode::Nothing)) => panic!("shouldn't have returned exit mode Nothing to main()"), 87 | } 88 | } 89 | } 90 | 91 | fn run_game( 92 | sdl_context: &Sdl, 93 | event_pump: &mut EventPump, 94 | screen_buffer: &mut Vec, 95 | canvas: &mut Canvas, 96 | texture: &mut Texture, 97 | filename: &str 98 | ) -> Result, String> { 99 | 100 | println!("loading game {}", filename); 101 | 102 | // Set up audio 103 | let mut temp_buffer = vec![]; // receives one sample each time the APU ticks. this is a staging buffer so we don't have to lock the mutex too much. 104 | let apu_buffer = Arc::new(Mutex::new(Vec::::new())); // stays in this thread, receives raw samples between frames 105 | let sdl_buffer = Arc::clone(&apu_buffer); // used in audio device's callback to select the samples it needs 106 | let audio_device = audio::initialize(sdl_context, sdl_buffer).expect("Could not create audio device"); 107 | let mut half_cycle = false; 108 | let mut audio_started = false; 109 | 110 | // Initialize hardware components 111 | let filepath = Path::new(filename).to_path_buf(); 112 | let mapper = get_mapper(filename.to_string()); 113 | let ppu = Ppu::new(mapper.clone()); 114 | let apu = Apu::new(); 115 | let mut cpu = Cpu::new(mapper.clone(), ppu, apu); 116 | 117 | // For throttling to 60 FPS 118 | let mut timer = Instant::now(); 119 | let mut fps_timer = Instant::now(); 120 | let mut fps = 0; 121 | let mut timer_counter = 0; // used to only check time every so many cycles 122 | 123 | // PROFILER.lock().unwrap().start("./main.profile").unwrap(); 124 | 'running: loop { 125 | // step CPU: perform 1 cpu instruction, getting back number of clock cycles it took 126 | let cpu_cycles = cpu.step(); 127 | // clock APU every other CPU cycle 128 | let mut apu_cycles = cpu_cycles / 2; 129 | if cpu_cycles & 1 == 1 { // if cpu step took an odd number of cycles 130 | if half_cycle { // and we have a half-cycle stored 131 | apu_cycles += 1; // use it 132 | half_cycle = false; 133 | } else { 134 | half_cycle = true; // or save it for next odd cpu step 135 | } 136 | } 137 | for _ in 0..apu_cycles { 138 | // can't read CPU from APU so have to pass byte in here 139 | let sample_byte = cpu.read(cpu.apu.dmc.current_address); 140 | temp_buffer.push(cpu.apu.clock(sample_byte)); 141 | } 142 | // clock PPU three times for every CPU cycle 143 | for _ in 0..cpu_cycles * 3 { 144 | let (pixel, end_of_frame) = cpu.ppu.clock(); 145 | match pixel { 146 | Some((x, y, color)) => draw_pixel(screen_buffer, x, y, color), 147 | None => (), 148 | }; 149 | if end_of_frame { 150 | fps += 1; // keep track of how many frames we've rendered this second 151 | draw_to_window(texture, canvas, &screen_buffer)?; // draw the buffer to the window with SDL 152 | let mut b = apu_buffer.lock().unwrap(); // unlock mutex to the real buffer 153 | b.append(&mut temp_buffer); // send this frame's audio data, emptying the temp buffer 154 | if !audio_started { 155 | audio_started = true; 156 | audio_device.resume(); 157 | } 158 | let now = Instant::now(); 159 | // if we're running faster than 60Hz, kill time 160 | if now < timer + Duration::from_millis(1000/60) { 161 | std::thread::sleep(timer + Duration::from_millis(1000/60) - now); 162 | } 163 | timer = Instant::now(); 164 | let outcome = process_events(event_pump, &filepath, &mut cpu); 165 | match outcome { 166 | GameExitMode::QuitApplication => break 'running, 167 | GameExitMode::Reset => return Ok(Some(GameExitMode::Reset)), 168 | GameExitMode::NewGame(g) => return Ok(Some(GameExitMode::NewGame(g))), 169 | GameExitMode::Nothing => (), 170 | } 171 | } 172 | } 173 | // handle keyboard events 174 | match poll_buttons(&cpu.strobe, &event_pump) { 175 | Some(button_states) => cpu.button_states = button_states, 176 | None => (), 177 | }; 178 | // calculate fps 179 | if timer_counter == 999 { 180 | let now = Instant::now(); 181 | if now > fps_timer + Duration::from_secs(1) { 182 | println!("frames per second: {}", fps); 183 | fps = 0; 184 | fps_timer = now; 185 | } 186 | timer_counter = 0; 187 | } else { 188 | timer_counter += 1; 189 | } 190 | } 191 | // PROFILER.lock().unwrap().stop().unwrap(); 192 | mapper.borrow().save_battery_backed_ram(); 193 | Ok(None) 194 | } 195 | 196 | fn process_events(event_pump: &mut EventPump, filepath: &PathBuf, cpu: &mut Cpu) -> GameExitMode { 197 | for event in event_pump.poll_iter() { 198 | match event { 199 | Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. } 200 | => return GameExitMode::QuitApplication, 201 | Event::KeyDown{ keycode: Some(Keycode::F2), .. } 202 | => return GameExitMode::Reset, 203 | Event::KeyDown{ keycode: Some(Keycode::F5), .. } => { 204 | let save_file = find_next_filename(filepath, Some("dat")) 205 | .expect("could not generate save state filename"); 206 | let res: Result<(), String> = save_state(cpu, &save_file) 207 | .or_else(|e| {println!("{}", e); Ok(())}); 208 | res.unwrap(); 209 | }, 210 | Event::KeyDown{ keycode: Some(Keycode::F9), .. } => { 211 | match find_last_save_state(filepath, Some("dat")) { 212 | Some(p) => { 213 | let res: Result<(), String> = load_state(cpu, &p) 214 | .or_else(|e| { println!("{}", e); Ok(()) } ); 215 | res.unwrap(); 216 | }, 217 | None => println!("no save state found for {:?}", filepath) 218 | } 219 | }, 220 | Event::DropFile{ timestamp: _t, window_id: _w, filename: f } => { 221 | if f.len() > 4 && &f[f.len()-4..] == ".dat" { 222 | let p = Path::new(&f).to_path_buf(); 223 | let res: Result<(), String> = load_state(cpu, &p) 224 | .or_else(|e| {println!("{}", e); Ok(())}); 225 | res.unwrap(); 226 | // } else if f.len() > 4 && &f[f.len()-4..] == ".nes" { 227 | } else { 228 | match check_signature(&f) { 229 | Ok(()) => return GameExitMode::NewGame(f), 230 | Err(e) => println!("{}", e), 231 | } 232 | } 233 | }, 234 | _ => (), 235 | } 236 | } 237 | return GameExitMode::Nothing 238 | } 239 | 240 | const INSTRUCTIONS: &str = "To play a game, drag an INES file (extension .nes) onto the main window. 241 | To save the game state, press F5. To load the most recent save state, press F9. 242 | To load another save state file, drag a .dat file onto the window while the game is running. 243 | Battery-backed RAM saves (what the NES cartridges have) will be written to a .sav file if used. 244 | To reset the console/current game, press F2. 245 | 246 | Controls 247 | ------------ 248 | A: D 249 | B: F 250 | Start: enter 251 | Select: (right) shift 252 | Up/Down/Left/Right: arrow keys 253 | "; 254 | 255 | /* 256 | 257 | TODO: 258 | - untangle CPU and APU/PPU? 259 | - better save file organization? 260 | 261 | 262 | Timing notes: 263 | The PPU is throttled to 60Hz by sleeping in the main loop. This locks the CPU to roughly its intended speed, 1.789773MHz NTSC. The APU runs at half that. 264 | The APU gives all of its samples to the SDL audio device, which takes them 60 times per second in batches of 735 (44,100/60). It selects the ones 265 | it needs at the proper interval and truncates its buffer. 266 | 267 | Failed tests from instr_test-v5/rom_singles/: 268 | 3, immediate, Failed. Just unofficial instructions? 269 | 0B AAC #n 270 | 2B AAC #n 271 | 4B ASR #n 272 | 6B ARR #n 273 | AB ATX #n 274 | CB AXS #n 275 | 7, abs_xy, 'illegal opcode using abs x: 9c' 276 | 277 | */ 278 | -------------------------------------------------------------------------------- /src/ppu/cpu_registers.rs: -------------------------------------------------------------------------------- 1 | impl super::Ppu { 2 | 3 | // cpu writes to 0x2000, PPUCTRL 4 | pub fn write_controller(&mut self, byte: u8) { 5 | 6 | // VRAM address increment per CPU read/write of PPUDATA 7 | self.address_increment = match byte & (1<<2) == 0 { // (0: add 1, going across; 1: add 32, going down) 8 | true => 1, 9 | false => 32, 10 | }; 11 | // Sprite pattern table address for 8x8 sprites 12 | self.sprite_pattern_table_base = match byte & (1<<3) == 0 { 13 | true => 0x0, 14 | false => 0x1000, 15 | }; 16 | // Background pattern table address 17 | self.background_pattern_table_base = match byte & (1<<4) == 0 { 18 | true => 0x0, 19 | false => 0x1000, 20 | }; 21 | // Sprite size (0: 8x8 pixels; 1: 8x16 pixels) 22 | self.sprite_size = if byte & (1<<5) != 0 { 16 } else {8}; 23 | // Ignoring PPU master/slave select for now 24 | self.should_generate_nmi = byte & (1<<7) != 0; 25 | self.nmi_change(); 26 | // Take care of t 27 | set_bit(&mut self.t, 10, byte as u16, 0); 28 | set_bit(&mut self.t, 11, byte as u16, 1); 29 | } 30 | 31 | // cpu writes to 0x2001, PPUMASK 32 | pub fn write_mask(&mut self, byte: u8) { 33 | self.grayscale = byte & (1<<0) != 0; 34 | self.show_background_left = byte & (1<<1) != 0; 35 | self.show_sprites_left = byte & (1<<2) != 0; 36 | self.show_background = byte & (1<<3) != 0; 37 | self.show_sprites = byte & (1<<4) != 0; 38 | self.emphasize_red = byte & (1<<5) != 0; 39 | self.emphasize_green = byte & (1<<6) != 0; 40 | self.emphasize_blue = byte & (1<<7) != 0; 41 | } 42 | 43 | // cpu reads ppu status from 0x2002, PPUSTATUS 44 | pub fn read_status(&mut self) -> u8 { 45 | let mut byte = self.recent_bits & 0b0001_1111; 46 | byte |= if self.sprite_overflow { 0b0010_0000 } else {0}; 47 | byte |= if self.sprite_zero_hit { 0b0100_0000 } else {0}; 48 | byte |= if self.vertical_blank { 0b1000_0000 } else {0}; 49 | self.w = 0; 50 | self.vertical_blank = false; 51 | self.nmi_change(); 52 | byte 53 | } 54 | 55 | // cpu writes to 0x2003, OAMADDR 56 | pub fn write_oam_address(&mut self, addr: usize) { 57 | self.oam_address = addr; 58 | } 59 | 60 | // cpu reads from 0x2004, OAMDATA 61 | pub fn read_oam_data(&mut self) -> u8 { 62 | self.primary_oam[self.oam_address] 63 | } 64 | 65 | // cpu writes to 0x2004, OAMDATA 66 | pub fn write_oam_data(&mut self, val: u8) { 67 | // Writes will increment OAMADDR after the write 68 | self.primary_oam[self.oam_address] = val; 69 | self.oam_address += 1; 70 | } 71 | 72 | // cpu writes to 0x2005, PPUSCROLL 73 | pub fn write_scroll(&mut self, val: u8) { 74 | match self.w { // first write 75 | 0 => { 76 | // t: ....... ...HGFED = d: HGFED... 77 | self.t &= !((1<<5)-1); // turn off bottom 5 bits of t 78 | self.t |= val as u16 >> 3; // set bottom 5 bits of t to top 5 bits of d 79 | // x: CBA = d: .....CBA 80 | self.x = val & ((1<<3) - 1); 81 | self.w = 1; 82 | }, 83 | 1 => { // second write 84 | let d = val as u16; 85 | // t: CBA..HG FED..... = d: HGFEDCBA 86 | set_bit(&mut self.t, 0xC, d, 0x0); 87 | set_bit(&mut self.t, 0xD, d, 0x1); 88 | set_bit(&mut self.t, 0xE, d, 0x2); 89 | set_bit(&mut self.t, 0x5, d, 0x3); 90 | set_bit(&mut self.t, 0x6, d, 0x4); 91 | set_bit(&mut self.t, 0x7, d, 0x5); 92 | set_bit(&mut self.t, 0x8, d, 0x6); 93 | set_bit(&mut self.t, 0x9, d, 0x7); 94 | self.w = 0; 95 | }, 96 | _ => panic!("uh oh, somehow w was incremented past 1 to {}", self.w), 97 | } 98 | } 99 | 100 | // cpu writes to 0x2006, PPUADDR 101 | pub fn write_address(&mut self, val: u8) { 102 | let d = val as u16; 103 | match self.w { // first write 104 | 0 => { 105 | // t: .FEDCBA ........ = d: ..FEDCBA 106 | set_bit(&mut self.t, 0x8, d, 0x0); 107 | set_bit(&mut self.t, 0x9, d, 0x1); 108 | set_bit(&mut self.t, 0xA, d, 0x2); 109 | set_bit(&mut self.t, 0xB, d, 0x3); 110 | set_bit(&mut self.t, 0xC, d, 0x4); 111 | set_bit(&mut self.t, 0xD, d, 0x5); 112 | // t: X...... ........ = 0 113 | set_bit(&mut self.t, 0xE, 0, 0); 114 | // setting the 16th instead of the 15th bit in the line above was the longest, most frustrating oversight. 115 | // caused weird problems with vertical scrolling and backgrounds that I initially thought were bugs with MMC3. 116 | self.w = 1; 117 | }, 118 | 1 => { // second write 119 | // t: ....... HGFEDCBA = d: HGFEDCBA 120 | self.t &= 0xFF00; // mask off bottom byte 121 | self.t += d; // apply d 122 | self.v = self.t; // After t is updated, contents of t copied into v 123 | self.w = 0; 124 | }, 125 | _ => panic!("uh oh, somehow w was incremented past 1 to {}", self.w), 126 | } 127 | } 128 | 129 | // cpu reads from 0x2007, PPUDATA 130 | pub fn read_data(&mut self) -> u8 { 131 | /* 132 | The PPUDATA read buffer (post-fetch) 133 | When reading while the VRAM address is in the range 0-$3EFF (i.e., before the palettes), 134 | the read will return the contents of an internal read buffer. This internal buffer is 135 | updated only when reading PPUDATA, and so is preserved across frames. After the CPU reads 136 | and gets the contents of the internal buffer, the PPU will immediately update the internal 137 | buffer with the byte at the current VRAM address. Thus, after setting the VRAM address, 138 | one should first read this register and discard the result. 139 | Reading palette data from $3F00-$3FFF works differently. The palette data is placed 140 | immediately on the data bus, and hence no dummy read is required. Reading the palettes 141 | still updates the internal buffer though, but the data placed in it is the mirrored nametable 142 | data that would appear "underneath" the palette. (Checking the PPU memory map should make this clearer.) 143 | 144 | So: reading returns value of buffer, not v. Therefore, if v has changed since it was last read, which it 145 | probably has, need to read twice, but that's advice to the programmer, not the emulator developer. 146 | As for 0x3F00 through 0x3FFF, the palette RAM indexes and their mirrors, need to find corresponding nametable? 147 | There are 4 nametables, duplicated once, so 8. There is one palette RAM index, mirrored 7 times, so 8. 148 | So to get from the fifth pallete RAM mirror, which would be 0x3F80, you'd select the 5th nametable, 149 | which would be the first mirrored nametable, 0x3000? 150 | No, just subtract 0x1000. https://forums.nesdev.com/viewtopic.php?f=3&t=18627: 151 | 152 | "However, I couldn't find any info on exactly which address should be used to populate the read buffer in this scenario. 153 | From other emulators, it appears to be PPU_ADDR - 0x1000, but I can't really intuit why that is the case." 154 | 155 | "It's the case because the majority of the time (that is, on just about every board but GTROM), 156 | video memory $3000-$3FFF mirrors $2000-$2FFF. When PA13 is high ($2000-$3FFF), nothing is listening 157 | to PA12 (the line that distinguishes $0000-$0FFF from $1000-$1FFF and distinguishes $2000-$2FFF from $3000-$3FFF)." 158 | */ 159 | 160 | // read value at v, current VRAM address (15 bits) 161 | let mem_val = self.read(self.v as usize); 162 | let ret_val; 163 | match self.v % 0x4000 { 164 | 0x0000..=0x3EFF => { 165 | ret_val = self.read_buffer; 166 | self.read_buffer = mem_val; 167 | }, 168 | 0x3F00..=0x3FFF => { 169 | ret_val = mem_val; 170 | self.read_buffer = self.read(self.v as usize - 0x1000); 171 | }, 172 | _ => panic!("reading from invalid PPU address: 0x{:04x}", self.v), 173 | }; 174 | if self.rendering() && (self.scanline < 240 || self.scanline == 261) { 175 | // During rendering (on the pre-render line and the visible lines 0-239, provided either background or sprite rendering is enabled), 176 | // it will update v in an odd way, triggering a coarse X increment and a Y increment simultaneously (with normal wrapping behavior). 177 | self.inc_coarse_x(); 178 | self.inc_y(); 179 | } else { 180 | // Outside of rendering, reads from or writes to $2007 will add either 1 or 32 to v depending on the VRAM increment bit set via $2000. 181 | self.v += self.address_increment; 182 | } 183 | ret_val 184 | } 185 | 186 | // cpu writes to 0x2007, PPUDATA 187 | pub fn write_data(&mut self, val: u8) { 188 | self.write(self.v as usize, val); 189 | if self.rendering() && (self.scanline < 240 || self.scanline == 261) { 190 | // During rendering (on the pre-render line and the visible lines 0-239, provided either background or sprite rendering is enabled), 191 | // it will update v in an odd way, triggering a coarse X increment and a Y increment simultaneously (with normal wrapping behavior). 192 | self.inc_coarse_x(); 193 | self.inc_y(); 194 | } else { 195 | // Outside of rendering, reads from or writes to $2007 will add either 1 or 32 to v depending on the VRAM increment bit set via $2000. 196 | self.v += self.address_increment; 197 | } 198 | } 199 | 200 | // cpu writes to 0x4014, OAMDATA 201 | pub fn write_oam_dma(&mut self, data: Vec) { 202 | self.primary_oam = data; 203 | } 204 | 205 | } 206 | 207 | pub fn set_bit(dest: &mut u16, dest_pos: usize, src: u16, src_pos: usize) { 208 | *dest &= 0xFFFF - (1 << dest_pos); // mask off destination bit 209 | *dest += (if src & (1 << src_pos) == 0 {0} else {1}) << dest_pos; // apply bit from src in src position 210 | } 211 | -------------------------------------------------------------------------------- /src/ppu/memory.rs: -------------------------------------------------------------------------------- 1 | use crate::cartridge::Mirror; 2 | 3 | impl super::Ppu { 4 | 5 | pub fn read(&mut self, address: usize) -> u8 { 6 | match address { 7 | 0x0000..=0x1FFF => self.mapper.borrow().read(address), 8 | 0x2000..=0x3EFF => self.read_nametable(address), 9 | 0x3F00..=0x3FFF => { 10 | let a = address % 0x0020; 11 | let value = self.palette_ram[a]; 12 | value 13 | }, 14 | _ => 0, 15 | } 16 | } 17 | 18 | pub fn write(&mut self, address: usize, value: u8) { 19 | // let address = addr % 0x4000; 20 | match address { 21 | 0x0000..=0x1FFF => self.mapper.borrow_mut().write(address, value), 22 | 0x2000..=0x3EFF => self.write_nametable(address, value), 23 | 0x3F00..=0x3FFF => { 24 | // I did not read this closely enough for a long time. 25 | // Addresses $3F10/$3F14/$3F18/$3F1C are mirrors of $3F00/$3F04/$3F08/$3F0C. 26 | // Note that this goes for writing as well as reading. 27 | // A symptom of not having implemented this correctly in an emulator is the sky being black in Super Mario Bros., 28 | // which writes the backdrop color through $3F10. 29 | match address % 0x10 { 30 | 0x00 => { 31 | self.palette_ram[0] = value; 32 | self.palette_ram[0x10] = value; 33 | }, 34 | 0x04 => { 35 | self.palette_ram[0x04] = value; 36 | self.palette_ram[0x14] = value; 37 | }, 38 | 0x08 => { 39 | self.palette_ram[0x08] = value; 40 | self.palette_ram[0x18] = value; 41 | }, 42 | 0x0C => { 43 | self.palette_ram[0x0C] = value; 44 | self.palette_ram[0x1C] = value; 45 | }, 46 | _ => self.palette_ram[address % 0x0020] = value, 47 | } 48 | }, 49 | _ => (), 50 | } 51 | } 52 | 53 | fn read_nametable(&mut self, address: usize) -> u8 { 54 | let base = address % 0x1000; 55 | let offset = base % 0x0400; 56 | match self.mapper.borrow().get_mirroring() { 57 | Mirror::LowBank => self.nametable_a[offset], 58 | Mirror::HighBank => self.nametable_b[offset], 59 | Mirror::Horizontal => { 60 | match base { 61 | 0x0000..=0x07FF => self.nametable_a[offset], 62 | 0x0800..=0x0FFF => self.nametable_b[offset], 63 | _ => panic!("panicked reading nametable base: {}", base), 64 | } 65 | }, 66 | Mirror::Vertical => { 67 | match base { 68 | 0x0000..=0x03FF | 0x0800..=0x0BFF => self.nametable_a[offset], 69 | 0x0400..=0x07FF | 0x0C00..=0x0FFF => self.nametable_b[offset], 70 | _ => panic!("panicked reading nametable base: {}", base), 71 | } 72 | }, 73 | Mirror::FourScreen => { 74 | match base { 75 | 0x0000..=0x03FF => self.nametable_a[offset], 76 | 0x0400..=0x07FF => self.nametable_b[offset], 77 | 0x0800..=0x0BFF => self.nametable_c[offset], 78 | 0x0C00..=0x0FFF => self.nametable_d[offset], 79 | _ => panic!("panicked reading nametable base: {}", base), 80 | } 81 | }, 82 | } 83 | } 84 | 85 | fn write_nametable(&mut self, address: usize, value: u8) { 86 | let base = address % 0x1000; 87 | let offset = base % 0x0400; 88 | match self.mapper.borrow().get_mirroring() { 89 | Mirror::LowBank => self.nametable_a[offset] = value, 90 | Mirror::HighBank => self.nametable_b[offset] = value, 91 | Mirror::Horizontal => { 92 | match base { 93 | 0x0000..=0x07FF => self.nametable_a[offset] = value, 94 | 0x0800..=0x0FFF => self.nametable_b[offset] = value, 95 | _ => panic!("panicked writing nametable base: {}", base), 96 | } 97 | }, 98 | Mirror::Vertical => { 99 | match base { 100 | 0x0000..=0x03FF | 0x0800..=0x0BFF => self.nametable_a[offset] = value, 101 | 0x0400..=0x07FF | 0x0C00..=0x0FFF => self.nametable_b[offset] = value, 102 | _ => panic!("panicked writing nametable base: {}", base), 103 | } 104 | }, 105 | Mirror::FourScreen => { 106 | match base { 107 | 0x0000..=0x03FF => self.nametable_a[offset] = value, 108 | 0x0400..=0x07FF => self.nametable_b[offset] = value, 109 | 0x0800..=0x0BFF => self.nametable_c[offset] = value, 110 | 0x0C00..=0x0FFF => self.nametable_d[offset] = value, 111 | _ => panic!("panicked writing nametable base: {}", base), 112 | } 113 | }, 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/ppu/mod.rs: -------------------------------------------------------------------------------- 1 | mod cpu_registers; 2 | mod rendering; 3 | mod memory; 4 | pub mod serialize; 5 | 6 | use std::cell::RefCell; 7 | use std::rc::Rc; 8 | use crate::cartridge::Mapper; 9 | 10 | pub struct Ppu { 11 | line_cycle: usize, // x coordinate 12 | scanline: usize, // y coordinate 13 | frame: usize, 14 | 15 | // Internal registers 16 | v: u16, 17 | t: u16, 18 | x: u8, // Fine X scroll (3 bits) 19 | w: u8, // First or second write toggle (1 bit) 20 | 21 | // Cartridge things 22 | pub mapper: Rc>, 23 | 24 | // Each nametable byte is a reference to the start of an 8-byte sequence in the pattern table. 25 | // That sequence represents an 8x8 tile, from top row to bottom. 26 | // First interpretation of how nametables work was wrong. There are two banks. (Or up to 4 on some games.) 27 | // Pictures on http://wiki.nesdev.com/w/index.php/Mirroring refer to them as A and B. 28 | // http://wiki.nesdev.com/w/index.php/MMC1 calls them higher and lower. 29 | // They can be mirrored at certain memory ranges. 30 | nametable_a: Vec, 31 | nametable_b: Vec, 32 | nametable_c: Vec, 33 | nametable_d: Vec, 34 | 35 | // The palette shared by both background and sprites. 36 | // Consists of 32 bytes, each of which represents an index into the global PALETTE_TABLE. 37 | // The first 16 bytes are for the background, the second half for the sprites. 38 | palette_ram: Vec, // Palette RAM indexes. 39 | 40 | // Background pattern shift registers and latches 41 | background_pattern_sr_low: u16, // 2 16-bit shift registers - These contain the pattern table data for two tiles. Every 8 cycles, the data for the next tile is loaded 42 | background_pattern_sr_high: u16, // into the upper 8 bits of this shift register. Meanwhile, the pixel to render is fetched from one of the lower 8 bits. 43 | nametable_byte: u8, // "The data fetched from these accesses is placed into internal latches, 44 | attribute_table_byte: u8, // and then fed to the appropriate shift registers when it's time to do so 45 | low_pattern_table_byte: u8, // (every 8 cycles)." 46 | high_pattern_table_byte: u8, 47 | 48 | // Background palette shift registers and latches 49 | background_palette_sr_low: u8, // 2 8-bit shift registers - 50 | background_palette_sr_high: u8, // These contain the palette attributes for the lower 8 pixels of the 16-bit [pattern/tile] shift register. 51 | background_palette_latch: u8, // These registers are fed by a latch which contains the palette attribute for the next tile. Every 8 cycles, 52 | // the latch is loaded with the palette attribute for the next tile. Because the PPU can only fetch an attribute byte every 8 cycles, each 53 | // sequential string of 8 pixels is forced to have the same palette attribute. 54 | 55 | // Sprite memory, shift registers, and latch 56 | pub primary_oam: Vec, // Primary OAM (holds 64 sprites for the frame) 57 | secondary_oam: Vec, // Secondary OAM (holds 8 sprites for the current scanline) 58 | sprite_attribute_latches: Vec, // 8 latches - These contain the attribute bytes [palette data] for up to 8 sprites. 59 | sprite_counters: Vec, // 8 counters - These contain the X positions for up to 8 sprites. 60 | sprite_indexes: Vec, // Indexes of the sprites-in-the-attribute-latches' within primary OAM 61 | sprite_pattern_table_srs: Vec<(u8, u8)>, // 8 pairs of 8-bit shift registers - These contain the pattern table data for up to 8 sprites, to be rendered on the current scanline. 62 | // Unused sprites are loaded with an all-transparent set of values. 63 | num_sprites: usize, // Number of sprites in the shift registers for the current scanline 64 | 65 | // Various flags set by registers 66 | address_increment: u16, 67 | sprite_pattern_table_base: usize, 68 | background_pattern_table_base: usize, 69 | oam_address: usize, 70 | sprite_size: u8, 71 | grayscale: bool, 72 | show_background_left: bool, // 1: Show background in leftmost 8 pixels of screen, 0: Hide 73 | show_sprites_left: bool, // 1: Show sprites in leftmost 8 pixels of screen, 0: Hide 74 | show_background: bool, // 1: Show background 75 | show_sprites: bool, // 1: Show sprites 76 | emphasize_red: bool, // Emphasize red 77 | emphasize_green: bool, // Emphasize green 78 | emphasize_blue: bool, // Emphasize blue 79 | sprite_overflow: bool, // Set if there are more than 8 sprites on a single line 80 | sprite_zero_hit: bool, // Set when the first pixel of the sprite in the zero index of primary OAM is rendered 81 | should_generate_nmi: bool, // Allows CPU to control whether NMIs trigger 82 | vertical_blank: bool, // true == in vertical blank, false == not 83 | 84 | // These three: god knows. 85 | // TODO: experiment more with NMI 86 | pub trigger_nmi: bool, // triggers NMI in the CPU when it checks in its step() 87 | previous_nmi: bool, 88 | nmi_delay: usize, 89 | 90 | read_buffer: u8, // used with PPUDATA register 91 | pub recent_bits: u8, // Least significant bits previously written into a PPU register 92 | 93 | previous_a12: u8, 94 | } 95 | 96 | impl Ppu { 97 | pub fn new(mapper: Rc>) -> Self { 98 | Ppu { 99 | line_cycle: 0, 100 | scanline: 0, 101 | frame: 0, 102 | v: 0, 103 | t: 0, 104 | x: 0, 105 | w: 0, 106 | mapper: mapper, 107 | nametable_a: vec![0u8; 0x0400], 108 | nametable_b: vec![0u8; 0x0400], 109 | nametable_c: vec![0u8; 0x0400], 110 | nametable_d: vec![0u8; 0x0400], 111 | palette_ram: vec![0u8; 0x0020], 112 | background_pattern_sr_low: 0, 113 | background_pattern_sr_high: 0, 114 | nametable_byte: 0, 115 | attribute_table_byte: 0, 116 | low_pattern_table_byte: 0, 117 | high_pattern_table_byte: 0, 118 | background_palette_sr_low: 0, 119 | background_palette_sr_high: 0, 120 | background_palette_latch: 0, 121 | primary_oam: vec![0u8; 0x0100], 122 | secondary_oam: vec![0u8; 0x0020], 123 | sprite_attribute_latches: vec![0u8; 8], 124 | sprite_counters: vec![0u8; 8], 125 | sprite_indexes: vec![0u8; 8], 126 | sprite_pattern_table_srs: vec![(0u8, 0u8); 8], 127 | num_sprites: 0, 128 | address_increment: 0, 129 | sprite_pattern_table_base: 0, 130 | background_pattern_table_base: 0, 131 | oam_address: 0, 132 | sprite_size: 0, 133 | grayscale: false, 134 | show_background_left: false, 135 | show_sprites_left: false, 136 | show_background: false, 137 | show_sprites: false, 138 | emphasize_red: false, 139 | emphasize_green: false, 140 | emphasize_blue: false, 141 | sprite_overflow: false, 142 | sprite_zero_hit: false, 143 | should_generate_nmi: false, 144 | vertical_blank: false, 145 | trigger_nmi: false, 146 | previous_nmi: false, 147 | nmi_delay: 0, 148 | read_buffer: 0, 149 | recent_bits: 0, 150 | previous_a12: 0, 151 | } 152 | } 153 | 154 | pub fn clock(&mut self) -> (Option<(usize, usize, [u8; 3])>, bool) { 155 | if self.nmi_delay > 0 { 156 | self.nmi_delay -= 1; 157 | if self.nmi_delay == 0 && self.should_generate_nmi && self.vertical_blank { 158 | self.trigger_nmi = true; 159 | } 160 | } 161 | 162 | let mut pixel: Option<(usize, usize, [u8; 3])> = None; 163 | let rendering = self.rendering(); 164 | 165 | // Visible scanlines (0-239) 166 | if rendering && (self.scanline < 240 || self.scanline == 261) { 167 | // background-related things 168 | match self.line_cycle { 169 | 0 => (), // This is an idle cycle. 170 | 1..=256 => { 171 | if self.scanline != 261 { 172 | pixel = Some(self.render_pixel()); 173 | } 174 | self.load_data_into_registers(); 175 | self.shift_registers(); 176 | self.perform_memory_fetch(); 177 | }, 178 | 257 => self.copy_horizontal(), // At dot 257 of each scanline, if rendering is enabled, the PPU copies all bits related to horizontal position from t to v 179 | 321..=336 => { 180 | self.load_data_into_registers(); 181 | self.shift_registers(); 182 | self.perform_memory_fetch(); 183 | }, 184 | x if x > 340 => panic!("cycle beyond 340"), 185 | _ => (), 186 | } 187 | } 188 | 189 | // sprite-related things 190 | if rendering && self.scanline < 240 { 191 | match self.line_cycle { 192 | 1 => self.secondary_oam = vec![0xFF; 0x20], 193 | 257 => { 194 | self.evaluate_sprites(); // ignoring all timing details 195 | self.fetch_sprites(); 196 | }, 197 | 321..=340 => (), // Read the first byte in secondary OAM (while the PPU fetches the first two background tiles for the next scanline) 198 | _ => (), 199 | } 200 | } 201 | 202 | // During dots 280 to 304 of the pre-render scanline (end of vblank) 203 | // If rendering is enabled, at the end of vblank, shortly after the horizontal bits 204 | // are copied from t to v at dot 257, the PPU will repeatedly copy the vertical bits 205 | // from t to v from dots 280 to 304, completing the full initialization of v from t: 206 | if rendering && self.scanline == 261 && self.line_cycle >= 280 && self.line_cycle <= 304 { 207 | self.copy_vertical(); 208 | } 209 | // At dot 256 of each scanline, if rendering is enabled, the PPU increments the vertical position in v. 210 | if rendering && self.line_cycle == 256 && (self.scanline < 240 || self.scanline == 261) { 211 | self.inc_y(); 212 | } 213 | 214 | // v blank 215 | if self.scanline == 241 && self.line_cycle == 1 { 216 | self.vertical_blank = true; 217 | self.nmi_change(); 218 | } 219 | if self.scanline == 261 && self.line_cycle == 1 { 220 | self.vertical_blank = false; 221 | self.nmi_change(); 222 | self.sprite_zero_hit = false; 223 | self.sprite_overflow = false; 224 | } 225 | 226 | // signal time to draw frame 227 | let end_of_frame = self.line_cycle == 256 && self.scanline == 240; 228 | 229 | // advance clock 230 | // For odd frames, the cycle at the end of the pre-render scanline is skipped 231 | if self.line_cycle == 339 && self.scanline == 261 && self.frame % 2 != 0 { 232 | self.line_cycle = 0; 233 | self.scanline = 0; 234 | self.frame = self.frame.wrapping_add(1); 235 | // Otherwise, if at the last cycle of the last row of a frame, advance it. 236 | } else if self.line_cycle == 340 && self.scanline == 261 { 237 | self.line_cycle = 0; 238 | self.scanline = 0; 239 | self.frame = self.frame.wrapping_add(1); 240 | // If at a normal line end, advance to next 241 | } else if self.line_cycle == 340 { 242 | self.line_cycle = 0; 243 | self.scanline += 1; 244 | // If none of the above, just go to next cycle in the row 245 | } else { 246 | self.line_cycle += 1; 247 | } 248 | 249 | // deal with mapper MMC3 250 | let current_a12 = if self.v & 1 << 12 != 0 { 1 } else { 0 }; 251 | if current_a12 != self.previous_a12 252 | && (0..241).contains(&self.scanline) 253 | && rendering 254 | { 255 | self.mapper.borrow_mut().clock() 256 | } 257 | self.previous_a12 = current_a12; 258 | 259 | (pixel, end_of_frame) 260 | } 261 | } 262 | 263 | const PALETTE_TABLE: [[u8; 3]; 64] = [ 264 | [ 84, 84, 84], [ 0, 30, 116], [ 8, 16, 144], [ 48, 0, 136], [ 68, 0, 100], [ 92, 0, 48], [ 84, 4, 0], [ 60, 24, 0], [ 32, 42, 0], [ 8, 58, 0], [ 0, 64, 0], [ 0, 60, 0], [ 0, 50, 60], [ 0, 0, 0], [ 0, 0, 0], [ 0, 0, 0], 265 | [152, 150, 152], [ 8, 76, 196], [ 48, 50, 236], [ 92, 30, 228], [136, 20, 176], [160, 20, 100], [152, 34, 32], [120, 60, 0], [ 84, 90, 0], [ 40, 114, 0], [ 8, 124, 0], [ 0, 118, 40], [ 0, 102, 120], [ 0, 0, 0], [ 0, 0, 0], [ 0, 0, 0], 266 | [236, 238, 236], [ 76, 154, 236], [120, 124, 236], [176, 98, 236], [228, 84, 236], [236, 88, 180], [236, 106, 100], [212, 136, 32], [160, 170, 0], [116, 196, 0], [ 76, 208, 32], [ 56, 204, 108], [ 56, 180, 204], [ 60, 60, 60], [ 0, 0, 0], [ 0, 0, 0], 267 | [236, 238, 236], [168, 204, 236], [188, 188, 236], [212, 178, 236], [236, 174, 236], [236, 174, 212], [236, 180, 176], [228, 196, 144], [204, 210, 120], [180, 222, 120], [168, 226, 144], [152, 226, 180], [160, 214, 228], [160, 162, 160], [ 0, 0, 0], [ 0, 0, 0], 268 | ]; 269 | -------------------------------------------------------------------------------- /src/ppu/rendering.rs: -------------------------------------------------------------------------------- 1 | use super::cpu_registers::set_bit; 2 | 3 | impl super::Ppu { 4 | 5 | pub fn perform_memory_fetch(&mut self) { 6 | match self.line_cycle % 8 { 7 | 0 => self.inc_coarse_x(), 8 | 1 => self.fetch_nametable_byte(), 9 | 3 => self.fetch_attribute_table_byte(), 10 | 5 => self.fetch_low_pattern_table_byte(), 11 | 7 => self.fetch_high_pattern_table_byte(), 12 | _ => (), 13 | }; 14 | } 15 | 16 | pub fn shift_registers(&mut self) { 17 | // Shift pattern output registers 18 | self.background_pattern_sr_low <<= 1; 19 | self.background_pattern_sr_high <<= 1; 20 | // Shift the palette shift registers 21 | self.background_palette_sr_low <<= 1; 22 | self.background_palette_sr_high <<= 1; 23 | // Shift bit 0 of the palette attribute latch into the bottom bit of the low palette shift register, 24 | self.background_palette_sr_low |= (self.background_palette_latch & 1 << 0 != 0) as u8; 25 | // and bit 1 of the palette attribute latch into the bottom bit of the high palette shift register 26 | self.background_palette_sr_high |= (self.background_palette_latch & 1 << 1 != 0) as u8; 27 | } 28 | 29 | pub fn load_data_into_registers(&mut self) { 30 | if self.line_cycle % 8 == 1 { // The shifters are reloaded during ticks 9, 17, 25, ..., 257. 31 | // These contain the pattern table data for two tiles. Every 8 cycles, the data for the next 32 | // tile is loaded into the upper 8 bits of this shift register. Meanwhile, the pixel to render is fetched from one of the lower 8 bits. 33 | self.background_pattern_sr_low |= self.low_pattern_table_byte as u16; 34 | self.background_pattern_sr_high |= self.high_pattern_table_byte as u16; 35 | self.background_palette_latch = self.attribute_table_byte; 36 | } 37 | } 38 | 39 | pub fn fetch_nametable_byte(&mut self) { 40 | // nametable address is the bottom 12 bits of v in the 0x2000 range 41 | self.nametable_byte = self.read(0x2000 | (self.v & 0b00001111_11111111) as usize); 42 | } 43 | 44 | pub fn fetch_attribute_table_byte(&mut self) { 45 | let address = 0x23C0 | (self.v & 0x0C00) | ((self.v >> 4) & 0x38) | ((self.v >> 2) & 0x07); 46 | let byte = self.read(address as usize); 47 | // figure out which two bits are being represented, ignoring fine x and fine y 48 | // left or right: 49 | let coarse_x = self.v & 0b00000000_00011111; 50 | let coarse_y = (self.v & 0b00000011_11100000) >> 5; 51 | let left_or_right = (coarse_x / 2) % 2; // 0 == left, 1 == right 52 | let top_or_bottom = (coarse_y / 2) % 2; // 0 == top, 1 == bottom 53 | // grab the needed two bits 54 | self.attribute_table_byte = match (top_or_bottom, left_or_right) { 55 | (0,0) => (byte >> 0) & 0b00000011, 56 | (0,1) => (byte >> 2) & 0b00000011, 57 | (1,0) => (byte >> 4) & 0b00000011, 58 | (1,1) => (byte >> 6) & 0b00000011, 59 | _ => panic!("should not get here"), 60 | }; 61 | } 62 | 63 | pub fn fetch_low_pattern_table_byte(&mut self) { 64 | // pattern table address should be the pattern table base (0x0 or 0x1000) 65 | let mut address = self.background_pattern_table_base; 66 | // plus the value of the nametable byte left-shifted by 4 (which the wiki doesn't really explain) 67 | address += (self.nametable_byte as usize) << 4; 68 | // plus the fine Y scroll 69 | address += ((self.v as usize) >> 12) & 7; 70 | self.low_pattern_table_byte = self.read(address); 71 | } 72 | 73 | pub fn fetch_high_pattern_table_byte(&mut self) { 74 | // same as low pattern table byte, but "Fetch the high-order byte of this sliver from an address 8 bytes higher." 75 | let mut address = self.background_pattern_table_base; 76 | address += (self.nametable_byte as usize) << 4; 77 | address += (self.v as usize >> 12) & 7; 78 | self.high_pattern_table_byte = self.read(address + 8); 79 | } 80 | 81 | pub fn render_pixel(&mut self) -> (usize, usize, [u8; 3]) { 82 | let (x, y) = (self.line_cycle - 1, self.scanline); 83 | let mut background_pixel = self.select_background_pixel(); 84 | let (mut sprite_pixel, current_sprite) = self.select_sprite_pixel(); 85 | 86 | // extract low and high bits from palette shift registers according to fine x, starting from left 87 | let low_palette_bit = (self.background_palette_sr_low & (1 << (7-self.x)) != 0) as u8; 88 | let high_palette_bit = (self.background_palette_sr_high & (1 << (7-self.x)) != 0) as u8; 89 | let palette_offset = (high_palette_bit << 1) | low_palette_bit; 90 | 91 | if x < 8 && !self.show_background_left { 92 | background_pixel = 0; 93 | } 94 | if x < 8 && !self.show_sprites_left { 95 | sprite_pixel = 0; 96 | } 97 | let mut palette_address = 0; 98 | if background_pixel == 0 && sprite_pixel != 0 { // displaying the sprite 99 | palette_address += 0x10; // second half of palette table, "Background/Sprite select" 100 | palette_address += (self.sprite_attribute_latches[current_sprite] & 0b11) << 2; // bottom two bits of attribute byte, left shifted by two 101 | palette_address += sprite_pixel; // bottom two bits are the value of the sprite pixel from pattern table 102 | } else if background_pixel != 0 && sprite_pixel == 0 { // displaying the background pixel 103 | palette_address += palette_offset << 2; // Palette number from attribute table or OAM 104 | palette_address += background_pixel; // Pixel value from tile data 105 | } else if background_pixel != 0 && sprite_pixel != 0 { 106 | if self.sprite_indexes[current_sprite] == 0 { // don't access index current_sprite. need to know which sprite we're on horizontally. 107 | self.sprite_zero_hit = true; 108 | } 109 | if self.sprite_attribute_latches[current_sprite] & (1 << 5) == 0 { // sprite has high priority 110 | palette_address += 0x10; 111 | palette_address += (self.sprite_attribute_latches[current_sprite] & 0b11) << 2; 112 | palette_address += sprite_pixel; 113 | } else { 114 | palette_address += palette_offset << 2; 115 | palette_address += background_pixel; 116 | } 117 | } 118 | // let pixel = self.read(palette_address as usize) as usize; 119 | let pixel = self.palette_ram[palette_address as usize] as usize; 120 | let mut color: [u8; 3] = super::PALETTE_TABLE[pixel]; 121 | if self.emphasize_red { 122 | color[0] = emphasize(&color[0]); 123 | color[1] = deemphasize(&color[1]); 124 | color[2] = deemphasize(&color[2]); 125 | } 126 | if self.emphasize_green { 127 | color[0] = deemphasize(&color[0]); 128 | color[1] = emphasize(&color[1]); 129 | color[2] = deemphasize(&color[2]); 130 | } 131 | if self.emphasize_blue { 132 | color[0] = deemphasize(&color[0]); 133 | color[1] = deemphasize(&color[1]); 134 | color[2] = emphasize(&color[2]); 135 | } 136 | (x,y,color) 137 | } 138 | 139 | pub fn select_background_pixel(&mut self) -> u8 { 140 | if self.show_background { 141 | // Returned background pixel is a value between 0 and 3. 142 | // the bit from background_pattern_sr_low (low pattern table byte) in the 0th place, 143 | // and the value of the background_pattern_sr_high (high pattern table byte) in the 1st place. 144 | let low_bit = (self.background_pattern_sr_low & (1 << (15 - self.x)) != 0) as u8; 145 | let high_bit = (self.background_pattern_sr_high & (1 << (15 - self.x)) != 0) as u8; 146 | (high_bit << 1) | low_bit 147 | } else { 148 | 0 149 | } 150 | } 151 | 152 | pub fn select_sprite_pixel(&mut self) -> (u8, usize) { 153 | // Returns (sprite_pixel, index of sprite_pixel within secondary_oam/shift registers) 154 | if self.show_sprites { 155 | // sprite pixel is a value between 0 and 3 representing the two sprite pattern table shift registers 156 | let mut low_bit = 0; 157 | let mut high_bit = 0; 158 | let mut secondary_index = 0; 159 | for i in 0..self.num_sprites { 160 | // If the counter is zero, the sprite becomes "active", and the respective pair of shift registers for the sprite is shifted once every cycle. 161 | // This output accompanies the data in the sprite's latch, to form a pixel. 162 | if self.sprite_counters[i] == 0 { 163 | // The current pixel for each "active" sprite is checked (from highest to lowest priority), 164 | // and the first non-transparent pixel moves on to a multiplexer, where it joins the BG pixel. 165 | secondary_index = i; 166 | low_bit = (self.sprite_pattern_table_srs[i].0 & 1<<7 != 0) as u8; 167 | high_bit = (self.sprite_pattern_table_srs[i].1 & 1<<7 != 0) as u8; 168 | if !(low_bit == 0 && high_bit == 0) { 169 | break; 170 | } 171 | } 172 | } 173 | // Have to shift pixels of all sprites with counter 0, whether or not they're the selected pixel. otherwise the pixels get pushed to the right. 174 | for i in 0..self.num_sprites { 175 | if self.sprite_counters[i] == 0 { 176 | self.sprite_pattern_table_srs[i].0 <<= 1; 177 | self.sprite_pattern_table_srs[i].1 <<= 1; 178 | } 179 | // Every cycle, the 8 x-position counters for the sprites are decremented by one. 180 | if self.sprite_counters[i] > 0 { 181 | self.sprite_counters[i] -= 1; 182 | } 183 | } 184 | ((high_bit << 1) | low_bit, secondary_index) 185 | } else { 186 | (0, 0) 187 | } 188 | } 189 | 190 | pub fn evaluate_sprites(&mut self) { 191 | let mut sprite_count = 0; 192 | for n in 0..64 { 193 | let y_coord = self.primary_oam[(n*4)+0]; 194 | if self.y_in_range(y_coord) { 195 | for i in 0..4 { 196 | self.secondary_oam[(sprite_count*4)+i] = self.primary_oam[(n*4)+i]; 197 | } 198 | self.sprite_indexes[sprite_count] = n as u8; 199 | sprite_count += 1; 200 | } else { 201 | // TODO: sprite evaluation bug 202 | } 203 | if sprite_count == 8 { 204 | break; 205 | } 206 | } 207 | self.num_sprites = sprite_count; 208 | 209 | } 210 | 211 | pub fn fetch_sprites(&mut self) { 212 | for i in 0..self.num_sprites { 213 | let mut address: usize; 214 | let sprite_y_position = self.secondary_oam[(4*i)+0] as usize; // byte 0 of sprite, sprite's vertical position on screen 215 | let sprite_tile_index = self.secondary_oam[(4*i)+1] as usize; // byte 1 of sprite, sprite's location within pattern table 216 | let sprite_attributes = self.secondary_oam[(4*i)+2]; // byte 2 of sprite, sprite's palette, priority, and flip attributes 217 | let sprite_x_position = self.secondary_oam[(4*i)+3]; // byte 3 of sprite, sprite's horizontal position on screen 218 | let flipped_vertically = sprite_attributes & (1<<7) != 0; 219 | let flipped_horizontally = sprite_attributes & (1<<6) != 0; 220 | // For 8x8 sprites, this is the tile number of this sprite within the pattern table selected in bit 3 of PPUCTRL ($2000). 221 | if self.sprite_size == 8 { 222 | address = self.sprite_pattern_table_base; 223 | address += sprite_tile_index*16; 224 | address += if !flipped_vertically { 225 | self.scanline - sprite_y_position // row-within-sprite offset is difference between current scanline and top of sprite 226 | } else { 227 | self.sprite_size as usize - 1 - (self.scanline - sprite_y_position) 228 | }; 229 | // For 8x16 sprites, the PPU ignores the pattern table selection and selects a pattern table from bit 0 of this number. 230 | } else { 231 | address = if sprite_tile_index & 1 == 0 { 0x0 } else { 0x1000 }; 232 | address += (sprite_tile_index & 0xFFFF-1) << 4; // turn off bottom bit BEFORE shifting 233 | let fine_y = if !flipped_vertically { 234 | self.scanline - sprite_y_position 235 | } else { 236 | self.sprite_size as usize - 1 - (self.scanline - sprite_y_position) 237 | }; 238 | if fine_y > 7 { 239 | address += 16; 240 | address += fine_y - 8; 241 | } else { 242 | address += fine_y; 243 | } 244 | } 245 | let low_pattern_table_byte = self.read(address); 246 | let high_pattern_table_byte = self.read(address + 8); 247 | let mut shift_reg_vals = (0, 0); 248 | for j in 0..8 { 249 | let current_bits = (low_pattern_table_byte & (1 << j), high_pattern_table_byte & (1 << j)); 250 | if !flipped_horizontally { 251 | // just copy each bit in same order 252 | shift_reg_vals.0 |= current_bits.0; 253 | shift_reg_vals.1 |= current_bits.1; 254 | } else { 255 | // get bit of pattern table byte, left shift it by 7 - bit position 256 | shift_reg_vals.0 |= ((current_bits.0 != 0) as u8) << (7 - j); 257 | shift_reg_vals.1 |= ((current_bits.1 != 0) as u8) << (7 - j); 258 | } 259 | } 260 | // put pattern table bytes into the shift registers, ready to be rendered 261 | self.sprite_pattern_table_srs[i] = shift_reg_vals; 262 | // In addition to this, the X positions and attributes for each sprite are loaded from the secondary OAM into their respective counters/latches. 263 | // This happens during the second garbage nametable fetch, with the attribute byte loaded during the first tick and the X coordinate during the second. 264 | self.sprite_attribute_latches[i] = sprite_attributes; 265 | self.sprite_counters[i] = sprite_x_position; 266 | } 267 | } 268 | 269 | pub fn inc_coarse_x(&mut self) { 270 | if self.v & 0x001F == 0x001F { // if coarse X == 31 271 | self.v &= !0x001F; // coarse X = 0 272 | self.v ^= 1<<10; // switch horizontal nametable 273 | } else { 274 | self.v += 1; 275 | } 276 | } 277 | 278 | pub fn inc_y(&mut self) { 279 | // If rendering is enabled, fine Y is incremented at dot 256 of each scanline, 280 | // overflowing to coarse Y, and finally adjusted to wrap among the nametables vertically. 281 | let mut fine_y = (self.v & 0b01110000_00000000) >> 12; 282 | let mut coarse_y = (self.v & 0b00000011_11100000) >> 5; 283 | if fine_y < 7 { 284 | fine_y += 1; 285 | } else { 286 | fine_y = 0; 287 | // Row 29 is the last row of tiles in a nametable. To wrap to the next nametable when 288 | // incrementing coarse Y from 29, the vertical nametable is switched by toggling bit 289 | // 11, and coarse Y wraps to row 0. 290 | if coarse_y == 29 { 291 | self.v ^= 1<<11; 292 | coarse_y = 0; 293 | // Coarse Y can be set out of bounds (> 29), which will cause the PPU to read the 294 | // attribute data stored there as tile data. If coarse Y is incremented from 31, 295 | // it will wrap to 0, but the nametable will not switch. 296 | } else if coarse_y == 32 { 297 | coarse_y = 0; 298 | } else { 299 | coarse_y += 1; 300 | } 301 | } 302 | // set resulting coarse y 303 | set_bit(&mut self.v, 0x5, coarse_y, 0x0); 304 | set_bit(&mut self.v, 0x6, coarse_y, 0x1); 305 | set_bit(&mut self.v, 0x7, coarse_y, 0x2); 306 | set_bit(&mut self.v, 0x8, coarse_y, 0x3); 307 | set_bit(&mut self.v, 0x9, coarse_y, 0x4); 308 | // and fine y 309 | set_bit(&mut self.v, 0xC, fine_y, 0x0); 310 | set_bit(&mut self.v, 0xD, fine_y, 0x1); 311 | set_bit(&mut self.v, 0xE, fine_y, 0x2); 312 | } 313 | 314 | pub fn copy_horizontal(&mut self) { 315 | // v: ....F.. ...EDCBA = t: ....F.. ...EDCBA 316 | let mask = 0b00000100_00011111; 317 | let t_vals = self.t & mask; // grab bits of t 318 | self.v &= !mask; // turn off bits of v 319 | self.v |= t_vals; // apply bits of t 320 | } 321 | 322 | pub fn copy_vertical(&mut self) { 323 | // v: IHGF.ED CBA..... = t: IHGF.ED CBA..... 324 | let mask = 0b01111011_11100000; 325 | let t_vals = self.t & mask; 326 | self.v &= !mask; 327 | self.v |= t_vals; 328 | } 329 | 330 | pub fn rendering(&self) -> bool { 331 | self.show_background || self.show_sprites 332 | } 333 | 334 | pub fn y_in_range(&self, y_coord: u8) -> bool { 335 | self.scanline >= (y_coord as usize) && 336 | self.scanline - (y_coord as usize) < self.sprite_size as usize 337 | } 338 | 339 | pub fn nmi_change(&mut self) { 340 | let nmi = self.should_generate_nmi && self.vertical_blank; 341 | if nmi && !self.previous_nmi { 342 | self.nmi_delay = 1; 343 | } 344 | self.previous_nmi = nmi; 345 | } 346 | } 347 | 348 | fn emphasize(byte: &u8) -> u8 { 349 | if *byte < 200 { 350 | byte + 55 351 | } else { 352 | 255 353 | } 354 | } 355 | 356 | fn deemphasize(byte: &u8) -> u8 { 357 | if *byte > 55 { 358 | byte - 55 359 | } else { 360 | 0 361 | } 362 | } 363 | -------------------------------------------------------------------------------- /src/ppu/serialize.rs: -------------------------------------------------------------------------------- 1 | use serde::{Serialize, Deserialize}; 2 | 3 | #[derive(Serialize, Deserialize, Debug)] 4 | pub struct PpuData { 5 | line_cycle: usize, 6 | scanline: usize, 7 | frame: usize, 8 | v: u16, 9 | t: u16, 10 | x: u8, 11 | w: u8, 12 | nametable_a: Vec, 13 | nametable_b: Vec, 14 | nametable_c: Vec, 15 | nametable_d: Vec, 16 | palette_ram: Vec, 17 | background_pattern_sr_low: u16, 18 | background_pattern_sr_high: u16, 19 | nametable_byte: u8, 20 | attribute_table_byte: u8, 21 | low_pattern_table_byte: u8, 22 | high_pattern_table_byte: u8, 23 | background_palette_sr_low: u8, 24 | background_palette_sr_high: u8, 25 | background_palette_latch: u8, 26 | primary_oam: Vec, 27 | secondary_oam: Vec, 28 | sprite_attribute_latches: Vec, 29 | sprite_counters: Vec, 30 | sprite_indexes: Vec, 31 | sprite_pattern_table_srs: Vec<(u8, u8)>, 32 | num_sprites: usize, 33 | address_increment: u16, 34 | sprite_pattern_table_base: usize, 35 | background_pattern_table_base:usize, 36 | oam_address: usize, 37 | sprite_size: u8, 38 | grayscale: bool, 39 | show_background_left: bool, 40 | show_sprites_left: bool, 41 | show_background: bool, 42 | show_sprites: bool, 43 | emphasize_red: bool, 44 | emphasize_green: bool, 45 | emphasize_blue: bool, 46 | sprite_overflow: bool, 47 | sprite_zero_hit: bool, 48 | should_generate_nmi: bool, 49 | vertical_blank: bool, 50 | trigger_nmi: bool, 51 | previous_nmi: bool, 52 | nmi_delay: usize, 53 | read_buffer: u8, 54 | recent_bits: u8, 55 | previous_a12: u8, 56 | } 57 | 58 | impl super::Ppu { 59 | pub fn save_state(&self) -> PpuData { 60 | PpuData{ 61 | line_cycle: self.line_cycle, 62 | scanline: self.scanline, 63 | frame: self.frame, 64 | v: self.v, 65 | t: self.t, 66 | x: self.x, 67 | w: self.w, 68 | nametable_a: self.nametable_a.clone(), 69 | nametable_b: self.nametable_b.clone(), 70 | nametable_c: self.nametable_c.clone(), 71 | nametable_d: self.nametable_d.clone(), 72 | palette_ram: self.palette_ram.clone(), 73 | background_pattern_sr_low: self.background_pattern_sr_low, 74 | background_pattern_sr_high: self.background_pattern_sr_high, 75 | nametable_byte: self.nametable_byte, 76 | attribute_table_byte: self.attribute_table_byte, 77 | low_pattern_table_byte: self.low_pattern_table_byte, 78 | high_pattern_table_byte: self.high_pattern_table_byte, 79 | background_palette_sr_low: self.background_palette_sr_low, 80 | background_palette_sr_high: self.background_palette_sr_high, 81 | background_palette_latch: self.background_palette_latch, 82 | primary_oam: self.primary_oam.clone(), 83 | secondary_oam: self.secondary_oam.clone(), 84 | sprite_attribute_latches: self.sprite_attribute_latches.clone(), 85 | sprite_counters: self.sprite_counters.clone(), 86 | sprite_indexes: self.sprite_indexes.clone(), 87 | sprite_pattern_table_srs: self.sprite_pattern_table_srs.clone(), 88 | num_sprites: self.num_sprites, 89 | address_increment: self.address_increment, 90 | sprite_pattern_table_base: self.sprite_pattern_table_base, 91 | background_pattern_table_base: self.background_pattern_table_base, 92 | oam_address: self.oam_address, 93 | sprite_size: self.sprite_size, 94 | grayscale: self.grayscale, 95 | show_background_left: self.show_background_left, 96 | show_sprites_left: self.show_sprites_left, 97 | show_background: self.show_background, 98 | show_sprites: self.show_sprites, 99 | emphasize_red: self.emphasize_red, 100 | emphasize_green: self.emphasize_green, 101 | emphasize_blue: self.emphasize_blue, 102 | sprite_overflow: self.sprite_overflow, 103 | sprite_zero_hit: self.sprite_zero_hit, 104 | should_generate_nmi: self.should_generate_nmi, 105 | vertical_blank: self.vertical_blank, 106 | trigger_nmi: self.trigger_nmi, 107 | previous_nmi: self.previous_nmi, 108 | nmi_delay: self.nmi_delay, 109 | read_buffer: self.read_buffer, 110 | recent_bits: self.recent_bits, 111 | previous_a12: self.previous_a12, 112 | } 113 | } 114 | 115 | pub fn load_state(&mut self, data: PpuData) { 116 | self.line_cycle = data.line_cycle; 117 | self.scanline = data.scanline; 118 | self.frame = data.frame; 119 | self.v = data.v; 120 | self.t = data.t; 121 | self.x = data.x; 122 | self.w = data.w; 123 | self.nametable_a = data.nametable_a; 124 | self.nametable_b = data.nametable_b; 125 | self.nametable_c = data.nametable_c; 126 | self.nametable_d = data.nametable_d; 127 | self.palette_ram = data.palette_ram; 128 | self.background_pattern_sr_low = data.background_pattern_sr_low; 129 | self.background_pattern_sr_high = data.background_pattern_sr_high; 130 | self.nametable_byte = data.nametable_byte; 131 | self.attribute_table_byte = data.attribute_table_byte; 132 | self.low_pattern_table_byte = data.low_pattern_table_byte; 133 | self.high_pattern_table_byte = data.high_pattern_table_byte; 134 | self.background_palette_sr_low = data.background_palette_sr_low; 135 | self.background_palette_sr_high = data.background_palette_sr_high; 136 | self.background_palette_latch = data.background_palette_latch; 137 | self.primary_oam = data.primary_oam; 138 | self.secondary_oam = data.secondary_oam; 139 | self.sprite_attribute_latches = data.sprite_attribute_latches; 140 | self.sprite_counters = data.sprite_counters; 141 | self.sprite_indexes = data.sprite_indexes; 142 | self.sprite_pattern_table_srs = data.sprite_pattern_table_srs; 143 | self.num_sprites = data.num_sprites; 144 | self.address_increment = data.address_increment; 145 | self.sprite_pattern_table_base = data.sprite_pattern_table_base; 146 | self.background_pattern_table_base = data.background_pattern_table_base; 147 | self.oam_address = data.oam_address; 148 | self.sprite_size = data.sprite_size; 149 | self.grayscale = data.grayscale; 150 | self.show_background_left = data.show_background_left; 151 | self.show_sprites_left = data.show_sprites_left; 152 | self.show_background = data.show_background; 153 | self.show_sprites = data.show_sprites; 154 | self.emphasize_red = data.emphasize_red; 155 | self.emphasize_green = data.emphasize_green; 156 | self.emphasize_blue = data.emphasize_blue; 157 | self.sprite_overflow = data.sprite_overflow; 158 | self.sprite_zero_hit = data.sprite_zero_hit; 159 | self.should_generate_nmi = data.should_generate_nmi; 160 | self.vertical_blank = data.vertical_blank; 161 | self.trigger_nmi = data.trigger_nmi; 162 | self.previous_nmi = data.previous_nmi; 163 | self.nmi_delay = data.nmi_delay; 164 | self.read_buffer = data.read_buffer; 165 | self.recent_bits = data.recent_bits; 166 | self.previous_a12 = data.previous_a12; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/screen.rs: -------------------------------------------------------------------------------- 1 | extern crate sdl2; 2 | 3 | use sdl2::Sdl; 4 | use sdl2::pixels::Color; 5 | use sdl2::render::{Canvas, Texture, TextureCreator}; 6 | use sdl2::video::{Window, WindowContext}; 7 | 8 | pub const SCALE_FACTOR: usize = 2; 9 | 10 | type RGBColor = [u8; 3]; 11 | 12 | pub fn init_window(context: &Sdl) -> Result<(Canvas, TextureCreator), String> { 13 | let video_subsystem = context.video()?; 14 | let window = video_subsystem.window("NESTUR", (256 * SCALE_FACTOR) as u32, (240 * SCALE_FACTOR) as u32) 15 | .position_centered() 16 | .opengl() 17 | .build() 18 | .map_err(|e| e.to_string())?; 19 | let mut canvas = window.into_canvas().build().map_err(|e| e.to_string())?; 20 | canvas.set_logical_size(256, 240) 21 | .map_err(|e| e.to_string())?; 22 | let texture_creator = canvas.texture_creator(); 23 | canvas.set_draw_color(Color::RGB(0, 0, 0)); 24 | canvas.clear(); 25 | canvas.present(); 26 | Ok((canvas, texture_creator)) 27 | } 28 | 29 | pub fn draw_pixel(buffer: &mut Vec, x: usize, y: usize, color: RGBColor) { 30 | let offset = (y * 3 * 256) + (x * 3); 31 | buffer.splice(offset..(offset+3), color); 32 | } 33 | 34 | pub fn draw_to_window(texture: &mut Texture, canvas: &mut Canvas, buffer: &Vec) -> Result<(), String> { 35 | texture.update(None, &buffer, 256*3) 36 | .map_err(|e| e.to_string())?; 37 | canvas.copy(&texture, None, None)?; 38 | canvas.present(); 39 | Ok(()) 40 | } 41 | -------------------------------------------------------------------------------- /src/state.rs: -------------------------------------------------------------------------------- 1 | use super::cpu; 2 | use super::ppu; 3 | use super::apu; 4 | use super::cartridge; 5 | 6 | use std::fs::{DirEntry, File}; 7 | use std::io::{Read, Write}; 8 | use std::path::{Path, PathBuf}; 9 | 10 | #[derive(serde::Serialize, serde::Deserialize)] 11 | struct SaveState { 12 | cpu: cpu::serialize::CpuData, 13 | ppu: ppu::serialize::PpuData, 14 | apu: apu::serialize::ApuData, 15 | mapper: cartridge::serialize::MapperData, 16 | } 17 | 18 | pub fn save_state(cpu: &cpu::Cpu, save_file: &PathBuf) -> Result<(), String> { 19 | let data = SaveState{ 20 | cpu: cpu.save_state(), 21 | ppu: cpu.ppu.save_state(), 22 | apu: cpu.apu.save_state(), 23 | mapper: cpu.mapper.borrow().save_state(), 24 | }; 25 | let serialized = serde_json::to_string(&data) 26 | .map_err(|e| e.to_string())?; 27 | let mut f = File::create(&save_file) 28 | .expect("could not create output file for save state"); 29 | f.write_all(serialized.as_bytes()) 30 | .map_err(|_| "couldn't write serialized data to file".to_string())?; 31 | println!("state saved to file: {:?}", save_file); 32 | Ok(()) 33 | } 34 | 35 | pub fn load_state(cpu: &mut cpu::Cpu, save_file: &PathBuf) -> Result<(), String> { 36 | if Path::new(&save_file).exists() { 37 | let mut f = File::open(save_file.clone()) 38 | .map_err(|e| e.to_string())?; 39 | let mut serialized_data = vec![]; 40 | f.read_to_end(&mut serialized_data) 41 | .map_err(|e| e.to_string())?; 42 | let serialized_string = std::str::from_utf8(&serialized_data) 43 | .map_err(|e| e.to_string())?; 44 | let state: SaveState = serde_json::from_str(serialized_string) 45 | .map_err(|e| e.to_string())?; 46 | cpu.load_state(state.cpu); 47 | cpu.ppu.load_state(state.ppu); 48 | cpu.apu.load_state(state.apu); 49 | cpu.mapper.borrow_mut().load_state(state.mapper); 50 | println!("loading save state from file: {:?}", save_file); 51 | Ok(()) 52 | } else { 53 | Err(format!("no save state file at {:?}", save_file)) 54 | } 55 | } 56 | 57 | pub fn find_next_filename(filepath: &PathBuf, new_ext: Option<&str>) -> Option { 58 | let path = match filepath.parent()?.to_str()? { 59 | "" => ".", 60 | x => x, 61 | }; 62 | let stem = filepath.file_stem()?.to_str()?; 63 | let ext = new_ext.or(Some(filepath.extension()?.to_str()?)).unwrap(); 64 | let sep = std::path::MAIN_SEPARATOR.to_string(); 65 | let mut i = 0; 66 | loop { 67 | let current_name = format!("{}{}{}-{}.{}", path, sep, stem, i, ext); 68 | let save_file = PathBuf::from(¤t_name); 69 | if !save_file.exists() { 70 | return Some(save_file) 71 | } 72 | i += 1; 73 | } 74 | } 75 | 76 | pub fn find_last_save_state(filepath: &PathBuf, new_ext: Option<&str>) -> Option { 77 | let path = match filepath.parent()?.to_str()? { 78 | "" => Path::new("."), 79 | _ => filepath.parent()?, 80 | }; 81 | let stem = filepath.file_stem()?.to_str()?; 82 | let ext = new_ext.or(Some(filepath.extension()?.to_str()?)).unwrap(); 83 | let files = std::fs::read_dir(path).expect("couldn't read directory"); 84 | let mut save_states = files 85 | .filter(|dir_entry| { 86 | let os_name = dir_entry.as_ref().unwrap().file_name(); 87 | let name = os_name.to_str().unwrap(); 88 | name.len() >= stem.len() 89 | && name.len() >= ext.len() 90 | && &name[..stem.len()] == stem 91 | && &name[name.len()-ext.len()..] == ext 92 | }) 93 | .collect::>>(); 94 | save_states.sort_by(|a, b| { 95 | let a_mod_time = a.as_ref().unwrap().metadata().unwrap().modified().unwrap(); 96 | let b_mod_time = b.as_ref().unwrap().metadata().unwrap().modified().unwrap(); 97 | b_mod_time.cmp(&a_mod_time) // puts in reverse order by last modified time 98 | }); 99 | match save_states.len() { 100 | 0 => None, 101 | _ => Some(save_states[0].as_ref().unwrap().path()), 102 | } 103 | } 104 | --------------------------------------------------------------------------------