Compare commits
	
		
			64 Commits
		
	
	
		
			refactor
			...
			4c657c9832
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 4c657c9832 | |||
| e72b11f946 | |||
| bbcbbd8ce3 | |||
| c4226e6e76 | |||
| 409314a4e5 | |||
| 952bf68bd0 | |||
| 865c11d53c | |||
| 90d2da9272 | |||
| 4a1a21a08f | |||
| 9d23b571fb | |||
| 70e0b5868d | |||
| a0e3c7c602 | |||
| ef4f3d9ec6 | |||
| 7f872a554b | |||
| dda0257655 | |||
| c10816c048 | |||
| 2405fd027f | |||
| 939c25ce1a | |||
| 6f5e863645 | |||
| ce5d58e1d2 | |||
| ebcd5bec5c | |||
| f2c49b398c | |||
| 6f76571d6c | |||
| 55da5a29d8 | |||
| 7dfcd44820 | |||
| 80a15039e0 | |||
| 32405c0734 | |||
| 2ef8fefbb7 | |||
| 548945a4ee | |||
| 1526b4a09d | |||
| 4125ea5c74 | |||
| b9519d9b7a | |||
| 01278ca83f | |||
| 2bb8bd6d8f | |||
| 080c1e7518 | |||
| 765f9d8288 | |||
| 8780c4a59b | |||
| e19a540650 | |||
| 4cfd951ede | |||
| de8fe1a367 | |||
| c5fa41b20d | |||
| 552cfd4a18 | |||
| 4c516804e4 | |||
| 9b3ab73bb1 | |||
| d9f1d661ae | |||
| da83032e24 | |||
| 67e2447d3f | |||
| 37cf3d92e4 | |||
| 293e5762c3 | |||
| 999f661e6b | |||
| e573411890 | |||
| 352a65b705 | |||
| 64230973f1 | |||
| dbbf87af52 | |||
| 1440cd1fc7 | |||
| 9964b49ce1 | |||
| 142231d355 | |||
| 227928e8ca | |||
| 1001b0b124 | |||
| 71ce3f43e0 | |||
| ce121864d2 | |||
| e1fe00ab64 | |||
| 5882678bc5 | |||
| bcd67cb317 | 
							
								
								
									
										1
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
								
							| @@ -4,5 +4,6 @@ | ||||
|         "tamasfe.even-better-toml", | ||||
|         "serayuzgur.crates", | ||||
|         "vadimcn.vscode-lldb", | ||||
|         "donaldhays.rgbds-z80" | ||||
|     ] | ||||
| } | ||||
							
								
								
									
										930
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										930
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										28
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								Cargo.toml
									
									
									
									
									
								
							| @@ -2,21 +2,27 @@ | ||||
| name = "gb" | ||||
| version = "0.1.0" | ||||
| authors = ["Rekai Musuka <rekai@musuka.dev>"] | ||||
| edition = "2018" | ||||
| resolver = "2" | ||||
| edition = "2021" | ||||
|  | ||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||
|  | ||||
| [dependencies] | ||||
| anyhow = "^1.0" | ||||
| bitfield = "^0.13" | ||||
| clap = "^2.33" | ||||
| gilrs = "^0.8" | ||||
| pixels = "^0.6" | ||||
| winit = "^0.25" | ||||
| winit_input_helper = "^0.10" | ||||
| rodio = "^0.14" | ||||
| rtrb = "^0.1.4" | ||||
| anyhow = "1.0" | ||||
| bitfield = "0.13" | ||||
| clap = "2.33" | ||||
| gilrs = "0.8" | ||||
| winit = "0.26" | ||||
| egui = "0.15" | ||||
| wgpu = "0.11" | ||||
| egui_wgpu_backend = "0.14" | ||||
| egui_winit_platform = "0.12" | ||||
| pollster = "0.2" | ||||
| rodio = "0.14" | ||||
| rtrb = "0.2" | ||||
| directories-next = "2.0" | ||||
| tracing = "0.1" | ||||
| tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] } | ||||
| thiserror = "1.0" | ||||
|  | ||||
| [profile.release] | ||||
| debug = true | ||||
|   | ||||
							
								
								
									
										40
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								README.md
									
									
									
									
									
								
							| @@ -2,13 +2,37 @@ | ||||
| [](https://ci.paoda.moe/paoda/gb) | ||||
|  | ||||
| ### Status | ||||
| * Passes Blargg's cpu_instrs Test | ||||
| * Renders Background & Window Tiles  | ||||
| * Implements a PPU FIFO | ||||
| * From [Blargg Test ROMs](https://github.com/L-P/blargg-test-roms/) | ||||
|     * [x] cpu_instrs | ||||
|     * [ ] instr_timing (kind of) | ||||
|     * [x] mem_timing | ||||
|     * [x] mem_timing-2 | ||||
|     * [ ] dmg_sound (partial)  | ||||
| * [x] [dmg-acid2](https://github.com/mattcurrie/dmg-acid2) | ||||
| * From [mooneye-gb](https://github.com/Gekkio/mooneye-gb): | ||||
|     * Cartridges: | ||||
|         * [x] MBC1 | ||||
|         * [ ] MBC1M | ||||
|         * [x] MBC2 | ||||
|         * [x] MBC5 | ||||
| * Implements a cycle-accurate PPU FIFO | ||||
|     * Doesn't \*exactly\* work just yet | ||||
|  | ||||
| Supports: ROM-only, MBC1, MBC2, MBC3 and MBC5 games. | ||||
|  | ||||
|  | ||||
| ### Notes | ||||
| * [gameboy-logs](https://github.com/wheremyfoodat/Gameboy-logs) suggests that there are still some underlying problems with the cpu implementation | ||||
| * The Sprite FIFO does not work as expected yet | ||||
| * Sound is neither emulated nor stubbed. Upon writing / reading to a APU related register the emulator will panic.  | ||||
| * Code cleanup is pending completion of some minimum viable product of the emulator | ||||
|  | ||||
| ### Controls | ||||
| Controls are defined [here](https://git.musuka.dev/paoda/gb/src/branch/main/src/joypad.rs#L114) | ||||
|  | ||||
| Key | Button | ||||
| --- | --- | ||||
| <kbd>X</kbd> | B | ||||
| <kbd>Z</kbd> | A | ||||
| <kbd>Enter</kbd> | START | ||||
| <kbd>Shift</kbd> | SELECT | ||||
|  | ||||
| Then use the Arrow keys for the D-Pad | ||||
|  | ||||
| ### Credits | ||||
| The Boot ROM found in the `bin/` directory was made by [Optix](https://github.com/Hacktix) over [here](https://github.com/Hacktix/Bootix) | ||||
							
								
								
									
										
											BIN
										
									
								
								bin/bootix_dmg.bin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/bootix_dmg.bin
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										326
									
								
								src/apu.rs
									
									
									
									
									
								
							
							
						
						
									
										326
									
								
								src/apu.rs
									
									
									
									
									
								
							| @@ -1,3 +1,4 @@ | ||||
| use crate::apu::gen::SAMPLE_RATE; | ||||
| use crate::bus::BusIo; | ||||
| use crate::emu::SM83_CLOCK_SPEED; | ||||
| use gen::SampleProducer; | ||||
| @@ -5,8 +6,8 @@ use types::ch1::{Sweep, SweepDirection}; | ||||
| use types::ch3::Volume as Ch3Volume; | ||||
| use types::ch4::{CounterWidth, Frequency as Ch4Frequency, PolynomialCounter}; | ||||
| use types::common::{EnvelopeDirection, FrequencyHigh, SoundDuty, VolumeEnvelope}; | ||||
| use types::fs::{FrameSequencer, State as FrameSequencerState}; | ||||
| use types::{ChannelControl, SoundOutput}; | ||||
| use types::fs::{FrameSequencer, State as FSState}; | ||||
| use types::{ChannelControl, NRx4, SoundOutput}; | ||||
|  | ||||
| pub mod gen; | ||||
| mod types; | ||||
| @@ -26,11 +27,13 @@ pub struct Apu { | ||||
|     /// Noise | ||||
|     ch4: Channel4, | ||||
|  | ||||
|     sequencer: FrameSequencer, | ||||
|     fs: FrameSequencer, | ||||
|     div_prev: Option<u16>, | ||||
|  | ||||
|     prod: Option<SampleProducer<f32>>, | ||||
|     pub(crate) prod: Option<SampleProducer<f32>>, | ||||
|     sample_counter: u64, | ||||
|  | ||||
|     cap: f32, | ||||
| } | ||||
|  | ||||
| impl BusIo for Apu { | ||||
| @@ -54,7 +57,7 @@ impl BusIo for Apu { | ||||
|             0x26 => self.ctrl.status(self), | ||||
|             0x30..=0x3F => self.ch3.read_byte(addr), | ||||
|             _ => { | ||||
|                 eprintln!("Read 0xFF from unused IO register {:#06X} [APU]", addr); | ||||
|                 tracing::warn!("Attempted read from {:#06X}", addr); | ||||
|                 0xFF | ||||
|             } | ||||
|         } | ||||
| @@ -66,29 +69,26 @@ impl BusIo for Apu { | ||||
|             0x11 if self.ctrl.enabled => self.ch1.set_duty(byte), | ||||
|             0x12 if self.ctrl.enabled => self.ch1.set_envelope(byte), | ||||
|             0x13 if self.ctrl.enabled => self.ch1.set_freq_lo(byte), | ||||
|             0x14 if self.ctrl.enabled => self.ch1.set_freq_hi(byte), | ||||
|             0x14 if self.ctrl.enabled => self.ch1.set_freq_hi(&self.fs, byte), | ||||
|             0x16 if self.ctrl.enabled => self.ch2.set_duty(byte), | ||||
|             0x17 if self.ctrl.enabled => self.ch2.set_envelope(byte), | ||||
|             0x18 if self.ctrl.enabled => self.ch2.set_freq_lo(byte), | ||||
|             0x19 if self.ctrl.enabled => self.ch2.set_freq_hi(byte), | ||||
|             0x19 if self.ctrl.enabled => self.ch2.set_freq_hi(&self.fs, byte), | ||||
|             0x1A if self.ctrl.enabled => self.ch3.set_dac_enabled(byte), | ||||
|             0x1B if self.ctrl.enabled => self.ch3.set_len(byte), | ||||
|             0x1C if self.ctrl.enabled => self.ch3.set_volume(byte), | ||||
|             0x1D if self.ctrl.enabled => self.ch3.set_freq_lo(byte), | ||||
|             0x1E if self.ctrl.enabled => self.ch3.set_freq_hi(byte), | ||||
|             0x1E if self.ctrl.enabled => self.ch3.set_freq_hi(&self.fs, byte), | ||||
|             0x20 if self.ctrl.enabled => self.ch4.set_len(byte), | ||||
|             0x21 if self.ctrl.enabled => self.ch4.set_envelope(byte), | ||||
|             0x22 if self.ctrl.enabled => self.ch4.set_poly(byte), | ||||
|             0x23 if self.ctrl.enabled => self.ch4.set_frequency(byte), | ||||
|             0x23 if self.ctrl.enabled => self.ch4.set_frequency(&self.fs, byte), | ||||
|             0x24 if self.ctrl.enabled => self.ctrl.set_channel(byte), | ||||
|             0x25 if self.ctrl.enabled => self.ctrl.set_output(byte), | ||||
|             0x26 => self.set_status(byte), | ||||
|             0x30..=0x3F => self.ch3.write_byte(addr, byte), | ||||
|             _ if !self.ctrl.enabled => {} | ||||
|             _ => eprintln!( | ||||
|                 "Wrote {:#04X} to unused IO register {:#06X} [APU]", | ||||
|                 byte, addr | ||||
|             ), | ||||
|             _ => tracing::warn!("Attempted write of {:#04X} to {:#06X}", byte, addr), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -99,9 +99,9 @@ impl Apu { | ||||
|  | ||||
|         // Frame Sequencer (512Hz) | ||||
|         if self.is_falling_edge(12, div) { | ||||
|             use FrameSequencerState::*; | ||||
|             use FSState::*; | ||||
|  | ||||
|             match self.sequencer.state() { | ||||
|             match self.fs.state() { | ||||
|                 Length => self.clock_length(), | ||||
|                 LengthAndSweep => { | ||||
|                     self.clock_length(); | ||||
| @@ -111,7 +111,7 @@ impl Apu { | ||||
|                 Nothing => {} | ||||
|             } | ||||
|  | ||||
|             self.sequencer.next(); | ||||
|             self.fs.next(); | ||||
|         } | ||||
|  | ||||
|         self.div_prev = Some(div); | ||||
| @@ -124,25 +124,33 @@ impl Apu { | ||||
|         if self.sample_counter >= SM83_CLOCK_SPEED { | ||||
|             self.sample_counter %= SM83_CLOCK_SPEED; | ||||
|  | ||||
|             if let Some(ref mut prod) = self.prod { | ||||
|             if let Some(prod) = self.prod.as_mut() { | ||||
|                 if prod.available_blocking() { | ||||
|                     // Sample the APU | ||||
|  | ||||
|                     let ch1_amplitude = | ||||
|                         Self::high_pass(&mut self.cap, self.ch1.amplitude(), self.ch1.enabled); | ||||
|                     let (left, right) = self.ctrl.out.ch1(); | ||||
|                     let ch1_left = if left { self.ch1.amplitude() } else { 0.0 }; | ||||
|                     let ch1_right = if right { self.ch1.amplitude() } else { 0.0 }; | ||||
|                     let ch1_left = if left { ch1_amplitude } else { 0.0 }; | ||||
|                     let ch1_right = if right { ch1_amplitude } else { 0.0 }; | ||||
|  | ||||
|                     let ch2_amplitude = | ||||
|                         Self::high_pass(&mut self.cap, self.ch2.amplitude(), self.ch2.enabled); | ||||
|                     let (left, right) = self.ctrl.out.ch2(); | ||||
|                     let ch2_left = if left { self.ch2.amplitude() } else { 0.0 }; | ||||
|                     let ch2_right = if right { self.ch2.amplitude() } else { 0.0 }; | ||||
|                     let ch2_left = if left { ch2_amplitude } else { 0.0 }; | ||||
|                     let ch2_right = if right { ch2_amplitude } else { 0.0 }; | ||||
|  | ||||
|                     let ch3_amplitude = | ||||
|                         Self::high_pass(&mut self.cap, self.ch3.amplitude(), self.ch3.enabled); | ||||
|                     let (left, right) = self.ctrl.out.ch3(); | ||||
|                     let ch3_left = if left { self.ch3.amplitude() } else { 0.0 }; | ||||
|                     let ch3_right = if right { self.ch3.amplitude() } else { 0.0 }; | ||||
|                     let ch3_left = if left { ch3_amplitude } else { 0.0 }; | ||||
|                     let ch3_right = if right { ch3_amplitude } else { 0.0 }; | ||||
|  | ||||
|                     let ch4_amplitude = | ||||
|                         Self::high_pass(&mut self.cap, self.ch4.amplitude(), self.ch4.enabled); | ||||
|                     let (left, right) = self.ctrl.out.ch4(); | ||||
|                     let ch4_left = if left { self.ch4.amplitude() } else { 0.0 }; | ||||
|                     let ch4_right = if right { self.ch4.amplitude() } else { 0.0 }; | ||||
|                     let ch4_left = if left { ch4_amplitude } else { 0.0 }; | ||||
|                     let ch4_right = if right { ch4_amplitude } else { 0.0 }; | ||||
|  | ||||
|                     let left_mixed = (ch1_left + ch2_left + ch3_left + ch4_left) / 4.0; | ||||
|                     let right_mixed = (ch1_right + ch2_right + ch3_right + ch4_right) / 4.0; | ||||
| @@ -164,7 +172,7 @@ impl Apu { | ||||
|  | ||||
|         if self.ctrl.enabled { | ||||
|             // Frame Sequencer reset to Step 0 | ||||
|             self.sequencer.reset(); | ||||
|             self.fs.reset(); | ||||
|  | ||||
|             // Square Duty units are reset to first step | ||||
|             self.ch1.duty_pos = 0; | ||||
| @@ -180,10 +188,6 @@ impl Apu { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn attach_producer(&mut self, prod: SampleProducer<f32>) { | ||||
|         self.prod = Some(prod); | ||||
|     } | ||||
|  | ||||
|     fn reset(&mut self) { | ||||
|         self.ch1.sweep = Default::default(); | ||||
|         self.ch1.duty = Default::default(); | ||||
| @@ -207,8 +211,8 @@ impl Apu { | ||||
|         self.ch4.poly = Default::default(); | ||||
|         self.ch4.freq = Default::default(); | ||||
|  | ||||
|         self.ctrl.channel = Default::default(); | ||||
|         self.ctrl.out = Default::default(); | ||||
|         self.ctrl.channel = ChannelControl(0); | ||||
|         self.ctrl.out = SoundOutput(0); | ||||
|  | ||||
|         // Disable the Channels | ||||
|         self.ch1.enabled = Default::default(); | ||||
| @@ -217,25 +221,13 @@ impl Apu { | ||||
|         self.ch4.enabled = Default::default(); | ||||
|     } | ||||
|  | ||||
|     fn process_length(freq_hi: &FrequencyHigh, length_timer: &mut u16, enabled: &mut bool) { | ||||
|         if freq_hi.length_disable() && *length_timer > 0 { | ||||
|             *length_timer -= 1; | ||||
|     fn process_length(freq: &impl NRx4, counter: &mut u16, enabled: &mut bool) { | ||||
|         if freq.length_enable() && *counter > 0 { | ||||
|             *counter -= 1; | ||||
|  | ||||
|             // Check in this scope ensures (only) the above subtraction | ||||
|             // made length_timer 0 | ||||
|             if *length_timer == 0 { | ||||
|                 *enabled = false; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn ch4_process_length(freq: &Ch4Frequency, length_timer: &mut u16, enabled: &mut bool) { | ||||
|         if freq.length_disable() && *length_timer > 0 { | ||||
|             *length_timer -= 1; | ||||
|  | ||||
|             // Check in this scope ensures (only) the above subtraction | ||||
|             // made length_timer 0 | ||||
|             if *length_timer == 0 { | ||||
|             if *counter == 0 { | ||||
|                 *enabled = false; | ||||
|             } | ||||
|         } | ||||
| @@ -244,25 +236,25 @@ impl Apu { | ||||
|     fn clock_length(&mut self) { | ||||
|         Self::process_length( | ||||
|             &self.ch1.freq_hi, | ||||
|             &mut self.ch1.length_timer, | ||||
|             &mut self.ch1.length_counter, | ||||
|             &mut self.ch1.enabled, | ||||
|         ); | ||||
|  | ||||
|         Self::process_length( | ||||
|             &self.ch2.freq_hi, | ||||
|             &mut self.ch2.length_timer, | ||||
|             &mut self.ch2.length_counter, | ||||
|             &mut self.ch2.enabled, | ||||
|         ); | ||||
|  | ||||
|         Self::process_length( | ||||
|             &self.ch3.freq_hi, | ||||
|             &mut self.ch3.length_timer, | ||||
|             &mut self.ch3.length_counter, | ||||
|             &mut self.ch3.enabled, | ||||
|         ); | ||||
|  | ||||
|         Self::ch4_process_length( | ||||
|         Self::process_length( | ||||
|             &self.ch4.freq, | ||||
|             &mut self.ch4.length_timer, | ||||
|             &mut self.ch4.length_counter, | ||||
|             &mut self.ch4.enabled, | ||||
|         ); | ||||
|     } | ||||
| @@ -279,7 +271,7 @@ impl Apu { | ||||
|             if self.ch1.sweep_enabled && period != 0 { | ||||
|                 let new_freq = self.ch1.calc_sweep_freq(); | ||||
|  | ||||
|                 if new_freq <= 2047 && self.ch1.sweep.shift_count() != 0 { | ||||
|                 if new_freq <= 0x7FF && self.ch1.sweep.shift_count() != 0 { | ||||
|                     self.ch1.set_frequency(new_freq); | ||||
|                     self.ch1.shadow_freq = new_freq; | ||||
|  | ||||
| @@ -337,9 +329,22 @@ impl Apu { | ||||
|             None => false, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn high_pass(capacitor: &mut f32, input: f32, enabled: bool) -> f32 { | ||||
|         const CHARGE_FACTOR: f32 = 0.999958; | ||||
|  | ||||
|         let mut output = 0.0; | ||||
|         if enabled { | ||||
|             output = input - *capacitor; | ||||
|             *capacitor = | ||||
|                 input - output * CHARGE_FACTOR.powi(SM83_CLOCK_SPEED as i32 / SAMPLE_RATE as i32); | ||||
|         } | ||||
|  | ||||
|         output | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default)] | ||||
| #[derive(Debug)] | ||||
| pub(crate) struct SoundControl { | ||||
|     /// 0xFF24 | NR50 - Channel Control | ||||
|     channel: ChannelControl, | ||||
| @@ -349,6 +354,16 @@ pub(crate) struct SoundControl { | ||||
|     enabled: bool, | ||||
| } | ||||
|  | ||||
| impl Default for SoundControl { | ||||
|     fn default() -> Self { | ||||
|         Self { | ||||
|             channel: ChannelControl(0), | ||||
|             out: SoundOutput(0), | ||||
|             enabled: Default::default(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl SoundControl { | ||||
|     /// 0xFF24 | NR50 - Channel Control | ||||
|     pub(crate) fn channel(&self) -> u8 { | ||||
| @@ -408,7 +423,7 @@ pub(crate) struct Channel1 { | ||||
|     sweep_enabled: bool, | ||||
|  | ||||
|     // Length Functionality | ||||
|     length_timer: u16, | ||||
|     length_counter: u16, | ||||
|  | ||||
|     freq_timer: u16, | ||||
|     duty_pos: u8, | ||||
| @@ -435,11 +450,11 @@ impl Channel1 { | ||||
|     /// 0xFF11 | NR11 - Channel 1 Sound length / Wave pattern duty | ||||
|     pub(crate) fn set_duty(&mut self, byte: u8) { | ||||
|         self.duty = byte.into(); | ||||
|         self.length_timer = 64 - self.duty.sound_length() as u16; | ||||
|         self.length_counter = 64 - self.duty.sound_length() as u16; | ||||
|     } | ||||
|  | ||||
|     /// 0xFF12 | NR12 - Channel 1 Volume Envelope | ||||
|     pub fn envelope(&self) -> u8 { | ||||
|     pub(crate) fn envelope(&self) -> u8 { | ||||
|         u8::from(self.envelope) | ||||
|     } | ||||
|  | ||||
| @@ -463,25 +478,30 @@ impl Channel1 { | ||||
|     } | ||||
|  | ||||
|     /// 0xFF14 | NR14 - Channel 1 Frequency high | ||||
|     pub(crate) fn set_freq_hi(&mut self, byte: u8) { | ||||
|         self.freq_hi = byte.into(); | ||||
|     pub(crate) fn set_freq_hi(&mut self, fs: &FrameSequencer, byte: u8) { | ||||
|         let mut new_freq: FrequencyHigh = byte.into(); | ||||
|  | ||||
|         // If this bit is set, a trigger event occurs | ||||
|         if self.freq_hi.initial() { | ||||
|             if self.is_dac_enabled() { | ||||
|                 self.enabled = true; | ||||
|             } | ||||
|         if new_freq.trigger() { | ||||
|             self.enabled = true; | ||||
|  | ||||
|             // Length behaviour during trigger event | ||||
|             if self.length_timer == 0 { | ||||
|                 self.length_timer = 64; | ||||
|             } | ||||
|             if self.length_counter == 0 { | ||||
|                 self.length_counter = 64; | ||||
|  | ||||
|             // Envelope Behaviour during trigger event | ||||
|                 // length_counter was 0 so length evidently wasn't enabled | ||||
|                 self.freq_hi.set_length_enable(false); | ||||
|             }; | ||||
|  | ||||
|             // reload freq_timer but last two bits are unmodified | ||||
|             self.freq_timer = obscure::square::freq_timer_reload(self.freq_timer, self.frequency()); | ||||
|  | ||||
|             // Volume Envelope loaded w/ period | ||||
|             self.period_timer = self.envelope.period(); | ||||
|  | ||||
|             // Channel Volume reloaded | ||||
|             self.current_volume = self.envelope.init_vol(); | ||||
|  | ||||
|             // Sweep behaviour during trigger event | ||||
|             // Channel 1 Sweep Behaviour | ||||
|             let sweep_period = self.sweep.period(); | ||||
|             let sweep_shift = self.sweep.shift_count(); | ||||
|  | ||||
| @@ -493,7 +513,19 @@ impl Channel1 { | ||||
|             if sweep_shift != 0 { | ||||
|                 let _ = self.calc_sweep_freq(); | ||||
|             } | ||||
|  | ||||
|             self.enabled = self.is_dac_enabled(); | ||||
|         } | ||||
|  | ||||
|         obscure::nrx4::length_update( | ||||
|             &mut new_freq, | ||||
|             fs, | ||||
|             &mut self.length_counter, | ||||
|             &mut self.enabled, | ||||
|             self.freq_hi.length_enable(), | ||||
|         ); | ||||
|  | ||||
|         self.freq_hi = new_freq; | ||||
|     } | ||||
|  | ||||
|     fn tick(&mut self) { | ||||
| @@ -555,7 +587,7 @@ impl Channel1 { | ||||
| pub(crate) struct Channel2 { | ||||
|     /// 0xFF16 | NR21 - Channel 2 Sound length / Wave Pattern Duty | ||||
|     duty: SoundDuty, | ||||
|     /// 0xFF17 | NR22 - Channel 2 Volume ENvelope | ||||
|     /// 0xFF17 | NR22 - Channel 2 Volume Envelope | ||||
|     envelope: VolumeEnvelope, | ||||
|     /// 0xFF18 | NR23 - Channel 2 Frequency low (lower 8 bits only) | ||||
|     freq_lo: u8, | ||||
| @@ -567,7 +599,7 @@ pub(crate) struct Channel2 { | ||||
|     current_volume: u8, | ||||
|  | ||||
|     // Length Functionality | ||||
|     length_timer: u16, | ||||
|     length_counter: u16, | ||||
|  | ||||
|     freq_timer: u16, | ||||
|     duty_pos: u8, | ||||
| @@ -584,15 +616,15 @@ impl Channel2 { | ||||
|     /// 0xFF16 | NR21 - Channel 2 Sound length / Wave Pattern Duty | ||||
|     pub(crate) fn set_duty(&mut self, byte: u8) { | ||||
|         self.duty = byte.into(); | ||||
|         self.length_timer = 64 - self.duty.sound_length() as u16; | ||||
|         self.length_counter = 64 - self.duty.sound_length() as u16; | ||||
|     } | ||||
|  | ||||
|     /// 0xFF17 | NR22 - Channel 2 Volume ENvelope | ||||
|     /// 0xFF17 | NR22 - Channel 2 Volume Envelope | ||||
|     pub(crate) fn envelope(&self) -> u8 { | ||||
|         u8::from(self.envelope) | ||||
|     } | ||||
|  | ||||
|     /// 0xFF17 | NR22 - Channel 2 Volume ENvelope | ||||
|     /// 0xFF17 | NR22 - Channel 2 Volume Envelope | ||||
|     pub(crate) fn set_envelope(&mut self, byte: u8) { | ||||
|         self.envelope = byte.into(); | ||||
|  | ||||
| @@ -612,22 +644,36 @@ impl Channel2 { | ||||
|     } | ||||
|  | ||||
|     /// 0xFF19 | NR24 - Channel 2 Frequency high | ||||
|     pub(crate) fn set_freq_hi(&mut self, byte: u8) { | ||||
|     pub(crate) fn set_freq_hi(&mut self, fs: &FrameSequencer, byte: u8) { | ||||
|         let prev_le = self.freq_hi.length_enable(); | ||||
|         self.freq_hi = byte.into(); | ||||
|  | ||||
|         if self.freq_hi.initial() { | ||||
|             // Envelope behaviour during trigger event | ||||
|         obscure::nrx4::length_update( | ||||
|             &mut self.freq_hi, | ||||
|             fs, | ||||
|             &mut self.length_counter, | ||||
|             &mut self.enabled, | ||||
|             prev_le, | ||||
|         ); | ||||
|  | ||||
|         if self.freq_hi.trigger() { | ||||
|             self.enabled = true; | ||||
|  | ||||
|             // Reload length counter if need be | ||||
|             if self.length_counter == 0 { | ||||
|                 self.length_counter = 64; | ||||
|             } | ||||
|  | ||||
|             // reload frequency timer | ||||
|             self.freq_timer = obscure::square::freq_timer_reload(self.freq_timer, self.frequency()); | ||||
|  | ||||
|             // reload envelope | ||||
|             self.period_timer = self.envelope.period(); | ||||
|  | ||||
|             // reload volume | ||||
|             self.current_volume = self.envelope.init_vol(); | ||||
|  | ||||
|             // Length behaviour during trigger event | ||||
|             if self.length_timer == 0 { | ||||
|                 self.length_timer = 64; | ||||
|             } | ||||
|  | ||||
|             if self.is_dac_enabled() { | ||||
|                 self.enabled = true; | ||||
|             } | ||||
|             self.enabled = self.is_dac_enabled(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -678,7 +724,7 @@ pub(crate) struct Channel3 { | ||||
|     wave_ram: [u8; WAVE_PATTERN_RAM_LEN], | ||||
|  | ||||
|     // Length Functionality | ||||
|     length_timer: u16, | ||||
|     length_counter: u16, | ||||
|  | ||||
|     freq_timer: u16, | ||||
|     offset: u8, | ||||
| @@ -724,7 +770,7 @@ impl Channel3 { | ||||
|     /// 0xFF1B | NR31 - Sound Length | ||||
|     pub(crate) fn set_len(&mut self, byte: u8) { | ||||
|         self.len = byte; | ||||
|         self.length_timer = 256 - self.len as u16; | ||||
|         self.length_counter = 256 - self.len as u16; | ||||
|     } | ||||
|  | ||||
|     /// 0xFF1C | NR32 - Channel 3 Volume | ||||
| @@ -756,18 +802,32 @@ impl Channel3 { | ||||
|     } | ||||
|  | ||||
|     /// 0xFF1E | NR34 - Channel 3 Frequency high | ||||
|     pub(crate) fn set_freq_hi(&mut self, byte: u8) { | ||||
|     pub(crate) fn set_freq_hi(&mut self, fs: &FrameSequencer, byte: u8) { | ||||
|         let prev_le = self.freq_hi.length_enable(); | ||||
|         self.freq_hi = byte.into(); | ||||
|  | ||||
|         if self.freq_hi.initial() { | ||||
|         obscure::nrx4::length_update( | ||||
|             &mut self.freq_hi, | ||||
|             fs, | ||||
|             &mut self.length_counter, | ||||
|             &mut self.enabled, | ||||
|             prev_le, | ||||
|         ); | ||||
|  | ||||
|         if self.freq_hi.trigger() { | ||||
|             self.enabled = true; | ||||
|  | ||||
|             // Length behaviour during trigger event | ||||
|             if self.length_timer == 0 { | ||||
|                 self.length_timer = 256; | ||||
|             if self.length_counter == 0 { | ||||
|                 self.length_counter = 256; | ||||
|             } | ||||
|  | ||||
|             if self.dac_enabled { | ||||
|                 self.enabled = true; | ||||
|             } | ||||
|             self.freq_timer = (2048 - self.frequency()) * 2; | ||||
|  | ||||
|             // reset wave channel's ptr into wave RAM | ||||
|             self.offset = 0; | ||||
|  | ||||
|             self.enabled = self.dac_enabled; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -826,7 +886,7 @@ pub(crate) struct Channel4 { | ||||
|     current_volume: u8, | ||||
|  | ||||
|     // Length Functionality | ||||
|     length_timer: u16, | ||||
|     length_counter: u16, | ||||
|  | ||||
|     /// Linear Feedback Shift Register (15-bit) | ||||
|     lf_shift: u16, | ||||
| @@ -840,7 +900,7 @@ impl Channel4 { | ||||
|     /// 0xFF20 | NR41 - Channel 4 Sound Length | ||||
|     pub(crate) fn set_len(&mut self, byte: u8) { | ||||
|         self.len = byte & 0x3F; | ||||
|         self.length_timer = 64 - self.len as u16; | ||||
|         self.length_counter = 64 - self.len as u16; | ||||
|     } | ||||
|  | ||||
|     /// 0xFF21 | NR42 - Channel 4 Volume Envelope | ||||
| @@ -873,25 +933,37 @@ impl Channel4 { | ||||
|     } | ||||
|  | ||||
|     /// 0xFF23 | NR44 - Channel 4 Counter / Consecutive Selector and Restart | ||||
|     pub(crate) fn set_frequency(&mut self, byte: u8) { | ||||
|     pub(crate) fn set_frequency(&mut self, fs: &FrameSequencer, byte: u8) { | ||||
|         let prev_le = self.freq.length_enable(); | ||||
|         self.freq = byte.into(); | ||||
|  | ||||
|         if self.freq.initial() { | ||||
|             // Envelope behaviour during trigger event | ||||
|         obscure::nrx4::length_update( | ||||
|             &mut self.freq, | ||||
|             fs, | ||||
|             &mut self.length_counter, | ||||
|             &mut self.enabled, | ||||
|             prev_le, | ||||
|         ); | ||||
|  | ||||
|         if self.freq.trigger() { | ||||
|             self.enabled = true; | ||||
|  | ||||
|             if self.length_counter == 0 { | ||||
|                 self.length_counter = 64; | ||||
|             } | ||||
|  | ||||
|             // FIXME: Frequency Timer reloaded? | ||||
|  | ||||
|             // reload envelope | ||||
|             self.period_timer = self.envelope.period(); | ||||
|  | ||||
|             //reload volume | ||||
|             self.current_volume = self.envelope.init_vol(); | ||||
|  | ||||
|             // Length behaviour during trigger event | ||||
|             if self.length_timer == 0 { | ||||
|                 self.length_timer = 64; | ||||
|             } | ||||
|  | ||||
|             // LFSR behaviour during trigger event | ||||
|             // LFSR reset | ||||
|             self.lf_shift = 0x7FFF; | ||||
|  | ||||
|             if self.is_dac_enabled() { | ||||
|                 self.enabled = true; | ||||
|             } | ||||
|             self.enabled = self.is_dac_enabled(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -936,3 +1008,39 @@ impl Channel4 { | ||||
|         code << 4 | ||||
|     } | ||||
| } | ||||
|  | ||||
| mod obscure { | ||||
|     pub(super) mod square { | ||||
|         pub(crate) fn freq_timer_reload(freq_timer: u16, frequency: u16) -> u16 { | ||||
|             (freq_timer & 0x0003) | (((2048 - frequency) * 4) & 0xFFFC) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(super) mod nrx4 { | ||||
|         use super::super::{FrameSequencer, NRx4}; | ||||
|  | ||||
|         /// Implements the obscure behaviour when writing to NRX4 under certain | ||||
|         /// conditions | ||||
|         /// | ||||
|         /// # Arguments | ||||
|         /// * `freq_hi` - mutable reference to a channel's frequency high register | ||||
|         /// * `fs` - reference to the APU's frame sequencer | ||||
|         /// * `counter` - mutable reference to a channel's internal enabled flag | ||||
|         /// * `prev_le` - what length_enable was before NRx4 was written with a new value | ||||
|         pub(crate) fn length_update( | ||||
|             freq: &mut impl NRx4, | ||||
|             fs: &FrameSequencer, | ||||
|             counter: &mut u16, | ||||
|             enabled: &mut bool, | ||||
|             prev_le: bool, | ||||
|         ) { | ||||
|             if !fs.next_clocks_length() && !prev_le && freq.length_enable() && *counter != 0 { | ||||
|                 *counter -= 1; | ||||
|  | ||||
|                 if *counter == 0 && !freq.trigger() { | ||||
|                     *enabled = false; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -5,33 +5,12 @@ pub(crate) const SAMPLE_RATE: u32 = 48000; // Hz | ||||
| const CHANNEL_COUNT: usize = 2; | ||||
| const BUFFER_CAPACITY: usize = 2048 * CHANNEL_COUNT; // # of samples * the # of channels | ||||
|  | ||||
| pub struct AudioSPSC<T> { | ||||
|     inner: RingBuffer<T>, | ||||
| } | ||||
|  | ||||
| impl<T> Default for AudioSPSC<T> { | ||||
|     fn default() -> Self { | ||||
|         Self { | ||||
|             inner: RingBuffer::new(BUFFER_CAPACITY), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<T> AudioSPSC<T> { | ||||
|     pub fn new(capacity: usize) -> Self { | ||||
|         Self { | ||||
|             inner: RingBuffer::new(capacity), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn init(self) -> (SampleProducer<T>, SampleConsumer<T>) { | ||||
|         let (prod, cons) = self.inner.split(); | ||||
|  | ||||
|         ( | ||||
|             SampleProducer { inner: prod }, | ||||
|             SampleConsumer { inner: cons }, | ||||
|         ) | ||||
|     } | ||||
| pub fn init<T>() -> (SampleProducer<T>, SampleConsumer<T>) { | ||||
|     let (prod, cons) = RingBuffer::new(BUFFER_CAPACITY); | ||||
|     ( | ||||
|         SampleProducer { inner: prod }, | ||||
|         SampleConsumer { inner: cons }, | ||||
|     ) | ||||
| } | ||||
|  | ||||
| pub struct SampleProducer<T> { | ||||
|   | ||||
| @@ -1,5 +1,11 @@ | ||||
| use bitfield::bitfield; | ||||
|  | ||||
| pub(crate) trait NRx4 { | ||||
|     fn trigger(&self) -> bool; | ||||
|     fn length_enable(&self) -> bool; | ||||
|     fn set_length_enable(&mut self, value: bool); | ||||
| } | ||||
|  | ||||
| pub(crate) mod ch1 { | ||||
|     use super::bitfield; | ||||
|  | ||||
| @@ -103,7 +109,7 @@ pub(super) mod ch3 { | ||||
| } | ||||
|  | ||||
| pub(super) mod ch4 { | ||||
|     use super::bitfield; | ||||
|     use super::{bitfield, NRx4}; | ||||
|  | ||||
|     bitfield! { | ||||
|         pub struct PolynomialCounter(u8); | ||||
| @@ -177,17 +183,21 @@ pub(super) mod ch4 { | ||||
|     bitfield! { | ||||
|         pub struct Frequency(u8); | ||||
|         impl Debug; | ||||
|         _initial, _: 7; | ||||
|         _length_disable, _: 6; | ||||
|         _trigger, _: 7; | ||||
|         _length_enable, _set_length_enable: 6; | ||||
|     } | ||||
|  | ||||
|     impl Frequency { | ||||
|         pub(crate) fn length_disable(&self) -> bool { | ||||
|             self._length_disable() | ||||
|     impl NRx4 for Frequency { | ||||
|         fn trigger(&self) -> bool { | ||||
|             self._trigger() | ||||
|         } | ||||
|  | ||||
|         pub(crate) fn initial(&self) -> bool { | ||||
|             self._initial() | ||||
|         fn length_enable(&self) -> bool { | ||||
|             self._length_enable() | ||||
|         } | ||||
|  | ||||
|         fn set_length_enable(&mut self, value: bool) { | ||||
|             self._set_length_enable(value); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -218,23 +228,27 @@ pub(super) mod ch4 { | ||||
| } | ||||
|  | ||||
| pub(super) mod common { | ||||
|     use super::bitfield; | ||||
|     use super::{bitfield, NRx4}; | ||||
|  | ||||
|     bitfield! { | ||||
|         pub struct FrequencyHigh(u8); | ||||
|         impl Debug; | ||||
|         _initial, _: 7; | ||||
|         _length_disable, _: 6; | ||||
|         _trigger, _: 7; | ||||
|         _length_enable, _set_length_enable: 6; | ||||
|         pub freq_bits, set_freq_bits: 2, 0; | ||||
|     } | ||||
|  | ||||
|     impl FrequencyHigh { | ||||
|         pub(crate) fn initial(&self) -> bool { | ||||
|             self._initial() | ||||
|     impl NRx4 for FrequencyHigh { | ||||
|         fn trigger(&self) -> bool { | ||||
|             self._trigger() | ||||
|         } | ||||
|  | ||||
|         pub(crate) fn length_disable(&self) -> bool { | ||||
|             self._length_disable() | ||||
|         fn length_enable(&self) -> bool { | ||||
|             self._length_enable() | ||||
|         } | ||||
|  | ||||
|         fn set_length_enable(&mut self, value: bool) { | ||||
|             self._set_length_enable(value); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -402,12 +416,6 @@ pub(super) mod common { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl Default for WavePattern { | ||||
|         fn default() -> Self { | ||||
|             Self::OneEighth // Rationale: OneEighth is 0x00 | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl From<WavePattern> for u8 { | ||||
|         fn from(pattern: WavePattern) -> Self { | ||||
|             pattern as Self | ||||
| @@ -465,12 +473,6 @@ impl Clone for SoundOutput { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for SoundOutput { | ||||
|     fn default() -> Self { | ||||
|         Self(0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<u8> for SoundOutput { | ||||
|     fn from(byte: u8) -> Self { | ||||
|         Self(byte) | ||||
| @@ -509,12 +511,6 @@ impl Clone for ChannelControl { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for ChannelControl { | ||||
|     fn default() -> Self { | ||||
|         Self(0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<u8> for ChannelControl { | ||||
|     fn from(byte: u8) -> Self { | ||||
|         Self(byte) | ||||
| @@ -557,6 +553,15 @@ pub(super) mod fs { | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         pub(crate) fn next_clocks_length(&self) -> bool { | ||||
|             use State::*; | ||||
|  | ||||
|             match self.state { | ||||
|                 Length | LengthAndSweep => true, | ||||
|                 Nothing | Envelope => false, | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         pub(crate) fn state(&self) -> State { | ||||
|             self.state | ||||
|         } | ||||
| @@ -567,7 +572,7 @@ pub(super) mod fs { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[derive(Debug, Clone, Copy)] | ||||
|     #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||||
|     pub(crate) enum State { | ||||
|         Length, | ||||
|         Nothing, | ||||
|   | ||||
							
								
								
									
										77
									
								
								src/bus.rs
									
									
									
									
									
								
							
							
						
						
									
										77
									
								
								src/bus.rs
									
									
									
									
									
								
							| @@ -10,37 +10,19 @@ use crate::work_ram::{VariableWorkRam, WorkRam}; | ||||
|  | ||||
| pub(crate) const BOOT_SIZE: usize = 0x100; | ||||
|  | ||||
| #[derive(Debug)] | ||||
| #[derive(Debug, Default)] | ||||
| pub struct Bus { | ||||
|     boot: Option<[u8; BOOT_SIZE]>, // Boot ROM is 256b long | ||||
|     cart: Option<Cartridge>, | ||||
|     pub(crate) cart: Option<Cartridge>, | ||||
|     pub(crate) ppu: Ppu, | ||||
|     work_ram: WorkRam, | ||||
|     var_ram: VariableWorkRam, | ||||
|     pub(crate) timer: Timer, | ||||
|     timer: Timer, | ||||
|     int: Interrupt, | ||||
|     pub(crate) apu: Apu, | ||||
|     high_ram: HighRam, | ||||
|     serial: Serial, | ||||
|     pub(crate) joypad: Joypad, | ||||
| } | ||||
|  | ||||
| impl Default for Bus { | ||||
|     fn default() -> Self { | ||||
|         Self { | ||||
|             boot: None, | ||||
|             cart: None, | ||||
|             ppu: Default::default(), | ||||
|             work_ram: Default::default(), | ||||
|             var_ram: Default::default(), | ||||
|             timer: Default::default(), | ||||
|             int: Default::default(), | ||||
|             apu: Default::default(), | ||||
|             high_ram: Default::default(), | ||||
|             serial: Default::default(), | ||||
|             joypad: Default::default(), | ||||
|         } | ||||
|     } | ||||
|     pub(crate) joyp: Joypad, | ||||
| } | ||||
|  | ||||
| impl Bus { | ||||
| @@ -51,26 +33,24 @@ impl Bus { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn load_cart(&mut self, rom: Vec<u8>) { | ||||
|         self.cart = Some(Cartridge::new(rom)); | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn cart_title(&self) -> Option<&str> { | ||||
|         self.cart.as_ref()?.title() | ||||
|     } | ||||
|  | ||||
|     #[allow(dead_code)] | ||||
|     pub(crate) fn boot_mapped(&self) -> bool { | ||||
|         self.boot.is_some() | ||||
|     } | ||||
|  | ||||
|     #[inline] | ||||
|     pub(crate) fn clock(&mut self) { | ||||
|         self.tick(4); | ||||
|     } | ||||
|  | ||||
|     #[inline] | ||||
|     fn tick(&mut self, limit: u8) { | ||||
|         for _ in 0..limit { | ||||
|             self.timer.tick(); | ||||
|  | ||||
|             if let Some(c) = self.cart.as_mut() { | ||||
|                 c.tick() | ||||
|             } | ||||
|             self.ppu.tick(); | ||||
|             self.apu.tick(self.timer.divider); | ||||
|             self.dma_tick() | ||||
| @@ -86,7 +66,7 @@ impl Bus { | ||||
| } | ||||
|  | ||||
| impl Bus { | ||||
|     pub fn oam_read_byte(&self, addr: u16) -> u8 { | ||||
|     pub(crate) fn oam_read_byte(&self, addr: u16) -> u8 { | ||||
|         match addr { | ||||
|             0x0000..=0x7FFF => { | ||||
|                 // 16KB ROM bank 00 (ends at 0x3FFF) | ||||
| @@ -99,14 +79,14 @@ impl Bus { | ||||
|  | ||||
|                 match self.cart.as_ref() { | ||||
|                     Some(cart) => cart.read_byte(addr), | ||||
|                     None => panic!("Tried to read from a non-existent cartridge"), | ||||
|                     None => 0xFF, | ||||
|                 } | ||||
|             } | ||||
|             0x8000..=0x9FFF => self.ppu.read_byte(addr), // 8KB Video RAM | ||||
|             0xA000..=0xBFFF => match self.cart.as_ref() { | ||||
|                 // 8KB External RAM | ||||
|                 Some(cart) => cart.read_byte(addr), | ||||
|                 None => panic!("Tried to read from a non-existent cartridge"), | ||||
|                 None => 0xFF, | ||||
|             }, | ||||
|             0xC000..=0xCFFF => self.work_ram.read_byte(addr), // 4KB Work RAM Bank 0 | ||||
|             0xD000..=0xDFFF => self.var_ram.read_byte(addr),  // 4KB Work RAM Bank 1 -> N | ||||
| @@ -133,7 +113,7 @@ impl Bus { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn oam_write_byte(&mut self, addr: u16, byte: u8) { | ||||
|     pub(crate) fn oam_write_byte(&mut self, addr: u16, byte: u8) { | ||||
|         self.ppu.oam.write_byte(addr, byte); | ||||
|     } | ||||
| } | ||||
| @@ -152,7 +132,7 @@ impl BusIo for Bus { | ||||
|  | ||||
|                 match self.cart.as_ref() { | ||||
|                     Some(cart) => cart.read_byte(addr), | ||||
|                     None => panic!("Tried to read from a non-existent cartridge"), | ||||
|                     None => 0xFF, | ||||
|                 } | ||||
|             } | ||||
|             0x8000..=0x9FFF => { | ||||
| @@ -165,7 +145,7 @@ impl BusIo for Bus { | ||||
|             0xA000..=0xBFFF => match self.cart.as_ref() { | ||||
|                 // 8KB External RAM | ||||
|                 Some(cart) => cart.read_byte(addr), | ||||
|                 None => panic!("Tried to read from a non-existent cartridge"), | ||||
|                 None => 0xFF, | ||||
|             }, | ||||
|             0xC000..=0xCFFF => self.work_ram.read_byte(addr), // 4KB Work RAM Bank 0 | ||||
|             0xD000..=0xDFFF => self.var_ram.read_byte(addr),  // 4KB Work RAM Bank 1 -> N | ||||
| @@ -212,7 +192,7 @@ impl BusIo for Bus { | ||||
|                 // Every address here starts with 0xFF so we can just check the | ||||
|                 // low byte to figure out which register it is | ||||
|                 match addr & 0x00FF { | ||||
|                     0x00 => self.joypad.p1, | ||||
|                     0x00 => self.joyp.p1, | ||||
|                     0x01 => self.serial.next, | ||||
|                     0x02 => self.serial.ctrl.into(), | ||||
|                     0x04 => (self.timer.divider >> 8) as u8, | ||||
| @@ -233,8 +213,9 @@ impl BusIo for Bus { | ||||
|                     0x49 => self.ppu.monochrome.obj_palette_1.into(), | ||||
|                     0x4A => self.ppu.pos.window_y, | ||||
|                     0x4B => self.ppu.pos.window_x, | ||||
|                     0x4F => 0xFF, // CGB VRAM Bank Select | ||||
|                     _ => { | ||||
|                         eprintln!("Read 0xFF from unused IO register {:#06X}.", addr); | ||||
|                         tracing::warn!("Attempted read from {:#06X} on IO", addr); | ||||
|                         0xFF | ||||
|                     } | ||||
|                 } | ||||
| @@ -255,9 +236,8 @@ impl BusIo for Bus { | ||||
|             0x0000..=0x7FFF => { | ||||
|                 // 16KB ROM bank 00 (ends at 0x3FFF) | ||||
|                 // and 16KB ROM Bank 01 -> NN (switchable via MB) | ||||
|                 match self.cart.as_mut() { | ||||
|                     Some(cart) => cart.write_byte(addr, byte), | ||||
|                     None => panic!("Tried to write into non-existent cartridge"), | ||||
|                 if let Some(cart) = self.cart.as_mut() { | ||||
|                     cart.write_byte(addr, byte); | ||||
|                 } | ||||
|             } | ||||
|             0x8000..=0x9FFF => { | ||||
| @@ -269,9 +249,8 @@ impl BusIo for Bus { | ||||
|             } | ||||
|             0xA000..=0xBFFF => { | ||||
|                 // 8KB External RAM | ||||
|                 match self.cart.as_mut() { | ||||
|                     Some(cart) => cart.write_byte(addr, byte), | ||||
|                     None => panic!("Tried to write into non-existent cartridge"), | ||||
|                 if let Some(cart) = self.cart.as_mut() { | ||||
|                     cart.write_byte(addr, byte); | ||||
|                 } | ||||
|             } | ||||
|             0xC000..=0xCFFF => self.work_ram.write_byte(addr, byte), // 4KB Work RAM Bank 0 | ||||
| @@ -311,7 +290,7 @@ impl BusIo for Bus { | ||||
|                 // Every address here starts with 0xFF so we can just check the | ||||
|                 // low byte to figure out which register it is | ||||
|                 match addr & 0x00FF { | ||||
|                     0x00 => self.joypad.update(byte), | ||||
|                     0x00 => self.joyp.update(byte), | ||||
|                     0x01 => self.serial.next = byte, | ||||
|                     0x02 => self.serial.ctrl = byte.into(), | ||||
|                     0x04 => self.timer.divider = 0x0000, | ||||
| @@ -345,13 +324,15 @@ impl BusIo for Bus { | ||||
|                     0x4A => self.ppu.pos.window_y = byte, | ||||
|                     0x4B => self.ppu.pos.window_x = byte, | ||||
|                     0x4D => {} // CGB Specific Register | ||||
|                     0x4F => {} // CGB VRAM Bank Select | ||||
|                     0x50 => { | ||||
|                         // Disable Boot ROM | ||||
|                         if byte != 0 { | ||||
|                             self.boot = None; | ||||
|                         } | ||||
|                     } | ||||
|                     _ => eprintln!("Wrote {:#04X} to unused IO register {:#06X}.", byte, addr), | ||||
|                     0x70 => {} // CGB WRAM Bank Select | ||||
|                     _ => tracing::warn!("Attempted write of {:#04X} to {:#06X} on IO", byte, addr), | ||||
|                 }; | ||||
|             } | ||||
|             0xFF80..=0xFFFE => { | ||||
| @@ -373,7 +354,7 @@ impl Bus { | ||||
|         let lcd_stat = self.ppu.int.lcd_stat(); | ||||
|  | ||||
|         // Read the current interrupt information from the Joypad | ||||
|         let joypad = self.joypad.interrupt(); | ||||
|         let joypad = self.joyp.interrupt(); | ||||
|  | ||||
|         // Read the current interrupt information from the Timer | ||||
|         let timer = self.timer.interrupt(); | ||||
| @@ -403,7 +384,7 @@ impl Bus { | ||||
|         self.ppu.int.set_lcd_stat(lcd_stat); | ||||
|  | ||||
|         // Update the Joypad's instance of the following interrupts | ||||
|         self.joypad.set_interrupt(joypad); | ||||
|         self.joyp.set_interrupt(joypad); | ||||
|  | ||||
|         // Update the Timer's instance of the following interrupts | ||||
|         self.timer.set_interrupt(timer); | ||||
|   | ||||
							
								
								
									
										712
									
								
								src/cartridge.rs
									
									
									
									
									
								
							
							
						
						
									
										712
									
								
								src/cartridge.rs
									
									
									
									
									
								
							| @@ -1,103 +1,128 @@ | ||||
| use bitfield::bitfield; | ||||
|  | ||||
| use crate::bus::BusIo; | ||||
| use crate::emu::SM83_CLOCK_SPEED; | ||||
| use crate::Cycle; | ||||
|  | ||||
| const RAM_SIZE_ADDRESS: usize = 0x0149; | ||||
| const ROM_SIZE_ADDRESS: usize = 0x0148; | ||||
| const MBC_TYPE_ADDRESS: usize = 0x0147; | ||||
| const ROM_TITLE_RANGE: std::ops::RangeInclusive<usize> = 0x0134..=0x0143; | ||||
| const MBC_KIND_ADDRESS: usize = 0x0147; | ||||
| const ROM_TITLE_START: usize = 0x134; | ||||
| const ROM_TITLE_MAX_SIZE: usize = 16; | ||||
| const ROM_MANUFACTURER_START: usize = 0x13F; | ||||
|  | ||||
| #[derive(Debug, Default)] | ||||
| #[derive(Debug)] | ||||
| pub(crate) struct Cartridge { | ||||
|     memory: Vec<u8>, | ||||
|     title: Option<String>, | ||||
|     mbc: Box<dyn MBCIo>, | ||||
|     mem: Vec<u8>, | ||||
|     pub(crate) title: Option<String>, | ||||
|     mbc: Box<dyn MbcIo>, | ||||
| } | ||||
|  | ||||
| impl Cartridge { | ||||
|     pub(crate) fn new(memory: Vec<u8>) -> Self { | ||||
|         let title = Self::find_title(&memory); | ||||
|         eprintln!("Cartridge Title: {:?}", title); | ||||
|     pub(crate) fn new(mem: Vec<u8>) -> Self { | ||||
|         let title_mem: &[u8; 16] = mem[ROM_TITLE_START..(ROM_TITLE_START + ROM_TITLE_MAX_SIZE)] | ||||
|             .try_into() | ||||
|             .expect("coerce slice containing cartridge title from ROM to [u8; 16]"); | ||||
|  | ||||
|         Self { | ||||
|             mbc: Self::detect_mbc(&memory), | ||||
|             title, | ||||
|             memory, | ||||
|         } | ||||
|         let title = Self::detect_title(title_mem); | ||||
|         let mbc = Self::detect_mbc(&mem); | ||||
|         tracing::info!("Title: {:?}", title); | ||||
|  | ||||
|         Self { mem, title, mbc } | ||||
|     } | ||||
|  | ||||
|     fn detect_mbc(memory: &[u8]) -> Box<dyn MBCIo> { | ||||
|         let ram_size = Self::detect_ram_info(memory); | ||||
|         let rom_size = Self::detect_rom_info(memory); | ||||
|         let mbc_kind = Self::find_mbc(memory); | ||||
|     pub(crate) fn ext_ram(&self) -> Option<&[u8]> { | ||||
|         self.mbc.ext_ram() | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn ext_ram_mut(&mut self) -> Option<&mut [u8]> { | ||||
|         self.mbc.ext_ram_mut() | ||||
|     } | ||||
|  | ||||
|     #[inline] | ||||
|     pub(crate) fn tick(&mut self) { | ||||
|         self.mbc.tick() | ||||
|     } | ||||
|  | ||||
|     fn detect_mbc(mem: &[u8]) -> Box<dyn MbcIo> { | ||||
|         let ram_size: RamSize = mem[RAM_SIZE_ADDRESS].into(); | ||||
|         let rom_size: RomSize = mem[ROM_SIZE_ADDRESS].into(); | ||||
|         let mbc_kind = Self::detect_mbc_kind(mem[MBC_KIND_ADDRESS]); | ||||
|         let ram_cap = ram_size.capacity(); | ||||
|         let rom_cap = rom_size.capacity(); | ||||
|  | ||||
|         eprintln!("Cartridge Ram Size: {} bytes", ram_cap); | ||||
|         eprintln!("Cartridge ROM Size: {} bytes", rom_size.capacity()); | ||||
|         eprintln!("MBC Type: {:?}", mbc_kind); | ||||
|         tracing::info!("RAM size: {} bytes", ram_cap); | ||||
|         tracing::info!("ROM size: {} bytes", rom_cap); | ||||
|         tracing::info!("MBC kind: {:?}", mbc_kind); | ||||
|  | ||||
|         match mbc_kind { | ||||
|             MBCKind::None => Box::new(NoMBC), | ||||
|             MBCKind::MBC1 => Box::new(MBC1::new(ram_size, rom_size)), | ||||
|             MBCKind::MBC1WithBattery => Box::new(MBC1::new(ram_size, rom_size)), // TODO: Implement Saving | ||||
|             MBCKind::MBC2 => Box::new(MBC2::new(rom_cap)), | ||||
|             MBCKind::MBC2WithBattery => Box::new(MBC2::new(rom_cap)), // TODO: Implement Saving | ||||
|             MBCKind::MBC3 => Box::new(MBC3::new(ram_cap)), | ||||
|             MBCKind::MBC3WithBattery => Box::new(MBC3::new(ram_cap)), // TODO: Implement Saving | ||||
|             MBCKind::MBC5 => Box::new(MBC5::new(ram_cap, rom_cap)), | ||||
|             MBCKind::MBC5WithBattery => Box::new(MBC5::new(ram_cap, rom_cap)), // TDO: Implement Saving | ||||
|             MbcKind::None => Box::new(NoMbc), | ||||
|             MbcKind::Mbc1(hw) => Box::new(Mbc1::new(hw, ram_size, rom_size)), | ||||
|             MbcKind::Mbc2(hw) => Box::new(Mbc2::new(hw, rom_cap)), | ||||
|             MbcKind::Mbc3(hw @ Mbc3Hardware::Rtc) => Box::new(Mbc3::new(hw, ram_cap)), | ||||
|             MbcKind::Mbc3(hw @ Mbc3Hardware::RtcBatteryRam) => Box::new(Mbc3::new(hw, ram_cap)), | ||||
|             MbcKind::Mbc4(hw @ Mbc5Hardware::None) => Box::new(Mbc5::new(hw, ram_cap, rom_cap)), | ||||
|             MbcKind::Mbc4(hw @ Mbc5Hardware::BatteryRam) => { | ||||
|                 Box::new(Mbc5::new(hw, ram_cap, rom_cap)) | ||||
|             } | ||||
|             kind => todo!("ROMS with {:?} are currently unsupported", kind), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn find_title(memory: &[u8]) -> Option<String> { | ||||
|         let slice = &memory[ROM_TITLE_RANGE]; | ||||
|         let with_nulls = std::str::from_utf8(slice).ok(); | ||||
|         let trimmed = with_nulls.map(|s| s.trim_matches('\0').trim()); | ||||
|     fn detect_title(title_mem: &[u8; ROM_TITLE_MAX_SIZE]) -> Option<String> { | ||||
|         const ALT_TITLE_LEN: usize = ROM_MANUFACTURER_START - ROM_TITLE_START; | ||||
|  | ||||
|         match trimmed { | ||||
|             Some("") | None => None, | ||||
|             Some(_) => trimmed.map(String::from), | ||||
|         // byte slice we have here is purposely not null terminated | ||||
|         let ascii = match title_mem.iter().position(|b| *b == 0x00) { | ||||
|             Some(end) => &title_mem[0..end], | ||||
|             None => &title_mem[0..ROM_TITLE_MAX_SIZE], | ||||
|         }; | ||||
|  | ||||
|         match std::str::from_utf8(ascii).ok() { | ||||
|             None => match std::str::from_utf8(&title_mem[0..ALT_TITLE_LEN]).ok() { | ||||
|                 Some("") | None => None, | ||||
|                 Some(title) => Some(String::from(title.trim())), | ||||
|             }, | ||||
|             Some("") => None, | ||||
|             Some(title) => Some(String::from(title.trim())), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn title(&self) -> Option<&str> { | ||||
|         self.title.as_deref() | ||||
|     } | ||||
|     fn detect_mbc_kind(id: u8) -> MbcKind { | ||||
|         use MbcKind::*; | ||||
|  | ||||
|     fn detect_ram_info(memory: &[u8]) -> RamSize { | ||||
|         let id = memory[RAM_SIZE_ADDRESS]; | ||||
|         id.into() | ||||
|     } | ||||
|  | ||||
|     fn detect_rom_info(memory: &[u8]) -> RomSize { | ||||
|         let id = memory[ROM_SIZE_ADDRESS]; | ||||
|         id.into() | ||||
|     } | ||||
|  | ||||
|     fn find_mbc(memory: &[u8]) -> MBCKind { | ||||
|         use MBCKind::*; | ||||
|  | ||||
|         match memory[MBC_TYPE_ADDRESS] { | ||||
|         match id { | ||||
|             0x00 => None, | ||||
|             0x01 | 0x02 => MBC1, | ||||
|             0x03 => MBC1WithBattery, | ||||
|             0x05 => MBC2, | ||||
|             0x06 => MBC2WithBattery, | ||||
|             0x19 | 0x1A => MBC5, | ||||
|             0x1B => MBC5WithBattery, | ||||
|             0x13 => MBC3WithBattery, | ||||
|             0x11 | 0x12 => MBC3, | ||||
|             id => unimplemented!("id {:#04X} is an unsupported MBC", id), | ||||
|             0x01 => Mbc1(Mbc1Hardware::None), | ||||
|             0x02 => Mbc1(Mbc1Hardware::Ram), | ||||
|             0x03 => Mbc1(Mbc1Hardware::BatteryRam), | ||||
|             0x05 => Mbc2(Mbc2Hardware::None), | ||||
|             0x06 => Mbc2(Mbc2Hardware::BatteryRam), | ||||
|             0x08 | 0x09 => unimplemented!("NoMBC + RAM and NoMBC + Battery unsupported"), | ||||
|             0x0B | 0x0C | 0x0D => unimplemented!("MM01 unsupported"), | ||||
|             0x0F => Mbc3(Mbc3Hardware::Rtc), | ||||
|             0x10 => Mbc3(Mbc3Hardware::RtcBatteryRam), | ||||
|             0x11 => Mbc3(Mbc3Hardware::None), | ||||
|             0x12 => Mbc3(Mbc3Hardware::Ram), | ||||
|             0x13 => Mbc3(Mbc3Hardware::BatteryRam), | ||||
|             0x19 => Mbc4(Mbc5Hardware::None), | ||||
|             0x1A => Mbc4(Mbc5Hardware::Ram), | ||||
|             0x1B => Mbc4(Mbc5Hardware::BatteryRam), | ||||
|             0x1C => Mbc4(Mbc5Hardware::Rumble), | ||||
|             0x1D => Mbc4(Mbc5Hardware::RumbleRam), | ||||
|             0x1E => Mbc4(Mbc5Hardware::RumbleBatteryRam), | ||||
|             id => unimplemented!("MBC with code {:#04X} is unsupported", id), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl BusIo for Cartridge { | ||||
|     fn read_byte(&self, addr: u16) -> u8 { | ||||
|         use MBCResult::*; | ||||
|         use MbcResult::*; | ||||
|  | ||||
|         match self.mbc.handle_read(addr) { | ||||
|             Address(addr) => self.memory[addr], | ||||
|             Value(byte) => byte, | ||||
|             Addr(addr) => self.mem[addr], | ||||
|             Byte(byte) => byte, | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -107,28 +132,31 @@ impl BusIo for Cartridge { | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| struct MBC1 { | ||||
| struct Mbc1 { | ||||
|     /// 5-bit number | ||||
|     rom_bank: u8, | ||||
|     /// 2-bit number | ||||
|     ram_bank: u8, | ||||
|     mode: bool, | ||||
|     ram_size: RamSize, | ||||
|     memory: Vec<u8>, | ||||
|     mem: Vec<u8>, | ||||
|     rom_size: RomSize, | ||||
|     mem_enabled: bool, | ||||
|  | ||||
|     hw: Mbc1Hardware, | ||||
| } | ||||
|  | ||||
| impl MBC1 { | ||||
|     fn new(ram_size: RamSize, rom_size: RomSize) -> Self { | ||||
| impl Mbc1 { | ||||
|     fn new(hw: Mbc1Hardware, ram_size: RamSize, rom_size: RomSize) -> Self { | ||||
|         Self { | ||||
|             rom_bank: 0x01, | ||||
|             memory: vec![0; ram_size.capacity() as usize], | ||||
|             mem: vec![0; ram_size.capacity() as usize], | ||||
|             ram_size, | ||||
|             rom_size, | ||||
|             ram_bank: Default::default(), | ||||
|             mode: Default::default(), | ||||
|             mem_enabled: Default::default(), | ||||
|             hw, | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -197,22 +225,34 @@ impl MBC1 { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl MBCIo for MBC1 { | ||||
|     fn handle_read(&self, addr: u16) -> MBCResult { | ||||
|         use MBCResult::*; | ||||
| impl Savable for Mbc1 { | ||||
|     fn ext_ram(&self) -> Option<&[u8]> { | ||||
|         match self.hw { | ||||
|             Mbc1Hardware::BatteryRam => Some(&self.mem), | ||||
|             _ => None, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn ext_ram_mut(&mut self) -> Option<&mut [u8]> { | ||||
|         match self.hw { | ||||
|             Mbc1Hardware::BatteryRam => Some(&mut self.mem), | ||||
|             _ => None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl MbcIo for Mbc1 { | ||||
|     fn handle_read(&self, addr: u16) -> MbcResult { | ||||
|         use MbcResult::*; | ||||
|  | ||||
|         match addr { | ||||
|             0x0000..=0x3FFF if self.mode => { | ||||
|                 Address(0x4000 * self.zero_bank() as usize + addr as usize) | ||||
|                 Addr(0x4000 * self.zero_bank() as usize + addr as usize) | ||||
|             } | ||||
|             0x0000..=0x3FFF => Address(addr as usize), | ||||
|             0x4000..=0x7FFF => { | ||||
|                 Address(0x4000 * self.high_bank() as usize + (addr as usize - 0x4000)) | ||||
|             } | ||||
|             0xA000..=0xBFFF if self.mem_enabled && self.ram_size != RamSize::None => { | ||||
|                 Value(self.memory[self.ram_addr(addr)]) | ||||
|             } | ||||
|             0xA000..=0xBFFF => Value(0xFF), | ||||
|             0x0000..=0x3FFF => Addr(addr as usize), | ||||
|             0x4000..=0x7FFF => Addr(0x4000 * self.high_bank() as usize + (addr as usize - 0x4000)), | ||||
|             0xA000..=0xBFFF if self.mem_enabled => Byte(self.mem[self.ram_addr(addr)]), | ||||
|             0xA000..=0xBFFF => Byte(0xFF), | ||||
|             _ => unreachable!("A read from {:#06X} should not be handled by MBC1", addr), | ||||
|         } | ||||
|     } | ||||
| @@ -227,9 +267,9 @@ impl MBCIo for MBC1 { | ||||
|             } | ||||
|             0x4000..=0x5FFF => self.ram_bank = byte & 0x03, | ||||
|             0x6000..=0x7FFF => self.mode = (byte & 0x01) == 0x01, | ||||
|             0xA000..=0xBFFF if self.mem_enabled && self.ram_size != RamSize::None => { | ||||
|             0xA000..=0xBFFF if self.mem_enabled => { | ||||
|                 let ram_addr = self.ram_addr(addr); | ||||
|                 self.memory[ram_addr] = byte; | ||||
|                 self.mem[ram_addr] = byte; | ||||
|             } | ||||
|             0xA000..=0xBFFF => {} // Ram isn't enabled, ignored write | ||||
|             _ => unreachable!("A write to {:#06X} should not be handled by MBC1", addr), | ||||
| @@ -237,55 +277,198 @@ impl MBCIo for MBC1 { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl RtcTick for Mbc1 { | ||||
|     fn tick(&mut self) {} | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Copy)] | ||||
| struct RtClock { | ||||
|     /// 6-bit unsigned integer | ||||
|     sec: u8, | ||||
|     /// 6-bit unsigned integer | ||||
|     min: u8, | ||||
|     /// 6-bit unsigned integer | ||||
|     hr: u8, | ||||
|     day_low: u8, | ||||
|     day_high: DayHigh, | ||||
|  | ||||
|     cycles: Cycle, | ||||
| } | ||||
|  | ||||
| impl RtClock { | ||||
|     fn inc_day(&mut self) { | ||||
|         // TODO: Figure out order of operations, the brackets are a bit too defensive here | ||||
|         let days: u16 = (((self.day_high.ninth() as u16) << 8) | self.day_low as u16) + 1; | ||||
|  | ||||
|         if days > 0x1FF { | ||||
|             self.day_high.set_carry(true); | ||||
|         } | ||||
|  | ||||
|         self.day_high.set_ninth(((days >> 8) & 0x01) == 0x01); | ||||
|         self.day_low = days as u8; | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl RtcTick for RtClock { | ||||
|     fn tick(&mut self) { | ||||
|         // This is the sort of situation where you'd want to use a scheduler. | ||||
|         if self.day_high.halt() { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         self.cycles += 1; | ||||
|  | ||||
|         if self.cycles >= SM83_CLOCK_SPEED { | ||||
|             self.cycles %= SM83_CLOCK_SPEED; | ||||
|  | ||||
|             self.sec += 1; | ||||
|  | ||||
|             if self.sec == 60 { | ||||
|                 self.sec = 0; | ||||
|                 self.min += 1; | ||||
|             } | ||||
|  | ||||
|             if self.min == 60 { | ||||
|                 self.min = 0; | ||||
|                 self.hr += 1; | ||||
|             } | ||||
|  | ||||
|             if self.hr == 24 { | ||||
|                 self.hr = 0; | ||||
|                 self.inc_day(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| trait RtcTick { | ||||
|     fn tick(&mut self); | ||||
| } | ||||
|  | ||||
| bitfield! { | ||||
|     struct DayHigh(u8); | ||||
|     impl Debug; | ||||
|     _, set_carry: 7; | ||||
|     halt, _: 6; | ||||
|     ninth, set_ninth: 0; | ||||
| } | ||||
|  | ||||
| impl Copy for DayHigh {} | ||||
| impl Clone for DayHigh { | ||||
|     fn clone(&self) -> Self { | ||||
|         *self | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for DayHigh { | ||||
|     fn default() -> Self { | ||||
|         Self(0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<u8> for DayHigh { | ||||
|     fn from(byte: u8) -> Self { | ||||
|         Self(byte) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<DayHigh> for u8 { | ||||
|     fn from(dh: DayHigh) -> Self { | ||||
|         dh.0 | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| enum MBC3Device { | ||||
|     ExternalRam, | ||||
|     RealTimeClock, | ||||
| enum Mbc3Device { | ||||
|     Ram, | ||||
|     Rtc(RtcRegister), | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| enum RtcRegister { | ||||
|     Sec, | ||||
|     Min, | ||||
|     Hr, | ||||
|     DayLow, | ||||
|     DayHigh, | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| struct MBC3 { | ||||
| struct Mbc3 { | ||||
|     /// 7-bit Number | ||||
|     rom_bank: u8, | ||||
|     /// 2-bit Number | ||||
|     ram_bank: u8, | ||||
|  | ||||
|     devs_enabled: bool, | ||||
|     mapped: Option<MBC3Device>, | ||||
|     memory: Vec<u8>, | ||||
|     mapped: Option<Mbc3Device>, | ||||
|     mem: Vec<u8>, | ||||
|  | ||||
|     // RTC Data Latch Previous Write | ||||
|     prev_latch_write: Option<u8>, | ||||
|  | ||||
|     hw: Mbc3Hardware, | ||||
|     rtc: RtClock, | ||||
|     rtc_latch: Option<RtClock>, | ||||
| } | ||||
|  | ||||
| impl MBC3 { | ||||
|     fn new(ram_cap: usize) -> Self { | ||||
| impl Mbc3 { | ||||
|     fn new(hw: Mbc3Hardware, ram_cap: usize) -> Self { | ||||
|         Self { | ||||
|             memory: vec![0; ram_cap], | ||||
|             mem: vec![0; ram_cap], | ||||
|             rom_bank: Default::default(), | ||||
|             ram_bank: Default::default(), | ||||
|             devs_enabled: Default::default(), | ||||
|             mapped: Default::default(), | ||||
|             prev_latch_write: Default::default(), | ||||
|             rtc: Default::default(), | ||||
|             rtc_latch: Default::default(), | ||||
|             hw, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl MBCIo for MBC3 { | ||||
|     fn handle_read(&self, addr: u16) -> MBCResult { | ||||
|         use MBCResult::*; | ||||
| impl Savable for Mbc3 { | ||||
|     fn ext_ram(&self) -> Option<&[u8]> { | ||||
|         match self.hw { | ||||
|             Mbc3Hardware::BatteryRam | Mbc3Hardware::RtcBatteryRam => Some(&self.mem), | ||||
|             _ => None, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn ext_ram_mut(&mut self) -> Option<&mut [u8]> { | ||||
|         match self.hw { | ||||
|             Mbc3Hardware::BatteryRam | Mbc3Hardware::RtcBatteryRam => Some(&mut self.mem), | ||||
|             _ => None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl MbcIo for Mbc3 { | ||||
|     fn handle_read(&self, addr: u16) -> MbcResult { | ||||
|         use MbcResult::*; | ||||
|         use RtcRegister::*; | ||||
|  | ||||
|         let res = match addr { | ||||
|             0x0000..=0x3FFF => Address(addr as usize), | ||||
|             0x4000..=0x7FFF => Address(0x4000 * self.rom_bank as usize + (addr as usize - 0x4000)), | ||||
|             0x0000..=0x3FFF => Addr(addr as usize), | ||||
|             0x4000..=0x7FFF => Addr(0x4000 * self.rom_bank as usize + (addr as usize - 0x4000)), | ||||
|             0xA000..=0xBFFF => match self.mapped { | ||||
|                 Some(MBC3Device::ExternalRam) if self.devs_enabled => { | ||||
|                     Value(self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)]) | ||||
|                 Some(Mbc3Device::Ram) if self.devs_enabled => { | ||||
|                     Byte(self.mem[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)]) | ||||
|                 } | ||||
|                 Some(MBC3Device::RealTimeClock) if self.devs_enabled => { | ||||
|                     todo!("Return Latched value of register") | ||||
|                 } | ||||
|                 _ => Value(0xFF), | ||||
|                 Some(Mbc3Device::Rtc(reg)) if self.devs_enabled => Byte( | ||||
|                     self.rtc_latch | ||||
|                         .as_ref() | ||||
|                         .map(|rtc| match reg { | ||||
|                             Sec => rtc.sec, | ||||
|                             Min => rtc.min, | ||||
|                             Hr => rtc.hr, | ||||
|                             DayLow => rtc.day_low, | ||||
|                             DayHigh => rtc.day_high.into(), | ||||
|                         }) | ||||
|                         .unwrap_or(0xFF), | ||||
|                 ), | ||||
|                 _ => Byte(0xFF), | ||||
|             }, | ||||
|             _ => unreachable!("A read from {:#06X} should not be handled by MBC3", addr), | ||||
|         }; | ||||
| @@ -294,6 +477,8 @@ impl MBCIo for MBC3 { | ||||
|     } | ||||
|  | ||||
|     fn handle_write(&mut self, addr: u16, byte: u8) { | ||||
|         use RtcRegister::*; | ||||
|  | ||||
|         match addr { | ||||
|             0x000..=0x1FFF => self.devs_enabled = (byte & 0x0F) == 0x0A, // Enable External RAM and Access to RTC if there is one | ||||
|             0x2000..=0x3FFF => { | ||||
| @@ -305,28 +490,39 @@ impl MBCIo for MBC3 { | ||||
|             0x4000..=0x5FFF => match byte { | ||||
|                 0x00 | 0x01 | 0x02 | 0x03 => { | ||||
|                     self.ram_bank = byte & 0x03; | ||||
|                     self.mapped = Some(MBC3Device::ExternalRam); | ||||
|                 } | ||||
|                 0x08 | 0x09 | 0x0A | 0x0B | 0x0C => { | ||||
|                     self.mapped = Some(MBC3Device::RealTimeClock); | ||||
|                     self.mapped = Some(Mbc3Device::Ram); | ||||
|                 } | ||||
|                 0x08 => self.mapped = Some(Mbc3Device::Rtc(Sec)), | ||||
|                 0x09 => self.mapped = Some(Mbc3Device::Rtc(Min)), | ||||
|                 0x0A => self.mapped = Some(Mbc3Device::Rtc(Hr)), | ||||
|                 0x0B => self.mapped = Some(Mbc3Device::Rtc(DayLow)), | ||||
|                 0x0C => self.mapped = Some(Mbc3Device::Rtc(DayHigh)), | ||||
|  | ||||
|                 _ => {} | ||||
|             }, | ||||
|             0x6000..=0x7FFF => { | ||||
|                 if let Some(0x00) = self.prev_latch_write { | ||||
|                     if byte == 0x01 { | ||||
|                         todo!("Perform Data Latch") | ||||
|                         self.rtc_latch = Some(self.rtc); | ||||
|                     } | ||||
|                 } | ||||
|                 self.prev_latch_write = Some(byte); | ||||
|             } | ||||
|             0xA000..=0xBFFF => match self.mapped { | ||||
|                 Some(MBC3Device::ExternalRam) if self.devs_enabled => { | ||||
|                     self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte | ||||
|                 } | ||||
|                 Some(MBC3Device::RealTimeClock) if self.devs_enabled => { | ||||
|                     todo!("Write to RTC") | ||||
|                 Some(Mbc3Device::Ram) if self.devs_enabled => { | ||||
|                     self.mem[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte | ||||
|                 } | ||||
|                 Some(Mbc3Device::Rtc(rtc_reg)) if self.devs_enabled => match rtc_reg { | ||||
|                     Sec => { | ||||
|                         self.rtc.sec = byte & 0x3F; | ||||
|                         // Writing to RTC S resets the internal sub-second counter | ||||
|                         self.rtc.cycles = 0; | ||||
|                     } | ||||
|                     Min => self.rtc.min = byte & 0x3F, | ||||
|                     Hr => self.rtc.hr = byte & 0x1F, | ||||
|                     DayLow => self.rtc.day_low = byte, | ||||
|                     DayHigh => self.rtc.day_high = (byte & 0xC1).into(), | ||||
|                 }, | ||||
|                 _ => {} | ||||
|             }, | ||||
|             _ => unreachable!("A write to {:#06X} should not be handled by MBC3", addr), | ||||
| @@ -334,27 +530,37 @@ impl MBCIo for MBC3 { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl RtcTick for Mbc3 { | ||||
|     fn tick(&mut self) { | ||||
|         if let Mbc3Hardware::RtcBatteryRam | Mbc3Hardware::Rtc = self.hw { | ||||
|             self.rtc.tick(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| struct MBC5 { | ||||
| struct Mbc5 { | ||||
|     /// 9-bit number | ||||
|     rom_bank: u16, | ||||
|     /// 4-bit number | ||||
|     ram_bank: u8, | ||||
|  | ||||
|     rom_cap: usize, | ||||
|  | ||||
|     memory: Vec<u8>, | ||||
|     mem: Vec<u8>, | ||||
|     mem_enabled: bool, | ||||
|  | ||||
|     hw: Mbc5Hardware, | ||||
| } | ||||
|  | ||||
| impl MBC5 { | ||||
|     fn new(ram_cap: usize, rom_cap: usize) -> Self { | ||||
| impl Mbc5 { | ||||
|     fn new(hw: Mbc5Hardware, ram_cap: usize, rom_cap: usize) -> Self { | ||||
|         Self { | ||||
|             rom_bank: 0x01, | ||||
|             memory: vec![0; ram_cap], | ||||
|             mem: vec![0; ram_cap], | ||||
|             rom_cap, | ||||
|             ram_bank: Default::default(), | ||||
|             mem_enabled: Default::default(), | ||||
|             hw, | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -363,17 +569,33 @@ impl MBC5 { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl MBCIo for MBC5 { | ||||
|     fn handle_read(&self, addr: u16) -> MBCResult { | ||||
|         use MBCResult::*; | ||||
| impl Savable for Mbc5 { | ||||
|     fn ext_ram(&self) -> Option<&[u8]> { | ||||
|         match self.hw { | ||||
|             Mbc5Hardware::RumbleBatteryRam | Mbc5Hardware::BatteryRam => Some(&self.mem), | ||||
|             _ => None, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn ext_ram_mut(&mut self) -> Option<&mut [u8]> { | ||||
|         match self.hw { | ||||
|             Mbc5Hardware::RumbleBatteryRam | Mbc5Hardware::BatteryRam => Some(&mut self.mem), | ||||
|             _ => None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl MbcIo for Mbc5 { | ||||
|     fn handle_read(&self, addr: u16) -> MbcResult { | ||||
|         use MbcResult::*; | ||||
|  | ||||
|         match addr { | ||||
|             0x0000..=0x3FFF => Address(addr as usize), | ||||
|             0x4000..=0x7FFF => Address(self.bank_addr(addr)), | ||||
|             0x0000..=0x3FFF => Addr(addr as usize), | ||||
|             0x4000..=0x7FFF => Addr(self.bank_addr(addr)), | ||||
|             0xA000..=0xBFFF if self.mem_enabled => { | ||||
|                 Value(self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)]) | ||||
|                 Byte(self.mem[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)]) | ||||
|             } | ||||
|             0xA000..=0xBFFF => Value(0xFF), | ||||
|             0xA000..=0xBFFF => Byte(0xFF), | ||||
|             _ => unreachable!("A read from {:#06X} should not be handled by MBC5", addr), | ||||
|         } | ||||
|     } | ||||
| @@ -385,7 +607,7 @@ impl MBCIo for MBC5 { | ||||
|             0x3000..=0x3FFF => self.rom_bank = (self.rom_bank & 0x00FF) | (byte as u16 & 0x01) << 8, | ||||
|             0x4000..=0x5FFF => self.ram_bank = byte & 0x0F, | ||||
|             0xA000..=0xBFFF if self.mem_enabled => { | ||||
|                 self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte; | ||||
|                 self.mem[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte; | ||||
|             } | ||||
|             0xA000..=0xBFFF => {} | ||||
|             _ => unreachable!("A write to {:#06X} should not be handled by MBC5", addr), | ||||
| @@ -393,25 +615,31 @@ impl MBCIo for MBC5 { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| struct MBC2 { | ||||
|     /// 4-bit number | ||||
|     rom_bank: u8, | ||||
|     memory: Box<[u8; Self::RAM_SIZE]>, | ||||
|     mem_enabled: bool, | ||||
|  | ||||
|     rom_cap: usize, | ||||
| impl RtcTick for Mbc5 { | ||||
|     fn tick(&mut self) {} | ||||
| } | ||||
|  | ||||
| impl MBC2 { | ||||
| #[derive(Debug)] | ||||
| struct Mbc2 { | ||||
|     /// 4-bit number | ||||
|     rom_bank: u8, | ||||
|     mem: Box<[u8; Self::RAM_SIZE]>, | ||||
|  | ||||
|     mem_enabled: bool, | ||||
|     rom_cap: usize, | ||||
|     hw: Mbc2Hardware, | ||||
| } | ||||
|  | ||||
| impl Mbc2 { | ||||
|     const RAM_SIZE: usize = 0x0200; | ||||
|  | ||||
|     fn new(rom_cap: usize) -> Self { | ||||
|     fn new(hw: Mbc2Hardware, rom_cap: usize) -> Self { | ||||
|         Self { | ||||
|             rom_bank: 0x01, | ||||
|             memory: Box::new([0; Self::RAM_SIZE]), | ||||
|             mem_enabled: Default::default(), | ||||
|             mem: Box::new([0; Self::RAM_SIZE]), | ||||
|             rom_cap, | ||||
|             mem_enabled: Default::default(), | ||||
|             hw, | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -420,18 +648,34 @@ impl MBC2 { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl MBCIo for MBC2 { | ||||
|     fn handle_read(&self, addr: u16) -> MBCResult { | ||||
|         use MBCResult::*; | ||||
| impl Savable for Mbc2 { | ||||
|     fn ext_ram(&self) -> Option<&[u8]> { | ||||
|         match self.hw { | ||||
|             Mbc2Hardware::BatteryRam => Some(self.mem.as_ref()), | ||||
|             Mbc2Hardware::None => None, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn ext_ram_mut(&mut self) -> Option<&mut [u8]> { | ||||
|         match self.hw { | ||||
|             Mbc2Hardware::BatteryRam => Some(self.mem.as_mut()), | ||||
|             Mbc2Hardware::None => None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl MbcIo for Mbc2 { | ||||
|     fn handle_read(&self, addr: u16) -> MbcResult { | ||||
|         use MbcResult::*; | ||||
|  | ||||
|         match addr { | ||||
|             0x0000..=0x3FFF => Address(addr as usize), | ||||
|             0x4000..=0x7FFF => Address(self.rom_addr(addr)), | ||||
|             0x0000..=0x3FFF => Addr(addr as usize), | ||||
|             0x4000..=0x7FFF => Addr(self.rom_addr(addr)), | ||||
|             0xA000..=0xBFFF if self.mem_enabled => { | ||||
|                 let mbc2_addr = addr as usize & (Self::RAM_SIZE - 1); | ||||
|                 Value(self.memory[mbc2_addr] | 0xF0) | ||||
|                 Byte(self.mem[mbc2_addr] | 0xF0) | ||||
|             } | ||||
|             0xA000..=0xBFFF => Value(0xFF), | ||||
|             0xA000..=0xBFFF => Byte(0xFF), | ||||
|             _ => unreachable!("A read from {:#06X} should not be handled by MBC2", addr), | ||||
|         } | ||||
|     } | ||||
| @@ -446,7 +690,7 @@ impl MBCIo for MBC2 { | ||||
|             0x0000..=0x3FFF => self.mem_enabled = nybble == 0x0A, | ||||
|             0xA000..=0xBFFF if self.mem_enabled => { | ||||
|                 let mbc2_addr = addr as usize & (Self::RAM_SIZE - 1); | ||||
|                 self.memory[mbc2_addr] = nybble; | ||||
|                 self.mem[mbc2_addr] = nybble; | ||||
|             } | ||||
|             0x4000..=0x7FFF | 0xA000..=0xBFFF => {} | ||||
|             _ => unreachable!("A write to {:#06X} should not be handled by MBC2", addr), | ||||
| @@ -454,47 +698,87 @@ impl MBCIo for MBC2 { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| struct NoMBC; | ||||
| impl RtcTick for Mbc2 { | ||||
|     fn tick(&mut self) {} | ||||
| } | ||||
|  | ||||
| impl MBCIo for NoMBC { | ||||
|     fn handle_read(&self, addr: u16) -> MBCResult { | ||||
|         MBCResult::Address(addr as usize) | ||||
| #[derive(Debug)] | ||||
| struct NoMbc; | ||||
|  | ||||
| impl Savable for NoMbc { | ||||
|     fn ext_ram(&self) -> Option<&[u8]> { | ||||
|         None | ||||
|     } | ||||
|  | ||||
|     fn handle_write(&mut self, _addr: u16, _byte: u8) { | ||||
|         // eprintln!("Tried to write {:#04X} to a read-only cartridge", byte); | ||||
|     fn ext_ram_mut(&mut self) -> Option<&mut [u8]> { | ||||
|         None | ||||
|     } | ||||
| } | ||||
|  | ||||
| trait MBCIo { | ||||
|     fn handle_read(&self, addr: u16) -> MBCResult; | ||||
| impl MbcIo for NoMbc { | ||||
|     fn handle_read(&self, addr: u16) -> MbcResult { | ||||
|         MbcResult::Addr(addr as usize) | ||||
|     } | ||||
|  | ||||
|     fn handle_write(&mut self, _: u16, byte: u8) { | ||||
|         tracing::warn!("Attempted write of {:#04X} to cartridge w/out MBC", byte); | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl RtcTick for NoMbc { | ||||
|     fn tick(&mut self) {} | ||||
| } | ||||
|  | ||||
| trait MbcIo: Savable + RtcTick { | ||||
|     fn handle_read(&self, addr: u16) -> MbcResult; | ||||
|     fn handle_write(&mut self, addr: u16, byte: u8); | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| enum MBCResult { | ||||
|     Address(usize), | ||||
|     Value(u8), | ||||
| enum MbcResult { | ||||
|     Addr(usize), | ||||
|     Byte(u8), | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| enum MBCKind { | ||||
| enum MbcKind { | ||||
|     None, | ||||
|     MBC1, | ||||
|     MBC1WithBattery, | ||||
|     MBC2, | ||||
|     MBC2WithBattery, | ||||
|     MBC3, | ||||
|     MBC3WithBattery, | ||||
|     MBC5, | ||||
|     MBC5WithBattery, | ||||
|     Mbc1(Mbc1Hardware), | ||||
|     Mbc2(Mbc2Hardware), | ||||
|     Mbc3(Mbc3Hardware), | ||||
|     Mbc4(Mbc5Hardware), | ||||
| } | ||||
|  | ||||
| impl Default for MBCKind { | ||||
|     fn default() -> Self { | ||||
|         Self::None | ||||
|     } | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| enum Mbc1Hardware { | ||||
|     None, | ||||
|     Ram, | ||||
|     BatteryRam, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| enum Mbc2Hardware { | ||||
|     None, | ||||
|     BatteryRam, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| enum Mbc3Hardware { | ||||
|     Rtc, | ||||
|     RtcBatteryRam, | ||||
|     None, | ||||
|     Ram, | ||||
|     BatteryRam, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| enum Mbc5Hardware { | ||||
|     None, | ||||
|     Ram, | ||||
|     BatteryRam, | ||||
|     Rumble, | ||||
|     RumbleRam, | ||||
|     RumbleBatteryRam, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, PartialEq)] | ||||
| @@ -522,12 +806,6 @@ impl RamSize { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for RamSize { | ||||
|     fn default() -> Self { | ||||
|         Self::None | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<u8> for RamSize { | ||||
|     fn from(byte: u8) -> Self { | ||||
|         use RamSize::*; | ||||
| @@ -596,14 +874,80 @@ impl From<u8> for RomSize { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl std::fmt::Debug for Box<dyn MBCIo> { | ||||
| impl std::fmt::Debug for Box<dyn MbcIo> { | ||||
|     fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||
|         todo!("Implement Debug for Box<dyn MBC> Trait Object"); | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for Box<dyn MBCIo> { | ||||
|     fn default() -> Self { | ||||
|         Box::new(NoMBC) | ||||
| trait Savable { | ||||
|     fn ext_ram(&self) -> Option<&[u8]>; | ||||
|     fn ext_ram_mut(&mut self) -> Option<&mut [u8]>; | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use super::Cartridge; | ||||
|  | ||||
|     #[test] | ||||
|     fn empty_rom_title() { | ||||
|         let title = [ | ||||
|             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|             0x00, 0x00, | ||||
|         ]; | ||||
|  | ||||
|         assert_eq!(None, Cartridge::detect_title(&title)); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn normal_rom_title() { | ||||
|         let title = [ | ||||
|             0x50, 0x4F, 0x4B, 0x45, 0x4D, 0x4F, 0x4E, 0x20, 0x42, 0x4C, 0x55, 0x45, 0x00, 0x00, | ||||
|             0x00, 0x00, | ||||
|         ]; | ||||
|  | ||||
|         assert_eq!( | ||||
|             Some(String::from("POKEMON BLUE")), | ||||
|             Cartridge::detect_title(&title) | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn extra_spaces_title() { | ||||
|         let title = [ | ||||
|             0x54, 0x4f, 0x4b, 0x49, 0x4d, 0x45, 0x4b, 0x49, 0x20, 0x43, 0x55, 0x4c, 0x20, 0x20, 0, | ||||
|             0, | ||||
|         ]; | ||||
|  | ||||
|         assert_eq!( | ||||
|             Some(String::from("TOKIMEKI CUL")), | ||||
|             Cartridge::detect_title(&title) | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn long_title() { | ||||
|         let title = [ | ||||
|             0x54, 0x4f, 0x4b, 0x49, 0x4d, 0x45, 0x4b, 0x49, 0x20, 0x43, 0x55, 0x4c, 0x54, 0x55, | ||||
|             0x52, 0x45, | ||||
|         ]; | ||||
|  | ||||
|         assert_eq!( | ||||
|             Some(String::from("TOKIMEKI CULTURE")), | ||||
|             Cartridge::detect_title(&title), | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn publisher_code_and_title() { | ||||
|         let title: [u8; 16] = [ | ||||
|             0x47, 0x52, 0x41, 0x4E, 0x44, 0x20, 0x54, 0x48, 0x45, 0x46, 0x54, 0x41, 0x4F, 0x41, | ||||
|             0x45, 0x80, | ||||
|         ]; | ||||
|  | ||||
|         assert_eq!( | ||||
|             Some(String::from("GRAND THEFT")), | ||||
|             Cartridge::detect_title(&title) | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										113
									
								
								src/cpu.rs
									
									
									
									
									
								
							
							
						
						
									
										113
									
								
								src/cpu.rs
									
									
									
									
									
								
							| @@ -5,9 +5,9 @@ use crate::Cycle; | ||||
| use bitfield::bitfield; | ||||
| use std::fmt::{Display, Formatter, Result as FmtResult}; | ||||
|  | ||||
| #[derive(Debug, Default)] | ||||
| #[derive(Debug)] | ||||
| pub struct Cpu { | ||||
|     pub bus: Bus, | ||||
|     pub(crate) bus: Bus, | ||||
|     reg: Registers, | ||||
|     flags: Flags, | ||||
|     ime: ImeState, | ||||
| @@ -15,28 +15,13 @@ pub struct Cpu { | ||||
| } | ||||
|  | ||||
| impl Cpu { | ||||
|     pub(crate) fn without_boot() -> Self { | ||||
|         Self { | ||||
|             reg: Registers { | ||||
|                 a: 0x01, | ||||
|                 b: 0x00, | ||||
|                 c: 0x13, | ||||
|                 d: 0x00, | ||||
|                 e: 0xD8, | ||||
|                 h: 0x01, | ||||
|                 l: 0x4D, | ||||
|                 sp: 0xFFFE, | ||||
|                 pc: 0x0100, | ||||
|             }, | ||||
|             flags: 0xb0.into(), | ||||
|             ..Default::default() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn with_boot(rom: [u8; BOOT_SIZE]) -> Self { | ||||
|     pub(crate) fn new(rom: [u8; BOOT_SIZE]) -> Self { | ||||
|         Self { | ||||
|             bus: Bus::with_boot(rom), | ||||
|             ..Default::default() | ||||
|             reg: Default::default(), | ||||
|             flags: Flags(0), | ||||
|             ime: ImeState::Disabled, | ||||
|             state: State::Execute, | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -57,10 +42,7 @@ impl Cpu { | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn is_halted(&self) -> bool { | ||||
|         match self.state { | ||||
|             State::Halt(_) => true, | ||||
|             _ => false, | ||||
|         } | ||||
|         matches!(self.state, State::Halt(_)) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn halt_kind(&self) -> Option<HaltKind> { | ||||
| @@ -71,6 +53,12 @@ impl Cpu { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for Cpu { | ||||
|     fn default() -> Self { | ||||
|         Cpu::new(*include_bytes!("../bin/bootix_dmg.bin")) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Cpu { | ||||
|     /// Fetch an [Instruction] from the memory bus | ||||
|     /// (4 cycles) | ||||
| @@ -105,7 +93,7 @@ impl Cpu { | ||||
|     /// routine. | ||||
|     /// | ||||
|     /// Handle HALT and interrupts. | ||||
|     pub fn step(&mut self) -> Cycle { | ||||
|     pub(crate) fn step(&mut self) -> Cycle { | ||||
|         // Log instructions | ||||
|         // if self.reg.pc > 0xFF { | ||||
|         //     let out = std::io::stdout(); | ||||
| @@ -120,13 +108,10 @@ impl Cpu { | ||||
|             use HaltKind::*; | ||||
|  | ||||
|             self.bus.clock(); | ||||
|  | ||||
|             let elapsed = match kind { | ||||
|             return match kind { | ||||
|                 ImeEnabled | NonePending => 4, | ||||
|                 SomePending => todo!("Implement HALT bug"), | ||||
|             }; | ||||
|  | ||||
|             return elapsed; | ||||
|         } | ||||
|  | ||||
|         let opcode = self.fetch(); | ||||
| @@ -135,11 +120,11 @@ impl Cpu { | ||||
|         self.handle_ei(); | ||||
|  | ||||
|         // For use in Blargg's Test ROMs | ||||
|         if self.read_byte(0xFF02) == 0x81 { | ||||
|             let c = self.read_byte(0xFF01) as char; | ||||
|             self.write_byte(0xFF02, 0x00); | ||||
|             eprint!("{}", c); | ||||
|         } | ||||
|         // if self.read_byte(0xFF02) == 0x81 { | ||||
|         //     let c = self.read_byte(0xFF01) as char; | ||||
|         //     self.write_byte(0xFF02, 0x00); | ||||
|         //     eprint!("{}", c); | ||||
|         // } | ||||
|  | ||||
|         elapsed | ||||
|     } | ||||
| @@ -156,14 +141,6 @@ impl BusIo for Cpu { | ||||
| } | ||||
|  | ||||
| impl Cpu { | ||||
|     pub(crate) fn bus(&self) -> &Bus { | ||||
|         &self.bus | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn bus_mut(&mut self) -> &mut Bus { | ||||
|         &mut self.bus | ||||
|     } | ||||
|  | ||||
|     fn handle_ei(&mut self) { | ||||
|         match self.ime { | ||||
|             ImeState::EiExecuted => self.ime = ImeState::Pending, | ||||
| @@ -261,12 +238,6 @@ enum State { | ||||
|     // Stop, | ||||
| } | ||||
|  | ||||
| impl Default for State { | ||||
|     fn default() -> Self { | ||||
|         Self::Execute | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Cpu { | ||||
|     pub(crate) fn set_register(&mut self, register: Register, value: u8) { | ||||
|         use Register::*; | ||||
| @@ -460,12 +431,6 @@ impl Clone for Flags { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for Flags { | ||||
|     fn default() -> Self { | ||||
|         Self(0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Display for Flags { | ||||
|     fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { | ||||
|         if self.z() { | ||||
| @@ -521,8 +486,38 @@ pub(crate) enum ImeState { | ||||
|     Enabled, | ||||
| } | ||||
|  | ||||
| impl Default for ImeState { | ||||
|     fn default() -> Self { | ||||
|         Self::Disabled | ||||
| pub(crate) mod dbg { | ||||
|     use super::{Cpu, ImeState, RegisterPair}; | ||||
|  | ||||
|     pub(crate) fn flags(cpu: &Cpu) -> u8 { | ||||
|         cpu.flags.into() | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn af(cpu: &Cpu) -> u16 { | ||||
|         cpu.register_pair(RegisterPair::AF) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn bc(cpu: &Cpu) -> u16 { | ||||
|         cpu.register_pair(RegisterPair::BC) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn de(cpu: &Cpu) -> u16 { | ||||
|         cpu.register_pair(RegisterPair::DE) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn hl(cpu: &Cpu) -> u16 { | ||||
|         cpu.register_pair(RegisterPair::HL) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn sp(cpu: &Cpu) -> u16 { | ||||
|         cpu.register_pair(RegisterPair::SP) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn pc(cpu: &Cpu) -> u16 { | ||||
|         cpu.register_pair(RegisterPair::PC) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn ime(cpu: &Cpu) -> bool { | ||||
|         matches!(cpu.ime, ImeState::Enabled) | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										220
									
								
								src/emu.rs
									
									
									
									
									
								
							
							
						
						
									
										220
									
								
								src/emu.rs
									
									
									
									
									
								
							| @@ -1,121 +1,163 @@ | ||||
| use crate::apu::gen::SampleProducer; | ||||
| use crate::bus::BOOT_SIZE; | ||||
| use crate::cartridge::Cartridge; | ||||
| use crate::cpu::Cpu; | ||||
| use crate::joypad::{self, Joypad}; | ||||
| use crate::{Cycle, GB_HEIGHT, GB_WIDTH}; | ||||
| use clap::crate_name; | ||||
| use gilrs::Gilrs; | ||||
| use std::fs::File; | ||||
| use std::io::{Read, Write}; | ||||
| use std::path::{Path, PathBuf}; | ||||
| use std::time::Duration; | ||||
| use winit_input_helper::WinitInputHelper; | ||||
| use thiserror::Error; | ||||
| use winit::event::KeyboardInput; | ||||
| use winit::event_loop::ControlFlow; | ||||
|  | ||||
| pub const SM83_CYCLE_TIME: Duration = Duration::from_nanos(1_000_000_000 / SM83_CLOCK_SPEED); | ||||
| pub const CYCLES_IN_FRAME: Cycle = 456 * 154; // 456 Cycles times 154 scanlines | ||||
| pub(crate) const SM83_CLOCK_SPEED: u64 = 0x40_0000; // Hz which is 4.194304Mhz | ||||
| const DEFAULT_TITLE: &str = "DMG-01 Emulator"; | ||||
| const DEFAULT_TITLE: &str = "Game Boy Screen"; | ||||
|  | ||||
| pub fn run_frame(emu: &mut Emulator, gamepad: &mut Gilrs, key: &WinitInputHelper) -> Cycle { | ||||
| #[inline] | ||||
| pub fn run_frame(cpu: &mut Cpu, gamepad: &mut Gilrs, key: KeyboardInput) { | ||||
|     run(cpu, gamepad, key, CYCLES_IN_FRAME) | ||||
| } | ||||
|  | ||||
| #[inline] | ||||
| pub fn run(cpu: &mut Cpu, gamepad: &mut Gilrs, key: KeyboardInput, cycles: Cycle) { | ||||
|     let mut elapsed = 0; | ||||
|  | ||||
|     if let Some(event) = gamepad.next_event() { | ||||
|         joypad::handle_gamepad_input(emu.joyp_mut(), event); | ||||
|         crate::joypad::handle_gamepad_input(&mut cpu.bus.joyp, event); | ||||
|     } | ||||
|     crate::joypad::handle_keyboard_input(&mut cpu.bus.joyp, key); | ||||
|  | ||||
|     joypad::handle_keyboard_input(emu.joyp_mut(), key); | ||||
|     while elapsed < CYCLES_IN_FRAME { | ||||
|         elapsed += emu.step(); | ||||
|     } | ||||
|  | ||||
|     elapsed | ||||
| } | ||||
|  | ||||
| pub fn draw_frame(emu: &Emulator, buf: &mut [u8; GB_HEIGHT * GB_WIDTH * 4]) { | ||||
|     buf.copy_from_slice(emu.cpu.bus().ppu.frame_buf()); | ||||
| } | ||||
|  | ||||
| pub struct Emulator { | ||||
|     cpu: Cpu, | ||||
|     timestamp: Cycle, | ||||
| } | ||||
|  | ||||
| impl Emulator { | ||||
|     fn new(cpu: Cpu) -> Self { | ||||
|         Self { | ||||
|             cpu, | ||||
|             timestamp: Default::default(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn step(&mut self) -> Cycle { | ||||
|         self.cpu.step() | ||||
|     } | ||||
|  | ||||
|     fn load_cart(&mut self, rom: Vec<u8>) { | ||||
|         self.cpu.bus_mut().load_cart(rom) | ||||
|     } | ||||
|  | ||||
|     fn joyp_mut(&mut self) -> &mut Joypad { | ||||
|         &mut self.cpu.bus_mut().joypad | ||||
|     } | ||||
|  | ||||
|     pub fn set_prod(&mut self, prod: SampleProducer<f32>) { | ||||
|         self.cpu.bus_mut().apu.attach_producer(prod) | ||||
|     } | ||||
|  | ||||
|     pub fn title(&self) -> &str { | ||||
|         self.cpu.bus().cart_title().unwrap_or(DEFAULT_TITLE) | ||||
|     while elapsed < cycles { | ||||
|         elapsed += cpu.step(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub mod build { | ||||
|     use std::fs::File; | ||||
|     use std::io::{Read, Result}; | ||||
|     use std::path::Path; | ||||
| pub fn save_and_exit(cpu: &Cpu, control_flow: &mut ControlFlow) { | ||||
|     write_save(cpu); | ||||
|     *control_flow = ControlFlow::Exit; | ||||
| } | ||||
|  | ||||
|     use crate::bus::BOOT_SIZE; | ||||
|     use crate::cpu::Cpu; | ||||
| #[inline] | ||||
| pub fn pixel_buf(cpu: &Cpu) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] { | ||||
|     cpu.bus.ppu.frame_buf.as_ref() | ||||
| } | ||||
|  | ||||
|     use super::Emulator; | ||||
| pub fn from_boot_rom<P: AsRef<Path>>(path: P) -> std::io::Result<Cpu> { | ||||
|     Ok(Cpu::new(read_boot(path)?)) | ||||
| } | ||||
|  | ||||
|     #[derive(Debug, Default)] | ||||
|     pub struct EmulatorBuilder { | ||||
|         boot: Option<[u8; BOOT_SIZE]>, | ||||
|         cart: Option<Vec<u8>>, | ||||
| pub fn read_game_rom<P: AsRef<Path>>(cpu: &mut Cpu, path: P) -> std::io::Result<()> { | ||||
|     cpu.bus.cart = Some(Cartridge::new(std::fs::read(path.as_ref())?)); | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| pub fn set_audio_prod(cpu: &mut Cpu, prod: SampleProducer<f32>) { | ||||
|     cpu.bus.apu.prod = Some(prod); | ||||
| } | ||||
|  | ||||
| pub fn rom_title(cpu: &Cpu) -> &str { | ||||
|     cpu.bus | ||||
|         .cart | ||||
|         .as_ref() | ||||
|         .map(|c| c.title.as_deref()) | ||||
|         .flatten() | ||||
|         .unwrap_or(DEFAULT_TITLE) | ||||
| } | ||||
|  | ||||
| pub fn write_save(cpu: &Cpu) { | ||||
|     match cpu.bus.cart.as_ref() { | ||||
|         Some(cart) => match write_save_to_file(cart) { | ||||
|             Ok(path) => tracing::info!("Wrote to save at {:?}", path), | ||||
|             Err(err @ SaveError::NotApplicable) => tracing::warn!("Unable to Save: {:?}", err), | ||||
|             Err(SaveError::DiffSize) => unreachable!(), | ||||
|             Err(SaveError::Io(err)) => tracing::error!("{:?}", err), | ||||
|         }, | ||||
|         None => tracing::error!("No cartridge is currently present"), | ||||
|     } | ||||
| } | ||||
|  | ||||
|     impl EmulatorBuilder { | ||||
|         pub fn new() -> Self { | ||||
|             Default::default() | ||||
| pub fn load_save(cpu: &mut Cpu) { | ||||
|     match cpu.bus.cart.as_mut() { | ||||
|         Some(cart) => match read_save_from_file(cart) { | ||||
|             Ok(path) => tracing::info!("Loaded save from {:?}", path), | ||||
|             Err(err @ SaveError::NotApplicable) => tracing::warn!("Unable to load save: {}", err), | ||||
|             Err(err @ SaveError::DiffSize) => tracing::error!("Unable to load save: {}", err), | ||||
|             Err(SaveError::Io(err)) => match err.kind() { | ||||
|                 std::io::ErrorKind::NotFound => tracing::warn!("Save not found"), | ||||
|                 _ => tracing::error!("{:?}", err), | ||||
|             }, | ||||
|         }, | ||||
|         None => tracing::error!("No cartridge is currently present"), | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn write_save_to_file(cart: &Cartridge) -> Result<PathBuf, SaveError> { | ||||
|     match cart.title.as_ref().zip(cart.ext_ram()) { | ||||
|         Some((title, ram)) => { | ||||
|             let mut save_path = data_path().unwrap_or_else(|| PathBuf::from(".")); | ||||
|             save_path.push(title); | ||||
|             save_path.set_extension("sav"); | ||||
|  | ||||
|             let mut file = File::create(&save_path)?; | ||||
|             file.write_all(ram)?; | ||||
|             Ok(save_path) | ||||
|         } | ||||
|         None => Err(SaveError::NotApplicable), | ||||
|     } | ||||
| } | ||||
|  | ||||
|         pub fn with_boot<P: AsRef<Path>>(mut self, path: P) -> Result<Self> { | ||||
|             let mut file = File::open(path.as_ref())?; | ||||
| fn read_save_from_file(cart: &mut Cartridge) -> Result<PathBuf, SaveError> { | ||||
|     match cart.title.clone().zip(cart.ext_ram_mut()) { | ||||
|         Some((title, ext_ram)) => { | ||||
|             let mut save_path = data_path().unwrap_or_else(|| PathBuf::from(".")); | ||||
|             save_path.push(title); | ||||
|             save_path.set_extension("sav"); | ||||
|  | ||||
|             let mut buf = [0x00; BOOT_SIZE]; | ||||
|             file.read_exact(&mut buf)?; | ||||
|             let mut file = File::open(&save_path)?; | ||||
|             let mut memory = Vec::new(); | ||||
|             file.read_to_end(&mut memory)?; | ||||
|  | ||||
|             self.boot = Some(buf); | ||||
|             Ok(self) | ||||
|         } | ||||
|  | ||||
|         pub fn with_cart<P: AsRef<Path>>(mut self, path: P) -> Result<Self> { | ||||
|             let mut file = File::open(path.as_ref())?; | ||||
|  | ||||
|             let mut buf = Vec::new(); | ||||
|             file.read_to_end(&mut buf)?; | ||||
|  | ||||
|             self.cart = Some(buf); | ||||
|             Ok(self) | ||||
|         } | ||||
|  | ||||
|         pub fn finish(mut self) -> Emulator { | ||||
|             let mut emu = Emulator::new(match self.boot { | ||||
|                 Some(rom) => Cpu::with_boot(rom), | ||||
|                 None => Cpu::without_boot(), | ||||
|             }); | ||||
|  | ||||
|             if let Some(rom) = self.cart.take() { | ||||
|                 emu.load_cart(rom) | ||||
|             if ext_ram.len() != memory.len() { | ||||
|                 return Err(SaveError::DiffSize); | ||||
|             } | ||||
|  | ||||
|             emu | ||||
|             ext_ram.copy_from_slice(&memory); | ||||
|             Ok(save_path) | ||||
|         } | ||||
|         None => Err(SaveError::NotApplicable), | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn read_boot<P: AsRef<Path>>(path: P) -> std::io::Result<[u8; BOOT_SIZE]> { | ||||
|     let mut buf = [0; BOOT_SIZE]; | ||||
|     let mut file = File::open(path.as_ref())?; | ||||
|  | ||||
|     file.read_exact(&mut buf)?; | ||||
|     Ok(buf) | ||||
| } | ||||
|  | ||||
| fn data_path() -> Option<PathBuf> { | ||||
|     match directories_next::ProjectDirs::from("dev", "musuka", crate_name!()) { | ||||
|         Some(dirs) => { | ||||
|             let data_local = dirs.data_local_dir(); | ||||
|             std::fs::create_dir_all(data_local).ok()?; | ||||
|             Some(data_local.to_path_buf()) | ||||
|         } | ||||
|         None => None, | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Error)] | ||||
| pub enum SaveError { | ||||
|     #[error("cartridge lacks title and/or external ram")] | ||||
|     NotApplicable, | ||||
|     #[error(transparent)] | ||||
|     Io(#[from] std::io::Error), | ||||
|     #[error("save file size differs from external ram")] | ||||
|     DiffSize, | ||||
| } | ||||
|   | ||||
							
								
								
									
										355
									
								
								src/gui.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										355
									
								
								src/gui.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,355 @@ | ||||
| use egui::{ClippedMesh, CtxRef, TextureId}; | ||||
| use egui_wgpu_backend::{BackendError, RenderPass, ScreenDescriptor}; | ||||
| use egui_winit_platform::Platform; | ||||
| use wgpu::{ | ||||
|     Adapter, CommandEncoder, Device, Extent3d, FilterMode, Instance, Queue, RequestDeviceError, | ||||
|     Surface, SurfaceConfiguration, SurfaceTexture, Texture, TextureFormat, TextureUsages, | ||||
|     TextureView, | ||||
| }; | ||||
| use winit::error::OsError; | ||||
| use winit::event::{ElementState, KeyboardInput}; | ||||
| use winit::event_loop::EventLoop; | ||||
| use winit::window::Window; | ||||
|  | ||||
| use crate::cpu::Cpu; | ||||
| use crate::{GB_HEIGHT, GB_WIDTH}; | ||||
|  | ||||
| const EGUI_DIMENSIONS: (usize, usize) = (1280, 720); | ||||
| const FILTER_MODE: FilterMode = FilterMode::Nearest; | ||||
| const WINDOW_TITLE: &str = "DMG-01 Emulator"; | ||||
|  | ||||
| const SCALE: f32 = 3.0; | ||||
|  | ||||
| /// Holds GUI State | ||||
| #[derive(Debug, Clone)] | ||||
| pub struct GuiState { | ||||
|     /// When true, egui winit should exit the application | ||||
|     pub quit: bool, | ||||
|     pub title: String, | ||||
|     pub mode: EmuMode, | ||||
| } | ||||
|  | ||||
| impl GuiState { | ||||
|     pub fn new(title: String) -> Self { | ||||
|         Self { | ||||
|             title, | ||||
|             quit: Default::default(), | ||||
|             mode: EmuMode::Running, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||||
| pub enum EmuMode { | ||||
|     Running, | ||||
|     StepFrame, | ||||
|     Stopped, | ||||
|     Step, | ||||
| } | ||||
|  | ||||
| /// To avoid using an [Option<KeyboardInput>] to keep track of user input from winit, | ||||
| /// we can use a "default" value. However, in order for this to work the chosen "default" | ||||
| /// value must be an **unused** key, so that it is ignored by the emulator. | ||||
| pub fn unused_key() -> KeyboardInput { | ||||
|     #![allow(deprecated)] | ||||
|  | ||||
|     KeyboardInput { | ||||
|         scancode: Default::default(), | ||||
|         state: ElementState::Released, | ||||
|         virtual_keycode: Default::default(), | ||||
|         modifiers: Default::default(), // this argument is deprecated | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub fn build_window<T>(event_loop: &EventLoop<T>) -> Result<Window, OsError> { | ||||
|     use winit::dpi::PhysicalSize; | ||||
|     use winit::window::WindowBuilder; | ||||
|  | ||||
|     WindowBuilder::new() | ||||
|         .with_decorations(true) | ||||
|         .with_resizable(true) | ||||
|         .with_transparent(false) | ||||
|         .with_title(WINDOW_TITLE) | ||||
|         .with_inner_size(PhysicalSize { | ||||
|             width: EGUI_DIMENSIONS.0 as f32, | ||||
|             height: EGUI_DIMENSIONS.1 as f32, | ||||
|         }) | ||||
|         .build(event_loop) | ||||
| } | ||||
|  | ||||
| pub fn create_surface(window: &Window) -> (Instance, Surface) { | ||||
|     use wgpu::Backends; | ||||
|  | ||||
|     let instance = Instance::new(Backends::PRIMARY); | ||||
|     let surface = unsafe { instance.create_surface(window) }; | ||||
|     (instance, surface) | ||||
| } | ||||
|  | ||||
| pub fn request_adapter(instance: &Instance, surface: &Surface) -> Option<Adapter> { | ||||
|     use wgpu::{PowerPreference, RequestAdapterOptions}; | ||||
|  | ||||
|     pollster::block_on(instance.request_adapter(&RequestAdapterOptions { | ||||
|         power_preference: PowerPreference::HighPerformance, | ||||
|         force_fallback_adapter: false, // TODO: What do I want to do with this? | ||||
|         compatible_surface: Some(surface), | ||||
|     })) | ||||
| } | ||||
|  | ||||
| pub fn request_device(adapter: &Adapter) -> Result<(Device, Queue), RequestDeviceError> { | ||||
|     use wgpu::{DeviceDescriptor, Features, Limits}; | ||||
|  | ||||
|     pollster::block_on(adapter.request_device( | ||||
|         &DeviceDescriptor { | ||||
|             label: None, | ||||
|             features: Features::default(), | ||||
|             limits: Limits::default(), | ||||
|         }, | ||||
|         None, | ||||
|     )) | ||||
| } | ||||
|  | ||||
| pub fn surface_config(window: &Window, format: TextureFormat) -> SurfaceConfiguration { | ||||
|     use wgpu::PresentMode; | ||||
|  | ||||
|     let size = window.inner_size(); | ||||
|     SurfaceConfiguration { | ||||
|         usage: TextureUsages::RENDER_ATTACHMENT, | ||||
|         format, | ||||
|         width: size.width as u32, | ||||
|         height: size.height as u32, | ||||
|         present_mode: PresentMode::Mailbox, | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub fn platform_desc(window: &Window) -> Platform { | ||||
|     use egui::FontDefinitions; | ||||
|     use egui_winit_platform::PlatformDescriptor; | ||||
|  | ||||
|     let size = window.inner_size(); | ||||
|     Platform::new(PlatformDescriptor { | ||||
|         physical_width: size.width as u32, | ||||
|         physical_height: size.height as u32, | ||||
|         scale_factor: window.scale_factor(), | ||||
|         font_definitions: FontDefinitions::default(), | ||||
|         ..Default::default() | ||||
|     }) | ||||
| } | ||||
|  | ||||
| pub fn texture_size() -> Extent3d { | ||||
|     Extent3d { | ||||
|         width: GB_WIDTH as u32, | ||||
|         height: GB_HEIGHT as u32, | ||||
|         ..Default::default() | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub fn create_texture(device: &Device, size: Extent3d) -> Texture { | ||||
|     use wgpu::{TextureDescriptor, TextureDimension}; | ||||
|  | ||||
|     device.create_texture(&TextureDescriptor { | ||||
|         size, | ||||
|         mip_level_count: 1, | ||||
|         sample_count: 1, | ||||
|         dimension: TextureDimension::D2, | ||||
|         format: TextureFormat::Rgba8UnormSrgb, | ||||
|         usage: TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING, | ||||
|         label: Some("gb_pixel_buffer"), | ||||
|     }) | ||||
| } | ||||
|  | ||||
| #[inline] | ||||
| pub fn write_to_texture( | ||||
|     queue: &Queue, | ||||
|     texture: &Texture, | ||||
|     data: &[u8; GB_WIDTH * 4 * GB_HEIGHT], | ||||
|     size: Extent3d, | ||||
| ) { | ||||
|     use std::num::NonZeroU32; | ||||
|     use wgpu::{ImageCopyTexture, ImageDataLayout, Origin3d, TextureAspect}; | ||||
|  | ||||
|     queue.write_texture( | ||||
|         ImageCopyTexture { | ||||
|             texture, | ||||
|             mip_level: 0, | ||||
|             origin: Origin3d::ZERO, | ||||
|             aspect: TextureAspect::All, | ||||
|         }, | ||||
|         data, | ||||
|         ImageDataLayout { | ||||
|             offset: 0, | ||||
|             bytes_per_row: NonZeroU32::new(4 * GB_WIDTH as u32), | ||||
|             rows_per_image: NonZeroU32::new(GB_HEIGHT as u32), | ||||
|         }, | ||||
|         size, | ||||
|     ); | ||||
| } | ||||
|  | ||||
| pub fn expose_texture_to_egui( | ||||
|     render_pass: &mut RenderPass, | ||||
|     device: &Device, | ||||
|     texture: &Texture, | ||||
| ) -> TextureId { | ||||
|     render_pass.egui_texture_from_wgpu_texture(device, texture, FILTER_MODE) | ||||
| } | ||||
|  | ||||
| #[inline] | ||||
| pub fn create_view(frame: &SurfaceTexture) -> TextureView { | ||||
|     use wgpu::TextureViewDescriptor; | ||||
|  | ||||
|     frame.texture.create_view(&TextureViewDescriptor::default()) | ||||
| } | ||||
|  | ||||
| #[inline] | ||||
| pub fn create_command_encoder(device: &Device) -> CommandEncoder { | ||||
|     use wgpu::CommandEncoderDescriptor; | ||||
|  | ||||
|     device.create_command_encoder(&CommandEncoderDescriptor { | ||||
|         label: Some("encoder"), | ||||
|     }) | ||||
| } | ||||
|  | ||||
| #[inline] | ||||
| pub fn create_screen_descriptor( | ||||
|     window: &Window, | ||||
|     config: &SurfaceConfiguration, | ||||
| ) -> ScreenDescriptor { | ||||
|     ScreenDescriptor { | ||||
|         physical_width: config.width, | ||||
|         physical_height: config.height, | ||||
|         scale_factor: window.scale_factor() as f32, | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[inline] | ||||
| pub fn execute_render_pass( | ||||
|     render_pass: &mut RenderPass, | ||||
|     encoder: &mut CommandEncoder, | ||||
|     view: &TextureView, | ||||
|     jobs: Vec<ClippedMesh>, | ||||
|     descriptor: &ScreenDescriptor, | ||||
| ) -> Result<(), BackendError> { | ||||
|     render_pass.execute(encoder, view, &jobs, descriptor, Some(wgpu::Color::BLACK)) | ||||
| } | ||||
|  | ||||
| #[inline] | ||||
| pub fn draw_egui(cpu: &Cpu, app: &mut GuiState, ctx: &CtxRef, texture_id: TextureId) { | ||||
|     use crate::{cpu, instruction, ppu}; | ||||
|  | ||||
|     fn selectable_text(ui: &mut egui::Ui, mut text: &str) -> egui::Response { | ||||
|         ui.add(egui::TextEdit::multiline(&mut text).code_editor()) | ||||
|     } | ||||
|  | ||||
|     egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { | ||||
|         egui::menu::menu(ui, "File", |ui| { | ||||
|             if ui.button("Quit").clicked() { | ||||
|                 app.quit = true; | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         egui::Window::new(&app.title).show(ctx, |ui| { | ||||
|             ui.image( | ||||
|                 texture_id, | ||||
|                 [GB_WIDTH as f32 * SCALE, GB_HEIGHT as f32 * SCALE], | ||||
|             ); | ||||
|         }); | ||||
|  | ||||
|         egui::Window::new("Disassembly").show(ctx, |ui| { | ||||
|             selectable_text(ui, &instruction::dbg::tmp_disasm(cpu, 20)); | ||||
|         }); | ||||
|  | ||||
|         egui::Window::new("Settings").show(ctx, |ui| { | ||||
|             egui::ComboBox::from_label("Emulation Mode") | ||||
|                 .selected_text(format!("{:?}", app.mode)) | ||||
|                 .show_ui(ui, |ui| { | ||||
|                     ui.selectable_value(&mut app.mode, EmuMode::Running, "Running"); | ||||
|                     ui.selectable_value(&mut app.mode, EmuMode::Stopped, "Stopped"); | ||||
|                     ui.selectable_value(&mut app.mode, EmuMode::StepFrame, "Step Frame"); | ||||
|                     ui.selectable_value(&mut app.mode, EmuMode::Step, "Step"); | ||||
|                 }) | ||||
|         }); | ||||
|  | ||||
|         egui::Window::new("GB Info").show(ctx, |ui| { | ||||
|             ui.horizontal(|ui| { | ||||
|                 ui.vertical(|ui| { | ||||
|                     ui.heading("CPU"); | ||||
|  | ||||
|                     ui.monospace(format!("AF: {:#06X}", cpu::dbg::af(cpu))); | ||||
|                     ui.monospace(format!("BC: {:#06X}", cpu::dbg::bc(cpu))); | ||||
|                     ui.monospace(format!("DE: {:#06X}", cpu::dbg::de(cpu))); | ||||
|                     ui.monospace(format!("HL: {:#06X}", cpu::dbg::hl(cpu))); | ||||
|                     ui.add_space(10.0); | ||||
|                     ui.monospace(format!("SP: {:#06X}", cpu::dbg::sp(cpu))); | ||||
|                     ui.monospace(format!("PC: {:#06X}", cpu::dbg::pc(cpu))); | ||||
|                 }); | ||||
|  | ||||
|                 ui.vertical(|ui| { | ||||
|                     let ppu = &cpu.bus.ppu; | ||||
|  | ||||
|                     ui.heading("PPU"); | ||||
|  | ||||
|                     ui.monospace(format!("LY: {}", ppu::dbg::ly(ppu))); | ||||
|                     ui.horizontal(|ui| { | ||||
|                         ui.monospace(format!("SCX: {}", ppu::dbg::scx(ppu))); | ||||
|                         ui.monospace(format!("SCY: {}", ppu::dbg::scy(ppu))); | ||||
|                     }); | ||||
|                     ui.horizontal(|ui| { | ||||
|                         ui.monospace(format!("WX: {}", ppu::dbg::wx(ppu))); | ||||
|                         ui.monospace(format!("WY: {}", ppu::dbg::wy(ppu))); | ||||
|                     }); | ||||
|  | ||||
|                     ui.monospace(format!("Mode: {:?}", ppu::dbg::mode(ppu))) | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             ui.add_space(10.0); | ||||
|  | ||||
|             let _ = ui.selectable_label(cpu::dbg::ime(cpu), "IME"); | ||||
|             ui.horizontal(|ui| { | ||||
|                 let irq = cpu.int_request(); | ||||
|  | ||||
|                 ui.label("IRQ:"); | ||||
|                 let _ = ui.selectable_label(irq & 0b01 == 0x01, "VBlank"); | ||||
|                 let _ = ui.selectable_label(irq >> 1 & 0x01 == 0x01, "LCD STAT"); | ||||
|                 let _ = ui.selectable_label(irq >> 2 & 0x01 == 0x01, "Timer"); | ||||
|                 let _ = ui.selectable_label(irq >> 3 & 0x01 == 0x01, "Serial"); | ||||
|                 let _ = ui.selectable_label(irq >> 4 & 0x01 == 0x01, "Joypad"); | ||||
|             }); | ||||
|  | ||||
|             ui.horizontal(|ui| { | ||||
|                 let ie = cpu.int_enable(); | ||||
|  | ||||
|                 let r_len = ctx.fonts().glyph_width(egui::TextStyle::Body, 'R'); | ||||
|                 let e_len = ctx.fonts().glyph_width(egui::TextStyle::Body, 'E'); | ||||
|                 let q_len = ctx.fonts().glyph_width(egui::TextStyle::Body, 'Q'); | ||||
|  | ||||
|                 ui.label("IE:"); | ||||
|                 ui.add_space(q_len - (e_len - r_len)); | ||||
|                 let _ = ui.selectable_label(ie & 0b01 == 0x01, "VBlank"); | ||||
|                 let _ = ui.selectable_label(ie >> 1 & 0x01 == 0x01, "LCD STAT"); | ||||
|                 let _ = ui.selectable_label(ie >> 2 & 0x01 == 0x01, "Timer"); | ||||
|                 let _ = ui.selectable_label(ie >> 3 & 0x01 == 0x01, "Serial"); | ||||
|                 let _ = ui.selectable_label(ie >> 4 & 0x01 == 0x01, "Joypad"); | ||||
|             }); | ||||
|  | ||||
|             ui.add_space(10.0); | ||||
|  | ||||
|             ui.horizontal(|ui| { | ||||
|                 let flags = cpu::dbg::flags(cpu); | ||||
|  | ||||
|                 let _ = ui.selectable_label((flags >> 7 & 0x01) == 0x01, "Zero"); | ||||
|                 let _ = ui.selectable_label((flags >> 6 & 0x01) == 0x01, "Negative"); | ||||
|                 let _ = ui.selectable_label((flags >> 5 & 0x01) == 0x01, "Half-Carry"); | ||||
|                 let _ = ui.selectable_label((flags >> 4 & 0x01) == 0x01, "Carry"); | ||||
|             }) | ||||
|         }) | ||||
|     }); | ||||
| } | ||||
|  | ||||
| pub mod kbd { | ||||
|     use winit::event::{ElementState, KeyboardInput, VirtualKeyCode}; | ||||
|  | ||||
|     pub fn space_released(input: &KeyboardInput) -> bool { | ||||
|         let keycode = input.virtual_keycode; | ||||
|         matches!(input.state, ElementState::Released if keycode == Some(VirtualKeyCode::Space)) | ||||
|     } | ||||
| } | ||||
| @@ -17,11 +17,11 @@ impl Default for HighRam { | ||||
| } | ||||
|  | ||||
| impl BusIo for HighRam { | ||||
|     fn write_byte(&mut self, addr: u16, byte: u8) { | ||||
|         self.buf[addr as usize - HIGH_RAM_START_ADDRESS] = byte; | ||||
|     } | ||||
|  | ||||
|     fn read_byte(&self, addr: u16) -> u8 { | ||||
|         self.buf[addr as usize - HIGH_RAM_START_ADDRESS] | ||||
|     } | ||||
|  | ||||
|     fn write_byte(&mut self, addr: u16, byte: u8) { | ||||
|         self.buf[addr as usize - HIGH_RAM_START_ADDRESS] = byte; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| use self::add::{Source as AddSource, Target as AddTarget}; | ||||
| use self::alu::Source as AluSource; | ||||
| use self::jump::{JumpCondition, JumpLocation}; | ||||
| use self::jump::{JpCond, JpLoc}; | ||||
| use self::load::{Source as LDSource, Target as LDTarget}; | ||||
| use self::table::{ | ||||
|     alu_imm_instr, alu_reg_instr, flag_instr, group1, group2, group3, jump_cond, prefix_alu, | ||||
| @@ -16,7 +16,7 @@ use crate::Cycle; | ||||
| pub(crate) enum Instruction { | ||||
|     NOP, | ||||
|     STOP, | ||||
|     JR(JumpCondition), | ||||
|     JR(JpCond), | ||||
|     LD(LDTarget, LDSource), | ||||
|     ADD(AddTarget, AddSource), | ||||
|     LDHL, | ||||
| @@ -38,13 +38,13 @@ pub(crate) enum Instruction { | ||||
|     XOR(AluSource), | ||||
|     OR(AluSource), | ||||
|     CP(AluSource), | ||||
|     RET(JumpCondition), | ||||
|     RET(JpCond), | ||||
|     POP(Group3RegisterPair), | ||||
|     RETI, | ||||
|     JP(JumpCondition, JumpLocation), | ||||
|     JP(JpCond, JpLoc), | ||||
|     DI, | ||||
|     EI, | ||||
|     CALL(JumpCondition), | ||||
|     CALL(JpCond), | ||||
|     PUSH(Group3RegisterPair), | ||||
|     RST(u8), | ||||
|     RLC(Register), | ||||
| @@ -58,6 +58,7 @@ pub(crate) enum Instruction { | ||||
|     BIT(u8, Register), | ||||
|     RES(u8, Register), | ||||
|     SET(u8, Register), | ||||
|     Invalid, | ||||
| } | ||||
|  | ||||
| impl std::fmt::Debug for Instruction { | ||||
| @@ -109,6 +110,7 @@ impl std::fmt::Debug for Instruction { | ||||
|             BIT(b, r) => write!(f, "BIT {}, {:?}", b, r), | ||||
|             RES(b, r) => write!(f, "RES {}, {:?}", b, r), | ||||
|             SET(b, r) => write!(f, "SET {}, {:?}", b, r), | ||||
|             Invalid => f.write_str("???"), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -116,7 +118,7 @@ impl std::fmt::Debug for Instruction { | ||||
| impl Instruction { | ||||
|     pub(crate) fn execute(cpu: &mut Cpu, instruction: Self) -> Cycle { | ||||
|         match instruction { | ||||
|             Instruction::NOP => (4), | ||||
|             Instruction::NOP => 4, | ||||
|             Instruction::LD(target, src) => match (target, src) { | ||||
|                 (LDTarget::IndirectImmediateWord, LDSource::SP) => { | ||||
|                     // LD (u16), SP | Store stack pointer in byte at 16-bit register | ||||
| @@ -293,7 +295,7 @@ impl Instruction { | ||||
|                 let addr = pc.wrapping_add(byte as u16); | ||||
|  | ||||
|                 match cond { | ||||
|                     JumpCondition::NotZero => { | ||||
|                     JpCond::NotZero => { | ||||
|                         if !flags.z() { | ||||
|                             Self::jump(cpu, addr); | ||||
|                             12 | ||||
| @@ -301,7 +303,7 @@ impl Instruction { | ||||
|                             8 | ||||
|                         } | ||||
|                     } | ||||
|                     JumpCondition::Zero => { | ||||
|                     JpCond::Zero => { | ||||
|                         if flags.z() { | ||||
|                             Self::jump(cpu, addr); | ||||
|                             12 | ||||
| @@ -309,7 +311,7 @@ impl Instruction { | ||||
|                             8 | ||||
|                         } | ||||
|                     } | ||||
|                     JumpCondition::NotCarry => { | ||||
|                     JpCond::NotCarry => { | ||||
|                         if !flags.c() { | ||||
|                             Self::jump(cpu, addr); | ||||
|                             12 | ||||
| @@ -317,7 +319,7 @@ impl Instruction { | ||||
|                             8 | ||||
|                         } | ||||
|                     } | ||||
|                     JumpCondition::Carry => { | ||||
|                     JpCond::Carry => { | ||||
|                         if flags.c() { | ||||
|                             Self::jump(cpu, addr); | ||||
|                             12 | ||||
| @@ -325,7 +327,7 @@ impl Instruction { | ||||
|                             8 | ||||
|                         } | ||||
|                     } | ||||
|                     JumpCondition::Always => { | ||||
|                     JpCond::Always => { | ||||
|                         Self::jump(cpu, addr); | ||||
|                         12 | ||||
|                     } | ||||
| @@ -334,7 +336,6 @@ impl Instruction { | ||||
|             Instruction::ADD(target, src) => match (target, src) { | ||||
|                 (AddTarget::HL, AddSource::Group1(pair)) => { | ||||
|                     // ADD HL, r16 | Add 16-bit register to HL | ||||
|                     // FIXME: Memory Timings are not properly emulated for this instruction | ||||
|                     use Group1RegisterPair::*; | ||||
|                     let mut flags: Flags = *cpu.flags(); | ||||
|  | ||||
| @@ -342,10 +343,11 @@ impl Instruction { | ||||
|                         BC | DE | HL | SP => { | ||||
|                             let left = cpu.register_pair(RegisterPair::HL); | ||||
|                             let right = cpu.register_pair(pair.as_register_pair()); | ||||
|                             cpu.set_register_pair( | ||||
|                                 RegisterPair::HL, | ||||
|                                 Self::add_u16(left, right, &mut flags), | ||||
|                             ); | ||||
|                             let result = Self::add_u16(left, right, &mut flags); | ||||
|  | ||||
|                             cpu.set_register(CpuRegister::L, result as u8); | ||||
|                             cpu.bus.clock(); | ||||
|                             cpu.set_register(CpuRegister::H, (result >> 8) as u8); | ||||
|                         } | ||||
|                     } | ||||
|                     cpu.set_flags(flags); | ||||
| @@ -361,12 +363,12 @@ impl Instruction { | ||||
|                     let (cycles, sum) = match reg { | ||||
|                         B | C | D | E | H | L | A => { | ||||
|                             let right = cpu.register(reg.cpu_register()); | ||||
|                             ((4), Self::add(left, right, &mut flags)) | ||||
|                             (4, Self::add(left, right, &mut flags)) | ||||
|                         } | ||||
|                         IndirectHL => { | ||||
|                             let addr = cpu.register_pair(RegisterPair::HL); | ||||
|                             let right = Self::read_byte(&mut cpu.bus, addr); | ||||
|                             ((8), Self::add(left, right, &mut flags)) | ||||
|                             (8, Self::add(left, right, &mut flags)) | ||||
|                         } | ||||
|                     }; | ||||
|  | ||||
| @@ -376,12 +378,15 @@ impl Instruction { | ||||
|                 } | ||||
|                 (AddTarget::SP, AddSource::ImmediateSignedByte) => { | ||||
|                     // ADD SP, i8 | Add i8 to stack pointer | ||||
|                     // FIXME: Memory Timings are not properly emulated for this instruction | ||||
|                     let mut flags: Flags = *cpu.flags(); | ||||
|  | ||||
|                     let left = cpu.register_pair(RegisterPair::SP); | ||||
|                     let sum = Self::add_u16_i8(left, Self::imm_byte(cpu) as i8, &mut flags); | ||||
|                     cpu.bus.clock(); // internal | ||||
|  | ||||
|                     cpu.set_register_pair(RegisterPair::SP, sum); | ||||
|                     cpu.bus.clock(); | ||||
|  | ||||
|                     cpu.set_flags(flags); | ||||
|                     16 | ||||
|                 } | ||||
| @@ -423,7 +428,6 @@ impl Instruction { | ||||
|                     AllRegisters::Group1(pair) => { | ||||
|                         // INC r16 | Increment 16-bit register | ||||
|                         // Note: No flags are set with this version of the INC instruction | ||||
|                         // FIXME: Memory Timings are not properly emulated for this instruction | ||||
|                         use Group1RegisterPair::*; | ||||
|  | ||||
|                         match pair { | ||||
| @@ -431,6 +435,7 @@ impl Instruction { | ||||
|                                 let pair = pair.as_register_pair(); | ||||
|                                 let left = cpu.register_pair(pair); | ||||
|                                 cpu.set_register_pair(pair, left.wrapping_add(1)); | ||||
|                                 cpu.bus.clock(); // internal | ||||
|                             } | ||||
|                         } | ||||
|                         8 | ||||
| @@ -462,7 +467,6 @@ impl Instruction { | ||||
|                     } | ||||
|                     AllRegisters::Group1(pair) => { | ||||
|                         // DEC r16 | Decrement Register Pair | ||||
|                         // FIXME: Memory Timings are not properly emulated for this instruction | ||||
|                         use Group1RegisterPair::*; | ||||
|  | ||||
|                         match pair { | ||||
| @@ -470,6 +474,7 @@ impl Instruction { | ||||
|                                 let pair = pair.as_register_pair(); | ||||
|                                 let left = cpu.register_pair(pair); | ||||
|                                 cpu.set_register_pair(pair, left.wrapping_sub(1)); | ||||
|                                 cpu.bus.clock(); // internal | ||||
|                             } | ||||
|                         }; | ||||
|                         8 | ||||
| @@ -610,13 +615,13 @@ impl Instruction { | ||||
|                         B | C | D | E | H | L | A => { | ||||
|                             let right = cpu.register(reg.cpu_register()); | ||||
|                             let sum = Self::add_with_carry_bit(left, right, flags.c(), &mut flags); | ||||
|                             ((4), sum) | ||||
|                             (4, sum) | ||||
|                         } | ||||
|                         IndirectHL => { | ||||
|                             let addr = cpu.register_pair(RegisterPair::HL); | ||||
|                             let right = Self::read_byte(&mut cpu.bus, addr); | ||||
|                             let sum = Self::add_with_carry_bit(left, right, flags.c(), &mut flags); | ||||
|                             ((8), sum) | ||||
|                             (8, sum) | ||||
|                         } | ||||
|                     }; | ||||
|                     cpu.set_register(CpuRegister::A, sum); | ||||
| @@ -646,12 +651,12 @@ impl Instruction { | ||||
|                     let (cycles, diff) = match reg { | ||||
|                         B | C | D | E | H | L | A => { | ||||
|                             let right = cpu.register(reg.cpu_register()); | ||||
|                             ((4), Self::sub(left, right, &mut flags)) | ||||
|                             (4, Self::sub(left, right, &mut flags)) | ||||
|                         } | ||||
|                         IndirectHL => { | ||||
|                             let addr = cpu.register_pair(RegisterPair::HL); | ||||
|                             let right = Self::read_byte(&mut cpu.bus, addr); | ||||
|                             ((8), Self::sub(left, right, &mut flags)) | ||||
|                             (8, Self::sub(left, right, &mut flags)) | ||||
|                         } | ||||
|                     }; | ||||
|                     cpu.set_register(CpuRegister::A, diff); | ||||
| @@ -681,13 +686,13 @@ impl Instruction { | ||||
|                         B | C | D | E | H | L | A => { | ||||
|                             let right = cpu.register(reg.cpu_register()); | ||||
|                             let diff = Self::sub_with_carry(left, right, flags.c(), &mut flags); | ||||
|                             ((4), diff) | ||||
|                             (4, diff) | ||||
|                         } | ||||
|                         IndirectHL => { | ||||
|                             let addr = cpu.register_pair(RegisterPair::HL); | ||||
|                             let right = Self::read_byte(&mut cpu.bus, addr); | ||||
|                             let diff = Self::sub_with_carry(left, right, flags.c(), &mut flags); | ||||
|                             ((8), diff) | ||||
|                             (8, diff) | ||||
|                         } | ||||
|                     }; | ||||
|                     cpu.set_register(CpuRegister::A, diff); | ||||
| @@ -717,7 +722,7 @@ impl Instruction { | ||||
|                         IndirectHL => { | ||||
|                             let addr = cpu.register_pair(RegisterPair::HL); | ||||
|                             let right = Self::read_byte(&mut cpu.bus, addr); | ||||
|                             ((8), left & right) | ||||
|                             (8, left & right) | ||||
|                         } | ||||
|                     }; | ||||
|                     cpu.set_register(CpuRegister::A, acc); | ||||
| @@ -743,7 +748,7 @@ impl Instruction { | ||||
|                         IndirectHL => { | ||||
|                             let addr = cpu.register_pair(RegisterPair::HL); | ||||
|                             let right = Self::read_byte(&mut cpu.bus, addr); | ||||
|                             ((8), left ^ right) | ||||
|                             (8, left ^ right) | ||||
|                         } | ||||
|                     }; | ||||
|                     cpu.set_register(CpuRegister::A, acc); | ||||
| @@ -769,7 +774,7 @@ impl Instruction { | ||||
|                         IndirectHL => { | ||||
|                             let addr = cpu.register_pair(RegisterPair::HL); | ||||
|                             let right = Self::read_byte(&mut cpu.bus, addr); | ||||
|                             ((8), left | right) | ||||
|                             (8, left | right) | ||||
|                         } | ||||
|                     }; | ||||
|                     cpu.set_register(CpuRegister::A, acc); | ||||
| @@ -834,7 +839,7 @@ impl Instruction { | ||||
|                 let flags: Flags = *cpu.flags(); | ||||
|  | ||||
|                 match cond { | ||||
|                     JumpCondition::NotZero => { | ||||
|                     JpCond::NotZero => { | ||||
|                         cpu.bus.clock(); // internal branch decision | ||||
|  | ||||
|                         if !flags.z() { | ||||
| @@ -845,7 +850,7 @@ impl Instruction { | ||||
|                             8 | ||||
|                         } | ||||
|                     } | ||||
|                     JumpCondition::Zero => { | ||||
|                     JpCond::Zero => { | ||||
|                         cpu.bus.clock(); // internal branch decision | ||||
|  | ||||
|                         if flags.z() { | ||||
| @@ -856,7 +861,7 @@ impl Instruction { | ||||
|                             8 | ||||
|                         } | ||||
|                     } | ||||
|                     JumpCondition::NotCarry => { | ||||
|                     JpCond::NotCarry => { | ||||
|                         cpu.bus.clock(); // internal branch decision | ||||
|  | ||||
|                         if !flags.c() { | ||||
| @@ -867,7 +872,7 @@ impl Instruction { | ||||
|                             8 | ||||
|                         } | ||||
|                     } | ||||
|                     JumpCondition::Carry => { | ||||
|                     JpCond::Carry => { | ||||
|                         cpu.bus.clock(); // internal branch decision | ||||
|  | ||||
|                         if flags.c() { | ||||
| @@ -878,7 +883,7 @@ impl Instruction { | ||||
|                             8 | ||||
|                         } | ||||
|                     } | ||||
|                     JumpCondition::Always => { | ||||
|                     JpCond::Always => { | ||||
|                         let addr = Self::pop(cpu); | ||||
|                         Self::jump(cpu, addr); | ||||
|                         16 | ||||
| @@ -905,13 +910,13 @@ impl Instruction { | ||||
|                 16 | ||||
|             } | ||||
|             Instruction::JP(cond, location) => match location { | ||||
|                 JumpLocation::HL => { | ||||
|                 JpLoc::HL => { | ||||
|                     // JP HL | Store HL in program counter | ||||
|                     let right = cpu.register_pair(RegisterPair::HL); | ||||
|                     cpu.set_register_pair(RegisterPair::PC, right); | ||||
|                     4 | ||||
|                 } | ||||
|                 JumpLocation::ImmediateWord => { | ||||
|                 JpLoc::ImmediateWord => { | ||||
|                     // JP cond u16 | Store u16 in program counter if condition is true | ||||
|                     // JP u16      | Store u16 in program counter | ||||
|                     let flags: Flags = *cpu.flags(); | ||||
| @@ -919,7 +924,7 @@ impl Instruction { | ||||
|                     let addr = Self::imm_word(cpu); | ||||
|  | ||||
|                     match cond { | ||||
|                         JumpCondition::NotZero => { | ||||
|                         JpCond::NotZero => { | ||||
|                             if !flags.z() { | ||||
|                                 Self::jump(cpu, addr); | ||||
|                                 16 | ||||
| @@ -927,7 +932,7 @@ impl Instruction { | ||||
|                                 12 | ||||
|                             } | ||||
|                         } | ||||
|                         JumpCondition::Zero => { | ||||
|                         JpCond::Zero => { | ||||
|                             if flags.z() { | ||||
|                                 Self::jump(cpu, addr); | ||||
|                                 16 | ||||
| @@ -935,7 +940,7 @@ impl Instruction { | ||||
|                                 12 | ||||
|                             } | ||||
|                         } | ||||
|                         JumpCondition::NotCarry => { | ||||
|                         JpCond::NotCarry => { | ||||
|                             if !flags.c() { | ||||
|                                 Self::jump(cpu, addr); | ||||
|                                 16 | ||||
| @@ -943,7 +948,7 @@ impl Instruction { | ||||
|                                 12 | ||||
|                             } | ||||
|                         } | ||||
|                         JumpCondition::Carry => { | ||||
|                         JpCond::Carry => { | ||||
|                             if flags.c() { | ||||
|                                 Self::jump(cpu, addr); | ||||
|                                 16 | ||||
| @@ -951,7 +956,7 @@ impl Instruction { | ||||
|                                 12 | ||||
|                             } | ||||
|                         } | ||||
|                         JumpCondition::Always => { | ||||
|                         JpCond::Always => { | ||||
|                             Self::jump(cpu, addr); | ||||
|                             16 | ||||
|                         } | ||||
| @@ -977,7 +982,7 @@ impl Instruction { | ||||
|                 let return_addr = cpu.register_pair(RegisterPair::PC); | ||||
|  | ||||
|                 match cond { | ||||
|                     JumpCondition::NotZero => { | ||||
|                     JpCond::NotZero => { | ||||
|                         if !flags.z() { | ||||
|                             cpu.bus.clock(); // internal branch decision | ||||
|                             Self::push(cpu, return_addr); | ||||
| @@ -987,7 +992,7 @@ impl Instruction { | ||||
|                             12 | ||||
|                         } | ||||
|                     } | ||||
|                     JumpCondition::Zero => { | ||||
|                     JpCond::Zero => { | ||||
|                         if flags.z() { | ||||
|                             cpu.bus.clock(); // internal branch decision | ||||
|                             Self::push(cpu, return_addr); | ||||
| @@ -997,7 +1002,7 @@ impl Instruction { | ||||
|                             12 | ||||
|                         } | ||||
|                     } | ||||
|                     JumpCondition::NotCarry => { | ||||
|                     JpCond::NotCarry => { | ||||
|                         if !flags.c() { | ||||
|                             cpu.bus.clock(); // internal branch decision | ||||
|                             Self::push(cpu, return_addr); | ||||
| @@ -1007,7 +1012,7 @@ impl Instruction { | ||||
|                             12 | ||||
|                         } | ||||
|                     } | ||||
|                     JumpCondition::Carry => { | ||||
|                     JpCond::Carry => { | ||||
|                         if flags.c() { | ||||
|                             cpu.bus.clock(); // internal branch decision | ||||
|                             Self::push(cpu, return_addr); | ||||
| @@ -1017,7 +1022,7 @@ impl Instruction { | ||||
|                             12 | ||||
|                         } | ||||
|                     } | ||||
|                     JumpCondition::Always => { | ||||
|                     JpCond::Always => { | ||||
|                         cpu.bus.clock(); // internal branch decision | ||||
|                         Self::push(cpu, return_addr); | ||||
|                         cpu.set_register_pair(RegisterPair::PC, addr); | ||||
| @@ -1053,14 +1058,14 @@ impl Instruction { | ||||
|                         let byte = cpu.register(reg); | ||||
|                         let rotated = byte.rotate_left(1); | ||||
|                         cpu.set_register(reg, rotated); | ||||
|                         ((8), byte >> 7, rotated) | ||||
|                         (8, byte >> 7, rotated) | ||||
|                     } | ||||
|                     IndirectHL => { | ||||
|                         let addr = cpu.register_pair(RegisterPair::HL); | ||||
|                         let byte = Self::read_byte(&mut cpu.bus, addr); | ||||
|                         let rotated = byte.rotate_left(1); | ||||
|                         Self::write_byte(&mut cpu.bus, addr, rotated); | ||||
|                         ((16), byte >> 7, rotated) | ||||
|                         (16, byte >> 7, rotated) | ||||
|                     } | ||||
|                 }; | ||||
|                 cpu.update_flags(rotated == 0, false, false, most_sgfnt == 0x01); | ||||
| @@ -1076,14 +1081,14 @@ impl Instruction { | ||||
|                         let byte = cpu.register(reg); | ||||
|                         let rotated = byte.rotate_right(1); | ||||
|                         cpu.set_register(reg, rotated); | ||||
|                         ((8), byte & 0x01, rotated) | ||||
|                         (8, byte & 0x01, rotated) | ||||
|                     } | ||||
|                     IndirectHL => { | ||||
|                         let addr = cpu.register_pair(RegisterPair::HL); | ||||
|                         let byte = Self::read_byte(&mut cpu.bus, addr); | ||||
|                         let rotated = byte.rotate_right(1); | ||||
|                         Self::write_byte(&mut cpu.bus, addr, rotated); | ||||
|                         ((16), byte & 0x01, rotated) | ||||
|                         (16, byte & 0x01, rotated) | ||||
|                     } | ||||
|                 }; | ||||
|                 cpu.update_flags(rotated == 0, false, false, least_sgfnt == 0x01); | ||||
| @@ -1101,14 +1106,14 @@ impl Instruction { | ||||
|                         let byte = cpu.register(reg); | ||||
|                         let (rotated, carry) = Self::rl_thru_carry(byte, flags.c()); | ||||
|                         cpu.set_register(reg, rotated); | ||||
|                         ((8), rotated, carry) | ||||
|                         (8, rotated, carry) | ||||
|                     } | ||||
|                     IndirectHL => { | ||||
|                         let addr = cpu.register_pair(RegisterPair::HL); | ||||
|                         let byte = Self::read_byte(&mut cpu.bus, addr); | ||||
|                         let (rotated, carry) = Self::rl_thru_carry(byte, flags.c()); | ||||
|                         Self::write_byte(&mut cpu.bus, addr, rotated); | ||||
|                         ((16), rotated, carry) | ||||
|                         (16, rotated, carry) | ||||
|                     } | ||||
|                 }; | ||||
|                 cpu.update_flags(rotated == 0, false, false, carry); | ||||
| @@ -1126,14 +1131,14 @@ impl Instruction { | ||||
|                         let byte = cpu.register(reg); | ||||
|                         let (rotated, carry) = Self::rr_thru_carry(byte, flags.c()); | ||||
|                         cpu.set_register(reg, rotated); | ||||
|                         ((8), rotated, carry) | ||||
|                         (8, rotated, carry) | ||||
|                     } | ||||
|                     IndirectHL => { | ||||
|                         let addr = cpu.register_pair(RegisterPair::HL); | ||||
|                         let byte = Self::read_byte(&mut cpu.bus, addr); | ||||
|                         let (rotated, carry) = Self::rr_thru_carry(byte, flags.c()); | ||||
|                         Self::write_byte(&mut cpu.bus, addr, rotated); | ||||
|                         ((16), rotated, carry) | ||||
|                         (16, rotated, carry) | ||||
|                     } | ||||
|                 }; | ||||
|                 cpu.update_flags(rotated == 0, false, false, carry); | ||||
| @@ -1149,14 +1154,14 @@ impl Instruction { | ||||
|                         let byte = cpu.register(reg); | ||||
|                         let shifted = byte << 1; | ||||
|                         cpu.set_register(reg, shifted); | ||||
|                         ((8), (byte >> 7) & 0x01, shifted) | ||||
|                         (8, (byte >> 7) & 0x01, shifted) | ||||
|                     } | ||||
|                     IndirectHL => { | ||||
|                         let addr = cpu.register_pair(RegisterPair::HL); | ||||
|                         let byte = Self::read_byte(&mut cpu.bus, addr); | ||||
|                         let shifted = byte << 1; | ||||
|                         Self::write_byte(&mut cpu.bus, addr, shifted); | ||||
|                         ((16), (byte >> 7) & 0x01, shifted) | ||||
|                         (16, (byte >> 7) & 0x01, shifted) | ||||
|                     } | ||||
|                 }; | ||||
|                 cpu.update_flags(shifted == 0, false, false, most_sgfnt == 0x01); | ||||
| @@ -1172,14 +1177,14 @@ impl Instruction { | ||||
|                         let byte = cpu.register(reg); | ||||
|                         let shifted = ((byte >> 7) & 0x01) << 7 | byte >> 1; | ||||
|                         cpu.set_register(reg, shifted); | ||||
|                         ((8), byte & 0x01, shifted) | ||||
|                         (8, byte & 0x01, shifted) | ||||
|                     } | ||||
|                     IndirectHL => { | ||||
|                         let addr = cpu.register_pair(RegisterPair::HL); | ||||
|                         let byte = Self::read_byte(&mut cpu.bus, addr); | ||||
|                         let shifted = ((byte >> 7) & 0x01) << 7 | byte >> 1; | ||||
|                         Self::write_byte(&mut cpu.bus, addr, shifted); | ||||
|                         ((16), byte & 0x01, shifted) | ||||
|                         (16, byte & 0x01, shifted) | ||||
|                     } | ||||
|                 }; | ||||
|                 cpu.update_flags(shifted == 0, false, false, least_sgfnt == 0x01); | ||||
| @@ -1194,14 +1199,14 @@ impl Instruction { | ||||
|                         let reg = reg.cpu_register(); | ||||
|                         let swapped = Self::swap_bits(cpu.register(reg)); | ||||
|                         cpu.set_register(reg, swapped); | ||||
|                         ((8), swapped) | ||||
|                         (8, swapped) | ||||
|                     } | ||||
|  | ||||
|                     IndirectHL => { | ||||
|                         let addr = cpu.register_pair(RegisterPair::HL); | ||||
|                         let swapped = Self::swap_bits(Self::read_byte(&mut cpu.bus, addr)); | ||||
|                         Self::write_byte(&mut cpu.bus, addr, swapped); | ||||
|                         ((16), swapped) | ||||
|                         (16, swapped) | ||||
|                     } | ||||
|                 }; | ||||
|                 cpu.update_flags(swapped == 0, false, false, false); | ||||
| @@ -1217,14 +1222,14 @@ impl Instruction { | ||||
|                         let byte = cpu.register(reg); | ||||
|                         let shifted = byte >> 1; | ||||
|                         cpu.set_register(reg, shifted); | ||||
|                         ((8), byte & 0x01, shifted) | ||||
|                         (8, byte & 0x01, shifted) | ||||
|                     } | ||||
|                     IndirectHL => { | ||||
|                         let addr = cpu.register_pair(RegisterPair::HL); | ||||
|                         let byte = Self::read_byte(&mut cpu.bus, addr); | ||||
|                         let shifted = byte >> 1; | ||||
|                         Self::write_byte(&mut cpu.bus, addr, shifted); | ||||
|                         ((16), byte & 0x01, shifted) | ||||
|                         (16, byte & 0x01, shifted) | ||||
|                     } | ||||
|                 }; | ||||
|                 cpu.update_flags(shift_reg == 0, false, false, least_sgfnt == 0x01); | ||||
| @@ -1239,12 +1244,12 @@ impl Instruction { | ||||
|                     B | C | D | E | H | L | A => { | ||||
|                         let reg = reg.cpu_register(); | ||||
|                         let byte = cpu.register(reg); | ||||
|                         ((8), ((byte >> bit) & 0x01) == 0x01) | ||||
|                         (8, ((byte >> bit) & 0x01) == 0x01) | ||||
|                     } | ||||
|                     IndirectHL => { | ||||
|                         let addr = cpu.register_pair(RegisterPair::HL); | ||||
|                         let byte = Self::read_byte(&mut cpu.bus, addr); | ||||
|                         ((12), ((byte >> bit) & 0x01) == 0x01) | ||||
|                         (12, ((byte >> bit) & 0x01) == 0x01) | ||||
|                     } | ||||
|                 }; | ||||
|                 flags.set_z(!is_set); | ||||
| @@ -1291,6 +1296,7 @@ impl Instruction { | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             Instruction::Invalid => panic!("Attempted to execute invalid instruction"), | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -1554,7 +1560,7 @@ impl Instruction { | ||||
|             // STOP | ||||
|             0o020 => STOP, | ||||
|             // JR i8 | ||||
|             0o030 => JR(JumpCondition::Always), | ||||
|             0o030 => JR(JpCond::Always), | ||||
|             // JR cond i8 | ||||
|             0o040 | 0o050 | 0o060 | 0o070 => JR(jump_cond((byte >> 3) & 0x03)), | ||||
|             // LD r16, u16 | ||||
| @@ -1619,16 +1625,16 @@ impl Instruction { | ||||
|             // POP r16 | ||||
|             0o301 | 0o321 | 0o341 | 0o361 => POP(group3((byte >> 4) & 0x03)), | ||||
|             // RET | ||||
|             0o311 => RET(JumpCondition::Always), | ||||
|             0o311 => RET(JpCond::Always), | ||||
|             // RETI | ||||
|             0o331 => RETI, | ||||
|             // JP HL | ||||
|             0o351 => JP(JumpCondition::Always, JumpLocation::HL), | ||||
|             0o351 => JP(JpCond::Always, JpLoc::HL), | ||||
|             // LD SP, HL | ||||
|             0o371 => LD(LDTarget::SP, LDSource::HL), | ||||
|             // JP cond u16 | ||||
|             0o302 | 0o312 | 0o322 | 0o332 => { | ||||
|                 JP(jump_cond((byte >> 3) & 0x03), JumpLocation::ImmediateWord) | ||||
|                 JP(jump_cond((byte >> 3) & 0x03), JpLoc::ImmediateWord) | ||||
|             } | ||||
|             // LD (0xFF00 + C), A | ||||
|             0o342 => LD(LDTarget::IoWithC, LDSource::A), | ||||
| @@ -1639,9 +1645,7 @@ impl Instruction { | ||||
|             // LD A, (u16) | ||||
|             0o372 => LD(LDTarget::A, LDSource::IndirectImmediateWord), | ||||
|             // JP u16 | ||||
|             0o303 => JP(JumpCondition::Always, JumpLocation::ImmediateWord), | ||||
|             // 0xCB Prefix | ||||
|             0o313 => unreachable!("{:#04X} should be handled by the prefixed decoder", byte), | ||||
|             0o303 => JP(JpCond::Always, JpLoc::ImmediateWord), | ||||
|             // DI | ||||
|             0o363 => DI, | ||||
|             // EI | ||||
| @@ -1650,12 +1654,12 @@ impl Instruction { | ||||
|             0o304 | 0o314 | 0o324 | 0o334 => CALL(jump_cond((byte >> 3) & 0x03)), | ||||
|             // PUSH r16 | ||||
|             0o305 | 0o325 | 0o345 | 0o365 => PUSH(group3((byte >> 4) & 0x03)), | ||||
|             0o315 => CALL(JumpCondition::Always), | ||||
|             0o315 => CALL(JpCond::Always), | ||||
|             0o306 | 0o316 | 0o326 | 0o336 | 0o346 | 0o356 | 0o366 | 0o376 => { | ||||
|                 alu_imm_instr((byte >> 3) & 0x07) | ||||
|             } | ||||
|             0o307 | 0o317 | 0o327 | 0o337 | 0o347 | 0o357 | 0o367 | 0o377 => RST(byte & 0b00111000), | ||||
|             _ => panic!("{:#04X} is an illegal opcode", byte), | ||||
|             _ => Invalid, // 0xCB is 0o313 | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -1678,7 +1682,7 @@ impl Instruction { | ||||
| mod jump { | ||||
|  | ||||
|     #[derive(Clone, Copy)] | ||||
|     pub(crate) enum JumpCondition { | ||||
|     pub(crate) enum JpCond { | ||||
|         Always, | ||||
|         NotZero, | ||||
|         Zero, | ||||
| @@ -1686,9 +1690,9 @@ mod jump { | ||||
|         Carry, | ||||
|     } | ||||
|  | ||||
|     impl std::fmt::Debug for JumpCondition { | ||||
|     impl std::fmt::Debug for JpCond { | ||||
|         fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||
|             use JumpCondition::*; | ||||
|             use JpCond::*; | ||||
|  | ||||
|             match self { | ||||
|                 Always => f.write_str(""), | ||||
| @@ -1701,14 +1705,14 @@ mod jump { | ||||
|     } | ||||
|  | ||||
|     #[derive(Clone, Copy)] | ||||
|     pub(crate) enum JumpLocation { | ||||
|     pub(crate) enum JpLoc { | ||||
|         HL, | ||||
|         ImmediateWord, | ||||
|     } | ||||
|  | ||||
|     impl std::fmt::Debug for JumpLocation { | ||||
|     impl std::fmt::Debug for JpLoc { | ||||
|         fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||
|             use JumpLocation::*; | ||||
|             use JpLoc::*; | ||||
|  | ||||
|             match *self { | ||||
|                 HL => f.write_str("HL"), | ||||
| @@ -1868,7 +1872,7 @@ mod load { | ||||
| mod table { | ||||
|     use super::add::{Source as AddSource, Target as AddTarget}; | ||||
|     use super::alu::Source as AluSource; | ||||
|     use super::{Instruction, JumpCondition}; | ||||
|     use super::{Instruction, JpCond}; | ||||
|     use crate::cpu::{Register as CpuRegister, RegisterPair}; | ||||
|  | ||||
|     #[derive(Clone, Copy)] | ||||
| @@ -1893,7 +1897,7 @@ mod table { | ||||
|     } | ||||
|  | ||||
|     impl Group1RegisterPair { | ||||
|         pub fn as_register_pair(&self) -> RegisterPair { | ||||
|         pub(crate) fn as_register_pair(&self) -> RegisterPair { | ||||
|             use Group1RegisterPair::*; | ||||
|  | ||||
|             match self { | ||||
| @@ -1927,7 +1931,7 @@ mod table { | ||||
|     } | ||||
|  | ||||
|     impl Group2RegisterPair { | ||||
|         pub fn as_register_pair(&self) -> RegisterPair { | ||||
|         pub(crate) fn as_register_pair(&self) -> RegisterPair { | ||||
|             use Group2RegisterPair::*; | ||||
|  | ||||
|             match self { | ||||
| @@ -1961,7 +1965,7 @@ mod table { | ||||
|     } | ||||
|  | ||||
|     impl Group3RegisterPair { | ||||
|         pub fn as_register_pair(&self) -> RegisterPair { | ||||
|         pub(crate) fn as_register_pair(&self) -> RegisterPair { | ||||
|             use Group3RegisterPair::*; | ||||
|  | ||||
|             match self { | ||||
| @@ -2003,7 +2007,7 @@ mod table { | ||||
|     } | ||||
|  | ||||
|     impl Register { | ||||
|         pub fn cpu_register(&self) -> CpuRegister { | ||||
|         pub(crate) fn cpu_register(&self) -> CpuRegister { | ||||
|             use Register::*; | ||||
|  | ||||
|             match self { | ||||
| @@ -2071,8 +2075,8 @@ mod table { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn jump_cond(code: u8) -> JumpCondition { | ||||
|         use JumpCondition::*; | ||||
|     pub(crate) fn jump_cond(code: u8) -> JpCond { | ||||
|         use JpCond::*; | ||||
|  | ||||
|         match code { | ||||
|             0b00 => NotZero, | ||||
| @@ -2147,3 +2151,153 @@ mod table { | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub(crate) mod dbg { | ||||
|     use super::add::{Source as AddSource, Target as AddTarget}; | ||||
|     use super::jump::JpCond; | ||||
|     use super::load::{Source as LDSource, Target as LDTarget}; | ||||
|     use super::{AllRegisters, BusIo, Cpu, Instruction, RegisterPair}; | ||||
|  | ||||
|     pub(crate) fn tmp_disasm(cpu: &Cpu, limit: u8) -> String { | ||||
|         let mut sm83_asm = String::new(); | ||||
|         let mut pc = cpu.register_pair(RegisterPair::PC); | ||||
|  | ||||
|         for _ in 0..limit { | ||||
|             let opcode = cpu.read_byte(pc); | ||||
|             pc += 1; | ||||
|  | ||||
|             let maybe_instr = if opcode == 0xCB { | ||||
|                 let opcode = cpu.read_byte(pc); | ||||
|                 pc += 1; | ||||
|  | ||||
|                 Instruction::prefixed(opcode) | ||||
|             } else { | ||||
|                 Instruction::unprefixed(opcode) | ||||
|             }; | ||||
|  | ||||
|             match maybe_instr { | ||||
|                 Instruction::Invalid => {} | ||||
|                 instr => { | ||||
|                     let asm = format!("{:04X} {:?}\n", pc - 1, instr); | ||||
|                     sm83_asm.push_str(&asm); | ||||
|                     pc += delta::pc_inc_count(instr) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         sm83_asm | ||||
|     } | ||||
|  | ||||
|     fn disasm(cpu: &Cpu, pc: u16, instr: Instruction) -> String { | ||||
|         use Instruction::*; | ||||
|  | ||||
|         let imm_byte = cpu.read_byte(pc + 1); | ||||
|         let imm_word = (cpu.read_byte(pc + 2) as u16) << 8 | imm_byte as u16; | ||||
|  | ||||
|         match instr { | ||||
|             NOP => "NOP".to_string(), | ||||
|             LD(LDTarget::IndirectImmediateWord, LDSource::SP) => { | ||||
|                 format!("LD ({:#06X}), SP", imm_word) | ||||
|             } | ||||
|             STOP => "STOP".to_string(), | ||||
|             JR(JpCond::Always) => format!("JR {}", imm_byte as i8), | ||||
|             JR(cond) => format!("JR {:?} {}", cond, imm_byte as i8), | ||||
|             LD(LDTarget::Group1(rp), LDSource::ImmediateWord) => { | ||||
|                 format!("LD {:?} {:#06X}", rp, imm_word) | ||||
|             } | ||||
|             ADD(AddTarget::HL, AddSource::Group1(rp)) => format!("ADD HL, {:?}", rp), | ||||
|             LD(LDTarget::IndirectGroup2(rp), LDSource::A) => format!("LD ({:?}), A", rp), | ||||
|             LD(LDTarget::A, LDSource::IndirectGroup2(rp)) => format!("LD A, ({:?})", rp), | ||||
|  | ||||
|             _ => todo!(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     mod delta { | ||||
|         use super::super::add::{Source as AddSource, Target as AddTarget}; | ||||
|         use super::super::alu::Source as AluSource; | ||||
|         use super::super::jump::{JpCond, JpLoc}; | ||||
|         use super::super::load::{Source as LDSource, Target as LDTarget}; | ||||
|         use super::super::{AllRegisters, Instruction}; | ||||
|  | ||||
|         pub(super) fn pc_inc_count(instr: Instruction) -> u16 { | ||||
|             use Instruction::*; | ||||
|  | ||||
|             match instr { | ||||
|                 // Unprefixed | ||||
|                 NOP => 0, | ||||
|                 LD(LDTarget::IndirectImmediateWord, LDSource::SP) => 2, | ||||
|                 STOP => 0, | ||||
|                 JR(_) => 1, | ||||
|                 LD(LDTarget::Group1(_), LDSource::ImmediateWord) => 2, | ||||
|                 ADD(AddTarget::HL, AddSource::Group1(_)) => 0, | ||||
|                 LD(LDTarget::IndirectGroup2(_), LDSource::A) => 0, | ||||
|                 LD(LDTarget::A, LDSource::IndirectGroup2(_)) => 0, | ||||
|                 INC(AllRegisters::Group1(_)) => 0, | ||||
|                 DEC(AllRegisters::Group1(_)) => 0, | ||||
|                 INC(AllRegisters::Register(_)) => 0, | ||||
|                 DEC(AllRegisters::Register(_)) => 0, | ||||
|                 LD(LDTarget::Register(_), LDSource::ImmediateByte) => 1, | ||||
|                 RLCA => 0, | ||||
|                 RRCA => 0, | ||||
|                 RLA => 0, | ||||
|                 RRA => 0, | ||||
|                 DAA => 0, | ||||
|                 CPL => 0, | ||||
|                 SCF => 0, | ||||
|                 CCF => 0, | ||||
|                 HALT => 0, | ||||
|                 LD(LDTarget::Register(_), LDSource::Register(_)) => 0, | ||||
|                 ADD(AddTarget::A, AddSource::Register(_)) => 0, | ||||
|                 ADC(AluSource::Register(_)) => 0, | ||||
|                 SUB(AluSource::Register(_)) => 0, | ||||
|                 SBC(AluSource::Register(_)) => 0, | ||||
|                 AND(AluSource::Register(_)) => 0, | ||||
|                 XOR(AluSource::Register(_)) => 0, | ||||
|                 OR(AluSource::Register(_)) => 0, | ||||
|                 CP(AluSource::Register(_)) => 0, | ||||
|                 RET(_) => 0, | ||||
|                 LD(LDTarget::IoWithImmediateOffset, LDSource::A) => 1, | ||||
|                 ADD(AddTarget::SP, AddSource::ImmediateSignedByte) => 1, | ||||
|                 LD(LDTarget::A, LDSource::IoWithImmediateOffset) => 1, | ||||
|                 LDHL => 1, | ||||
|                 POP(_) => 0, | ||||
|                 RETI => 0, | ||||
|                 JP(JpCond::Always, JpLoc::HL) => 0, | ||||
|                 LD(LDTarget::SP, LDSource::HL) => 0, | ||||
|                 JP(_, JpLoc::ImmediateWord) => 2, | ||||
|                 LD(LDTarget::IoWithC, LDSource::A) => 0, | ||||
|                 LD(LDTarget::IndirectImmediateWord, LDSource::A) => 2, | ||||
|                 LD(LDTarget::A, LDSource::IoWithC) => 0, | ||||
|                 LD(LDTarget::A, LDSource::IndirectImmediateWord) => 2, | ||||
|                 DI => 0, | ||||
|                 EI => 0, | ||||
|                 CALL(_) => 0, | ||||
|                 PUSH(_) => 0, | ||||
|                 ADD(AddTarget::A, AddSource::ImmediateByte) => 1, | ||||
|                 ADC(AluSource::ImmediateByte) => 1, | ||||
|                 SUB(AluSource::ImmediateByte) => 1, | ||||
|                 SBC(AluSource::ImmediateByte) => 1, | ||||
|                 AND(AluSource::ImmediateByte) => 1, | ||||
|                 XOR(AluSource::ImmediateByte) => 1, | ||||
|                 OR(AluSource::ImmediateByte) => 1, | ||||
|                 CP(AluSource::ImmediateByte) => 1, | ||||
|                 RST(_) => 0, | ||||
|  | ||||
|                 // Prefixed | ||||
|                 RLC(_) => 0, | ||||
|                 RRC(_) => 0, | ||||
|                 RL(_) => 0, | ||||
|                 RR(_) => 0, | ||||
|                 SLA(_) => 0, | ||||
|                 SRA(_) => 0, | ||||
|                 SWAP(_) => 0, | ||||
|                 SRL(_) => 0, | ||||
|                 BIT(_, _) => 0, | ||||
|                 RES(_, _) => 0, | ||||
|                 SET(_, _) => 0, | ||||
|                 _ => unreachable!("{:?} is an illegal instruction", instr), | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,20 @@ | ||||
| use bitfield::bitfield; | ||||
|  | ||||
| #[derive(Debug, Default)] | ||||
| #[derive(Debug)] | ||||
| pub(crate) struct Interrupt { | ||||
|     pub(crate) flag: InterruptFlag, | ||||
|     pub(crate) enable: InterruptEnable, | ||||
| } | ||||
|  | ||||
| impl Default for Interrupt { | ||||
|     fn default() -> Self { | ||||
|         Self { | ||||
|             flag: InterruptFlag(0), | ||||
|             enable: InterruptEnable(0), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| bitfield! { | ||||
|    pub struct InterruptEnable(u8); | ||||
|     impl Debug; | ||||
| @@ -23,12 +32,6 @@ impl Clone for InterruptEnable { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for InterruptEnable { | ||||
|     fn default() -> Self { | ||||
|         Self(0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<u8> for InterruptEnable { | ||||
|     fn from(byte: u8) -> Self { | ||||
|         Self(byte) | ||||
| @@ -58,12 +61,6 @@ impl Clone for InterruptFlag { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for InterruptFlag { | ||||
|     fn default() -> Self { | ||||
|         Self(0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<u8> for InterruptFlag { | ||||
|     fn from(byte: u8) -> Self { | ||||
|         Self(byte) | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| use gilrs::{Button, Event as GamepadEvent, EventType as GamepadEventType}; | ||||
| use winit_input_helper::WinitInputHelper; | ||||
| use winit::event::{ElementState, KeyboardInput, VirtualKeyCode}; | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub struct Joypad { | ||||
| @@ -110,72 +110,39 @@ impl ButtonEvent { | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub fn handle_keyboard_input(pad: &mut Joypad, input: &WinitInputHelper) { | ||||
|     use winit::event::VirtualKeyCode; | ||||
|  | ||||
|     // TODO: What do I have to do to get a match statement here? | ||||
|  | ||||
| #[inline] | ||||
| pub(crate) fn handle_keyboard_input(pad: &mut Joypad, key: KeyboardInput) { | ||||
|     let state = &mut pad.ext; | ||||
|     let irq = &mut pad.interrupt; | ||||
|  | ||||
|     if input.key_pressed(VirtualKeyCode::Down) { | ||||
|         state.dpad_down.update(true, irq); | ||||
|     } | ||||
|     if input.key_released(VirtualKeyCode::Down) { | ||||
|         state.dpad_down.update(false, irq); | ||||
|     } | ||||
|  | ||||
|     if input.key_pressed(VirtualKeyCode::Up) { | ||||
|         state.dpad_up.update(true, irq); | ||||
|     } | ||||
|     if input.key_released(VirtualKeyCode::Up) { | ||||
|         state.dpad_up.update(false, irq); | ||||
|     } | ||||
|  | ||||
|     if input.key_pressed(VirtualKeyCode::Left) { | ||||
|         state.dpad_left.update(true, irq); | ||||
|     } | ||||
|     if input.key_released(VirtualKeyCode::Left) { | ||||
|         state.dpad_left.update(false, irq); | ||||
|     } | ||||
|  | ||||
|     if input.key_pressed(VirtualKeyCode::Right) { | ||||
|         state.dpad_right.update(true, irq); | ||||
|     } | ||||
|     if input.key_released(VirtualKeyCode::Right) { | ||||
|         state.dpad_right.update(false, irq); | ||||
|     } | ||||
|  | ||||
|     if input.key_pressed(VirtualKeyCode::T) { | ||||
|         state.start.update(true, irq); | ||||
|     } | ||||
|     if input.key_released(VirtualKeyCode::T) { | ||||
|         state.start.update(false, irq); | ||||
|     } | ||||
|  | ||||
|     if input.key_pressed(VirtualKeyCode::Y) { | ||||
|         state.select.update(true, irq); | ||||
|     } | ||||
|     if input.key_released(VirtualKeyCode::Y) { | ||||
|         state.select.update(false, irq); | ||||
|     } | ||||
|  | ||||
|     if input.key_pressed(VirtualKeyCode::Z) { | ||||
|         state.south.update(true, irq); | ||||
|     } | ||||
|     if input.key_released(VirtualKeyCode::Z) { | ||||
|         state.south.update(false, irq); | ||||
|     } | ||||
|  | ||||
|     if input.key_pressed(VirtualKeyCode::X) { | ||||
|         state.east.update(true, irq); | ||||
|     } | ||||
|     if input.key_released(VirtualKeyCode::X) { | ||||
|         state.east.update(false, irq); | ||||
|     match key.state { | ||||
|         ElementState::Pressed => match key.virtual_keycode { | ||||
|             Some(VirtualKeyCode::Down) => state.dpad_down.update(true, irq), | ||||
|             Some(VirtualKeyCode::Up) => state.dpad_up.update(true, irq), | ||||
|             Some(VirtualKeyCode::Left) => state.dpad_left.update(true, irq), | ||||
|             Some(VirtualKeyCode::Right) => state.dpad_right.update(true, irq), | ||||
|             Some(VirtualKeyCode::Return) => state.start.update(true, irq), | ||||
|             Some(VirtualKeyCode::RShift) => state.select.update(true, irq), | ||||
|             Some(VirtualKeyCode::Z) => state.south.update(true, irq), | ||||
|             Some(VirtualKeyCode::X) => state.east.update(true, irq), | ||||
|             None | Some(_) => {} | ||||
|         }, | ||||
|         ElementState::Released => match key.virtual_keycode { | ||||
|             Some(VirtualKeyCode::Down) => state.dpad_down.update(false, irq), | ||||
|             Some(VirtualKeyCode::Up) => state.dpad_up.update(false, irq), | ||||
|             Some(VirtualKeyCode::Left) => state.dpad_left.update(false, irq), | ||||
|             Some(VirtualKeyCode::Right) => state.dpad_right.update(false, irq), | ||||
|             Some(VirtualKeyCode::Return) => state.start.update(false, irq), | ||||
|             Some(VirtualKeyCode::RShift) => state.select.update(false, irq), | ||||
|             Some(VirtualKeyCode::Z) => state.south.update(false, irq), | ||||
|             Some(VirtualKeyCode::X) => state.east.update(false, irq), | ||||
|             None | Some(_) => {} | ||||
|         }, | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub fn handle_gamepad_input(pad: &mut Joypad, event: GamepadEvent) { | ||||
| #[inline] | ||||
| pub(crate) fn handle_gamepad_input(pad: &mut Joypad, event: GamepadEvent) { | ||||
|     use Button::*; | ||||
|     use GamepadEventType::*; | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,6 @@ | ||||
| pub use apu::gen::AudioSPSC; | ||||
| #![allow(clippy::derivable_impls)] // Will remove this if bitfield-rs allows default impls | ||||
|  | ||||
| pub use apu::gen::init as spsc_init; | ||||
| pub type Cycle = u64; | ||||
|  | ||||
| pub const GB_WIDTH: usize = 160; | ||||
| @@ -9,6 +11,7 @@ mod bus; | ||||
| mod cartridge; | ||||
| mod cpu; | ||||
| pub mod emu; | ||||
| pub mod gui; | ||||
| mod high_ram; | ||||
| mod instruction; | ||||
| mod interrupt; | ||||
|   | ||||
							
								
								
									
										226
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										226
									
								
								src/main.rs
									
									
									
									
									
								
							| @@ -1,22 +1,19 @@ | ||||
| use std::convert::TryInto; | ||||
| use std::time::Instant; | ||||
|  | ||||
| use anyhow::{anyhow, Result}; | ||||
| use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg}; | ||||
| use gb::emu::build::EmulatorBuilder; | ||||
| use gb::{AudioSPSC, Cycle, GB_HEIGHT, GB_WIDTH}; | ||||
| use egui_wgpu_backend::RenderPass; | ||||
| use gb::gui::EmuMode; | ||||
| use gb::{emu, gui}; | ||||
| use gilrs::Gilrs; | ||||
| use pixels::{PixelsBuilder, SurfaceTexture}; | ||||
| use gui::GuiState; | ||||
| use rodio::{OutputStream, Sink}; | ||||
| use winit::dpi::{LogicalSize, PhysicalSize}; | ||||
| use winit::event::{Event, VirtualKeyCode}; | ||||
| use winit::event_loop::{ControlFlow, EventLoop}; | ||||
| use winit::window::{Window, WindowBuilder}; | ||||
| use winit_input_helper::WinitInputHelper; | ||||
| use tracing_subscriber::EnvFilter; | ||||
| use winit::event::{Event, WindowEvent}; | ||||
| use winit::event_loop::EventLoop; | ||||
|  | ||||
| const WINDOW_SCALE: usize = 3; | ||||
| const AUDIO_ENABLED: bool = false; | ||||
| const AUDIO_ENABLED: bool = true; | ||||
|  | ||||
| fn main() -> Result<()> { | ||||
| fn main() { | ||||
|     let app = App::new(crate_name!()) | ||||
|         .version(crate_version!()) | ||||
|         .author(crate_authors!()) | ||||
| @@ -27,7 +24,6 @@ fn main() -> Result<()> { | ||||
|             Arg::with_name("rom") | ||||
|                 .value_name("ROM_FILE") | ||||
|                 .takes_value(true) | ||||
|                 .required(true) | ||||
|                 .index(1) | ||||
|                 .help("Path to the Game ROM"), | ||||
|         ) | ||||
| @@ -41,104 +37,174 @@ fn main() -> Result<()> { | ||||
|         ) | ||||
|         .get_matches(); | ||||
|  | ||||
|     let mut emu_build = | ||||
|         EmulatorBuilder::new().with_cart(m.value_of("rom").expect("ROM path provided"))?; | ||||
|  | ||||
|     if let Some(path) = m.value_of("boot") { | ||||
|         emu_build = emu_build.with_boot(path)?; | ||||
|     // Set up subscriber | ||||
|     if std::env::var("RUST_LOG").is_err() { | ||||
|         std::env::set_var("RUST_LOG", "gb=info"); | ||||
|     } | ||||
|  | ||||
|     let mut emu = emu_build.finish(); | ||||
|     let rom_title = emu.title(); | ||||
|     tracing_subscriber::fmt::fmt() | ||||
|         .with_env_filter(EnvFilter::from_default_env()) | ||||
|         .init(); | ||||
|  | ||||
|     let mut gamepad = Gilrs::new().expect("Initialize Controller Support"); | ||||
|     // --Here lies a lot of winit + wgpu Boilerplate-- | ||||
|     let event_loop: EventLoop<Event<()>> = EventLoop::with_user_event(); | ||||
|     let window = gui::build_window(&event_loop).expect("build window"); | ||||
|  | ||||
|     // Initialize GUI | ||||
|     let event_loop = EventLoop::new(); | ||||
|     let mut input = WinitInputHelper::new(); | ||||
|     let window = create_window(&event_loop, rom_title)?; | ||||
|     let (instance, surface) = gui::create_surface(&window); | ||||
|     let adapter = gui::request_adapter(&instance, &surface).expect("request adaptor"); | ||||
|     let (device, queue) = gui::request_device(&adapter).expect("request device"); | ||||
|     let format = surface | ||||
|         .get_preferred_format(&adapter) | ||||
|         .expect("get surface format"); | ||||
|  | ||||
|     let mut pixels = { | ||||
|         let size = window.inner_size(); | ||||
|         let surface_texture = SurfaceTexture::new(size.width, size.height, &window); | ||||
|     let mut config = gui::surface_config(&window, format); | ||||
|     surface.configure(&device, &config); | ||||
|     let mut platform = gui::platform_desc(&window); | ||||
|     let mut render_pass = RenderPass::new(&device, format, 1); | ||||
|  | ||||
|         PixelsBuilder::new(GB_WIDTH as u32, GB_HEIGHT as u32, surface_texture) | ||||
|             .enable_vsync(false) | ||||
|             .build()? | ||||
|     // We interrupt your boiler plate to initialize the emulator so that | ||||
|     // we can copy it's empty pixel buffer to the GPU | ||||
|     let mut cpu = match m.value_of("boot") { | ||||
|         Some(path) => { | ||||
|             tracing::info!("User-provided boot ROM"); | ||||
|             emu::from_boot_rom(path).expect("initialize emulator with custom boot rom") | ||||
|         } | ||||
|         None => { | ||||
|             tracing::info!("Built-in boot ROM"); | ||||
|             Default::default() | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     // Set up the wgpu (and then EGUI) texture we'll be working with. | ||||
|     let texture_size = gui::texture_size(); | ||||
|     let texture = gui::create_texture(&device, texture_size); | ||||
|     gui::write_to_texture(&queue, &texture, emu::pixel_buf(&cpu), texture_size); | ||||
|     let texture_id = gui::expose_texture_to_egui(&mut render_pass, &device, &texture); | ||||
|  | ||||
|     // Load ROM if filepath was provided | ||||
|     if let Some(path) = m.value_of("rom") { | ||||
|         tracing::info!("User-provided cartridge ROM"); | ||||
|         emu::read_game_rom(&mut cpu, path).expect("read game rom from path"); | ||||
|     } | ||||
|  | ||||
|     emu::load_save(&mut cpu); | ||||
|  | ||||
|     let rom_title = emu::rom_title(&cpu).to_string(); | ||||
|  | ||||
|     tracing::info!("Initialize Gamepad"); | ||||
|     let mut gamepad = Gilrs::new().expect("Initialize Controller Support"); | ||||
|  | ||||
|     // Initialize Audio | ||||
|     let (_stream, stream_handle) = OutputStream::try_default().expect("Initialized Audio"); | ||||
|  | ||||
|     if AUDIO_ENABLED { | ||||
|         let spsc: AudioSPSC<f32> = Default::default(); | ||||
|         let (prod, cons) = spsc.init(); | ||||
|         let (prod, cons) = gb::spsc_init(); | ||||
|         let sink = { | ||||
|             let s = Sink::try_new(&stream_handle)?; | ||||
|             let s = Sink::try_new(&stream_handle).expect("create sink from audio stream handle"); | ||||
|             s.append(cons); | ||||
|             s.set_volume(0.1); | ||||
|             s | ||||
|         }; | ||||
|  | ||||
|         emu.set_prod(prod); | ||||
|         emu::set_audio_prod(&mut cpu, prod); | ||||
|  | ||||
|         tracing::info!("Spawn Audio Thread"); | ||||
|         std::thread::spawn(move || { | ||||
|             sink.sleep_until_end(); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     let mut cycle_count: Cycle = Default::default(); | ||||
|     // Set up state for the Immediate-mode GUI | ||||
|     let mut app = GuiState::new(rom_title); | ||||
|     let mut last_key = gui::unused_key(); | ||||
|  | ||||
|     // used for egui animations | ||||
|     let start_time = Instant::now(); | ||||
|  | ||||
|     event_loop.run(move |event, _, control_flow| { | ||||
|         if let Event::RedrawRequested(_) = event { | ||||
|             if pixels | ||||
|                 .render() | ||||
|                 .map_err(|e| anyhow!("pixels.render() failed: {}", e)) | ||||
|                 .is_err() | ||||
|             { | ||||
|                 *control_flow = ControlFlow::Exit; | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|         platform.handle_event(&event); | ||||
|  | ||||
|         if input.update(&event) { | ||||
|             if input.key_pressed(VirtualKeyCode::Escape) || input.quit() { | ||||
|                 *control_flow = ControlFlow::Exit; | ||||
|                 return; | ||||
|             } | ||||
|         match event { | ||||
|             Event::MainEventsCleared => { | ||||
|                 if app.quit { | ||||
|                     emu::save_and_exit(&cpu, control_flow); | ||||
|                 } | ||||
|  | ||||
|             if let Some(size) = input.window_resized() { | ||||
|                 pixels.resize_surface(size.width, size.height); | ||||
|             } | ||||
|                 match app.mode { | ||||
|                     EmuMode::Running => emu::run_frame(&mut cpu, &mut gamepad, last_key), | ||||
|                     EmuMode::StepFrame if gui::kbd::space_released(&last_key) => { | ||||
|                         emu::run_frame(&mut cpu, &mut gamepad, last_key) | ||||
|                     } | ||||
|                     EmuMode::Step if gui::kbd::space_released(&last_key) => { | ||||
|                         emu::run(&mut cpu, &mut gamepad, last_key, 4); | ||||
|                     } | ||||
|                     _ => {} | ||||
|                 }; | ||||
|  | ||||
|             cycle_count += gb::emu::run_frame(&mut emu, &mut gamepad, &input); | ||||
|                 // Input has been consumed, reset it | ||||
|                 last_key = gui::unused_key(); | ||||
|  | ||||
|             if cycle_count >= gb::emu::CYCLES_IN_FRAME { | ||||
|                 cycle_count %= gb::emu::CYCLES_IN_FRAME; | ||||
|  | ||||
|                 let buf: &mut [u8; GB_WIDTH * GB_HEIGHT * 4] = pixels | ||||
|                     .get_frame() | ||||
|                     .try_into() | ||||
|                     .expect("Size of Pixel Buffer is GB_WIDTH * GB_HEIGHT * 4"); | ||||
|  | ||||
|                 gb::emu::draw_frame(&emu, buf); | ||||
|                 window.request_redraw(); | ||||
|             } | ||||
|             Event::RedrawRequested(..) => { | ||||
|                 platform.update_time(start_time.elapsed().as_secs_f64()); | ||||
|  | ||||
|                 let data = emu::pixel_buf(&cpu); | ||||
|                 gui::write_to_texture(&queue, &texture, data, texture_size); | ||||
|  | ||||
|                 let output_frame = match surface.get_current_texture() { | ||||
|                     Ok(frame) => frame, | ||||
|                     Err(e) => { | ||||
|                         eprintln!("Dropped frame with error: {}", e); | ||||
|                         return; | ||||
|                     } | ||||
|                 }; | ||||
|                 let output_view = gui::create_view(&output_frame); | ||||
|  | ||||
|                 // Begin to draw Egui components | ||||
|                 platform.begin_frame(); | ||||
|                 gui::draw_egui(&cpu, &mut app, &platform.context(), texture_id); | ||||
|                 // End the UI frame. We could now handle the output and draw the UI with the backend. | ||||
|                 let (_, paint_commands) = platform.end_frame(Some(&window)); | ||||
|                 let paint_jobs = platform.context().tessellate(paint_commands); | ||||
|  | ||||
|                 let mut encoder = gui::create_command_encoder(&device); | ||||
|                 let screen_descriptor = gui::create_screen_descriptor(&window, &config); | ||||
|  | ||||
|                 // Upload all resources for the GPU. | ||||
|                 render_pass.update_texture(&device, &queue, &platform.context().texture()); | ||||
|                 render_pass.update_user_textures(&device, &queue); | ||||
|                 render_pass.update_buffers(&device, &queue, &paint_jobs, &screen_descriptor); | ||||
|  | ||||
|                 // Record all render passes. | ||||
|                 gui::execute_render_pass( | ||||
|                     &mut render_pass, | ||||
|                     &mut encoder, | ||||
|                     &output_view, | ||||
|                     paint_jobs, | ||||
|                     &screen_descriptor, | ||||
|                 ) | ||||
|                 .expect("record render passes"); | ||||
|  | ||||
|                 // Submit the commands. | ||||
|                 queue.submit(std::iter::once(encoder.finish())); | ||||
|  | ||||
|                 // Redraw egui | ||||
|                 output_frame.present(); | ||||
|             } | ||||
|             Event::WindowEvent { event, .. } => match event { | ||||
|                 WindowEvent::Resized(size) => { | ||||
|                     config.width = size.width; | ||||
|                     config.height = size.height; | ||||
|                     surface.configure(&device, &config); | ||||
|                 } | ||||
|                 WindowEvent::CloseRequested => { | ||||
|                     emu::save_and_exit(&cpu, control_flow); | ||||
|                 } | ||||
|                 WindowEvent::KeyboardInput { input, .. } => last_key = input, | ||||
|                 _ => {} | ||||
|             }, | ||||
|             _ => {} | ||||
|         } | ||||
|     }); | ||||
| } | ||||
|  | ||||
| fn create_window(event_loop: &EventLoop<()>, title: &str) -> Result<Window> { | ||||
|     let logical = LogicalSize::new(GB_WIDTH as f64, GB_HEIGHT as f64); | ||||
|     let physical = PhysicalSize::new( | ||||
|         (GB_WIDTH * WINDOW_SCALE) as f32, | ||||
|         (GB_HEIGHT * WINDOW_SCALE) as f32, | ||||
|     ); | ||||
|  | ||||
|     Ok(WindowBuilder::new() | ||||
|         .with_title(title) | ||||
|         .with_min_inner_size(logical) | ||||
|         .with_inner_size(physical) | ||||
|         .with_resizable(true) | ||||
|         .build(event_loop)?) | ||||
| } | ||||
|   | ||||
							
								
								
									
										632
									
								
								src/ppu.rs
									
									
									
									
									
								
							
							
						
						
									
										632
									
								
								src/ppu.rs
									
									
									
									
									
								
							| @@ -4,7 +4,6 @@ use crate::GB_HEIGHT; | ||||
| use crate::GB_WIDTH; | ||||
| use dma::DirectMemoryAccess; | ||||
| use std::collections::VecDeque; | ||||
| use std::convert::TryInto; | ||||
| pub(crate) use types::PpuMode; | ||||
| use types::{ | ||||
|     BackgroundPalette, GrayShade, LCDControl, LCDStatus, ObjectFlags, ObjectPalette, | ||||
| @@ -43,20 +42,20 @@ pub struct Ppu { | ||||
|     pub(crate) monochrome: Monochrome, | ||||
|     pub(crate) pos: ScreenPosition, | ||||
|     vram: Box<[u8; VRAM_SIZE]>, | ||||
|     pub(crate) oam: ObjectAttributeTable, | ||||
|     pub(crate) oam: ObjectAttrTable, | ||||
|     pub(crate) dma: DirectMemoryAccess, | ||||
|     scan_state: OamScanState, | ||||
|     scan_dot: Cycle, | ||||
|     fetch: PixelFetcher, | ||||
|     fifo: PixelFifo, | ||||
|     obj_buffer: ObjectBuffer, | ||||
|     frame_buf: Box<[u8; GB_WIDTH * GB_HEIGHT * 4]>, | ||||
|     window_stat: WindowStatus, | ||||
|     pub(crate) frame_buf: Box<[u8; GB_WIDTH * GB_HEIGHT * 4]>, | ||||
|     win_stat: WindowStatus, | ||||
|  | ||||
|     scanline_start: bool, | ||||
|     to_discard: u8, | ||||
|  | ||||
|     x_pos: u8, | ||||
|     cycle: Cycle, | ||||
|     dot: Cycle, | ||||
| } | ||||
|  | ||||
| impl BusIo for Ppu { | ||||
| @@ -71,7 +70,7 @@ impl BusIo for Ppu { | ||||
|  | ||||
| impl Ppu { | ||||
|     pub(crate) fn tick(&mut self) { | ||||
|         self.cycle += 1; | ||||
|         self.dot += 1; | ||||
|  | ||||
|         if !self.ctrl.lcd_enabled() { | ||||
|             return; | ||||
| @@ -79,7 +78,26 @@ impl Ppu { | ||||
|  | ||||
|         match self.stat.mode() { | ||||
|             PpuMode::OamScan => { | ||||
|                 if self.cycle >= 80 { | ||||
|                 // Cycles 1 -> 80 | ||||
|                 if self.dot >= 80 { | ||||
|                     self.x_pos = 0; | ||||
|                     self.scanline_start = true; | ||||
|                     self.fetch.back.tile_high_reset = true; | ||||
|                     self.to_discard = 0; | ||||
|                     self.fifo.back.clear(); | ||||
|                     self.fifo.obj.clear(); | ||||
|  | ||||
|                     // Sort Sprites | ||||
|                     self.obj_buffer.inner.sort_by(|left, right| { | ||||
|                         left.zip(*right) | ||||
|                             .map(|(left, right)| right.x.cmp(&left.x)) | ||||
|                             .unwrap_or(std::cmp::Ordering::Greater) | ||||
|                     }); | ||||
|  | ||||
|                     // if self.obj_buffer.len != 0 { | ||||
|                     //     dbg!(&self.obj_buffer); | ||||
|                     // } | ||||
|  | ||||
|                     self.stat.set_mode(PpuMode::Drawing); | ||||
|                 } | ||||
|  | ||||
| @@ -88,7 +106,7 @@ impl Ppu { | ||||
|             PpuMode::Drawing => { | ||||
|                 if self.ctrl.lcd_enabled() { | ||||
|                     // Only Draw when the LCD Is Enabled | ||||
|                     self.draw(self.cycle); | ||||
|                     self.draw(); | ||||
|                 } else { | ||||
|                     self.reset(); | ||||
|                 } | ||||
| @@ -104,29 +122,22 @@ impl Ppu { | ||||
|  | ||||
|                     // Increment Window line counter if scanline had any window pixels on it | ||||
|                     // only increment once per scanline though | ||||
|                     if self.window_stat.should_draw() { | ||||
|                         self.fetch.back.window_line.increment(); | ||||
|                     if self.win_stat.enabled { | ||||
|                         self.fetch.back.wl_count += 1; | ||||
|                     } | ||||
|  | ||||
|                     self.x_pos = 0; | ||||
|                     self.scanline_start = true; | ||||
|                     self.to_discard = 0; | ||||
|  | ||||
|                     self.fetch.hblank_reset(); | ||||
|                     self.window_stat.hblank_reset(); | ||||
|                     self.win_stat.enabled = false; | ||||
|  | ||||
|                     self.obj_buffer.clear(); | ||||
|  | ||||
|                     self.fifo.back.clear(); | ||||
|                     self.fifo.obj.clear(); | ||||
|  | ||||
|                     self.stat.set_mode(PpuMode::HBlank); | ||||
|                 } | ||||
|             } | ||||
|             PpuMode::HBlank => { | ||||
|                 // This mode will always end at 456 cycles | ||||
|  | ||||
|                 if self.cycle >= 456 { | ||||
|                     self.cycle %= 456; | ||||
|                 if self.dot >= 456 { | ||||
|                     self.dot %= 456; | ||||
|                     self.pos.line_y += 1; | ||||
|  | ||||
|                     // Update LY==LYC bit | ||||
| @@ -143,9 +154,10 @@ impl Ppu { | ||||
|                         self.int.set_vblank(true); | ||||
|  | ||||
|                         // Reset Window Line Counter in Fetcher | ||||
|                         self.fetch.vblank_reset(); | ||||
|                         self.fetch.back.wl_count = 0; | ||||
|  | ||||
|                         // Reset WY=LY coincidence flag | ||||
|                         self.window_stat.vblank_reset(); | ||||
|                         self.win_stat.coincidence = false; | ||||
|  | ||||
|                         if self.stat.vblank_int() { | ||||
|                             // Enable Vblank LCDStat Interrupt | ||||
| @@ -159,7 +171,7 @@ impl Ppu { | ||||
|                             self.int.set_lcd_stat(true); | ||||
|                         } | ||||
|  | ||||
|                         self.scan_state.reset(); | ||||
|                         self.scan_dot = Default::default(); | ||||
|                         PpuMode::OamScan | ||||
|                     }; | ||||
|  | ||||
| @@ -167,8 +179,8 @@ impl Ppu { | ||||
|                 } | ||||
|             } | ||||
|             PpuMode::VBlank => { | ||||
|                 if self.cycle > 456 { | ||||
|                     self.cycle %= 456; | ||||
|                 if self.dot >= 456 { | ||||
|                     self.dot %= 456; | ||||
|                     self.pos.line_y += 1; | ||||
|  | ||||
|                     // Update LY==LYC bit | ||||
| @@ -188,8 +200,7 @@ impl Ppu { | ||||
|                             self.int.set_lcd_stat(true); | ||||
|                         } | ||||
|  | ||||
|                         self.scan_state.reset(); | ||||
|  | ||||
|                         self.scan_dot = Default::default(); | ||||
|                         self.stat.set_mode(PpuMode::OamScan); | ||||
|                     } | ||||
|                 } | ||||
| @@ -198,89 +209,81 @@ impl Ppu { | ||||
|     } | ||||
|  | ||||
|     fn scan_oam(&mut self) { | ||||
|         match self.scan_state.mode() { | ||||
|             OamScanMode::Scan if !self.dma.is_active() => { | ||||
|                 if !self.window_stat.coincidence() && self.scan_state.count() == 0 { | ||||
|                     // Determine whether we should draw the window next frame | ||||
|                     self.window_stat | ||||
|                         .set_coincidence(self.pos.line_y == self.pos.window_y); | ||||
|                 } | ||||
|  | ||||
|                 let sprite_height = self.ctrl.obj_size().as_u8(); | ||||
|                 let index = self.scan_state.count(); | ||||
|  | ||||
|                 let attr = self.oam.attribute(index as usize); | ||||
|                 let line_y = self.pos.line_y + 16; | ||||
|  | ||||
|                 if attr.x > 0 | ||||
|                     && line_y >= attr.y | ||||
|                     && line_y < (attr.y + sprite_height) | ||||
|                     && !self.obj_buffer.is_full() | ||||
|                 { | ||||
|                     self.obj_buffer.add(attr); | ||||
|                 } | ||||
|  | ||||
|                 self.scan_state.increase(); | ||||
|         if self.scan_dot % 2 == 0 { | ||||
|             if self.dma.is_active() { | ||||
|                 return; | ||||
|             } | ||||
|             _ => {} | ||||
|         } | ||||
|  | ||||
|         self.scan_state.next(); | ||||
|             if !self.win_stat.coincidence && self.scan_dot == 0 { | ||||
|                 self.win_stat.coincidence = self.pos.line_y == self.pos.window_y; | ||||
|             } | ||||
|  | ||||
|             let obj_height = self.ctrl.obj_size().size(); | ||||
|             let attr = self.oam.attribute(self.scan_dot as usize / 2); | ||||
|             let line_y = self.pos.line_y + 16; | ||||
|  | ||||
|             if attr.x > 0 | ||||
|                 && line_y >= attr.y | ||||
|                 && line_y < (attr.y + obj_height) | ||||
|                 && !self.obj_buffer.is_full() | ||||
|             { | ||||
|                 self.obj_buffer.add(attr); | ||||
|             } | ||||
|         } | ||||
|         self.scan_dot += 1; | ||||
|     } | ||||
|  | ||||
|     fn draw(&mut self, _cycle: Cycle) { | ||||
|     fn draw(&mut self) { | ||||
|         use FetcherState::*; | ||||
|  | ||||
|         let mut iter = self.obj_buffer.iter_mut(); | ||||
|         let default = &mut None; | ||||
|         let mut obj_attr = &mut None; | ||||
|  | ||||
|         let obj_attr = loop { | ||||
|             match iter.next() { | ||||
|                 Some(attr_opt) => { | ||||
|                     if let Some(attr) = attr_opt { | ||||
|                         if attr.x <= (self.x_pos + 8) { | ||||
|                             self.fetch.back.reset(); | ||||
|                             self.fetch.back.pause(); | ||||
|                             self.fifo.pause(); | ||||
|         for maybe_attr in &mut self.obj_buffer.inner { | ||||
|             match maybe_attr { | ||||
|                 Some(attr) if self.ctrl.obj_enabled() => { | ||||
|                     if attr.x <= (self.x_pos + 8) { | ||||
|                         self.fetch.back.reset(); | ||||
|                         self.fetch.back.enabled = false; | ||||
|                         self.fifo.pause(); | ||||
|  | ||||
|                             break attr_opt; | ||||
|                         } | ||||
|                         obj_attr = maybe_attr; | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|                 None => break default, | ||||
|                 _ => break, | ||||
|             } | ||||
|         }; | ||||
|         } | ||||
|  | ||||
|         if let Some(attr) = obj_attr { | ||||
|             match self.fetch.obj.state { | ||||
|                 TileNumber => { | ||||
|                 TileNumberA => self.fetch.obj.state = TileNumberB, | ||||
|                 TileNumberB => { | ||||
|                     self.fetch.obj.tile.with_id(attr.tile_index); | ||||
|                     self.fetch.obj.next(SleepOne); | ||||
|                     self.fetch.obj.state = TileLowA; | ||||
|                 } | ||||
|                 SleepOne => self.fetch.obj.next(TileLow), | ||||
|                 TileLow => { | ||||
|                 TileLowA => self.fetch.obj.state = TileLowB, | ||||
|                 TileLowB => { | ||||
|                     let obj_size = self.ctrl.obj_size(); | ||||
|  | ||||
|                     let addr = PixelFetcher::get_obj_addr(&attr, &self.pos, obj_size); | ||||
|                     let addr = PixelFetcher::obj_addr(attr, &self.pos, obj_size); | ||||
|  | ||||
|                     let byte = self.read_byte(addr); | ||||
|                     self.fetch.obj.tile.with_low_byte(byte); | ||||
|                     self.fetch.obj.tile.with_low(byte); | ||||
|  | ||||
|                     self.fetch.obj.next(SleepTwo); | ||||
|                     self.fetch.obj.state = TileHighA; | ||||
|                 } | ||||
|                 SleepTwo => self.fetch.obj.next(TileHigh), | ||||
|                 TileHigh => { | ||||
|                 TileHighA => self.fetch.obj.state = TileHighB, | ||||
|                 TileHighB => { | ||||
|                     let obj_size = self.ctrl.obj_size(); | ||||
|  | ||||
|                     let addr = PixelFetcher::get_obj_addr(&attr, &self.pos, obj_size); | ||||
|                     let addr = PixelFetcher::obj_addr(attr, &self.pos, obj_size); | ||||
|  | ||||
|                     let byte = self.read_byte(addr + 1); | ||||
|                     self.fetch.obj.tile.with_high_byte(byte); | ||||
|                     self.fetch.obj.tile.with_high(byte); | ||||
|  | ||||
|                     self.fetch.obj.next(SleepThree); | ||||
|                     self.fetch.obj.state = ToFifoA; | ||||
|                 } | ||||
|                 SleepThree => self.fetch.obj.next(ToFifoOne), | ||||
|                 ToFifoOne => { | ||||
|                 ToFifoA => { | ||||
|                     // Load into Fifo | ||||
|                     let (high, low) = self | ||||
|                         .fetch | ||||
| @@ -312,73 +315,65 @@ impl Ppu { | ||||
|                         self.fifo.obj.push_back(fifo_info); | ||||
|                     } | ||||
|  | ||||
|                     self.fetch.back.resume(); | ||||
|                     self.fetch.back.enabled = true; | ||||
|                     self.fifo.resume(); | ||||
|                     let _ = std::mem::take(obj_attr); | ||||
|  | ||||
|                     self.fetch.obj.next(ToFifoTwo); | ||||
|                     self.fetch.obj.state = ToFifoB; | ||||
|                 } | ||||
|                 ToFifoTwo => self.fetch.obj.reset(), | ||||
|                 ToFifoB => self.fetch.obj.reset(), | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if self.ctrl.window_enabled() | ||||
|             && !self.window_stat.should_draw() | ||||
|             && self.window_stat.coincidence() | ||||
|             && self.x_pos as i16 >= self.pos.window_x as i16 - 7 | ||||
|         { | ||||
|             self.window_stat.set_should_draw(true); | ||||
|             self.fetch.back.reset(); | ||||
|             self.fetch.x_pos = 0; | ||||
|             self.fifo.back.clear(); | ||||
|         } | ||||
|  | ||||
|         if self.fetch.back.is_enabled() { | ||||
|         if self.fetch.back.enabled { | ||||
|             match self.fetch.back.state { | ||||
|                 TileNumber => { | ||||
|                     let x_pos = self.fetch.x_pos; | ||||
|                 TileNumberA => self.fetch.back.state = TileNumberB, | ||||
|                 TileNumberB => { | ||||
|                     // Are we rendering the Window currently? | ||||
|                     self.fetch.back.draw_window = self.win_stat.enabled; | ||||
|  | ||||
|                     self.fetch | ||||
|                         .back | ||||
|                         .should_render_window(self.window_stat.should_draw()); | ||||
|  | ||||
|                     let addr = self.fetch.bg_tile_num_addr(&self.ctrl, &self.pos, x_pos); | ||||
|                     let addr = | ||||
|                         self.fetch | ||||
|                             .back | ||||
|                             .tile_id_addr(&self.ctrl, &self.pos, self.fetch.x_pos); | ||||
|  | ||||
|                     let id = self.read_byte(addr); | ||||
|                     self.fetch.back.tile.with_id(id); | ||||
|  | ||||
|                     // Move on to the Next state in 2 T-cycles | ||||
|                     self.fetch.back.next(SleepOne); | ||||
|                     self.fetch.back.state = TileLowA; | ||||
|                 } | ||||
|                 SleepOne => self.fetch.back.next(TileLow), | ||||
|                 TileLow => { | ||||
|                     let addr = self.fetch.bg_byte_addr(&self.ctrl, &self.pos); | ||||
|                 TileLowA => self.fetch.back.state = TileLowB, | ||||
|                 TileLowB => { | ||||
|                     let id = self.fetch.back.tile.id.expect("Tile ID present"); | ||||
|  | ||||
|                     let low = self.read_byte(addr); | ||||
|                     self.fetch.back.tile.with_low_byte(low); | ||||
|                     let addr = self.fetch.back.tile_addr(&self.ctrl, &self.pos, id); | ||||
|                     let byte = self.read_byte(addr); | ||||
|                     self.fetch.back.tile.with_low(byte); | ||||
|  | ||||
|                     self.fetch.back.next(SleepTwo); | ||||
|                     self.fetch.back.state = TileHighA; | ||||
|                 } | ||||
|                 SleepTwo => self.fetch.back.next(TileHigh), | ||||
|                 TileHigh => { | ||||
|                     let addr = self.fetch.bg_byte_addr(&self.ctrl, &self.pos); | ||||
|                 TileHighA => self.fetch.back.state = TileHighB, | ||||
|                 TileHighB => { | ||||
|                     let id = self.fetch.back.tile.id.expect("Tile ID present"); | ||||
|  | ||||
|                     let high = self.read_byte(addr + 1); | ||||
|                     self.fetch.back.tile.with_high_byte(high); | ||||
|                     let addr = self.fetch.back.tile_addr(&self.ctrl, &self.pos, id); | ||||
|                     let byte = self.read_byte(addr + 1); | ||||
|                     self.fetch.back.tile.with_high(byte); | ||||
|  | ||||
|                     self.fetch.back.next(SleepThree); | ||||
|                 } | ||||
|                 SleepThree => self.fetch.back.next(ToFifoOne), | ||||
|                 ToFifoOne => { | ||||
|                     self.fetch.back.next(ToFifoTwo); | ||||
|                 } | ||||
|                 ToFifoTwo => { | ||||
|                     if let Ok(()) = self.fetch.send_to_fifo(&mut self.fifo) { | ||||
|                         self.fetch.x_pos += 1; | ||||
|                         self.fetch.back.next(TileNumber); | ||||
|                         self.fetch.back.tile = Default::default(); | ||||
|                     if self.fetch.back.tile_high_reset { | ||||
|                         self.fetch.back.reset(); | ||||
|                         self.fetch.back.tile_high_reset = false; | ||||
|                     } else { | ||||
|                         self.fetch.back.state = ToFifoA; | ||||
|                     } | ||||
|                 } | ||||
|                 ToFifoA => { | ||||
|                     if self.fetch.send_to_fifo(&mut self.fifo).is_ok() { | ||||
|                         self.fetch.x_pos += 1; | ||||
|                         self.fetch.back.state = ToFifoB; | ||||
|                     } | ||||
|                 } | ||||
|                 ToFifoB => self.fetch.back.reset(), | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -388,7 +383,7 @@ impl Ppu { | ||||
|                 self.scanline_start = false; | ||||
|             } | ||||
|  | ||||
|             if self.to_discard > 0 && !self.fifo.back.is_empty() { | ||||
|             if !self.win_stat.enabled && self.to_discard > 0 && !self.fifo.back.is_empty() { | ||||
|                 let _ = self.fifo.back.pop_front(); | ||||
|                 self.to_discard -= 1; | ||||
|  | ||||
| @@ -406,6 +401,17 @@ impl Ppu { | ||||
|  | ||||
|                 self.x_pos += 1; | ||||
|             } | ||||
|  | ||||
|             if self.ctrl.window_enabled() | ||||
|                 && !self.win_stat.enabled | ||||
|                 && self.win_stat.coincidence | ||||
|                 && self.x_pos as i16 >= self.pos.window_x as i16 - 7 | ||||
|             { | ||||
|                 self.win_stat.enabled = true; | ||||
|                 self.fetch.back.reset(); | ||||
|                 self.fetch.x_pos = 0; | ||||
|                 self.fifo.back.clear(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -413,53 +419,44 @@ impl Ppu { | ||||
|         self.pos.line_y = 0; | ||||
|         self.stat.set_mode(PpuMode::HBlank); | ||||
|  | ||||
|         // TODO: Is this an unnecessary performance hit? | ||||
|         let mut blank = WHITE.repeat(self.frame_buf.len() / 4); | ||||
|         self.frame_buf.swap_with_slice(&mut blank); | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn frame_buf(&self) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] { | ||||
|         &self.frame_buf | ||||
|     } | ||||
|  | ||||
|     fn clock_fifo(&mut self) -> Option<GrayShade> { | ||||
|         use ObjectPaletteKind::*; | ||||
|         use RenderPriority::*; | ||||
|  | ||||
|         let obj_palette_0 = &self.monochrome.obj_palette_0; | ||||
|         let obj_palette_1 = &self.monochrome.obj_palette_1; | ||||
|  | ||||
|         match self.fifo.back.pop_front() { | ||||
|             Some(bg_pixel) => match self.fifo.obj.pop_front() { | ||||
|                 Some(obj_pixel) if self.ctrl.obj_enabled() => match obj_pixel.priority { | ||||
|                     Object | BackgroundAndWindow if obj_pixel.shade_id == 0 => { | ||||
|                         Some(self.bg_pixel(bg_pixel.shade_id)) | ||||
|                     } | ||||
|                     BackgroundAndWindow if bg_pixel.shade_id != 0 => { | ||||
|                         Some(self.bg_pixel(bg_pixel.shade_id)) | ||||
|                     } | ||||
|                     Object | BackgroundAndWindow => { | ||||
|                         let maybe_sprite = match obj_pixel.palette_kind { | ||||
|                             Zero => obj_palette_0.shade(obj_pixel.shade_id), | ||||
|                             One => obj_palette_1.shade(obj_pixel.shade_id), | ||||
|                         }; | ||||
|  | ||||
|                         let sprite = maybe_sprite | ||||
|                             .expect("Sprite w/ a colour id of 0 has already been handled"); | ||||
|                         Some(sprite) | ||||
|                     } | ||||
|         self.fifo | ||||
|             .back | ||||
|             .pop_front() | ||||
|             .map(|bg| match self.fifo.obj.pop_front() { | ||||
|                 Some(obj) => match obj.priority { | ||||
|                     _ if obj.shade_id == 0 => self.bg_pixel(bg), | ||||
|                     BackgroundAndWindow if bg.shade_id != 0 => self.bg_pixel(bg), | ||||
|                     _ => self.obj_pixel(obj), | ||||
|                 }, | ||||
|                 _ => Some(self.bg_pixel(bg_pixel.shade_id)), | ||||
|             }, | ||||
|             None => None, | ||||
|                 None => self.bg_pixel(bg), | ||||
|             }) | ||||
|     } | ||||
|  | ||||
|     fn obj_pixel(&self, obj: ObjPixelProperty) -> GrayShade { | ||||
|         use ObjectPaletteKind::*; | ||||
|         assert_ne!(obj.shade_id, 0); | ||||
|  | ||||
|         let p0 = &self.monochrome.obj_palette_0; | ||||
|         let p1 = &self.monochrome.obj_palette_1; | ||||
|  | ||||
|         match obj.palette_kind { | ||||
|             Zero => p0.shade(obj.shade_id).expect("Object shade id is non-zero"), | ||||
|             One => p1.shade(obj.shade_id).expect("Object shade id is non-zero"), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn bg_pixel(&self, shade_id: u8) -> GrayShade { | ||||
|     fn bg_pixel(&self, bg: BgPixelProperty) -> GrayShade { | ||||
|         let bg_palette = &self.monochrome.bg_palette; | ||||
|  | ||||
|         if self.ctrl.bg_win_enabled() { | ||||
|             bg_palette.shade(shade_id) | ||||
|             bg_palette.shade(bg.shade_id) | ||||
|         } else { | ||||
|             bg_palette.shade(0) | ||||
|         } | ||||
| @@ -470,19 +467,19 @@ impl Default for Ppu { | ||||
|     fn default() -> Self { | ||||
|         Self { | ||||
|             vram: Box::new([0u8; VRAM_SIZE]), | ||||
|             cycle: Default::default(), | ||||
|             dot: Default::default(), | ||||
|             frame_buf: Box::new([0; GB_WIDTH * GB_HEIGHT * 4]), | ||||
|             int: Default::default(), | ||||
|             ctrl: Default::default(), | ||||
|             ctrl: LCDControl(0), | ||||
|             monochrome: Default::default(), | ||||
|             pos: Default::default(), | ||||
|             stat: Default::default(), | ||||
|             stat: LCDStatus(0x80), // bit 7 is always 1 | ||||
|             oam: Default::default(), | ||||
|             scan_state: Default::default(), | ||||
|             scan_dot: Default::default(), | ||||
|             fetch: Default::default(), | ||||
|             fifo: Default::default(), | ||||
|             obj_buffer: Default::default(), | ||||
|             window_stat: Default::default(), | ||||
|             win_stat: Default::default(), | ||||
|             dma: Default::default(), | ||||
|             x_pos: 0, | ||||
|             scanline_start: true, | ||||
| @@ -531,7 +528,7 @@ pub(crate) struct ScreenPosition { | ||||
|     pub(crate) window_x: u8, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default)] | ||||
| #[derive(Debug)] | ||||
| pub(crate) struct Monochrome { | ||||
|     /// 0xFF47 | BGP - Background Palette Data | ||||
|     pub(crate) bg_palette: BackgroundPalette, | ||||
| @@ -541,12 +538,22 @@ pub(crate) struct Monochrome { | ||||
|     pub(crate) obj_palette_1: ObjectPalette, | ||||
| } | ||||
|  | ||||
| impl Default for Monochrome { | ||||
|     fn default() -> Self { | ||||
|         Self { | ||||
|             bg_palette: BackgroundPalette(0), | ||||
|             obj_palette_0: ObjectPalette(0), | ||||
|             obj_palette_1: ObjectPalette(0), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub(crate) struct ObjectAttributeTable { | ||||
| pub(crate) struct ObjectAttrTable { | ||||
|     buf: Box<[u8; OAM_SIZE]>, | ||||
| } | ||||
|  | ||||
| impl BusIo for ObjectAttributeTable { | ||||
| impl BusIo for ObjectAttrTable { | ||||
|     fn read_byte(&self, addr: u16) -> u8 { | ||||
|         let index = (addr - 0xFE00) as usize; | ||||
|         self.buf[index] | ||||
| @@ -558,8 +565,8 @@ impl BusIo for ObjectAttributeTable { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl ObjectAttributeTable { | ||||
|     fn attribute(&self, index: usize) -> ObjectAttribute { | ||||
| impl ObjectAttrTable { | ||||
|     fn attribute(&self, index: usize) -> ObjectAttr { | ||||
|         let start = index * 4; | ||||
|  | ||||
|         let slice: &[u8; 4] = self.buf[start..(start + 4)] | ||||
| @@ -570,7 +577,7 @@ impl ObjectAttributeTable { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for ObjectAttributeTable { | ||||
| impl Default for ObjectAttrTable { | ||||
|     fn default() -> Self { | ||||
|         Self { | ||||
|             buf: Box::new([0; OAM_SIZE]), | ||||
| @@ -578,15 +585,15 @@ impl Default for ObjectAttributeTable { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] | ||||
| struct ObjectAttribute { | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| struct ObjectAttr { | ||||
|     y: u8, | ||||
|     x: u8, | ||||
|     tile_index: u8, | ||||
|     flags: ObjectFlags, | ||||
| } | ||||
|  | ||||
| impl From<[u8; 4]> for ObjectAttribute { | ||||
| impl From<[u8; 4]> for ObjectAttr { | ||||
|     fn from(bytes: [u8; 4]) -> Self { | ||||
|         Self { | ||||
|             y: bytes[0], | ||||
| @@ -597,7 +604,7 @@ impl From<[u8; 4]> for ObjectAttribute { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> From<&'a [u8; 4]> for ObjectAttribute { | ||||
| impl<'a> From<&'a [u8; 4]> for ObjectAttr { | ||||
|     fn from(bytes: &'a [u8; 4]) -> Self { | ||||
|         Self { | ||||
|             y: bytes[0], | ||||
| @@ -610,7 +617,7 @@ impl<'a> From<&'a [u8; 4]> for ObjectAttribute { | ||||
|  | ||||
| #[derive(Debug)] | ||||
| struct ObjectBuffer { | ||||
|     inner: [Option<ObjectAttribute>; OBJECT_LIMIT], | ||||
|     inner: [Option<ObjectAttr>; OBJECT_LIMIT], | ||||
|     len: usize, | ||||
| } | ||||
|  | ||||
| @@ -624,14 +631,10 @@ impl ObjectBuffer { | ||||
|         self.len = 0; | ||||
|     } | ||||
|  | ||||
|     fn add(&mut self, attr: ObjectAttribute) { | ||||
|     fn add(&mut self, attr: ObjectAttr) { | ||||
|         self.inner[self.len] = Some(attr); | ||||
|         self.len += 1; | ||||
|     } | ||||
|  | ||||
|     fn iter_mut(&mut self) -> std::slice::IterMut<'_, Option<ObjectAttribute>> { | ||||
|         self.inner.iter_mut() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for ObjectBuffer { | ||||
| @@ -657,61 +660,6 @@ impl PixelFetcher { | ||||
|         self.x_pos = 0; | ||||
|     } | ||||
|  | ||||
|     fn vblank_reset(&mut self) { | ||||
|         self.back.vblank_reset(); | ||||
|     } | ||||
|  | ||||
|     fn bg_tile_num_addr(&self, control: &LCDControl, pos: &ScreenPosition, x_pos: u8) -> u16 { | ||||
|         let line_y = pos.line_y; | ||||
|         let scroll_y = pos.scroll_y; | ||||
|         let scroll_x = pos.scroll_x; | ||||
|         let is_window = self.back.is_window_tile(); | ||||
|  | ||||
|         // Determine which tile map is being used | ||||
|         let tile_map = if is_window { | ||||
|             control.win_tile_map_addr() | ||||
|         } else { | ||||
|             control.bg_tile_map_addr() | ||||
|         }; | ||||
|         let tile_map_addr = tile_map.into_address(); | ||||
|  | ||||
|         // Both Offsets are used to offset the tile map address we found above | ||||
|         // Offsets are ANDed wih 0x3FF so that we stay in bounds of tile map memory | ||||
|  | ||||
|         let scx_offset = if is_window { 0 } else { scroll_x / 8 }; | ||||
|         let y_offset = if is_window { | ||||
|             self.back.window_line.count() as u16 / 8 | ||||
|         } else { | ||||
|             ((line_y as u16 + scroll_y as u16) & 0xFF) / 8 | ||||
|         }; | ||||
|  | ||||
|         let x_offset = (scx_offset + x_pos) & 0x1F; | ||||
|         let offset = (32 * y_offset) + (x_offset as u16); | ||||
|  | ||||
|         tile_map_addr + (offset & 0x3FF) | ||||
|     } | ||||
|  | ||||
|     fn bg_byte_addr(&mut self, control: &LCDControl, pos: &ScreenPosition) -> u16 { | ||||
|         let line_y = pos.line_y; | ||||
|         let scroll_y = pos.scroll_y; | ||||
|         let is_window = self.back.is_window_tile(); | ||||
|  | ||||
|         let id = self.back.tile.id.expect("Tile Number is present"); | ||||
|  | ||||
|         let tile_data_addr = match control.tile_data_addr() { | ||||
|             TileDataAddress::X8800 => 0x9000u16.wrapping_add(((id as i8) as i16 * 16) as u16), | ||||
|             TileDataAddress::X8000 => 0x8000 + (id as u16 * 16), | ||||
|         }; | ||||
|  | ||||
|         let offset = if is_window { | ||||
|             self.back.window_line.count() as u16 % 8 | ||||
|         } else { | ||||
|             (line_y as u16 + scroll_y as u16) % 8 | ||||
|         }; | ||||
|  | ||||
|         tile_data_addr + (offset * 2) | ||||
|     } | ||||
|  | ||||
|     fn send_to_fifo(&self, fifo: &mut PixelFifo) -> Result<(), ()> { | ||||
|         if !fifo.back.is_empty() { | ||||
|             return Err(()); | ||||
| @@ -735,7 +683,7 @@ impl PixelFetcher { | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn get_obj_addr(attr: &ObjectAttribute, pos: &ScreenPosition, size: ObjectSize) -> u16 { | ||||
|     fn obj_addr(attr: &ObjectAttr, pos: &ScreenPosition, size: ObjectSize) -> u16 { | ||||
|         let line_y = pos.line_y; | ||||
|  | ||||
|         // TODO: Why is the offset 14 and 30 respectively? | ||||
| @@ -757,7 +705,6 @@ impl PixelFetcher { | ||||
| } | ||||
|  | ||||
| trait Fetcher { | ||||
|     fn next(&mut self, state: FetcherState); | ||||
|     fn reset(&mut self); | ||||
|     fn hblank_reset(&mut self); | ||||
| } | ||||
| @@ -766,42 +713,63 @@ trait Fetcher { | ||||
| struct BackgroundFetcher { | ||||
|     state: FetcherState, | ||||
|     tile: TileBuilder, | ||||
|     window_line: WindowLineCounter, | ||||
|     is_window_tile: bool, | ||||
|     wl_count: u8, | ||||
|     draw_window: bool, | ||||
|     enabled: bool, | ||||
|     tile_high_reset: bool, | ||||
| } | ||||
|  | ||||
| impl BackgroundFetcher { | ||||
|     fn should_render_window(&mut self, value: bool) { | ||||
|         self.is_window_tile = value; | ||||
|     fn tile_id_addr(&self, control: &LCDControl, pos: &ScreenPosition, x_pos: u8) -> u16 { | ||||
|         let line_y = pos.line_y; | ||||
|         let scroll_y = pos.scroll_y; | ||||
|         let scroll_x = pos.scroll_x; | ||||
|         let is_window = self.draw_window; | ||||
|  | ||||
|         // Determine which tile map is being used | ||||
|         let tile_map = if is_window { | ||||
|             control.win_tile_map_addr() | ||||
|         } else { | ||||
|             control.bg_tile_map_addr() | ||||
|         }; | ||||
|         let tile_map_addr = tile_map.into_address(); | ||||
|  | ||||
|         // Both Offsets are used to offset the tile map address we found above | ||||
|         // Offsets are ANDed wih 0x3FF so that we stay in bounds of tile map memory | ||||
|  | ||||
|         let scx_offset = if is_window { 0 } else { scroll_x / 8 }; | ||||
|         let y_offset = if is_window { | ||||
|             self.wl_count as u16 / 8 | ||||
|         } else { | ||||
|             ((line_y as u16 + scroll_y as u16) & 0xFF) / 8 | ||||
|         }; | ||||
|  | ||||
|         let x_offset = (scx_offset + x_pos) & 0x1F; | ||||
|         let offset = (32 * y_offset) + (x_offset as u16); | ||||
|  | ||||
|         tile_map_addr + (offset & 0x3FF) | ||||
|     } | ||||
|  | ||||
|     fn is_window_tile(&self) -> bool { | ||||
|         self.is_window_tile | ||||
|     } | ||||
|     fn tile_addr(&mut self, control: &LCDControl, pos: &ScreenPosition, id: u8) -> u16 { | ||||
|         let line_y = pos.line_y; | ||||
|         let scroll_y = pos.scroll_y; | ||||
|  | ||||
|     fn pause(&mut self) { | ||||
|         self.enabled = false; | ||||
|     } | ||||
|         let tile_data_addr = match control.tile_data_addr() { | ||||
|             TileDataAddress::X8800 => 0x9000u16.wrapping_add((id as i8 as i16 * 16) as u16), | ||||
|             TileDataAddress::X8000 => 0x8000 + (id as u16 * 16), | ||||
|         }; | ||||
|  | ||||
|     fn resume(&mut self) { | ||||
|         self.enabled = true; | ||||
|     } | ||||
|         let offset = if self.draw_window { | ||||
|             self.wl_count as u16 % 8 | ||||
|         } else { | ||||
|             (line_y as u16 + scroll_y as u16) % 8 | ||||
|         }; | ||||
|  | ||||
|     fn is_enabled(&self) -> bool { | ||||
|         self.enabled | ||||
|     } | ||||
|  | ||||
|     fn vblank_reset(&mut self) { | ||||
|         self.window_line.vblank_reset(); | ||||
|         tile_data_addr + (offset * 2) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Fetcher for BackgroundFetcher { | ||||
|     fn next(&mut self, state: FetcherState) { | ||||
|         self.state = state | ||||
|     } | ||||
|  | ||||
|     fn reset(&mut self) { | ||||
|         self.state = Default::default(); | ||||
|         self.tile = Default::default(); | ||||
| @@ -810,7 +778,7 @@ impl Fetcher for BackgroundFetcher { | ||||
|     fn hblank_reset(&mut self) { | ||||
|         self.reset(); | ||||
|  | ||||
|         self.is_window_tile = false; | ||||
|         self.draw_window = false; | ||||
|  | ||||
|         self.enabled = true; | ||||
|     } | ||||
| @@ -821,9 +789,10 @@ impl Default for BackgroundFetcher { | ||||
|         Self { | ||||
|             state: Default::default(), | ||||
|             tile: Default::default(), | ||||
|             is_window_tile: Default::default(), | ||||
|             window_line: Default::default(), | ||||
|             draw_window: Default::default(), | ||||
|             wl_count: Default::default(), | ||||
|             enabled: true, | ||||
|             tile_high_reset: true, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -835,55 +804,31 @@ struct ObjectFetcher { | ||||
| } | ||||
|  | ||||
| impl Fetcher for ObjectFetcher { | ||||
|     fn next(&mut self, state: FetcherState) { | ||||
|         self.state = state | ||||
|     } | ||||
|  | ||||
|     fn reset(&mut self) { | ||||
|         self.state = Default::default(); | ||||
|         self.tile = Default::default(); | ||||
|     } | ||||
|  | ||||
|     fn hblank_reset(&mut self) { | ||||
|         self.state = Default::default(); | ||||
|         self.tile = Default::default(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default)] | ||||
| struct WindowLineCounter { | ||||
|     count: u8, | ||||
| } | ||||
|  | ||||
| impl WindowLineCounter { | ||||
|     fn increment(&mut self) { | ||||
|         self.count += 1; | ||||
|     } | ||||
|  | ||||
|     fn vblank_reset(&mut self) { | ||||
|         self.count = 0; | ||||
|     } | ||||
|  | ||||
|     fn count(&self) -> u8 { | ||||
|         self.count | ||||
|         self.reset() | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| enum FetcherState { | ||||
|     TileNumber, | ||||
|     SleepOne, | ||||
|     TileLow, | ||||
|     SleepTwo, | ||||
|     TileHigh, | ||||
|     SleepThree, | ||||
|     ToFifoOne, | ||||
|     ToFifoTwo, | ||||
|     TileNumberA, | ||||
|     TileNumberB, | ||||
|     TileLowA, | ||||
|     TileLowB, | ||||
|     TileHighA, | ||||
|     TileHighB, | ||||
|     ToFifoA, | ||||
|     ToFifoB, | ||||
| } | ||||
|  | ||||
| impl Default for FetcherState { | ||||
|     fn default() -> Self { | ||||
|         Self::TileNumber | ||||
|         Self::TileNumberA | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -892,7 +837,7 @@ struct BgPixelProperty { | ||||
|     shade_id: u8, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default)] | ||||
| #[derive(Debug)] | ||||
| struct ObjPixelProperty { | ||||
|     shade_id: u8, | ||||
|     palette_kind: ObjectPaletteKind, | ||||
| @@ -944,11 +889,11 @@ impl TileBuilder { | ||||
|         self.id = Some(id); | ||||
|     } | ||||
|  | ||||
|     fn with_low_byte(&mut self, data: u8) { | ||||
|     fn with_low(&mut self, data: u8) { | ||||
|         self.low = Some(data); | ||||
|     } | ||||
|  | ||||
|     fn with_high_byte(&mut self, data: u8) { | ||||
|     fn with_high(&mut self, data: u8) { | ||||
|         self.high = Some(data); | ||||
|     } | ||||
|  | ||||
| @@ -957,84 +902,39 @@ impl TileBuilder { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default)] | ||||
| struct OamScanState { | ||||
|     count: u8, | ||||
|     mode: OamScanMode, | ||||
| } | ||||
|  | ||||
| impl OamScanState { | ||||
|     fn increase(&mut self) { | ||||
|         self.count += 1; | ||||
|         self.count %= 40; | ||||
|     } | ||||
|  | ||||
|     fn reset(&mut self) { | ||||
|         self.count = Default::default(); | ||||
|         self.mode = Default::default(); | ||||
|     } | ||||
|  | ||||
|     fn count(&self) -> u8 { | ||||
|         self.count | ||||
|     } | ||||
|  | ||||
|     fn mode(&self) -> &OamScanMode { | ||||
|         &self.mode | ||||
|     } | ||||
|  | ||||
|     fn next(&mut self) { | ||||
|         use OamScanMode::*; | ||||
|  | ||||
|         self.mode = match self.mode { | ||||
|             Scan => Sleep, | ||||
|             Sleep => Scan, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, PartialEq)] | ||||
| enum OamScanMode { | ||||
|     Scan, | ||||
|     Sleep, | ||||
| } | ||||
|  | ||||
| impl Default for OamScanMode { | ||||
|     fn default() -> Self { | ||||
|         Self::Scan | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default)] | ||||
| struct WindowStatus { | ||||
|     /// This will be true if WY == LY at any point in the frame thus far | ||||
|     coincidence: bool, | ||||
|     /// This will be true if the conditions which tell the PPU to start | ||||
|     /// drawing from the window tile map is true | ||||
|     should_draw: bool, | ||||
|     enabled: bool, | ||||
| } | ||||
|  | ||||
| impl WindowStatus { | ||||
|     fn should_draw(&self) -> bool { | ||||
|         self.should_draw | ||||
| pub(crate) mod dbg { | ||||
|     use super::{Ppu, PpuMode}; | ||||
|  | ||||
|     pub(crate) fn ly(ppu: &Ppu) -> u8 { | ||||
|         ppu.pos.line_y | ||||
|     } | ||||
|  | ||||
|     fn coincidence(&self) -> bool { | ||||
|         self.coincidence | ||||
|     pub(crate) fn scx(ppu: &Ppu) -> u8 { | ||||
|         ppu.pos.scroll_x | ||||
|     } | ||||
|  | ||||
|     fn set_should_draw(&mut self, value: bool) { | ||||
|         self.should_draw = value; | ||||
|     pub(crate) fn scy(ppu: &Ppu) -> u8 { | ||||
|         ppu.pos.scroll_y | ||||
|     } | ||||
|  | ||||
|     fn set_coincidence(&mut self, value: bool) { | ||||
|         self.coincidence = value; | ||||
|     pub(crate) fn mode(ppu: &Ppu) -> PpuMode { | ||||
|         ppu.stat.mode() | ||||
|     } | ||||
|  | ||||
|     fn hblank_reset(&mut self) { | ||||
|         self.should_draw = false; | ||||
|     pub(crate) fn wx(ppu: &Ppu) -> i16 { | ||||
|         ppu.pos.window_x as i16 | ||||
|     } | ||||
|  | ||||
|     fn vblank_reset(&mut self) { | ||||
|         self.coincidence = false; | ||||
|     pub(crate) fn wy(ppu: &Ppu) -> i16 { | ||||
|         ppu.pos.window_y as i16 | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| use crate::Cycle; | ||||
|  | ||||
| #[derive(Debug, Default)] | ||||
| #[derive(Debug)] | ||||
| pub(crate) struct DirectMemoryAccess { | ||||
|     pub(crate) state: DmaState, | ||||
|     cycle: Cycle, | ||||
| @@ -8,6 +8,16 @@ pub(crate) struct DirectMemoryAccess { | ||||
|     pub(crate) start: DmaAddress, | ||||
| } | ||||
|  | ||||
| impl Default for DirectMemoryAccess { | ||||
|     fn default() -> Self { | ||||
|         Self { | ||||
|             state: DmaState::Disabled, | ||||
|             cycle: Default::default(), | ||||
|             start: Default::default(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl DirectMemoryAccess { | ||||
|     pub(crate) fn tick(&mut self) -> Option<(u16, u16)> { | ||||
|         match self.state { | ||||
| @@ -69,12 +79,6 @@ pub(crate) enum DmaState { | ||||
|     Transferring, | ||||
| } | ||||
|  | ||||
| impl Default for DmaState { | ||||
|     fn default() -> Self { | ||||
|         Self::Disabled | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, Default)] | ||||
| pub(crate) struct DmaAddress(Option<u16>); | ||||
|  | ||||
|   | ||||
| @@ -29,12 +29,6 @@ impl Clone for LCDStatus { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for LCDStatus { | ||||
|     fn default() -> Self { | ||||
|         Self(0x80) // bit 7 is always 1 | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<LCDStatus> for u8 { | ||||
|     fn from(status: LCDStatus) -> Self { | ||||
|         status.0 | ||||
| @@ -67,12 +61,6 @@ impl From<PpuMode> for u8 { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for PpuMode { | ||||
|     fn default() -> Self { | ||||
|         Self::HBlank | ||||
|     } | ||||
| } | ||||
|  | ||||
| bitfield! { | ||||
|     pub struct LCDControl(u8); | ||||
|     impl Debug; | ||||
| @@ -93,12 +81,6 @@ impl Clone for LCDControl { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for LCDControl { | ||||
|     fn default() -> Self { | ||||
|         Self(0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<u8> for LCDControl { | ||||
|     fn from(byte: u8) -> Self { | ||||
|         Self(byte) | ||||
| @@ -142,12 +124,6 @@ impl From<TileMapAddress> for u8 { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for TileMapAddress { | ||||
|     fn default() -> Self { | ||||
|         Self::X9800 | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| pub enum TileDataAddress { | ||||
|     X8800 = 0, | ||||
| @@ -170,12 +146,6 @@ impl From<TileDataAddress> for u8 { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for TileDataAddress { | ||||
|     fn default() -> Self { | ||||
|         Self::X8800 | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| pub enum ObjectSize { | ||||
|     Eight = 0, | ||||
| @@ -183,7 +153,7 @@ pub enum ObjectSize { | ||||
| } | ||||
|  | ||||
| impl ObjectSize { | ||||
|     pub(crate) fn as_u8(&self) -> u8 { | ||||
|     pub(crate) fn size(&self) -> u8 { | ||||
|         use ObjectSize::*; | ||||
|  | ||||
|         match self { | ||||
| @@ -209,12 +179,6 @@ impl From<ObjectSize> for u8 { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for ObjectSize { | ||||
|     fn default() -> Self { | ||||
|         Self::Eight | ||||
|     } | ||||
| } | ||||
|  | ||||
| bitfield! { | ||||
|     pub struct BackgroundPalette(u8); | ||||
|     impl Debug; | ||||
| @@ -243,12 +207,6 @@ impl Clone for BackgroundPalette { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for BackgroundPalette { | ||||
|     fn default() -> Self { | ||||
|         Self(0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<u8> for BackgroundPalette { | ||||
|     fn from(byte: u8) -> Self { | ||||
|         Self(byte) | ||||
| @@ -288,12 +246,6 @@ impl Clone for ObjectPalette { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for ObjectPalette { | ||||
|     fn default() -> Self { | ||||
|         Self(0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<u8> for ObjectPalette { | ||||
|     fn from(byte: u8) -> Self { | ||||
|         Self(byte) | ||||
| @@ -361,24 +313,12 @@ impl From<ObjectFlags> for u8 { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for ObjectFlags { | ||||
|     fn default() -> Self { | ||||
|         Self(0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| pub enum ObjectPaletteKind { | ||||
|     Zero = 0, | ||||
|     One = 1, | ||||
| } | ||||
|  | ||||
| impl Default for ObjectPaletteKind { | ||||
|     fn default() -> Self { | ||||
|         Self::Zero | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<u8> for ObjectPaletteKind { | ||||
|     fn from(byte: u8) -> Self { | ||||
|         match byte & 0b01 { | ||||
| @@ -417,12 +357,6 @@ impl From<RenderPriority> for u8 { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for RenderPriority { | ||||
|     fn default() -> Self { | ||||
|         Self::Object | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| pub enum GrayShade { | ||||
|     White = 0, | ||||
| @@ -442,12 +376,6 @@ impl GrayShade { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for GrayShade { | ||||
|     fn default() -> Self { | ||||
|         Self::White | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<u8> for GrayShade { | ||||
|     fn from(byte: u8) -> Self { | ||||
|         match byte & 0b11 { | ||||
|   | ||||
| @@ -1,79 +0,0 @@ | ||||
| use crate::Cycle; | ||||
| use std::collections::BinaryHeap; | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub(crate) struct Scheduler { | ||||
|     timestamp: Cycle, | ||||
|     queue: BinaryHeap<Event>, | ||||
| } | ||||
|  | ||||
| impl Scheduler { | ||||
|     pub(crate) fn init() -> Self { | ||||
|         let mut scheduler = Self { | ||||
|             timestamp: Default::default(), | ||||
|             queue: Default::default(), | ||||
|         }; | ||||
|  | ||||
|         scheduler.push(Event { | ||||
|             kind: EventKind::TimestampOverflow, | ||||
|             timestamp: Cycle::MAX, | ||||
|             cb: |_delay| panic!("Reached Cycle::MAX"), | ||||
|         }); | ||||
|  | ||||
|         scheduler | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn push(&mut self, event: Event) { | ||||
|         self.queue.push(event); | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn step(&mut self, cycles: Cycle) { | ||||
|         self.timestamp += cycles; | ||||
|  | ||||
|         loop { | ||||
|             let should_pop = match self.queue.peek() { | ||||
|                 Some(event) => self.timestamp >= event.timestamp, | ||||
|                 None => false, | ||||
|             }; | ||||
|  | ||||
|             if !should_pop { | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             let event = self.queue.pop().expect("Pop Event from Scheduler Queue"); | ||||
|  | ||||
|             (event.cb)(self.timestamp - event.timestamp); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub(crate) struct Event { | ||||
|     kind: EventKind, | ||||
|     cb: fn(Cycle), | ||||
|     pub(crate) timestamp: Cycle, | ||||
| } | ||||
|  | ||||
| impl Eq for Event {} | ||||
| impl PartialEq for Event { | ||||
|     fn eq(&self, other: &Self) -> bool { | ||||
|         self.kind == other.kind && self.timestamp == other.timestamp | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl PartialOrd for Event { | ||||
|     fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { | ||||
|         Some(self.cmp(other)) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Ord for Event { | ||||
|     fn cmp(&self, other: &Self) -> std::cmp::Ordering { | ||||
|         self.timestamp.cmp(&other.timestamp) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||||
| pub(crate) enum EventKind { | ||||
|     TimestampOverflow, | ||||
| } | ||||
| @@ -1,6 +1,6 @@ | ||||
| use bitfield::bitfield; | ||||
|  | ||||
| #[derive(Debug, Default)] | ||||
| #[derive(Debug)] | ||||
| pub(crate) struct Serial { | ||||
|     /// 0xFF01 | SB - Serial Transfer Data | ||||
|     pub(crate) next: u8, | ||||
| @@ -8,6 +8,15 @@ pub(crate) struct Serial { | ||||
|     pub(crate) ctrl: SerialControl, | ||||
| } | ||||
|  | ||||
| impl Default for Serial { | ||||
|     fn default() -> Self { | ||||
|         Self { | ||||
|             next: Default::default(), | ||||
|             ctrl: SerialControl(0), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| bitfield! { | ||||
|     pub struct SerialControl(u8); | ||||
|     impl Debug; | ||||
| @@ -23,12 +32,6 @@ impl Clone for SerialControl { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for SerialControl { | ||||
|     fn default() -> Self { | ||||
|         Self(0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<u8> for SerialControl { | ||||
|     fn from(byte: u8) -> Self { | ||||
|         Self(byte) | ||||
| @@ -47,12 +50,6 @@ enum ShiftClock { | ||||
|     Internal = 1, | ||||
| } | ||||
|  | ||||
| impl Default for ShiftClock { | ||||
|     fn default() -> Self { | ||||
|         Self::External | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<u8> for ShiftClock { | ||||
|     fn from(byte: u8) -> Self { | ||||
|         match byte & 0b01 { | ||||
| @@ -69,12 +66,6 @@ enum ClockSpeed { | ||||
|     Fast = 1, | ||||
| } | ||||
|  | ||||
| impl Default for ClockSpeed { | ||||
|     fn default() -> Self { | ||||
|         Self::Normal | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<u8> for ClockSpeed { | ||||
|     fn from(byte: u8) -> Self { | ||||
|         match byte & 0b01 { | ||||
|   | ||||
							
								
								
									
										39
									
								
								src/timer.rs
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								src/timer.rs
									
									
									
									
									
								
							| @@ -21,17 +21,6 @@ impl Timer { | ||||
|         use State::*; | ||||
|         use TimerSpeed::*; | ||||
|  | ||||
|         match self.state { | ||||
|             TIMAOverflow(_) | AbortedTIMAOverflow(_) => self.next(), | ||||
|             LoadTMA => { | ||||
|                 self.counter = self.modulo; | ||||
|                 self.interrupt = true; | ||||
|  | ||||
|                 self.next(); | ||||
|             } | ||||
|             Normal => {} | ||||
|         } | ||||
|  | ||||
|         self.divider = self.divider.wrapping_add(1); | ||||
|  | ||||
|         // Get Bit Position | ||||
| @@ -53,6 +42,17 @@ impl Timer { | ||||
|         } | ||||
|  | ||||
|         self.and_result = Some(new_result); | ||||
|  | ||||
|         match self.state { | ||||
|             TIMAOverflow(_) | AbortedTIMAOverflow(_) => self.next(), | ||||
|             LoadTIMA => { | ||||
|                 self.counter = self.modulo; | ||||
|                 self.interrupt = true; | ||||
|  | ||||
|                 self.next(); | ||||
|             } | ||||
|             Normal => {} | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// 0xFF05 | TIMA - Timer Counter | ||||
| @@ -70,7 +70,7 @@ impl Timer { | ||||
|                 self.counter = byte; | ||||
|                 self.state = AbortedTIMAOverflow(step); | ||||
|             } | ||||
|             LoadTMA => {} | ||||
|             LoadTIMA => { /* Ignored */ } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -95,9 +95,8 @@ impl Timer { | ||||
|         use State::*; | ||||
|  | ||||
|         self.state = match self.state { | ||||
|             Normal | LoadTMA => Normal, | ||||
|             AbortedTIMAOverflow(4) => Normal, | ||||
|             TIMAOverflow(4) => LoadTMA, | ||||
|             Normal | LoadTIMA | AbortedTIMAOverflow(3) => Normal, | ||||
|             TIMAOverflow(3) => LoadTIMA, | ||||
|             AbortedTIMAOverflow(step) => AbortedTIMAOverflow(step + 1), | ||||
|             TIMAOverflow(step) => TIMAOverflow(step + 1), | ||||
|         } | ||||
| @@ -107,7 +106,7 @@ impl Timer { | ||||
| impl Default for Timer { | ||||
|     fn default() -> Self { | ||||
|         Self { | ||||
|             ctrl: Default::default(), | ||||
|             ctrl: TimerControl(0), | ||||
|             counter: 0, | ||||
|             modulo: 0, | ||||
|             divider: 0, | ||||
| @@ -158,12 +157,6 @@ impl Clone for TimerControl { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for TimerControl { | ||||
|     fn default() -> Self { | ||||
|         Self(0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<u8> for TimerControl { | ||||
|     fn from(byte: u8) -> Self { | ||||
|         Self(byte) | ||||
| @@ -181,5 +174,5 @@ enum State { | ||||
|     TIMAOverflow(u8), | ||||
|     AbortedTIMAOverflow(u8), | ||||
|     Normal, | ||||
|     LoadTMA, | ||||
|     LoadTIMA, | ||||
| } | ||||
|   | ||||
| @@ -11,13 +11,13 @@ pub(crate) struct WorkRam { | ||||
| } | ||||
|  | ||||
| impl BusIo for WorkRam { | ||||
|     fn write_byte(&mut self, addr: u16, byte: u8) { | ||||
|         self.bank[addr as usize - WORK_RAM_START_ADDRESS] = byte; | ||||
|     } | ||||
|  | ||||
|     fn read_byte(&self, addr: u16) -> u8 { | ||||
|         self.bank[addr as usize - WORK_RAM_START_ADDRESS] | ||||
|     } | ||||
|  | ||||
|     fn write_byte(&mut self, addr: u16, byte: u8) { | ||||
|         self.bank[addr as usize - WORK_RAM_START_ADDRESS] = byte; | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for WorkRam { | ||||
| @@ -42,11 +42,11 @@ impl Default for VariableWorkRam { | ||||
| } | ||||
|  | ||||
| impl BusIo for VariableWorkRam { | ||||
|     fn write_byte(&mut self, addr: u16, byte: u8) { | ||||
|         self.buf[addr as usize - VARIABLE_WORK_RAM_START_ADDRESS] = byte; | ||||
|     } | ||||
|  | ||||
|     fn read_byte(&self, addr: u16) -> u8 { | ||||
|         self.buf[addr as usize - VARIABLE_WORK_RAM_START_ADDRESS] | ||||
|     } | ||||
|  | ||||
|     fn write_byte(&mut self, addr: u16, byte: u8) { | ||||
|         self.buf[addr as usize - VARIABLE_WORK_RAM_START_ADDRESS] = byte; | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user