Compare commits
	
		
			49 Commits
		
	
	
		
			2ef8fefbb7
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 8a352ca807 | |||
| 068e4124a6 | |||
| fedae65cc8 | |||
| 41586d8cc4 | |||
| 79740ac245 | |||
| d6bfde081e | |||
| d2f57ee66b | |||
| 3841d3a98c | |||
| 85940c8744 | |||
| c7adf54976 | |||
| f0f9003247 | |||
| 5eba097ad4 | |||
| 756c806c2a | |||
| 77af5453f0 | |||
| 41bc4f7afe | |||
| 36e46d3780 | |||
| 926c66746d | |||
| cf2357f917 | |||
| 3fb7edd333 | |||
| 3ab512d663 | |||
| 61156ca8a8 | |||
| 2c411d5792 | |||
| 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 | 
							
								
								
									
										3
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
								
							@@ -1,8 +1,9 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    "recommendations": [
 | 
					    "recommendations": [
 | 
				
			||||||
        "matklad.rust-analyzer",
 | 
					        "rust-lang.rust-analyzer",
 | 
				
			||||||
        "tamasfe.even-better-toml",
 | 
					        "tamasfe.even-better-toml",
 | 
				
			||||||
        "serayuzgur.crates",
 | 
					        "serayuzgur.crates",
 | 
				
			||||||
        "vadimcn.vscode-lldb",
 | 
					        "vadimcn.vscode-lldb",
 | 
				
			||||||
 | 
					        "donaldhays.rgbds-z80"
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										2625
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2625
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										20
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								Cargo.toml
									
									
									
									
									
								
							@@ -8,17 +8,21 @@ edition = "2021"
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
[dependencies]
 | 
					[dependencies]
 | 
				
			||||||
anyhow = "1.0"
 | 
					anyhow = "1.0"
 | 
				
			||||||
bitfield = "0.13"
 | 
					bitfield = "0.17"
 | 
				
			||||||
clap = "2.33"
 | 
					clap = { version = "4.0", features = ["cargo"] }
 | 
				
			||||||
gilrs = "0.8"
 | 
					gilrs = "0.11"
 | 
				
			||||||
pixels = "0.7"
 | 
					winit = "0.28"
 | 
				
			||||||
winit = "0.25"
 | 
					egui = "0.21"
 | 
				
			||||||
winit_input_helper = "0.10"
 | 
					egui_wgpu_backend = "0.22"
 | 
				
			||||||
rodio = "0.14"
 | 
					egui_winit_platform = "0.18"
 | 
				
			||||||
rtrb = "0.2"
 | 
					pollster = "0.3"
 | 
				
			||||||
 | 
					rodio = "0.19"
 | 
				
			||||||
 | 
					rtrb = "0.3"
 | 
				
			||||||
directories-next = "2.0"
 | 
					directories-next = "2.0"
 | 
				
			||||||
tracing = "0.1"
 | 
					tracing = "0.1"
 | 
				
			||||||
tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] }
 | 
					tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] }
 | 
				
			||||||
 | 
					thiserror = "1.0"
 | 
				
			||||||
 | 
					once_cell = "1.10"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[profile.release]
 | 
					[profile.release]
 | 
				
			||||||
debug = true
 | 
					debug = true
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										13
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								README.md
									
									
									
									
									
								
							@@ -1,5 +1,4 @@
 | 
				
			|||||||
# Rekai's Gameboy Emulator
 | 
					# Rekai's Gameboy Emulator
 | 
				
			||||||
[](https://ci.paoda.moe/paoda/gb)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Status
 | 
					### Status
 | 
				
			||||||
* From [Blargg Test ROMs](https://github.com/L-P/blargg-test-roms/)
 | 
					* From [Blargg Test ROMs](https://github.com/L-P/blargg-test-roms/)
 | 
				
			||||||
@@ -7,7 +6,7 @@
 | 
				
			|||||||
    * [x] instr_timing
 | 
					    * [x] instr_timing
 | 
				
			||||||
    * [x] mem_timing
 | 
					    * [x] mem_timing
 | 
				
			||||||
    * [x] mem_timing-2
 | 
					    * [x] mem_timing-2
 | 
				
			||||||
    * [ ] Partially dmg_sound 
 | 
					    * [ ] dmg_sound (partial) 
 | 
				
			||||||
* [x] [dmg-acid2](https://github.com/mattcurrie/dmg-acid2)
 | 
					* [x] [dmg-acid2](https://github.com/mattcurrie/dmg-acid2)
 | 
				
			||||||
* From [mooneye-gb](https://github.com/Gekkio/mooneye-gb):
 | 
					* From [mooneye-gb](https://github.com/Gekkio/mooneye-gb):
 | 
				
			||||||
    * Cartridges:
 | 
					    * Cartridges:
 | 
				
			||||||
@@ -16,14 +15,20 @@
 | 
				
			|||||||
        * [x] MBC2
 | 
					        * [x] MBC2
 | 
				
			||||||
        * [x] MBC5
 | 
					        * [x] MBC5
 | 
				
			||||||
* Implements a cycle-accurate PPU FIFO
 | 
					* Implements a cycle-accurate PPU FIFO
 | 
				
			||||||
    * Doesn't \*exactly\* work right just yet
 | 
					    * Doesn't \*exactly\* work just yet
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Supports: ROM-only, MBC1, MBC2, MBC3 and MBC5 games.
 | 
					Supports: ROM-only, MBC1, MBC2, MBC3 and MBC5 games.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Compiling
 | 
				
			||||||
 | 
					This project was last successfully built on [Rust 1.64.0](https://github.com/rust-lang/rust/tree/1.64.0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. `git clone https://github.com/paoda/gb`
 | 
				
			||||||
 | 
					2. `cd gb`
 | 
				
			||||||
 | 
					3. `cargo run --release`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Controls
 | 
					### Controls
 | 
				
			||||||
Controls are defined [here](https://git.musuka.dev/paoda/gb/src/branch/main/src/joypad.rs#L114)
 | 
					Controls are defined [here](https://github.com/paoda/gb/blob/85940c874460b9cb31bf9b8211bf7dda596d114a/src/joypad.rs#L114)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Key | Button
 | 
					Key | Button
 | 
				
			||||||
--- | ---
 | 
					--- | ---
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										268
									
								
								src/apu.rs
									
									
									
									
									
								
							
							
						
						
									
										268
									
								
								src/apu.rs
									
									
									
									
									
								
							@@ -7,7 +7,7 @@ use types::ch3::Volume as Ch3Volume;
 | 
				
			|||||||
use types::ch4::{CounterWidth, Frequency as Ch4Frequency, PolynomialCounter};
 | 
					use types::ch4::{CounterWidth, Frequency as Ch4Frequency, PolynomialCounter};
 | 
				
			||||||
use types::common::{EnvelopeDirection, FrequencyHigh, SoundDuty, VolumeEnvelope};
 | 
					use types::common::{EnvelopeDirection, FrequencyHigh, SoundDuty, VolumeEnvelope};
 | 
				
			||||||
use types::fs::{FrameSequencer, State as FSState};
 | 
					use types::fs::{FrameSequencer, State as FSState};
 | 
				
			||||||
use types::{ChannelControl, SoundOutput};
 | 
					use types::{ChannelControl, NRx4, SoundOutput};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub mod gen;
 | 
					pub mod gen;
 | 
				
			||||||
mod types;
 | 
					mod types;
 | 
				
			||||||
@@ -27,10 +27,10 @@ pub struct Apu {
 | 
				
			|||||||
    /// Noise
 | 
					    /// Noise
 | 
				
			||||||
    ch4: Channel4,
 | 
					    ch4: Channel4,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sequencer: FrameSequencer,
 | 
					    fs: FrameSequencer,
 | 
				
			||||||
    div_prev: Option<u16>,
 | 
					    div_prev: Option<u16>,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    prod: Option<SampleProducer<f32>>,
 | 
					    pub(crate) prod: Option<SampleProducer<f32>>,
 | 
				
			||||||
    sample_counter: u64,
 | 
					    sample_counter: u64,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    cap: f32,
 | 
					    cap: f32,
 | 
				
			||||||
@@ -69,20 +69,20 @@ impl BusIo for Apu {
 | 
				
			|||||||
            0x11 if self.ctrl.enabled => self.ch1.set_duty(byte),
 | 
					            0x11 if self.ctrl.enabled => self.ch1.set_duty(byte),
 | 
				
			||||||
            0x12 if self.ctrl.enabled => self.ch1.set_envelope(byte),
 | 
					            0x12 if self.ctrl.enabled => self.ch1.set_envelope(byte),
 | 
				
			||||||
            0x13 if self.ctrl.enabled => self.ch1.set_freq_lo(byte),
 | 
					            0x13 if self.ctrl.enabled => self.ch1.set_freq_lo(byte),
 | 
				
			||||||
            0x14 if self.ctrl.enabled => self.ch1.set_freq_hi(&self.sequencer, byte),
 | 
					            0x14 if self.ctrl.enabled => self.ch1.set_freq_hi(&self.fs, byte),
 | 
				
			||||||
            0x16 if self.ctrl.enabled => self.ch2.set_duty(byte),
 | 
					            0x16 if self.ctrl.enabled => self.ch2.set_duty(byte),
 | 
				
			||||||
            0x17 if self.ctrl.enabled => self.ch2.set_envelope(byte),
 | 
					            0x17 if self.ctrl.enabled => self.ch2.set_envelope(byte),
 | 
				
			||||||
            0x18 if self.ctrl.enabled => self.ch2.set_freq_lo(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),
 | 
					            0x1A if self.ctrl.enabled => self.ch3.set_dac_enabled(byte),
 | 
				
			||||||
            0x1B if self.ctrl.enabled => self.ch3.set_len(byte),
 | 
					            0x1B if self.ctrl.enabled => self.ch3.set_len(byte),
 | 
				
			||||||
            0x1C if self.ctrl.enabled => self.ch3.set_volume(byte),
 | 
					            0x1C if self.ctrl.enabled => self.ch3.set_volume(byte),
 | 
				
			||||||
            0x1D if self.ctrl.enabled => self.ch3.set_freq_lo(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),
 | 
					            0x20 if self.ctrl.enabled => self.ch4.set_len(byte),
 | 
				
			||||||
            0x21 if self.ctrl.enabled => self.ch4.set_envelope(byte),
 | 
					            0x21 if self.ctrl.enabled => self.ch4.set_envelope(byte),
 | 
				
			||||||
            0x22 if self.ctrl.enabled => self.ch4.set_poly(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),
 | 
					            0x24 if self.ctrl.enabled => self.ctrl.set_channel(byte),
 | 
				
			||||||
            0x25 if self.ctrl.enabled => self.ctrl.set_output(byte),
 | 
					            0x25 if self.ctrl.enabled => self.ctrl.set_output(byte),
 | 
				
			||||||
            0x26 => self.set_status(byte),
 | 
					            0x26 => self.set_status(byte),
 | 
				
			||||||
@@ -101,7 +101,7 @@ impl Apu {
 | 
				
			|||||||
        if self.is_falling_edge(12, div) {
 | 
					        if self.is_falling_edge(12, div) {
 | 
				
			||||||
            use FSState::*;
 | 
					            use FSState::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            match self.sequencer.state() {
 | 
					            match self.fs.state() {
 | 
				
			||||||
                Length => self.clock_length(),
 | 
					                Length => self.clock_length(),
 | 
				
			||||||
                LengthAndSweep => {
 | 
					                LengthAndSweep => {
 | 
				
			||||||
                    self.clock_length();
 | 
					                    self.clock_length();
 | 
				
			||||||
@@ -111,7 +111,7 @@ impl Apu {
 | 
				
			|||||||
                Nothing => {}
 | 
					                Nothing => {}
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            self.sequencer.next();
 | 
					            self.fs.next();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.div_prev = Some(div);
 | 
					        self.div_prev = Some(div);
 | 
				
			||||||
@@ -172,7 +172,7 @@ impl Apu {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if self.ctrl.enabled {
 | 
					        if self.ctrl.enabled {
 | 
				
			||||||
            // Frame Sequencer reset to Step 0
 | 
					            // Frame Sequencer reset to Step 0
 | 
				
			||||||
            self.sequencer.reset();
 | 
					            self.fs.reset();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Square Duty units are reset to first step
 | 
					            // Square Duty units are reset to first step
 | 
				
			||||||
            self.ch1.duty_pos = 0;
 | 
					            self.ch1.duty_pos = 0;
 | 
				
			||||||
@@ -188,10 +188,6 @@ impl Apu {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn attach_producer(&mut self, prod: SampleProducer<f32>) {
 | 
					 | 
				
			||||||
        self.prod = Some(prod);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn reset(&mut self) {
 | 
					    fn reset(&mut self) {
 | 
				
			||||||
        self.ch1.sweep = Default::default();
 | 
					        self.ch1.sweep = Default::default();
 | 
				
			||||||
        self.ch1.duty = Default::default();
 | 
					        self.ch1.duty = Default::default();
 | 
				
			||||||
@@ -215,8 +211,8 @@ impl Apu {
 | 
				
			|||||||
        self.ch4.poly = Default::default();
 | 
					        self.ch4.poly = Default::default();
 | 
				
			||||||
        self.ch4.freq = Default::default();
 | 
					        self.ch4.freq = Default::default();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.ctrl.channel = Default::default();
 | 
					        self.ctrl.channel = ChannelControl(0);
 | 
				
			||||||
        self.ctrl.out = Default::default();
 | 
					        self.ctrl.out = SoundOutput(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Disable the Channels
 | 
					        // Disable the Channels
 | 
				
			||||||
        self.ch1.enabled = Default::default();
 | 
					        self.ch1.enabled = Default::default();
 | 
				
			||||||
@@ -225,8 +221,8 @@ impl Apu {
 | 
				
			|||||||
        self.ch4.enabled = Default::default();
 | 
					        self.ch4.enabled = Default::default();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn process_length(freq_hi: &FrequencyHigh, counter: &mut u16, enabled: &mut bool) {
 | 
					    fn process_length(freq: &impl NRx4, counter: &mut u16, enabled: &mut bool) {
 | 
				
			||||||
        if freq_hi.length_enable() && *counter > 0 {
 | 
					        if freq.length_enable() && *counter > 0 {
 | 
				
			||||||
            *counter -= 1;
 | 
					            *counter -= 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Check in this scope ensures (only) the above subtraction
 | 
					            // Check in this scope ensures (only) the above subtraction
 | 
				
			||||||
@@ -237,18 +233,6 @@ impl Apu {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn ch4_process_length(freq: &Ch4Frequency, length_timer: &mut u16, enabled: &mut bool) {
 | 
					 | 
				
			||||||
        if freq.length_enable() && *length_timer > 0 {
 | 
					 | 
				
			||||||
            *length_timer -= 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Check in this scope ensures (only) the above subtraction
 | 
					 | 
				
			||||||
            // made length_timer 0
 | 
					 | 
				
			||||||
            if *length_timer == 0 {
 | 
					 | 
				
			||||||
                *enabled = false;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn clock_length(&mut self) {
 | 
					    fn clock_length(&mut self) {
 | 
				
			||||||
        Self::process_length(
 | 
					        Self::process_length(
 | 
				
			||||||
            &self.ch1.freq_hi,
 | 
					            &self.ch1.freq_hi,
 | 
				
			||||||
@@ -258,19 +242,19 @@ impl Apu {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        Self::process_length(
 | 
					        Self::process_length(
 | 
				
			||||||
            &self.ch2.freq_hi,
 | 
					            &self.ch2.freq_hi,
 | 
				
			||||||
            &mut self.ch2.length_timer,
 | 
					            &mut self.ch2.length_counter,
 | 
				
			||||||
            &mut self.ch2.enabled,
 | 
					            &mut self.ch2.enabled,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Self::process_length(
 | 
					        Self::process_length(
 | 
				
			||||||
            &self.ch3.freq_hi,
 | 
					            &self.ch3.freq_hi,
 | 
				
			||||||
            &mut self.ch3.length_timer,
 | 
					            &mut self.ch3.length_counter,
 | 
				
			||||||
            &mut self.ch3.enabled,
 | 
					            &mut self.ch3.enabled,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Self::ch4_process_length(
 | 
					        Self::process_length(
 | 
				
			||||||
            &self.ch4.freq,
 | 
					            &self.ch4.freq,
 | 
				
			||||||
            &mut self.ch4.length_timer,
 | 
					            &mut self.ch4.length_counter,
 | 
				
			||||||
            &mut self.ch4.enabled,
 | 
					            &mut self.ch4.enabled,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -287,7 +271,7 @@ impl Apu {
 | 
				
			|||||||
            if self.ch1.sweep_enabled && period != 0 {
 | 
					            if self.ch1.sweep_enabled && period != 0 {
 | 
				
			||||||
                let new_freq = self.ch1.calc_sweep_freq();
 | 
					                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.set_frequency(new_freq);
 | 
				
			||||||
                    self.ch1.shadow_freq = new_freq;
 | 
					                    self.ch1.shadow_freq = new_freq;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -360,7 +344,7 @@ impl Apu {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Default)]
 | 
					#[derive(Debug)]
 | 
				
			||||||
pub(crate) struct SoundControl {
 | 
					pub(crate) struct SoundControl {
 | 
				
			||||||
    /// 0xFF24 | NR50 - Channel Control
 | 
					    /// 0xFF24 | NR50 - Channel Control
 | 
				
			||||||
    channel: ChannelControl,
 | 
					    channel: ChannelControl,
 | 
				
			||||||
@@ -370,6 +354,16 @@ pub(crate) struct SoundControl {
 | 
				
			|||||||
    enabled: bool,
 | 
					    enabled: bool,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Default for SoundControl {
 | 
				
			||||||
 | 
					    fn default() -> Self {
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            channel: ChannelControl(0),
 | 
				
			||||||
 | 
					            out: SoundOutput(0),
 | 
				
			||||||
 | 
					            enabled: Default::default(),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl SoundControl {
 | 
					impl SoundControl {
 | 
				
			||||||
    /// 0xFF24 | NR50 - Channel Control
 | 
					    /// 0xFF24 | NR50 - Channel Control
 | 
				
			||||||
    pub(crate) fn channel(&self) -> u8 {
 | 
					    pub(crate) fn channel(&self) -> u8 {
 | 
				
			||||||
@@ -485,39 +479,29 @@ impl Channel1 {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /// 0xFF14 | NR14 - Channel 1 Frequency high
 | 
					    /// 0xFF14 | NR14 - Channel 1 Frequency high
 | 
				
			||||||
    pub(crate) fn set_freq_hi(&mut self, fs: &FrameSequencer, byte: u8) {
 | 
					    pub(crate) fn set_freq_hi(&mut self, fs: &FrameSequencer, byte: u8) {
 | 
				
			||||||
        let prev_le = self.freq_hi.length_enable();
 | 
					        let mut new_freq: FrequencyHigh = byte.into();
 | 
				
			||||||
        self.freq_hi = byte.into();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if !fs.next_clocks_length() {
 | 
					 | 
				
			||||||
            if !prev_le && self.freq_hi.length_enable() && self.length_counter != 0 {
 | 
					 | 
				
			||||||
                self.length_counter -= 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if self.length_counter == 0 && !self.freq_hi.initial() {
 | 
					 | 
				
			||||||
                    self.enabled = false;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // If this bit is set, a trigger event occurs
 | 
					        // If this bit is set, a trigger event occurs
 | 
				
			||||||
        if self.freq_hi.initial() {
 | 
					        if new_freq.trigger() {
 | 
				
			||||||
            if self.is_dac_enabled() {
 | 
					 | 
				
			||||||
            self.enabled = true;
 | 
					            self.enabled = true;
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Length behaviour during trigger event
 | 
					 | 
				
			||||||
            if self.length_counter == 0 {
 | 
					            if self.length_counter == 0 {
 | 
				
			||||||
                self.length_counter = if self.freq_hi.length_enable() && !fs.next_clocks_length() {
 | 
					                self.length_counter = 64;
 | 
				
			||||||
                    63
 | 
					
 | 
				
			||||||
                } else {
 | 
					                // length_counter was 0 so length evidently wasn't enabled
 | 
				
			||||||
                    64
 | 
					                self.freq_hi.set_length_enable(false);
 | 
				
			||||||
                };
 | 
					 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Envelope Behaviour during trigger event
 | 
					            // 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();
 | 
					            self.period_timer = self.envelope.period();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Channel Volume reloaded
 | 
				
			||||||
            self.current_volume = self.envelope.init_vol();
 | 
					            self.current_volume = self.envelope.init_vol();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Sweep behaviour during trigger event
 | 
					            // Channel 1 Sweep Behaviour
 | 
				
			||||||
            let sweep_period = self.sweep.period();
 | 
					            let sweep_period = self.sweep.period();
 | 
				
			||||||
            let sweep_shift = self.sweep.shift_count();
 | 
					            let sweep_shift = self.sweep.shift_count();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -529,7 +513,19 @@ impl Channel1 {
 | 
				
			|||||||
            if sweep_shift != 0 {
 | 
					            if sweep_shift != 0 {
 | 
				
			||||||
                let _ = self.calc_sweep_freq();
 | 
					                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) {
 | 
					    fn tick(&mut self) {
 | 
				
			||||||
@@ -591,7 +587,7 @@ impl Channel1 {
 | 
				
			|||||||
pub(crate) struct Channel2 {
 | 
					pub(crate) struct Channel2 {
 | 
				
			||||||
    /// 0xFF16 | NR21 - Channel 2 Sound length / Wave Pattern Duty
 | 
					    /// 0xFF16 | NR21 - Channel 2 Sound length / Wave Pattern Duty
 | 
				
			||||||
    duty: SoundDuty,
 | 
					    duty: SoundDuty,
 | 
				
			||||||
    /// 0xFF17 | NR22 - Channel 2 Volume ENvelope
 | 
					    /// 0xFF17 | NR22 - Channel 2 Volume Envelope
 | 
				
			||||||
    envelope: VolumeEnvelope,
 | 
					    envelope: VolumeEnvelope,
 | 
				
			||||||
    /// 0xFF18 | NR23 - Channel 2 Frequency low (lower 8 bits only)
 | 
					    /// 0xFF18 | NR23 - Channel 2 Frequency low (lower 8 bits only)
 | 
				
			||||||
    freq_lo: u8,
 | 
					    freq_lo: u8,
 | 
				
			||||||
@@ -603,7 +599,7 @@ pub(crate) struct Channel2 {
 | 
				
			|||||||
    current_volume: u8,
 | 
					    current_volume: u8,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Length Functionality
 | 
					    // Length Functionality
 | 
				
			||||||
    length_timer: u16,
 | 
					    length_counter: u16,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    freq_timer: u16,
 | 
					    freq_timer: u16,
 | 
				
			||||||
    duty_pos: u8,
 | 
					    duty_pos: u8,
 | 
				
			||||||
@@ -620,15 +616,15 @@ impl Channel2 {
 | 
				
			|||||||
    /// 0xFF16 | NR21 - Channel 2 Sound length / Wave Pattern Duty
 | 
					    /// 0xFF16 | NR21 - Channel 2 Sound length / Wave Pattern Duty
 | 
				
			||||||
    pub(crate) fn set_duty(&mut self, byte: u8) {
 | 
					    pub(crate) fn set_duty(&mut self, byte: u8) {
 | 
				
			||||||
        self.duty = byte.into();
 | 
					        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 {
 | 
					    pub(crate) fn envelope(&self) -> u8 {
 | 
				
			||||||
        u8::from(self.envelope)
 | 
					        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) {
 | 
					    pub(crate) fn set_envelope(&mut self, byte: u8) {
 | 
				
			||||||
        self.envelope = byte.into();
 | 
					        self.envelope = byte.into();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -648,22 +644,36 @@ impl Channel2 {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// 0xFF19 | NR24 - Channel 2 Frequency high
 | 
					    /// 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();
 | 
					        self.freq_hi = byte.into();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.freq_hi.initial() {
 | 
					        obscure::nrx4::length_update(
 | 
				
			||||||
            // Envelope behaviour during trigger event
 | 
					            &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();
 | 
					            self.period_timer = self.envelope.period();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // reload volume
 | 
				
			||||||
            self.current_volume = self.envelope.init_vol();
 | 
					            self.current_volume = self.envelope.init_vol();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Length behaviour during trigger event
 | 
					            self.enabled = self.is_dac_enabled();
 | 
				
			||||||
            if self.length_timer == 0 {
 | 
					 | 
				
			||||||
                self.length_timer = 64;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if self.is_dac_enabled() {
 | 
					 | 
				
			||||||
                self.enabled = true;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -714,7 +724,7 @@ pub(crate) struct Channel3 {
 | 
				
			|||||||
    wave_ram: [u8; WAVE_PATTERN_RAM_LEN],
 | 
					    wave_ram: [u8; WAVE_PATTERN_RAM_LEN],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Length Functionality
 | 
					    // Length Functionality
 | 
				
			||||||
    length_timer: u16,
 | 
					    length_counter: u16,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    freq_timer: u16,
 | 
					    freq_timer: u16,
 | 
				
			||||||
    offset: u8,
 | 
					    offset: u8,
 | 
				
			||||||
@@ -760,7 +770,7 @@ impl Channel3 {
 | 
				
			|||||||
    /// 0xFF1B | NR31 - Sound Length
 | 
					    /// 0xFF1B | NR31 - Sound Length
 | 
				
			||||||
    pub(crate) fn set_len(&mut self, byte: u8) {
 | 
					    pub(crate) fn set_len(&mut self, byte: u8) {
 | 
				
			||||||
        self.len = byte;
 | 
					        self.len = byte;
 | 
				
			||||||
        self.length_timer = 256 - self.len as u16;
 | 
					        self.length_counter = 256 - self.len as u16;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// 0xFF1C | NR32 - Channel 3 Volume
 | 
					    /// 0xFF1C | NR32 - Channel 3 Volume
 | 
				
			||||||
@@ -792,18 +802,32 @@ impl Channel3 {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// 0xFF1E | NR34 - Channel 3 Frequency high
 | 
					    /// 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();
 | 
					        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
 | 
					            // Length behaviour during trigger event
 | 
				
			||||||
            if self.length_timer == 0 {
 | 
					            if self.length_counter == 0 {
 | 
				
			||||||
                self.length_timer = 256;
 | 
					                self.length_counter = 256;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if self.dac_enabled {
 | 
					            self.freq_timer = (2048 - self.frequency()) * 2;
 | 
				
			||||||
                self.enabled = true;
 | 
					
 | 
				
			||||||
            }
 | 
					            // reset wave channel's ptr into wave RAM
 | 
				
			||||||
 | 
					            self.offset = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.enabled = self.dac_enabled;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -862,7 +886,7 @@ pub(crate) struct Channel4 {
 | 
				
			|||||||
    current_volume: u8,
 | 
					    current_volume: u8,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Length Functionality
 | 
					    // Length Functionality
 | 
				
			||||||
    length_timer: u16,
 | 
					    length_counter: u16,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Linear Feedback Shift Register (15-bit)
 | 
					    /// Linear Feedback Shift Register (15-bit)
 | 
				
			||||||
    lf_shift: u16,
 | 
					    lf_shift: u16,
 | 
				
			||||||
@@ -876,7 +900,7 @@ impl Channel4 {
 | 
				
			|||||||
    /// 0xFF20 | NR41 - Channel 4 Sound Length
 | 
					    /// 0xFF20 | NR41 - Channel 4 Sound Length
 | 
				
			||||||
    pub(crate) fn set_len(&mut self, byte: u8) {
 | 
					    pub(crate) fn set_len(&mut self, byte: u8) {
 | 
				
			||||||
        self.len = byte & 0x3F;
 | 
					        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
 | 
					    /// 0xFF21 | NR42 - Channel 4 Volume Envelope
 | 
				
			||||||
@@ -909,25 +933,37 @@ impl Channel4 {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// 0xFF23 | NR44 - Channel 4 Counter / Consecutive Selector and Restart
 | 
					    /// 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();
 | 
					        self.freq = byte.into();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.freq.initial() {
 | 
					        obscure::nrx4::length_update(
 | 
				
			||||||
            // Envelope behaviour during trigger event
 | 
					            &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();
 | 
					            self.period_timer = self.envelope.period();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            //reload volume
 | 
				
			||||||
            self.current_volume = self.envelope.init_vol();
 | 
					            self.current_volume = self.envelope.init_vol();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Length behaviour during trigger event
 | 
					            // LFSR reset
 | 
				
			||||||
            if self.length_timer == 0 {
 | 
					 | 
				
			||||||
                self.length_timer = 64;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // LFSR behaviour during trigger event
 | 
					 | 
				
			||||||
            self.lf_shift = 0x7FFF;
 | 
					            self.lf_shift = 0x7FFF;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if self.is_dac_enabled() {
 | 
					            self.enabled = self.is_dac_enabled();
 | 
				
			||||||
                self.enabled = true;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -972,3 +1008,39 @@ impl Channel4 {
 | 
				
			|||||||
        code << 4
 | 
					        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;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,11 @@
 | 
				
			|||||||
use bitfield::bitfield;
 | 
					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 {
 | 
					pub(crate) mod ch1 {
 | 
				
			||||||
    use super::bitfield;
 | 
					    use super::bitfield;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -103,7 +109,7 @@ pub(super) mod ch3 {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub(super) mod ch4 {
 | 
					pub(super) mod ch4 {
 | 
				
			||||||
    use super::bitfield;
 | 
					    use super::{bitfield, NRx4};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    bitfield! {
 | 
					    bitfield! {
 | 
				
			||||||
        pub struct PolynomialCounter(u8);
 | 
					        pub struct PolynomialCounter(u8);
 | 
				
			||||||
@@ -163,7 +169,7 @@ pub(super) mod ch4 {
 | 
				
			|||||||
            match byte & 0x01 {
 | 
					            match byte & 0x01 {
 | 
				
			||||||
                0b00 => Self::Short,
 | 
					                0b00 => Self::Short,
 | 
				
			||||||
                0b01 => Self::Long,
 | 
					                0b01 => Self::Long,
 | 
				
			||||||
                _ => unreachable!("{:#04X} is not a valid value for CounterWidth"),
 | 
					                _ => unreachable!("{:#04X} is not a valid value for CounterWidth", byte),
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -177,17 +183,21 @@ pub(super) mod ch4 {
 | 
				
			|||||||
    bitfield! {
 | 
					    bitfield! {
 | 
				
			||||||
        pub struct Frequency(u8);
 | 
					        pub struct Frequency(u8);
 | 
				
			||||||
        impl Debug;
 | 
					        impl Debug;
 | 
				
			||||||
        _initial, _: 7;
 | 
					        _trigger, _: 7;
 | 
				
			||||||
        _length_enable, _: 6;
 | 
					        _length_enable, _set_length_enable: 6;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    impl Frequency {
 | 
					    impl NRx4 for Frequency {
 | 
				
			||||||
        pub(crate) fn length_enable(&self) -> bool {
 | 
					        fn trigger(&self) -> bool {
 | 
				
			||||||
 | 
					            self._trigger()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fn length_enable(&self) -> bool {
 | 
				
			||||||
            self._length_enable()
 | 
					            self._length_enable()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        pub(crate) fn initial(&self) -> bool {
 | 
					        fn set_length_enable(&mut self, value: bool) {
 | 
				
			||||||
            self._initial()
 | 
					            self._set_length_enable(value);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -218,24 +228,28 @@ pub(super) mod ch4 {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub(super) mod common {
 | 
					pub(super) mod common {
 | 
				
			||||||
    use super::bitfield;
 | 
					    use super::{bitfield, NRx4};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    bitfield! {
 | 
					    bitfield! {
 | 
				
			||||||
        pub struct FrequencyHigh(u8);
 | 
					        pub struct FrequencyHigh(u8);
 | 
				
			||||||
        impl Debug;
 | 
					        impl Debug;
 | 
				
			||||||
        _initial, _: 7;
 | 
					        _trigger, _: 7;
 | 
				
			||||||
        _length_enable, _: 6;
 | 
					        _length_enable, _set_length_enable: 6;
 | 
				
			||||||
        pub freq_bits, set_freq_bits: 2, 0;
 | 
					        pub freq_bits, set_freq_bits: 2, 0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    impl FrequencyHigh {
 | 
					    impl NRx4 for FrequencyHigh {
 | 
				
			||||||
        pub(crate) fn initial(&self) -> bool {
 | 
					        fn trigger(&self) -> bool {
 | 
				
			||||||
            self._initial()
 | 
					            self._trigger()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        pub(crate) fn length_enable(&self) -> bool {
 | 
					        fn length_enable(&self) -> bool {
 | 
				
			||||||
            self._length_enable()
 | 
					            self._length_enable()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fn set_length_enable(&mut self, value: bool) {
 | 
				
			||||||
 | 
					            self._set_length_enable(value);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    impl Copy for FrequencyHigh {}
 | 
					    impl Copy for FrequencyHigh {}
 | 
				
			||||||
@@ -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 {
 | 
					    impl From<WavePattern> for u8 {
 | 
				
			||||||
        fn from(pattern: WavePattern) -> Self {
 | 
					        fn from(pattern: WavePattern) -> Self {
 | 
				
			||||||
            pattern as 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 {
 | 
					impl From<u8> for SoundOutput {
 | 
				
			||||||
    fn from(byte: u8) -> Self {
 | 
					    fn from(byte: u8) -> Self {
 | 
				
			||||||
        Self(byte)
 | 
					        Self(byte)
 | 
				
			||||||
@@ -509,12 +511,6 @@ impl Clone for ChannelControl {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for ChannelControl {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self(0)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<u8> for ChannelControl {
 | 
					impl From<u8> for ChannelControl {
 | 
				
			||||||
    fn from(byte: u8) -> Self {
 | 
					    fn from(byte: u8) -> Self {
 | 
				
			||||||
        Self(byte)
 | 
					        Self(byte)
 | 
				
			||||||
@@ -557,22 +553,10 @@ pub(super) mod fs {
 | 
				
			|||||||
            };
 | 
					            };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fn peek(&self) -> State {
 | 
					 | 
				
			||||||
            use State::*;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            match (self.step + 1) % 8 {
 | 
					 | 
				
			||||||
                1 | 3 | 5 => Nothing,
 | 
					 | 
				
			||||||
                0 | 4 => Length,
 | 
					 | 
				
			||||||
                2 | 6 => LengthAndSweep,
 | 
					 | 
				
			||||||
                7 => Envelope,
 | 
					 | 
				
			||||||
                num => unreachable!("Step {} is invalid for the Frame Sequencer", num),
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        pub(crate) fn next_clocks_length(&self) -> bool {
 | 
					        pub(crate) fn next_clocks_length(&self) -> bool {
 | 
				
			||||||
            use State::*;
 | 
					            use State::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            match self.peek() {
 | 
					            match self.state {
 | 
				
			||||||
                Length | LengthAndSweep => true,
 | 
					                Length | LengthAndSweep => true,
 | 
				
			||||||
                Nothing | Envelope => false,
 | 
					                Nothing | Envelope => false,
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										73
									
								
								src/bus.rs
									
									
									
									
									
								
							
							
						
						
									
										73
									
								
								src/bus.rs
									
									
									
									
									
								
							@@ -10,37 +10,19 @@ use crate::work_ram::{VariableWorkRam, WorkRam};
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
pub(crate) const BOOT_SIZE: usize = 0x100;
 | 
					pub(crate) const BOOT_SIZE: usize = 0x100;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug)]
 | 
					#[derive(Debug, Default)]
 | 
				
			||||||
pub struct Bus {
 | 
					pub struct Bus {
 | 
				
			||||||
    boot: Option<[u8; BOOT_SIZE]>, // Boot ROM is 256b long
 | 
					    boot: Option<[u8; BOOT_SIZE]>, // Boot ROM is 256b long
 | 
				
			||||||
    cart: Option<Cartridge>,
 | 
					    pub(crate) cart: Option<Cartridge>,
 | 
				
			||||||
    ppu: Ppu,
 | 
					    pub(crate) ppu: Ppu,
 | 
				
			||||||
    work_ram: WorkRam,
 | 
					    work_ram: WorkRam,
 | 
				
			||||||
    var_ram: VariableWorkRam,
 | 
					    var_ram: VariableWorkRam,
 | 
				
			||||||
    timer: Timer,
 | 
					    timer: Timer,
 | 
				
			||||||
    int: Interrupt,
 | 
					    int: Interrupt,
 | 
				
			||||||
    apu: Apu,
 | 
					    pub(crate) apu: Apu,
 | 
				
			||||||
    high_ram: HighRam,
 | 
					    high_ram: HighRam,
 | 
				
			||||||
    serial: Serial,
 | 
					    serial: Serial,
 | 
				
			||||||
    joypad: Joypad,
 | 
					    pub(crate) joyp: 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(),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Bus {
 | 
					impl Bus {
 | 
				
			||||||
@@ -51,14 +33,6 @@ 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)]
 | 
					    #[allow(dead_code)]
 | 
				
			||||||
    pub(crate) fn boot_mapped(&self) -> bool {
 | 
					    pub(crate) fn boot_mapped(&self) -> bool {
 | 
				
			||||||
        self.boot.is_some()
 | 
					        self.boot.is_some()
 | 
				
			||||||
@@ -73,6 +47,10 @@ impl Bus {
 | 
				
			|||||||
    fn tick(&mut self, limit: u8) {
 | 
					    fn tick(&mut self, limit: u8) {
 | 
				
			||||||
        for _ in 0..limit {
 | 
					        for _ in 0..limit {
 | 
				
			||||||
            self.timer.tick();
 | 
					            self.timer.tick();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if let Some(c) = self.cart.as_mut() {
 | 
				
			||||||
 | 
					                c.tick()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            self.ppu.tick();
 | 
					            self.ppu.tick();
 | 
				
			||||||
            self.apu.tick(self.timer.divider);
 | 
					            self.apu.tick(self.timer.divider);
 | 
				
			||||||
            self.dma_tick()
 | 
					            self.dma_tick()
 | 
				
			||||||
@@ -85,31 +63,6 @@ impl Bus {
 | 
				
			|||||||
            self.oam_write_byte(dest_addr, byte);
 | 
					            self.oam_write_byte(dest_addr, byte);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[inline]
 | 
					 | 
				
			||||||
    pub(crate) fn joyp_mut(&mut self) -> &mut Joypad {
 | 
					 | 
				
			||||||
        &mut self.joypad
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[inline]
 | 
					 | 
				
			||||||
    pub(crate) fn cart(&self) -> Option<&Cartridge> {
 | 
					 | 
				
			||||||
        self.cart.as_ref()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[inline]
 | 
					 | 
				
			||||||
    pub(crate) fn cart_mut(&mut self) -> Option<&mut Cartridge> {
 | 
					 | 
				
			||||||
        self.cart.as_mut()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[inline]
 | 
					 | 
				
			||||||
    pub(crate) fn apu_mut(&mut self) -> &mut Apu {
 | 
					 | 
				
			||||||
        &mut self.apu
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[inline]
 | 
					 | 
				
			||||||
    pub fn ppu(&self) -> &Ppu {
 | 
					 | 
				
			||||||
        &self.ppu
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Bus {
 | 
					impl Bus {
 | 
				
			||||||
@@ -239,7 +192,7 @@ impl BusIo for Bus {
 | 
				
			|||||||
                // Every address here starts with 0xFF so we can just check the
 | 
					                // Every address here starts with 0xFF so we can just check the
 | 
				
			||||||
                // low byte to figure out which register it is
 | 
					                // low byte to figure out which register it is
 | 
				
			||||||
                match addr & 0x00FF {
 | 
					                match addr & 0x00FF {
 | 
				
			||||||
                    0x00 => self.joypad.p1,
 | 
					                    0x00 => self.joyp.p1,
 | 
				
			||||||
                    0x01 => self.serial.next,
 | 
					                    0x01 => self.serial.next,
 | 
				
			||||||
                    0x02 => self.serial.ctrl.into(),
 | 
					                    0x02 => self.serial.ctrl.into(),
 | 
				
			||||||
                    0x04 => (self.timer.divider >> 8) as u8,
 | 
					                    0x04 => (self.timer.divider >> 8) as u8,
 | 
				
			||||||
@@ -337,7 +290,7 @@ impl BusIo for Bus {
 | 
				
			|||||||
                // Every address here starts with 0xFF so we can just check the
 | 
					                // Every address here starts with 0xFF so we can just check the
 | 
				
			||||||
                // low byte to figure out which register it is
 | 
					                // low byte to figure out which register it is
 | 
				
			||||||
                match addr & 0x00FF {
 | 
					                match addr & 0x00FF {
 | 
				
			||||||
                    0x00 => self.joypad.update(byte),
 | 
					                    0x00 => self.joyp.update(byte),
 | 
				
			||||||
                    0x01 => self.serial.next = byte,
 | 
					                    0x01 => self.serial.next = byte,
 | 
				
			||||||
                    0x02 => self.serial.ctrl = byte.into(),
 | 
					                    0x02 => self.serial.ctrl = byte.into(),
 | 
				
			||||||
                    0x04 => self.timer.divider = 0x0000,
 | 
					                    0x04 => self.timer.divider = 0x0000,
 | 
				
			||||||
@@ -401,7 +354,7 @@ impl Bus {
 | 
				
			|||||||
        let lcd_stat = self.ppu.int.lcd_stat();
 | 
					        let lcd_stat = self.ppu.int.lcd_stat();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Read the current interrupt information from the Joypad
 | 
					        // 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
 | 
					        // Read the current interrupt information from the Timer
 | 
				
			||||||
        let timer = self.timer.interrupt();
 | 
					        let timer = self.timer.interrupt();
 | 
				
			||||||
@@ -431,7 +384,7 @@ impl Bus {
 | 
				
			|||||||
        self.ppu.int.set_lcd_stat(lcd_stat);
 | 
					        self.ppu.int.set_lcd_stat(lcd_stat);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Update the Joypad's instance of the following interrupts
 | 
					        // 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
 | 
					        // Update the Timer's instance of the following interrupts
 | 
				
			||||||
        self.timer.set_interrupt(timer);
 | 
					        self.timer.set_interrupt(timer);
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										745
									
								
								src/cartridge.rs
									
									
									
									
									
								
							
							
						
						
									
										745
									
								
								src/cartridge.rs
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										92
									
								
								src/cpu.rs
									
									
									
									
									
								
							
							
						
						
									
										92
									
								
								src/cpu.rs
									
									
									
									
									
								
							@@ -5,7 +5,7 @@ use crate::Cycle;
 | 
				
			|||||||
use bitfield::bitfield;
 | 
					use bitfield::bitfield;
 | 
				
			||||||
use std::fmt::{Display, Formatter, Result as FmtResult};
 | 
					use std::fmt::{Display, Formatter, Result as FmtResult};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Default)]
 | 
					#[derive(Debug)]
 | 
				
			||||||
pub struct Cpu {
 | 
					pub struct Cpu {
 | 
				
			||||||
    pub(crate) bus: Bus,
 | 
					    pub(crate) bus: Bus,
 | 
				
			||||||
    reg: Registers,
 | 
					    reg: Registers,
 | 
				
			||||||
@@ -15,29 +15,13 @@ pub struct Cpu {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Cpu {
 | 
					impl Cpu {
 | 
				
			||||||
    #[allow(dead_code)]
 | 
					    pub(crate) fn new(rom: [u8; BOOT_SIZE]) -> Self {
 | 
				
			||||||
    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 {
 | 
					 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            bus: Bus::with_boot(rom),
 | 
					            bus: Bus::with_boot(rom),
 | 
				
			||||||
            ..Default::default()
 | 
					            reg: Default::default(),
 | 
				
			||||||
 | 
					            flags: Flags(0),
 | 
				
			||||||
 | 
					            ime: ImeState::Disabled,
 | 
				
			||||||
 | 
					            state: State::Execute,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -69,6 +53,12 @@ impl Cpu {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Default for Cpu {
 | 
				
			||||||
 | 
					    fn default() -> Self {
 | 
				
			||||||
 | 
					        Cpu::new(*include_bytes!("../bin/bootix_dmg.bin"))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Cpu {
 | 
					impl Cpu {
 | 
				
			||||||
    /// Fetch an [Instruction] from the memory bus
 | 
					    /// Fetch an [Instruction] from the memory bus
 | 
				
			||||||
    /// (4 cycles)
 | 
					    /// (4 cycles)
 | 
				
			||||||
@@ -151,16 +141,6 @@ impl BusIo for Cpu {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Cpu {
 | 
					impl Cpu {
 | 
				
			||||||
    #[inline]
 | 
					 | 
				
			||||||
    pub(crate) fn bus(&self) -> &Bus {
 | 
					 | 
				
			||||||
        &self.bus
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[inline]
 | 
					 | 
				
			||||||
    pub(crate) fn bus_mut(&mut self) -> &mut Bus {
 | 
					 | 
				
			||||||
        &mut self.bus
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn handle_ei(&mut self) {
 | 
					    fn handle_ei(&mut self) {
 | 
				
			||||||
        match self.ime {
 | 
					        match self.ime {
 | 
				
			||||||
            ImeState::EiExecuted => self.ime = ImeState::Pending,
 | 
					            ImeState::EiExecuted => self.ime = ImeState::Pending,
 | 
				
			||||||
@@ -258,12 +238,6 @@ enum State {
 | 
				
			|||||||
    // Stop,
 | 
					    // Stop,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for State {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self::Execute
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Cpu {
 | 
					impl Cpu {
 | 
				
			||||||
    pub(crate) fn set_register(&mut self, register: Register, value: u8) {
 | 
					    pub(crate) fn set_register(&mut self, register: Register, value: u8) {
 | 
				
			||||||
        use Register::*;
 | 
					        use Register::*;
 | 
				
			||||||
@@ -457,12 +431,6 @@ impl Clone for Flags {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for Flags {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self(0)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Display for Flags {
 | 
					impl Display for Flags {
 | 
				
			||||||
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
 | 
					    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
 | 
				
			||||||
        if self.z() {
 | 
					        if self.z() {
 | 
				
			||||||
@@ -518,8 +486,38 @@ pub(crate) enum ImeState {
 | 
				
			|||||||
    Enabled,
 | 
					    Enabled,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for ImeState {
 | 
					pub(crate) mod dbg {
 | 
				
			||||||
    fn default() -> Self {
 | 
					    use super::{Cpu, ImeState, RegisterPair};
 | 
				
			||||||
        Self::Disabled
 | 
					
 | 
				
			||||||
 | 
					    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)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										222
									
								
								src/emu.rs
									
									
									
									
									
								
							
							
						
						
									
										222
									
								
								src/emu.rs
									
									
									
									
									
								
							@@ -1,7 +1,7 @@
 | 
				
			|||||||
use crate::apu::gen::SampleProducer;
 | 
					use crate::apu::gen::SampleProducer;
 | 
				
			||||||
use crate::bus::BOOT_SIZE;
 | 
					use crate::bus::BOOT_SIZE;
 | 
				
			||||||
 | 
					use crate::cartridge::Cartridge;
 | 
				
			||||||
use crate::cpu::Cpu;
 | 
					use crate::cpu::Cpu;
 | 
				
			||||||
use crate::joypad::{self, Joypad};
 | 
					 | 
				
			||||||
use crate::{Cycle, GB_HEIGHT, GB_WIDTH};
 | 
					use crate::{Cycle, GB_HEIGHT, GB_WIDTH};
 | 
				
			||||||
use clap::crate_name;
 | 
					use clap::crate_name;
 | 
				
			||||||
use gilrs::Gilrs;
 | 
					use gilrs::Gilrs;
 | 
				
			||||||
@@ -9,130 +9,139 @@ use std::fs::File;
 | 
				
			|||||||
use std::io::{Read, Write};
 | 
					use std::io::{Read, Write};
 | 
				
			||||||
use std::path::{Path, PathBuf};
 | 
					use std::path::{Path, PathBuf};
 | 
				
			||||||
use std::time::Duration;
 | 
					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 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 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
 | 
					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;
 | 
					    let mut elapsed = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if let Some(event) = gamepad.next_event() {
 | 
					    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);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    joypad::handle_keyboard_input(emu.joyp_mut(), key);
 | 
					    crate::joypad::handle_keyboard_input(&mut cpu.bus.joyp, key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    while elapsed < CYCLES_IN_FRAME {
 | 
					    while elapsed < cycles {
 | 
				
			||||||
        elapsed += emu.step();
 | 
					        elapsed += cpu.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 Default for Emulator {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self::new()
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Emulator {
 | 
					pub fn save_and_exit(cpu: &Cpu, control_flow: &mut ControlFlow) {
 | 
				
			||||||
    pub fn new() -> Self {
 | 
					    write_save(cpu);
 | 
				
			||||||
        Self {
 | 
					    *control_flow = ControlFlow::Exit;
 | 
				
			||||||
            cpu: Cpu::with_boot(*include_bytes!("../bin/bootix_dmg.bin")),
 | 
					}
 | 
				
			||||||
            timestamp: Default::default(),
 | 
					
 | 
				
			||||||
 | 
					#[inline]
 | 
				
			||||||
 | 
					pub fn pixel_buf(cpu: &Cpu) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] {
 | 
				
			||||||
 | 
					    use crate::ppu::Device;
 | 
				
			||||||
 | 
					    cpu.bus.ppu.frame_buf.get(Device::Host)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn from_boot_rom<P: AsRef<Path>>(path: P) -> std::io::Result<Cpu> {
 | 
				
			||||||
 | 
					    Ok(Cpu::new(read_boot(path)?))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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()
 | 
				
			||||||
 | 
					        .and_then(|c| c.title.as_deref())
 | 
				
			||||||
 | 
					        .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"),
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 file = File::open(&save_path)?;
 | 
				
			||||||
 | 
					            let mut memory = Vec::new();
 | 
				
			||||||
 | 
					            file.read_to_end(&mut memory)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if ext_ram.len() != memory.len() {
 | 
				
			||||||
 | 
					                return Err(SaveError::DiffSize);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn from_boot_rom<P: AsRef<Path>>(path: P) -> std::io::Result<Self> {
 | 
					            ext_ram.copy_from_slice(&memory);
 | 
				
			||||||
        Ok(Self {
 | 
					            Ok(save_path)
 | 
				
			||||||
            cpu: Cpu::with_boot(Self::read_boot(path)?),
 | 
					 | 
				
			||||||
            timestamp: Default::default(),
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        None => Err(SaveError::NotApplicable),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn read_boot<P: AsRef<Path>>(path: P) -> std::io::Result<[u8; BOOT_SIZE]> {
 | 
					fn read_boot<P: AsRef<Path>>(path: P) -> std::io::Result<[u8; BOOT_SIZE]> {
 | 
				
			||||||
    let mut buf = [0; BOOT_SIZE];
 | 
					    let mut buf = [0; BOOT_SIZE];
 | 
				
			||||||
    let mut file = File::open(path.as_ref())?;
 | 
					    let mut file = File::open(path.as_ref())?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    file.read_exact(&mut buf)?;
 | 
					    file.read_exact(&mut buf)?;
 | 
				
			||||||
    Ok(buf)
 | 
					    Ok(buf)
 | 
				
			||||||
    }
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn step(&mut self) -> Cycle {
 | 
					fn data_path() -> Option<PathBuf> {
 | 
				
			||||||
        let cycles = self.cpu.step();
 | 
					 | 
				
			||||||
        self.timestamp += cycles;
 | 
					 | 
				
			||||||
        cycles
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn read_game_rom<P: AsRef<Path>>(&mut self, path: P) -> std::io::Result<()> {
 | 
					 | 
				
			||||||
        self.load_rom(std::fs::read(path.as_ref())?);
 | 
					 | 
				
			||||||
        Ok(())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn load_rom(&mut self, rom: Vec<u8>) {
 | 
					 | 
				
			||||||
        self.cpu.bus_mut().load_cart(rom);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[inline]
 | 
					 | 
				
			||||||
    fn joyp_mut(&mut self) -> &mut Joypad {
 | 
					 | 
				
			||||||
        self.cpu.bus_mut().joyp_mut()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn set_prod(&mut self, prod: SampleProducer<f32>) {
 | 
					 | 
				
			||||||
        self.cpu.bus_mut().apu_mut().attach_producer(prod)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn title(&self) -> &str {
 | 
					 | 
				
			||||||
        self.cpu.bus().cart_title().unwrap_or(DEFAULT_TITLE)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn try_write_sav(&self) -> std::io::Result<()> {
 | 
					 | 
				
			||||||
        if let Some(ext_ram) = self.cpu.bus().cart().map(|c| c.ext_ram()).flatten() {
 | 
					 | 
				
			||||||
            if let Some(title) = self.cpu.bus().cart_title() {
 | 
					 | 
				
			||||||
                let mut save_path = Self::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(ext_ram)?;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Ok(())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn try_load_sav(&mut self) -> std::io::Result<()> {
 | 
					 | 
				
			||||||
        if let Some(cart) = self.cpu.bus_mut().cart_mut() {
 | 
					 | 
				
			||||||
            if let Some(title) = cart.title() {
 | 
					 | 
				
			||||||
                let mut save_path = Self::data_path().unwrap_or_else(|| PathBuf::from("."));
 | 
					 | 
				
			||||||
                save_path.push(title);
 | 
					 | 
				
			||||||
                save_path.set_extension("sav");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if let Ok(mut file) = File::open(&save_path) {
 | 
					 | 
				
			||||||
                    tracing::info!("Load {:?}", save_path);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    let mut memory = Vec::new();
 | 
					 | 
				
			||||||
                    file.read_to_end(&mut memory)?;
 | 
					 | 
				
			||||||
                    cart.write_ext_ram(memory);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Ok(())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn data_path() -> Option<PathBuf> {
 | 
					 | 
				
			||||||
    match directories_next::ProjectDirs::from("dev", "musuka", crate_name!()) {
 | 
					    match directories_next::ProjectDirs::from("dev", "musuka", crate_name!()) {
 | 
				
			||||||
        Some(dirs) => {
 | 
					        Some(dirs) => {
 | 
				
			||||||
            let data_local = dirs.data_local_dir();
 | 
					            let data_local = dirs.data_local_dir();
 | 
				
			||||||
@@ -141,5 +150,14 @@ impl Emulator {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        None => None,
 | 
					        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,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										472
									
								
								src/gui.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										472
									
								
								src/gui.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,472 @@
 | 
				
			|||||||
 | 
					use egui::{Context, TextureId};
 | 
				
			||||||
 | 
					use egui_wgpu_backend::{wgpu, RenderPass, ScreenDescriptor};
 | 
				
			||||||
 | 
					use egui_winit_platform::Platform;
 | 
				
			||||||
 | 
					use wgpu::{
 | 
				
			||||||
 | 
					    Adapter, CommandEncoder, Device, Extent3d, FilterMode, Instance, Queue, RequestDeviceError,
 | 
				
			||||||
 | 
					    Surface, SurfaceCapabilities, SurfaceConfiguration, Texture, TextureView,
 | 
				
			||||||
 | 
					    TextureViewDescriptor,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use winit::dpi::PhysicalSize;
 | 
				
			||||||
 | 
					use winit::error::OsError;
 | 
				
			||||||
 | 
					use winit::event::{ElementState, Event, KeyboardInput};
 | 
				
			||||||
 | 
					use winit::event_loop::{ControlFlow, EventLoop};
 | 
				
			||||||
 | 
					use winit::window::Window;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::cpu::Cpu;
 | 
				
			||||||
 | 
					use crate::{emu, 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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct Gui {
 | 
				
			||||||
 | 
					    pub should_quit: bool,
 | 
				
			||||||
 | 
					    pub title: String,
 | 
				
			||||||
 | 
					    pub mode: EmuMode,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    window: Window,
 | 
				
			||||||
 | 
					    platform: Platform,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    surface: Surface,
 | 
				
			||||||
 | 
					    device: Device,
 | 
				
			||||||
 | 
					    queue: Queue,
 | 
				
			||||||
 | 
					    surface_config: SurfaceConfiguration,
 | 
				
			||||||
 | 
					    render_pass: RenderPass,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    texture: Texture,
 | 
				
			||||||
 | 
					    texture_size: Extent3d,
 | 
				
			||||||
 | 
					    texture_id: TextureId,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Gui {
 | 
				
			||||||
 | 
					    pub fn new<T>(title: String, event_loop: &EventLoop<T>, cpu: &Cpu) -> Self {
 | 
				
			||||||
 | 
					        use wgpu::InstanceDescriptor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let window = build_window(event_loop).expect("build window");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let instance = Instance::new(InstanceDescriptor::default());
 | 
				
			||||||
 | 
					        let surface = unsafe {
 | 
				
			||||||
 | 
					            instance
 | 
				
			||||||
 | 
					                .create_surface(&window)
 | 
				
			||||||
 | 
					                .expect("create wgpu instance surface")
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let adapter = request_adapter(&instance, &surface).expect("request adaptor");
 | 
				
			||||||
 | 
					        let (device, queue) = request_device(&adapter).expect("request device");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let capabilities = surface.get_capabilities(&adapter);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let surface_config = surface_config(&window, capabilities);
 | 
				
			||||||
 | 
					        surface.configure(&device, &surface_config);
 | 
				
			||||||
 | 
					        let platform = platform(&window);
 | 
				
			||||||
 | 
					        let mut render_pass = RenderPass::new(&device, surface_config.format, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let texture_size = texture_size();
 | 
				
			||||||
 | 
					        let texture = create_texture(&device, texture_size);
 | 
				
			||||||
 | 
					        write_to_texture(&queue, &texture, emu::pixel_buf(cpu), texture_size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let view = texture.create_view(&TextureViewDescriptor::default());
 | 
				
			||||||
 | 
					        let texture_id = expose_texture_to_egui(&mut render_pass, &device, &view);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            should_quit: Default::default(),
 | 
				
			||||||
 | 
					            title,
 | 
				
			||||||
 | 
					            mode: EmuMode::Running,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            window,
 | 
				
			||||||
 | 
					            platform,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            surface,
 | 
				
			||||||
 | 
					            device,
 | 
				
			||||||
 | 
					            queue,
 | 
				
			||||||
 | 
					            surface_config,
 | 
				
			||||||
 | 
					            render_pass,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            texture,
 | 
				
			||||||
 | 
					            texture_size,
 | 
				
			||||||
 | 
					            texture_id,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn maybe_quit(&self, cpu: &Cpu, control_flow: &mut ControlFlow) {
 | 
				
			||||||
 | 
					        if self.should_quit {
 | 
				
			||||||
 | 
					            emu::save_and_exit(cpu, control_flow);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn request_redraw(&self) {
 | 
				
			||||||
 | 
					        self.window.request_redraw();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn handle_event<T>(&mut self, event: &Event<T>) {
 | 
				
			||||||
 | 
					        self.platform.handle_event(event);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn update_time(&mut self, elapsed_seconds: f64) {
 | 
				
			||||||
 | 
					        self.platform.update_time(elapsed_seconds);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn resize(&mut self, size: PhysicalSize<u32>) {
 | 
				
			||||||
 | 
					        // See: https://github.com/rust-windowing/winit/issues/208
 | 
				
			||||||
 | 
					        if size.width > 0 && size.height > 0 {
 | 
				
			||||||
 | 
					            self.surface_config.width = size.width;
 | 
				
			||||||
 | 
					            self.surface_config.height = size.height;
 | 
				
			||||||
 | 
					            self.surface.configure(&self.device, &self.surface_config);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn paint(&mut self, cpu: &Cpu) {
 | 
				
			||||||
 | 
					        use wgpu::{Color, SurfaceError};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let data = emu::pixel_buf(cpu);
 | 
				
			||||||
 | 
					        write_to_texture(&self.queue, &self.texture, data, self.texture_size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let output_frame = match self.surface.get_current_texture() {
 | 
				
			||||||
 | 
					            Ok(frame) => frame,
 | 
				
			||||||
 | 
					            Err(SurfaceError::Outdated) => return, // Occurs on minimization on Windows
 | 
				
			||||||
 | 
					            Err(e) => {
 | 
				
			||||||
 | 
					                eprintln!("Dropped frame with error: {}", e);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let output_view = output_frame
 | 
				
			||||||
 | 
					            .texture
 | 
				
			||||||
 | 
					            .create_view(&TextureViewDescriptor::default());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Begin to draw Egui components
 | 
				
			||||||
 | 
					        self.platform.begin_frame();
 | 
				
			||||||
 | 
					        self.draw_egui(cpu, &self.platform.context(), self.texture_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // End the UI frame. We could now handle the output and draw the UI with the backend.
 | 
				
			||||||
 | 
					        let full_output = self.platform.end_frame(Some(&self.window));
 | 
				
			||||||
 | 
					        let paint_jobs = self.platform.context().tessellate(full_output.shapes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let mut encoder = create_command_encoder(&self.device);
 | 
				
			||||||
 | 
					        let screen_descriptor = create_screen_descriptor(&self.window, &self.surface_config);
 | 
				
			||||||
 | 
					        let tdelta = full_output.textures_delta;
 | 
				
			||||||
 | 
					        // Upload all resources for the GPU.
 | 
				
			||||||
 | 
					        self.render_pass
 | 
				
			||||||
 | 
					            .add_textures(&self.device, &self.queue, &tdelta)
 | 
				
			||||||
 | 
					            .expect("add texture ok");
 | 
				
			||||||
 | 
					        self.render_pass
 | 
				
			||||||
 | 
					            .update_buffers(&self.device, &self.queue, &paint_jobs, &screen_descriptor);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Record all render passes.
 | 
				
			||||||
 | 
					        self.render_pass
 | 
				
			||||||
 | 
					            .execute(
 | 
				
			||||||
 | 
					                &mut encoder,
 | 
				
			||||||
 | 
					                &output_view,
 | 
				
			||||||
 | 
					                &paint_jobs,
 | 
				
			||||||
 | 
					                &screen_descriptor,
 | 
				
			||||||
 | 
					                Some(Color::BLACK),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .expect("execute render pass");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Submit the commands.
 | 
				
			||||||
 | 
					        self.queue.submit(std::iter::once(encoder.finish()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Redraw egui
 | 
				
			||||||
 | 
					        output_frame.present();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.render_pass
 | 
				
			||||||
 | 
					            .remove_textures(tdelta)
 | 
				
			||||||
 | 
					            .expect("remove texture delta");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[inline]
 | 
				
			||||||
 | 
					    pub fn draw_egui(&mut self, cpu: &Cpu, ctx: &Context, 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| {
 | 
				
			||||||
 | 
					            ui.menu_button("File", |ui| {
 | 
				
			||||||
 | 
					                if ui.button("Quit").clicked() {
 | 
				
			||||||
 | 
					                    self.should_quit = true;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            egui::Window::new(&self.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!("{:?}", self.mode))
 | 
				
			||||||
 | 
					                    .show_ui(ui, |ui| {
 | 
				
			||||||
 | 
					                        ui.selectable_value(&mut self.mode, EmuMode::Running, "Running");
 | 
				
			||||||
 | 
					                        ui.selectable_value(&mut self.mode, EmuMode::Stopped, "Stopped");
 | 
				
			||||||
 | 
					                        ui.selectable_value(&mut self.mode, EmuMode::StepFrame, "Step Frame");
 | 
				
			||||||
 | 
					                        ui.selectable_value(&mut self.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),
 | 
				
			||||||
 | 
					                            ppu::dbg::dot(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();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // TODO: Reimplement this
 | 
				
			||||||
 | 
					                    // 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");
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[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
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn build_window<T>(event_loop: &EventLoop<T>) -> Result<Window, OsError> {
 | 
				
			||||||
 | 
					    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)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn request_adapter(instance: &Instance, surface: &Surface) -> Option<Adapter> {
 | 
				
			||||||
 | 
					    use wgpu::{PowerPreference, RequestAdapterOptions};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pollster::block_on(instance.request_adapter(&RequestAdapterOptions {
 | 
				
			||||||
 | 
					        power_preference: PowerPreference::HighPerformance,
 | 
				
			||||||
 | 
					        compatible_surface: Some(surface),
 | 
				
			||||||
 | 
					        force_fallback_adapter: false, // TODO: What do I want to do with this?
 | 
				
			||||||
 | 
					    }))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn request_device(adapter: &Adapter) -> Result<(Device, Queue), RequestDeviceError> {
 | 
				
			||||||
 | 
					    use wgpu::{DeviceDescriptor, Features, Limits};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pollster::block_on(adapter.request_device(
 | 
				
			||||||
 | 
					        &DeviceDescriptor {
 | 
				
			||||||
 | 
					            features: Features::default(),
 | 
				
			||||||
 | 
					            limits: Limits::default(),
 | 
				
			||||||
 | 
					            label: None,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        None,
 | 
				
			||||||
 | 
					    ))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn surface_config(window: &Window, capabilities: SurfaceCapabilities) -> SurfaceConfiguration {
 | 
				
			||||||
 | 
					    use egui_wgpu_backend::wgpu::{PresentMode, TextureFormat, TextureUsages};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let size = window.inner_size();
 | 
				
			||||||
 | 
					    SurfaceConfiguration {
 | 
				
			||||||
 | 
					        usage: TextureUsages::RENDER_ATTACHMENT,
 | 
				
			||||||
 | 
					        format: capabilities.formats[0],
 | 
				
			||||||
 | 
					        width: size.width as u32,
 | 
				
			||||||
 | 
					        height: size.height as u32,
 | 
				
			||||||
 | 
					        present_mode: PresentMode::Fifo,
 | 
				
			||||||
 | 
					        alpha_mode: capabilities.alpha_modes[0],
 | 
				
			||||||
 | 
					        view_formats: vec![TextureFormat::Rgba8UnormSrgb],
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn platform(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()
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn texture_size() -> Extent3d {
 | 
				
			||||||
 | 
					    Extent3d {
 | 
				
			||||||
 | 
					        width: GB_WIDTH as u32,
 | 
				
			||||||
 | 
					        height: GB_HEIGHT as u32,
 | 
				
			||||||
 | 
					        ..Default::default()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn create_texture(device: &Device, size: Extent3d) -> Texture {
 | 
				
			||||||
 | 
					    use wgpu::{TextureDescriptor, TextureDimension, TextureFormat, TextureUsages};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    device.create_texture(&TextureDescriptor {
 | 
				
			||||||
 | 
					        size,
 | 
				
			||||||
 | 
					        mip_level_count: 1,
 | 
				
			||||||
 | 
					        sample_count: 1,
 | 
				
			||||||
 | 
					        dimension: TextureDimension::D2,
 | 
				
			||||||
 | 
					        format: TextureFormat::Bgra8Unorm,
 | 
				
			||||||
 | 
					        usage: TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING,
 | 
				
			||||||
 | 
					        label: Some("gb_pixel_buffer"),
 | 
				
			||||||
 | 
					        view_formats: &[TextureFormat::Bgra8Unorm],
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[inline]
 | 
				
			||||||
 | 
					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,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn expose_texture_to_egui(
 | 
				
			||||||
 | 
					    render_pass: &mut RenderPass,
 | 
				
			||||||
 | 
					    device: &Device,
 | 
				
			||||||
 | 
					    view: &TextureView,
 | 
				
			||||||
 | 
					) -> TextureId {
 | 
				
			||||||
 | 
					    render_pass.egui_texture_from_wgpu_texture(device, view, FILTER_MODE)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[inline]
 | 
				
			||||||
 | 
					fn create_command_encoder(device: &Device) -> CommandEncoder {
 | 
				
			||||||
 | 
					    use wgpu::CommandEncoderDescriptor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    device.create_command_encoder(&CommandEncoderDescriptor {
 | 
				
			||||||
 | 
					        label: Some("encoder"),
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[inline]
 | 
				
			||||||
 | 
					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,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 {
 | 
					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 {
 | 
					    fn read_byte(&self, addr: u16) -> u8 {
 | 
				
			||||||
        self.buf[addr as usize - HIGH_RAM_START_ADDRESS]
 | 
					        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::add::{Source as AddSource, Target as AddTarget};
 | 
				
			||||||
use self::alu::Source as AluSource;
 | 
					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::load::{Source as LDSource, Target as LDTarget};
 | 
				
			||||||
use self::table::{
 | 
					use self::table::{
 | 
				
			||||||
    alu_imm_instr, alu_reg_instr, flag_instr, group1, group2, group3, jump_cond, prefix_alu,
 | 
					    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 {
 | 
					pub(crate) enum Instruction {
 | 
				
			||||||
    NOP,
 | 
					    NOP,
 | 
				
			||||||
    STOP,
 | 
					    STOP,
 | 
				
			||||||
    JR(JumpCondition),
 | 
					    JR(JpCond),
 | 
				
			||||||
    LD(LDTarget, LDSource),
 | 
					    LD(LDTarget, LDSource),
 | 
				
			||||||
    ADD(AddTarget, AddSource),
 | 
					    ADD(AddTarget, AddSource),
 | 
				
			||||||
    LDHL,
 | 
					    LDHL,
 | 
				
			||||||
@@ -38,13 +38,13 @@ pub(crate) enum Instruction {
 | 
				
			|||||||
    XOR(AluSource),
 | 
					    XOR(AluSource),
 | 
				
			||||||
    OR(AluSource),
 | 
					    OR(AluSource),
 | 
				
			||||||
    CP(AluSource),
 | 
					    CP(AluSource),
 | 
				
			||||||
    RET(JumpCondition),
 | 
					    RET(JpCond),
 | 
				
			||||||
    POP(Group3RegisterPair),
 | 
					    POP(Group3RegisterPair),
 | 
				
			||||||
    RETI,
 | 
					    RETI,
 | 
				
			||||||
    JP(JumpCondition, JumpLocation),
 | 
					    JP(JpCond, JpLoc),
 | 
				
			||||||
    DI,
 | 
					    DI,
 | 
				
			||||||
    EI,
 | 
					    EI,
 | 
				
			||||||
    CALL(JumpCondition),
 | 
					    CALL(JpCond),
 | 
				
			||||||
    PUSH(Group3RegisterPair),
 | 
					    PUSH(Group3RegisterPair),
 | 
				
			||||||
    RST(u8),
 | 
					    RST(u8),
 | 
				
			||||||
    RLC(Register),
 | 
					    RLC(Register),
 | 
				
			||||||
@@ -58,6 +58,7 @@ pub(crate) enum Instruction {
 | 
				
			|||||||
    BIT(u8, Register),
 | 
					    BIT(u8, Register),
 | 
				
			||||||
    RES(u8, Register),
 | 
					    RES(u8, Register),
 | 
				
			||||||
    SET(u8, Register),
 | 
					    SET(u8, Register),
 | 
				
			||||||
 | 
					    Invalid,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl std::fmt::Debug for Instruction {
 | 
					impl std::fmt::Debug for Instruction {
 | 
				
			||||||
@@ -109,6 +110,7 @@ impl std::fmt::Debug for Instruction {
 | 
				
			|||||||
            BIT(b, r) => write!(f, "BIT {}, {:?}", b, r),
 | 
					            BIT(b, r) => write!(f, "BIT {}, {:?}", b, r),
 | 
				
			||||||
            RES(b, r) => write!(f, "RES {}, {:?}", b, r),
 | 
					            RES(b, r) => write!(f, "RES {}, {:?}", b, r),
 | 
				
			||||||
            SET(b, r) => write!(f, "SET {}, {:?}", b, r),
 | 
					            SET(b, r) => write!(f, "SET {}, {:?}", b, r),
 | 
				
			||||||
 | 
					            Invalid => f.write_str("???"),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -293,7 +295,7 @@ impl Instruction {
 | 
				
			|||||||
                let addr = pc.wrapping_add(byte as u16);
 | 
					                let addr = pc.wrapping_add(byte as u16);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                match cond {
 | 
					                match cond {
 | 
				
			||||||
                    JumpCondition::NotZero => {
 | 
					                    JpCond::NotZero => {
 | 
				
			||||||
                        if !flags.z() {
 | 
					                        if !flags.z() {
 | 
				
			||||||
                            Self::jump(cpu, addr);
 | 
					                            Self::jump(cpu, addr);
 | 
				
			||||||
                            12
 | 
					                            12
 | 
				
			||||||
@@ -301,7 +303,7 @@ impl Instruction {
 | 
				
			|||||||
                            8
 | 
					                            8
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    JumpCondition::Zero => {
 | 
					                    JpCond::Zero => {
 | 
				
			||||||
                        if flags.z() {
 | 
					                        if flags.z() {
 | 
				
			||||||
                            Self::jump(cpu, addr);
 | 
					                            Self::jump(cpu, addr);
 | 
				
			||||||
                            12
 | 
					                            12
 | 
				
			||||||
@@ -309,7 +311,7 @@ impl Instruction {
 | 
				
			|||||||
                            8
 | 
					                            8
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    JumpCondition::NotCarry => {
 | 
					                    JpCond::NotCarry => {
 | 
				
			||||||
                        if !flags.c() {
 | 
					                        if !flags.c() {
 | 
				
			||||||
                            Self::jump(cpu, addr);
 | 
					                            Self::jump(cpu, addr);
 | 
				
			||||||
                            12
 | 
					                            12
 | 
				
			||||||
@@ -317,7 +319,7 @@ impl Instruction {
 | 
				
			|||||||
                            8
 | 
					                            8
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    JumpCondition::Carry => {
 | 
					                    JpCond::Carry => {
 | 
				
			||||||
                        if flags.c() {
 | 
					                        if flags.c() {
 | 
				
			||||||
                            Self::jump(cpu, addr);
 | 
					                            Self::jump(cpu, addr);
 | 
				
			||||||
                            12
 | 
					                            12
 | 
				
			||||||
@@ -325,7 +327,7 @@ impl Instruction {
 | 
				
			|||||||
                            8
 | 
					                            8
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    JumpCondition::Always => {
 | 
					                    JpCond::Always => {
 | 
				
			||||||
                        Self::jump(cpu, addr);
 | 
					                        Self::jump(cpu, addr);
 | 
				
			||||||
                        12
 | 
					                        12
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
@@ -837,7 +839,7 @@ impl Instruction {
 | 
				
			|||||||
                let flags: Flags = *cpu.flags();
 | 
					                let flags: Flags = *cpu.flags();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                match cond {
 | 
					                match cond {
 | 
				
			||||||
                    JumpCondition::NotZero => {
 | 
					                    JpCond::NotZero => {
 | 
				
			||||||
                        cpu.bus.clock(); // internal branch decision
 | 
					                        cpu.bus.clock(); // internal branch decision
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        if !flags.z() {
 | 
					                        if !flags.z() {
 | 
				
			||||||
@@ -848,7 +850,7 @@ impl Instruction {
 | 
				
			|||||||
                            8
 | 
					                            8
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    JumpCondition::Zero => {
 | 
					                    JpCond::Zero => {
 | 
				
			||||||
                        cpu.bus.clock(); // internal branch decision
 | 
					                        cpu.bus.clock(); // internal branch decision
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        if flags.z() {
 | 
					                        if flags.z() {
 | 
				
			||||||
@@ -859,7 +861,7 @@ impl Instruction {
 | 
				
			|||||||
                            8
 | 
					                            8
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    JumpCondition::NotCarry => {
 | 
					                    JpCond::NotCarry => {
 | 
				
			||||||
                        cpu.bus.clock(); // internal branch decision
 | 
					                        cpu.bus.clock(); // internal branch decision
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        if !flags.c() {
 | 
					                        if !flags.c() {
 | 
				
			||||||
@@ -870,7 +872,7 @@ impl Instruction {
 | 
				
			|||||||
                            8
 | 
					                            8
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    JumpCondition::Carry => {
 | 
					                    JpCond::Carry => {
 | 
				
			||||||
                        cpu.bus.clock(); // internal branch decision
 | 
					                        cpu.bus.clock(); // internal branch decision
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        if flags.c() {
 | 
					                        if flags.c() {
 | 
				
			||||||
@@ -881,7 +883,7 @@ impl Instruction {
 | 
				
			|||||||
                            8
 | 
					                            8
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    JumpCondition::Always => {
 | 
					                    JpCond::Always => {
 | 
				
			||||||
                        let addr = Self::pop(cpu);
 | 
					                        let addr = Self::pop(cpu);
 | 
				
			||||||
                        Self::jump(cpu, addr);
 | 
					                        Self::jump(cpu, addr);
 | 
				
			||||||
                        16
 | 
					                        16
 | 
				
			||||||
@@ -908,13 +910,13 @@ impl Instruction {
 | 
				
			|||||||
                16
 | 
					                16
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Instruction::JP(cond, location) => match location {
 | 
					            Instruction::JP(cond, location) => match location {
 | 
				
			||||||
                JumpLocation::HL => {
 | 
					                JpLoc::HL => {
 | 
				
			||||||
                    // JP HL | Store HL in program counter
 | 
					                    // JP HL | Store HL in program counter
 | 
				
			||||||
                    let right = cpu.register_pair(RegisterPair::HL);
 | 
					                    let right = cpu.register_pair(RegisterPair::HL);
 | 
				
			||||||
                    cpu.set_register_pair(RegisterPair::PC, right);
 | 
					                    cpu.set_register_pair(RegisterPair::PC, right);
 | 
				
			||||||
                    4
 | 
					                    4
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                JumpLocation::ImmediateWord => {
 | 
					                JpLoc::ImmediateWord => {
 | 
				
			||||||
                    // JP cond u16 | Store u16 in program counter if condition is true
 | 
					                    // JP cond u16 | Store u16 in program counter if condition is true
 | 
				
			||||||
                    // JP u16      | Store u16 in program counter
 | 
					                    // JP u16      | Store u16 in program counter
 | 
				
			||||||
                    let flags: Flags = *cpu.flags();
 | 
					                    let flags: Flags = *cpu.flags();
 | 
				
			||||||
@@ -922,7 +924,7 @@ impl Instruction {
 | 
				
			|||||||
                    let addr = Self::imm_word(cpu);
 | 
					                    let addr = Self::imm_word(cpu);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    match cond {
 | 
					                    match cond {
 | 
				
			||||||
                        JumpCondition::NotZero => {
 | 
					                        JpCond::NotZero => {
 | 
				
			||||||
                            if !flags.z() {
 | 
					                            if !flags.z() {
 | 
				
			||||||
                                Self::jump(cpu, addr);
 | 
					                                Self::jump(cpu, addr);
 | 
				
			||||||
                                16
 | 
					                                16
 | 
				
			||||||
@@ -930,7 +932,7 @@ impl Instruction {
 | 
				
			|||||||
                                12
 | 
					                                12
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        JumpCondition::Zero => {
 | 
					                        JpCond::Zero => {
 | 
				
			||||||
                            if flags.z() {
 | 
					                            if flags.z() {
 | 
				
			||||||
                                Self::jump(cpu, addr);
 | 
					                                Self::jump(cpu, addr);
 | 
				
			||||||
                                16
 | 
					                                16
 | 
				
			||||||
@@ -938,7 +940,7 @@ impl Instruction {
 | 
				
			|||||||
                                12
 | 
					                                12
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        JumpCondition::NotCarry => {
 | 
					                        JpCond::NotCarry => {
 | 
				
			||||||
                            if !flags.c() {
 | 
					                            if !flags.c() {
 | 
				
			||||||
                                Self::jump(cpu, addr);
 | 
					                                Self::jump(cpu, addr);
 | 
				
			||||||
                                16
 | 
					                                16
 | 
				
			||||||
@@ -946,7 +948,7 @@ impl Instruction {
 | 
				
			|||||||
                                12
 | 
					                                12
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        JumpCondition::Carry => {
 | 
					                        JpCond::Carry => {
 | 
				
			||||||
                            if flags.c() {
 | 
					                            if flags.c() {
 | 
				
			||||||
                                Self::jump(cpu, addr);
 | 
					                                Self::jump(cpu, addr);
 | 
				
			||||||
                                16
 | 
					                                16
 | 
				
			||||||
@@ -954,7 +956,7 @@ impl Instruction {
 | 
				
			|||||||
                                12
 | 
					                                12
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        JumpCondition::Always => {
 | 
					                        JpCond::Always => {
 | 
				
			||||||
                            Self::jump(cpu, addr);
 | 
					                            Self::jump(cpu, addr);
 | 
				
			||||||
                            16
 | 
					                            16
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
@@ -980,7 +982,7 @@ impl Instruction {
 | 
				
			|||||||
                let return_addr = cpu.register_pair(RegisterPair::PC);
 | 
					                let return_addr = cpu.register_pair(RegisterPair::PC);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                match cond {
 | 
					                match cond {
 | 
				
			||||||
                    JumpCondition::NotZero => {
 | 
					                    JpCond::NotZero => {
 | 
				
			||||||
                        if !flags.z() {
 | 
					                        if !flags.z() {
 | 
				
			||||||
                            cpu.bus.clock(); // internal branch decision
 | 
					                            cpu.bus.clock(); // internal branch decision
 | 
				
			||||||
                            Self::push(cpu, return_addr);
 | 
					                            Self::push(cpu, return_addr);
 | 
				
			||||||
@@ -990,7 +992,7 @@ impl Instruction {
 | 
				
			|||||||
                            12
 | 
					                            12
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    JumpCondition::Zero => {
 | 
					                    JpCond::Zero => {
 | 
				
			||||||
                        if flags.z() {
 | 
					                        if flags.z() {
 | 
				
			||||||
                            cpu.bus.clock(); // internal branch decision
 | 
					                            cpu.bus.clock(); // internal branch decision
 | 
				
			||||||
                            Self::push(cpu, return_addr);
 | 
					                            Self::push(cpu, return_addr);
 | 
				
			||||||
@@ -1000,7 +1002,7 @@ impl Instruction {
 | 
				
			|||||||
                            12
 | 
					                            12
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    JumpCondition::NotCarry => {
 | 
					                    JpCond::NotCarry => {
 | 
				
			||||||
                        if !flags.c() {
 | 
					                        if !flags.c() {
 | 
				
			||||||
                            cpu.bus.clock(); // internal branch decision
 | 
					                            cpu.bus.clock(); // internal branch decision
 | 
				
			||||||
                            Self::push(cpu, return_addr);
 | 
					                            Self::push(cpu, return_addr);
 | 
				
			||||||
@@ -1010,7 +1012,7 @@ impl Instruction {
 | 
				
			|||||||
                            12
 | 
					                            12
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    JumpCondition::Carry => {
 | 
					                    JpCond::Carry => {
 | 
				
			||||||
                        if flags.c() {
 | 
					                        if flags.c() {
 | 
				
			||||||
                            cpu.bus.clock(); // internal branch decision
 | 
					                            cpu.bus.clock(); // internal branch decision
 | 
				
			||||||
                            Self::push(cpu, return_addr);
 | 
					                            Self::push(cpu, return_addr);
 | 
				
			||||||
@@ -1020,7 +1022,7 @@ impl Instruction {
 | 
				
			|||||||
                            12
 | 
					                            12
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    JumpCondition::Always => {
 | 
					                    JpCond::Always => {
 | 
				
			||||||
                        cpu.bus.clock(); // internal branch decision
 | 
					                        cpu.bus.clock(); // internal branch decision
 | 
				
			||||||
                        Self::push(cpu, return_addr);
 | 
					                        Self::push(cpu, return_addr);
 | 
				
			||||||
                        cpu.set_register_pair(RegisterPair::PC, addr);
 | 
					                        cpu.set_register_pair(RegisterPair::PC, addr);
 | 
				
			||||||
@@ -1294,6 +1296,7 @@ impl Instruction {
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            Instruction::Invalid => panic!("Attempted to execute invalid instruction"),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1557,7 +1560,7 @@ impl Instruction {
 | 
				
			|||||||
            // STOP
 | 
					            // STOP
 | 
				
			||||||
            0o020 => STOP,
 | 
					            0o020 => STOP,
 | 
				
			||||||
            // JR i8
 | 
					            // JR i8
 | 
				
			||||||
            0o030 => JR(JumpCondition::Always),
 | 
					            0o030 => JR(JpCond::Always),
 | 
				
			||||||
            // JR cond i8
 | 
					            // JR cond i8
 | 
				
			||||||
            0o040 | 0o050 | 0o060 | 0o070 => JR(jump_cond((byte >> 3) & 0x03)),
 | 
					            0o040 | 0o050 | 0o060 | 0o070 => JR(jump_cond((byte >> 3) & 0x03)),
 | 
				
			||||||
            // LD r16, u16
 | 
					            // LD r16, u16
 | 
				
			||||||
@@ -1622,16 +1625,16 @@ impl Instruction {
 | 
				
			|||||||
            // POP r16
 | 
					            // POP r16
 | 
				
			||||||
            0o301 | 0o321 | 0o341 | 0o361 => POP(group3((byte >> 4) & 0x03)),
 | 
					            0o301 | 0o321 | 0o341 | 0o361 => POP(group3((byte >> 4) & 0x03)),
 | 
				
			||||||
            // RET
 | 
					            // RET
 | 
				
			||||||
            0o311 => RET(JumpCondition::Always),
 | 
					            0o311 => RET(JpCond::Always),
 | 
				
			||||||
            // RETI
 | 
					            // RETI
 | 
				
			||||||
            0o331 => RETI,
 | 
					            0o331 => RETI,
 | 
				
			||||||
            // JP HL
 | 
					            // JP HL
 | 
				
			||||||
            0o351 => JP(JumpCondition::Always, JumpLocation::HL),
 | 
					            0o351 => JP(JpCond::Always, JpLoc::HL),
 | 
				
			||||||
            // LD SP, HL
 | 
					            // LD SP, HL
 | 
				
			||||||
            0o371 => LD(LDTarget::SP, LDSource::HL),
 | 
					            0o371 => LD(LDTarget::SP, LDSource::HL),
 | 
				
			||||||
            // JP cond u16
 | 
					            // JP cond u16
 | 
				
			||||||
            0o302 | 0o312 | 0o322 | 0o332 => {
 | 
					            0o302 | 0o312 | 0o322 | 0o332 => {
 | 
				
			||||||
                JP(jump_cond((byte >> 3) & 0x03), JumpLocation::ImmediateWord)
 | 
					                JP(jump_cond((byte >> 3) & 0x03), JpLoc::ImmediateWord)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            // LD (0xFF00 + C), A
 | 
					            // LD (0xFF00 + C), A
 | 
				
			||||||
            0o342 => LD(LDTarget::IoWithC, LDSource::A),
 | 
					            0o342 => LD(LDTarget::IoWithC, LDSource::A),
 | 
				
			||||||
@@ -1642,9 +1645,7 @@ impl Instruction {
 | 
				
			|||||||
            // LD A, (u16)
 | 
					            // LD A, (u16)
 | 
				
			||||||
            0o372 => LD(LDTarget::A, LDSource::IndirectImmediateWord),
 | 
					            0o372 => LD(LDTarget::A, LDSource::IndirectImmediateWord),
 | 
				
			||||||
            // JP u16
 | 
					            // JP u16
 | 
				
			||||||
            0o303 => JP(JumpCondition::Always, JumpLocation::ImmediateWord),
 | 
					            0o303 => JP(JpCond::Always, JpLoc::ImmediateWord),
 | 
				
			||||||
            // 0xCB Prefix
 | 
					 | 
				
			||||||
            0o313 => unreachable!("{:#04X} should be handled by the prefixed decoder", byte),
 | 
					 | 
				
			||||||
            // DI
 | 
					            // DI
 | 
				
			||||||
            0o363 => DI,
 | 
					            0o363 => DI,
 | 
				
			||||||
            // EI
 | 
					            // EI
 | 
				
			||||||
@@ -1653,12 +1654,14 @@ impl Instruction {
 | 
				
			|||||||
            0o304 | 0o314 | 0o324 | 0o334 => CALL(jump_cond((byte >> 3) & 0x03)),
 | 
					            0o304 | 0o314 | 0o324 | 0o334 => CALL(jump_cond((byte >> 3) & 0x03)),
 | 
				
			||||||
            // PUSH r16
 | 
					            // PUSH r16
 | 
				
			||||||
            0o305 | 0o325 | 0o345 | 0o365 => PUSH(group3((byte >> 4) & 0x03)),
 | 
					            0o305 | 0o325 | 0o345 | 0o365 => PUSH(group3((byte >> 4) & 0x03)),
 | 
				
			||||||
            0o315 => CALL(JumpCondition::Always),
 | 
					            // CALL u16
 | 
				
			||||||
 | 
					            0o315 => CALL(JpCond::Always),
 | 
				
			||||||
 | 
					            // ADD, ADC, SUB, SBC, AND, XOR, OR, and CP
 | 
				
			||||||
            0o306 | 0o316 | 0o326 | 0o336 | 0o346 | 0o356 | 0o366 | 0o376 => {
 | 
					            0o306 | 0o316 | 0o326 | 0o336 | 0o346 | 0o356 | 0o366 | 0o376 => {
 | 
				
			||||||
                alu_imm_instr((byte >> 3) & 0x07)
 | 
					                alu_imm_instr((byte >> 3) & 0x07)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            0o307 | 0o317 | 0o327 | 0o337 | 0o347 | 0o357 | 0o367 | 0o377 => RST(byte & 0b00111000),
 | 
					            0o307 | 0o317 | 0o327 | 0o337 | 0o347 | 0o357 | 0o367 | 0o377 => RST(byte & 0b00111000),
 | 
				
			||||||
            _ => panic!("{:#04X} is an illegal opcode", byte),
 | 
					            _ => Invalid, // 0xCB is 0o313
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1681,7 +1684,7 @@ impl Instruction {
 | 
				
			|||||||
mod jump {
 | 
					mod jump {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[derive(Clone, Copy)]
 | 
					    #[derive(Clone, Copy)]
 | 
				
			||||||
    pub(crate) enum JumpCondition {
 | 
					    pub(crate) enum JpCond {
 | 
				
			||||||
        Always,
 | 
					        Always,
 | 
				
			||||||
        NotZero,
 | 
					        NotZero,
 | 
				
			||||||
        Zero,
 | 
					        Zero,
 | 
				
			||||||
@@ -1689,9 +1692,9 @@ mod jump {
 | 
				
			|||||||
        Carry,
 | 
					        Carry,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    impl std::fmt::Debug for JumpCondition {
 | 
					    impl std::fmt::Debug for JpCond {
 | 
				
			||||||
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
					        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
				
			||||||
            use JumpCondition::*;
 | 
					            use JpCond::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            match self {
 | 
					            match self {
 | 
				
			||||||
                Always => f.write_str(""),
 | 
					                Always => f.write_str(""),
 | 
				
			||||||
@@ -1704,14 +1707,14 @@ mod jump {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[derive(Clone, Copy)]
 | 
					    #[derive(Clone, Copy)]
 | 
				
			||||||
    pub(crate) enum JumpLocation {
 | 
					    pub(crate) enum JpLoc {
 | 
				
			||||||
        HL,
 | 
					        HL,
 | 
				
			||||||
        ImmediateWord,
 | 
					        ImmediateWord,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    impl std::fmt::Debug for JumpLocation {
 | 
					    impl std::fmt::Debug for JpLoc {
 | 
				
			||||||
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
					        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
				
			||||||
            use JumpLocation::*;
 | 
					            use JpLoc::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            match *self {
 | 
					            match *self {
 | 
				
			||||||
                HL => f.write_str("HL"),
 | 
					                HL => f.write_str("HL"),
 | 
				
			||||||
@@ -1871,7 +1874,7 @@ mod load {
 | 
				
			|||||||
mod table {
 | 
					mod table {
 | 
				
			||||||
    use super::add::{Source as AddSource, Target as AddTarget};
 | 
					    use super::add::{Source as AddSource, Target as AddTarget};
 | 
				
			||||||
    use super::alu::Source as AluSource;
 | 
					    use super::alu::Source as AluSource;
 | 
				
			||||||
    use super::{Instruction, JumpCondition};
 | 
					    use super::{Instruction, JpCond};
 | 
				
			||||||
    use crate::cpu::{Register as CpuRegister, RegisterPair};
 | 
					    use crate::cpu::{Register as CpuRegister, RegisterPair};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[derive(Clone, Copy)]
 | 
					    #[derive(Clone, Copy)]
 | 
				
			||||||
@@ -2070,12 +2073,12 @@ mod table {
 | 
				
			|||||||
            0b101 => L,
 | 
					            0b101 => L,
 | 
				
			||||||
            0b110 => IndirectHL,
 | 
					            0b110 => IndirectHL,
 | 
				
			||||||
            0b111 => A,
 | 
					            0b111 => A,
 | 
				
			||||||
            _ => unreachable!("{:#04X} is not a valid Register"),
 | 
					            _ => unreachable!("{:#04X} is not a valid Register", code),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub(crate) fn jump_cond(code: u8) -> JumpCondition {
 | 
					    pub(crate) fn jump_cond(code: u8) -> JpCond {
 | 
				
			||||||
        use JumpCondition::*;
 | 
					        use JpCond::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        match code {
 | 
					        match code {
 | 
				
			||||||
            0b00 => NotZero,
 | 
					            0b00 => NotZero,
 | 
				
			||||||
@@ -2150,3 +2153,268 @@ mod table {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub(crate) mod dbg {
 | 
				
			||||||
 | 
					    use std::borrow::Cow;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    use super::add::{Source as AddSource, Target as AddTarget};
 | 
				
			||||||
 | 
					    use super::alu::Source as AluSource;
 | 
				
			||||||
 | 
					    use super::jump::{JpCond, JpLoc};
 | 
				
			||||||
 | 
					    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
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub(crate) fn new_disasm(cpu: &Cpu, limit: u8) -> String {
 | 
				
			||||||
 | 
					        let mut assembly = 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 output = format!("${:04X} {}\n", pc - 1, disasm(cpu, pc, instr));
 | 
				
			||||||
 | 
					                    assembly.push_str(&output);
 | 
				
			||||||
 | 
					                    pc += delta::pc_inc_count(instr);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assembly
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO: It might be better if I pass in a mutable writer instead of usnig a String
 | 
				
			||||||
 | 
					    fn disasm(cpu: &Cpu, pc: u16, instr: Instruction) -> Cow<str> {
 | 
				
			||||||
 | 
					        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 {
 | 
				
			||||||
 | 
					            // Unprefixed Instructions
 | 
				
			||||||
 | 
					            NOP => "NOP".into(),
 | 
				
			||||||
 | 
					            LD(LDTarget::IndirectImmediateWord, LDSource::SP) => {
 | 
				
			||||||
 | 
					                format!("LD ({:#06X}), SP", imm_word).into()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            STOP => "STOP".into(),
 | 
				
			||||||
 | 
					            JR(JpCond::Always) => format!("JR {}", imm_byte as i8).into(),
 | 
				
			||||||
 | 
					            JR(cond) => format!("JR {:?} {}", cond, imm_byte as i8).into(),
 | 
				
			||||||
 | 
					            LD(LDTarget::Group1(rp), LDSource::ImmediateWord) => {
 | 
				
			||||||
 | 
					                format!("LD {:?} {:#06X}", rp, imm_word).into()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            ADD(AddTarget::HL, AddSource::Group1(rp)) => format!("ADD HL, {:?}", rp).into(),
 | 
				
			||||||
 | 
					            LD(LDTarget::IndirectGroup2(rp), LDSource::A) => format!("LD ({:?}), A", rp).into(),
 | 
				
			||||||
 | 
					            LD(LDTarget::A, LDSource::IndirectGroup2(rp)) => format!("LD A, ({:?})", rp).into(),
 | 
				
			||||||
 | 
					            INC(AllRegisters::Group1(rp)) => format!("INC {:?}", rp).into(),
 | 
				
			||||||
 | 
					            DEC(AllRegisters::Group1(rp)) => format!("DEC {:?}", rp).into(),
 | 
				
			||||||
 | 
					            INC(AllRegisters::Register(reg)) => format!("INC {:?}", reg).into(),
 | 
				
			||||||
 | 
					            DEC(AllRegisters::Register(reg)) => format!("DEC {:?}", reg).into(),
 | 
				
			||||||
 | 
					            LD(LDTarget::Register(reg), LDSource::ImmediateByte) => {
 | 
				
			||||||
 | 
					                format!("LD {:?}, {:#04X}", reg, imm_byte).into()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            RLCA => "RLCA".into(),
 | 
				
			||||||
 | 
					            RRCA => "RRCA".into(),
 | 
				
			||||||
 | 
					            RLA => "RLA".into(),
 | 
				
			||||||
 | 
					            RRA => "RRA".into(),
 | 
				
			||||||
 | 
					            DAA => "DAA".into(),
 | 
				
			||||||
 | 
					            CPL => "CPL".into(),
 | 
				
			||||||
 | 
					            SCF => "SCF".into(),
 | 
				
			||||||
 | 
					            CCF => "CCF".into(),
 | 
				
			||||||
 | 
					            HALT => "HALT".into(),
 | 
				
			||||||
 | 
					            LD(LDTarget::Register(left), LDSource::Register(right)) => {
 | 
				
			||||||
 | 
					                format!("LD {:?}, {:?}", left, right).into()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            ADD(AddTarget::A, AddSource::Register(reg)) => format!("ADD A, {:?}", reg).into(),
 | 
				
			||||||
 | 
					            ADC(AluSource::Register(reg)) => format!("ADC {:?}", reg).into(),
 | 
				
			||||||
 | 
					            SUB(AluSource::Register(reg)) => format!("SUB {:?}", reg).into(),
 | 
				
			||||||
 | 
					            SBC(AluSource::Register(reg)) => format!("SBC {:?}", reg).into(),
 | 
				
			||||||
 | 
					            AND(AluSource::Register(reg)) => format!("AND {:?}", reg).into(),
 | 
				
			||||||
 | 
					            XOR(AluSource::Register(reg)) => format!("XOR {:?}", reg).into(),
 | 
				
			||||||
 | 
					            OR(AluSource::Register(reg)) => format!("OR {:?}", reg).into(),
 | 
				
			||||||
 | 
					            CP(AluSource::Register(reg)) => format!("CP {:?}", reg).into(),
 | 
				
			||||||
 | 
					            RET(JpCond::Always) => "RET".into(),
 | 
				
			||||||
 | 
					            RET(cond) => format!("RET {:?}", cond).into(),
 | 
				
			||||||
 | 
					            LD(LDTarget::IoWithImmediateOffset, LDSource::A) => {
 | 
				
			||||||
 | 
					                format!("LD ({:#06X}) , A", 0xFF00 + imm_byte as u16).into()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            ADD(AddTarget::SP, AddSource::ImmediateSignedByte) => {
 | 
				
			||||||
 | 
					                format!("ADD SP, {}", imm_byte as i8).into()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            LD(LDTarget::A, LDSource::IoWithImmediateOffset) => {
 | 
				
			||||||
 | 
					                format!("LD A, ({:#06X})", 0xFF00 + imm_byte as u16).into()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            LDHL => format!("LD HL, SP + {}", imm_byte as i8).into(),
 | 
				
			||||||
 | 
					            POP(rp) => format!("POP {:?}", rp).into(),
 | 
				
			||||||
 | 
					            RETI => "RETI".into(),
 | 
				
			||||||
 | 
					            JP(JpCond::Always, JpLoc::HL) => "JP HL".into(),
 | 
				
			||||||
 | 
					            LD(LDTarget::SP, LDSource::HL) => "LD SP, HL".into(),
 | 
				
			||||||
 | 
					            JP(JpCond::Always, JpLoc::ImmediateWord) => format!("JP {:#06X}", imm_word).into(),
 | 
				
			||||||
 | 
					            JP(cond, JpLoc::ImmediateWord) => format!("JP {:?} {:#06X}", cond, imm_word).into(),
 | 
				
			||||||
 | 
					            LD(LDTarget::IoWithC, LDSource::A) => "LD (0xFF00 + C), A".into(),
 | 
				
			||||||
 | 
					            LD(LDTarget::IndirectImmediateWord, LDSource::A) => {
 | 
				
			||||||
 | 
					                format!("LD ({:#06X}), A", imm_byte).into()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            LD(LDTarget::A, LDSource::IoWithC) => "LD A, (0xFF00 + C)".into(),
 | 
				
			||||||
 | 
					            LD(LDTarget::A, LDSource::IndirectImmediateWord) => {
 | 
				
			||||||
 | 
					                format!("LD A, ({:#06X})", imm_word).into()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            DI => "DI".into(),
 | 
				
			||||||
 | 
					            EI => "EI".into(),
 | 
				
			||||||
 | 
					            CALL(cond) => format!("CALL {:?} {:#06X}", cond, imm_word).into(),
 | 
				
			||||||
 | 
					            PUSH(rp) => format!("PUSH {:?}", rp).into(),
 | 
				
			||||||
 | 
					            ADD(AddTarget::A, AddSource::ImmediateByte) => {
 | 
				
			||||||
 | 
					                format!("ADD A, {:#04X}", imm_byte).into()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            ADC(AluSource::ImmediateByte) => format!("ADC {:#04X}", imm_byte).into(),
 | 
				
			||||||
 | 
					            SUB(AluSource::ImmediateByte) => format!("SUB {:#04X}", imm_byte).into(),
 | 
				
			||||||
 | 
					            SBC(AluSource::ImmediateByte) => format!("SBC {:#04X}", imm_byte).into(),
 | 
				
			||||||
 | 
					            AND(AluSource::ImmediateByte) => format!("AND {:#04X}", imm_byte).into(),
 | 
				
			||||||
 | 
					            XOR(AluSource::ImmediateByte) => format!("XOR {:#04X}", imm_byte).into(),
 | 
				
			||||||
 | 
					            OR(AluSource::ImmediateByte) => format!("OR {:#04X}", imm_byte).into(),
 | 
				
			||||||
 | 
					            CP(AluSource::ImmediateByte) => format!("CP {:#04X}", imm_byte).into(),
 | 
				
			||||||
 | 
					            RST(v) => format!("RST {:#04X}", v).into(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Prefixed Instructions
 | 
				
			||||||
 | 
					            RLC(reg) => format!("RLC {:?}", reg).into(),
 | 
				
			||||||
 | 
					            RRC(reg) => format!("RRC {:?}", reg).into(),
 | 
				
			||||||
 | 
					            RL(reg) => format!("RL {:?}", reg).into(),
 | 
				
			||||||
 | 
					            RR(reg) => format!("RR {:?}", reg).into(),
 | 
				
			||||||
 | 
					            SLA(reg) => format!("SLA {:?}", reg).into(),
 | 
				
			||||||
 | 
					            SRA(reg) => format!("SRA {:?}", reg).into(),
 | 
				
			||||||
 | 
					            SWAP(reg) => format!("SWAP {:?}", reg).into(),
 | 
				
			||||||
 | 
					            SRL(reg) => format!("SRL {:?}", reg).into(),
 | 
				
			||||||
 | 
					            BIT(bit, reg) => format!("BIT {}, {:?}", bit, reg).into(),
 | 
				
			||||||
 | 
					            RES(bit, reg) => format!("RES {}, {:?}", bit, reg).into(),
 | 
				
			||||||
 | 
					            SET(bit, reg) => format!("SET {}, {:?}", bit, reg).into(),
 | 
				
			||||||
 | 
					            _ => unreachable!("{:?} is an illegal instruction", instr),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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;
 | 
					use bitfield::bitfield;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Default)]
 | 
					#[derive(Debug)]
 | 
				
			||||||
pub(crate) struct Interrupt {
 | 
					pub(crate) struct Interrupt {
 | 
				
			||||||
    pub(crate) flag: InterruptFlag,
 | 
					    pub(crate) flag: InterruptFlag,
 | 
				
			||||||
    pub(crate) enable: InterruptEnable,
 | 
					    pub(crate) enable: InterruptEnable,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Default for Interrupt {
 | 
				
			||||||
 | 
					    fn default() -> Self {
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            flag: InterruptFlag(0),
 | 
				
			||||||
 | 
					            enable: InterruptEnable(0),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bitfield! {
 | 
					bitfield! {
 | 
				
			||||||
   pub struct InterruptEnable(u8);
 | 
					   pub struct InterruptEnable(u8);
 | 
				
			||||||
    impl Debug;
 | 
					    impl Debug;
 | 
				
			||||||
@@ -23,12 +32,6 @@ impl Clone for InterruptEnable {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for InterruptEnable {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self(0)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<u8> for InterruptEnable {
 | 
					impl From<u8> for InterruptEnable {
 | 
				
			||||||
    fn from(byte: u8) -> Self {
 | 
					    fn from(byte: u8) -> Self {
 | 
				
			||||||
        Self(byte)
 | 
					        Self(byte)
 | 
				
			||||||
@@ -58,12 +61,6 @@ impl Clone for InterruptFlag {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for InterruptFlag {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self(0)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<u8> for InterruptFlag {
 | 
					impl From<u8> for InterruptFlag {
 | 
				
			||||||
    fn from(byte: u8) -> Self {
 | 
					    fn from(byte: u8) -> Self {
 | 
				
			||||||
        Self(byte)
 | 
					        Self(byte)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
use gilrs::{Button, Event as GamepadEvent, EventType as GamepadEventType};
 | 
					use gilrs::{Button, Event as GamepadEvent, EventType as GamepadEventType};
 | 
				
			||||||
use winit_input_helper::WinitInputHelper;
 | 
					use winit::event::{ElementState, KeyboardInput, VirtualKeyCode};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug)]
 | 
					#[derive(Debug)]
 | 
				
			||||||
pub struct Joypad {
 | 
					pub struct Joypad {
 | 
				
			||||||
@@ -111,68 +111,33 @@ impl ButtonEvent {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[inline]
 | 
					#[inline]
 | 
				
			||||||
pub(crate) fn handle_keyboard_input(pad: &mut Joypad, input: &WinitInputHelper) {
 | 
					pub(crate) fn handle_keyboard_input(pad: &mut Joypad, key: KeyboardInput) {
 | 
				
			||||||
    use winit::event::VirtualKeyCode;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // TODO: What do I have to do to get a match statement here?
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let state = &mut pad.ext;
 | 
					    let state = &mut pad.ext;
 | 
				
			||||||
    let irq = &mut pad.interrupt;
 | 
					    let irq = &mut pad.interrupt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if input.key_pressed(VirtualKeyCode::Down) {
 | 
					    match key.state {
 | 
				
			||||||
        state.dpad_down.update(true, irq);
 | 
					        ElementState::Pressed => match key.virtual_keycode {
 | 
				
			||||||
    }
 | 
					            Some(VirtualKeyCode::Down) => state.dpad_down.update(true, irq),
 | 
				
			||||||
    if input.key_released(VirtualKeyCode::Down) {
 | 
					            Some(VirtualKeyCode::Up) => state.dpad_up.update(true, irq),
 | 
				
			||||||
        state.dpad_down.update(false, 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),
 | 
				
			||||||
    if input.key_pressed(VirtualKeyCode::Up) {
 | 
					            Some(VirtualKeyCode::RShift) => state.select.update(true, irq),
 | 
				
			||||||
        state.dpad_up.update(true, irq);
 | 
					            Some(VirtualKeyCode::Z) => state.south.update(true, irq),
 | 
				
			||||||
    }
 | 
					            Some(VirtualKeyCode::X) => state.east.update(true, irq),
 | 
				
			||||||
    if input.key_released(VirtualKeyCode::Up) {
 | 
					            None | Some(_) => {}
 | 
				
			||||||
        state.dpad_up.update(false, irq);
 | 
					        },
 | 
				
			||||||
    }
 | 
					        ElementState::Released => match key.virtual_keycode {
 | 
				
			||||||
 | 
					            Some(VirtualKeyCode::Down) => state.dpad_down.update(false, irq),
 | 
				
			||||||
    if input.key_pressed(VirtualKeyCode::Left) {
 | 
					            Some(VirtualKeyCode::Up) => state.dpad_up.update(false, irq),
 | 
				
			||||||
        state.dpad_left.update(true, irq);
 | 
					            Some(VirtualKeyCode::Left) => state.dpad_left.update(false, irq),
 | 
				
			||||||
    }
 | 
					            Some(VirtualKeyCode::Right) => state.dpad_right.update(false, irq),
 | 
				
			||||||
    if input.key_released(VirtualKeyCode::Left) {
 | 
					            Some(VirtualKeyCode::Return) => state.start.update(false, irq),
 | 
				
			||||||
        state.dpad_left.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),
 | 
				
			||||||
    if input.key_pressed(VirtualKeyCode::Right) {
 | 
					            None | Some(_) => {}
 | 
				
			||||||
        state.dpad_right.update(true, irq);
 | 
					        },
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if input.key_released(VirtualKeyCode::Right) {
 | 
					 | 
				
			||||||
        state.dpad_right.update(false, irq);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if input.key_pressed(VirtualKeyCode::Return) {
 | 
					 | 
				
			||||||
        state.start.update(true, irq);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if input.key_released(VirtualKeyCode::Return) {
 | 
					 | 
				
			||||||
        state.start.update(false, irq);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if input.key_pressed(VirtualKeyCode::RShift) {
 | 
					 | 
				
			||||||
        state.select.update(true, irq);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if input.key_released(VirtualKeyCode::RShift) {
 | 
					 | 
				
			||||||
        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);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,5 @@
 | 
				
			|||||||
 | 
					#![allow(clippy::derivable_impls)] // Will remove this if bitfield-rs allows default impls
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub use apu::gen::init as spsc_init;
 | 
					pub use apu::gen::init as spsc_init;
 | 
				
			||||||
pub type Cycle = u64;
 | 
					pub type Cycle = u64;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -9,6 +11,7 @@ mod bus;
 | 
				
			|||||||
mod cartridge;
 | 
					mod cartridge;
 | 
				
			||||||
mod cpu;
 | 
					mod cpu;
 | 
				
			||||||
pub mod emu;
 | 
					pub mod emu;
 | 
				
			||||||
 | 
					pub mod gui;
 | 
				
			||||||
mod high_ram;
 | 
					mod high_ram;
 | 
				
			||||||
mod instruction;
 | 
					mod instruction;
 | 
				
			||||||
mod interrupt;
 | 
					mod interrupt;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										158
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										158
									
								
								src/main.rs
									
									
									
									
									
								
							@@ -1,41 +1,28 @@
 | 
				
			|||||||
use anyhow::Result;
 | 
					use std::path::PathBuf;
 | 
				
			||||||
use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg};
 | 
					use std::time::Instant;
 | 
				
			||||||
use gb::emu::{Emulator, CYCLES_IN_FRAME};
 | 
					
 | 
				
			||||||
use gb::{Cycle, GB_HEIGHT, GB_WIDTH};
 | 
					use clap::{arg, command, value_parser};
 | 
				
			||||||
 | 
					use gb::gui::{EmuMode, Gui};
 | 
				
			||||||
 | 
					use gb::{emu, gui};
 | 
				
			||||||
use gilrs::Gilrs;
 | 
					use gilrs::Gilrs;
 | 
				
			||||||
use pixels::{PixelsBuilder, SurfaceTexture};
 | 
					 | 
				
			||||||
use rodio::{OutputStream, Sink};
 | 
					use rodio::{OutputStream, Sink};
 | 
				
			||||||
use tracing_subscriber::EnvFilter;
 | 
					use tracing_subscriber::EnvFilter;
 | 
				
			||||||
use winit::dpi::{LogicalSize, PhysicalSize};
 | 
					use winit::event::{Event, WindowEvent};
 | 
				
			||||||
use winit::event::{Event, VirtualKeyCode};
 | 
					use winit::event_loop::{EventLoop, EventLoopBuilder};
 | 
				
			||||||
use winit::event_loop::{ControlFlow, EventLoop};
 | 
					 | 
				
			||||||
use winit::window::{Window, WindowBuilder};
 | 
					 | 
				
			||||||
use winit_input_helper::WinitInputHelper;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const WINDOW_SCALE: usize = 3;
 | 
					 | 
				
			||||||
const AUDIO_ENABLED: bool = true;
 | 
					const AUDIO_ENABLED: bool = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn main() -> Result<()> {
 | 
					fn main() {
 | 
				
			||||||
    let app = App::new(crate_name!())
 | 
					    let m = command!()
 | 
				
			||||||
        .version(crate_version!())
 | 
					 | 
				
			||||||
        .author(crate_authors!())
 | 
					 | 
				
			||||||
        .about(crate_description!());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let m = app
 | 
					 | 
				
			||||||
        .arg(
 | 
					        .arg(
 | 
				
			||||||
            Arg::with_name("rom")
 | 
					            arg!(-b --boot <FILE> "path to boot ROM")
 | 
				
			||||||
                .value_name("ROM_FILE")
 | 
					                .required(false)
 | 
				
			||||||
                .takes_value(true)
 | 
					                .value_parser(value_parser!(PathBuf)),
 | 
				
			||||||
                .index(1)
 | 
					 | 
				
			||||||
                .help("Path to the Game ROM"),
 | 
					 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .arg(
 | 
					        .arg(
 | 
				
			||||||
            Arg::with_name("boot")
 | 
					            arg!([ROM_FILE] "path to game ROM")
 | 
				
			||||||
                .short("b")
 | 
					                .required(true)
 | 
				
			||||||
                .long("boot")
 | 
					                .value_parser(value_parser!(PathBuf)),
 | 
				
			||||||
                .value_name("FILE")
 | 
					 | 
				
			||||||
                .takes_value(true)
 | 
					 | 
				
			||||||
                .help("Path to Boot ROM"),
 | 
					 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .get_matches();
 | 
					        .get_matches();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -48,56 +35,42 @@ fn main() -> Result<()> {
 | 
				
			|||||||
        .with_env_filter(EnvFilter::from_default_env())
 | 
					        .with_env_filter(EnvFilter::from_default_env())
 | 
				
			||||||
        .init();
 | 
					        .init();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut emu = match m.value_of("boot") {
 | 
					    // Init CPU
 | 
				
			||||||
 | 
					    let mut cpu = match m.get_one::<PathBuf>("boot") {
 | 
				
			||||||
        Some(path) => {
 | 
					        Some(path) => {
 | 
				
			||||||
            tracing::info!("User-provided boot ROM");
 | 
					            tracing::info!("User-provided boot ROM");
 | 
				
			||||||
            Emulator::from_boot_rom(path)?
 | 
					            emu::from_boot_rom(path).expect("initialize emulator with custom boot rom")
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        None => {
 | 
					        None => {
 | 
				
			||||||
            tracing::info!("Built-in boot ROM");
 | 
					            tracing::info!("Built-in boot ROM");
 | 
				
			||||||
            Emulator::new()
 | 
					            Default::default()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if let Some(path) = m.value_of("rom") {
 | 
					    // Load ROM if filepath was provided
 | 
				
			||||||
 | 
					    if let Some(path) = m.get_one::<PathBuf>("ROM_FILE") {
 | 
				
			||||||
        tracing::info!("User-provided cartridge ROM");
 | 
					        tracing::info!("User-provided cartridge ROM");
 | 
				
			||||||
        emu.read_game_rom(path)?;
 | 
					        emu::read_game_rom(&mut cpu, path).expect("read game rom from path");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    emu::load_save(&mut cpu);
 | 
				
			||||||
    // Load Save file if it exists
 | 
					    let rom_title = emu::rom_title(&cpu).to_string();
 | 
				
			||||||
    emu.try_load_sav().expect("Load save if exists");
 | 
					 | 
				
			||||||
    let rom_title = emu.title();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    tracing::info!("Initialize Gamepad");
 | 
					    tracing::info!("Initialize Gamepad");
 | 
				
			||||||
    let mut gamepad = Gilrs::new().expect("Initialize Controller Support");
 | 
					    let mut gamepad = Gilrs::new().expect("Initialize Controller Support");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Initialize GUI
 | 
					 | 
				
			||||||
    let event_loop = EventLoop::new();
 | 
					 | 
				
			||||||
    let mut input = WinitInputHelper::new();
 | 
					 | 
				
			||||||
    let window = create_window(&event_loop, rom_title)?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let mut pixels = {
 | 
					 | 
				
			||||||
        let size = window.inner_size();
 | 
					 | 
				
			||||||
        let surface_texture = SurfaceTexture::new(size.width, size.height, &window);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        PixelsBuilder::new(GB_WIDTH as u32, GB_HEIGHT as u32, surface_texture)
 | 
					 | 
				
			||||||
            .enable_vsync(false)
 | 
					 | 
				
			||||||
            .build()?
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Initialize Audio
 | 
					    // Initialize Audio
 | 
				
			||||||
    let (_stream, stream_handle) = OutputStream::try_default().expect("Initialized Audio");
 | 
					    let (_stream, stream_handle) = OutputStream::try_default().expect("Initialized Audio");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if AUDIO_ENABLED {
 | 
					    if AUDIO_ENABLED {
 | 
				
			||||||
        let (prod, cons) = gb::spsc_init();
 | 
					        let (prod, cons) = gb::spsc_init();
 | 
				
			||||||
        let sink = {
 | 
					        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.append(cons);
 | 
				
			||||||
            s.set_volume(0.1);
 | 
					            s.set_volume(0.1);
 | 
				
			||||||
            s
 | 
					            s
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        emu.set_prod(prod);
 | 
					        emu::set_audio_prod(&mut cpu, prod);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        tracing::info!("Spawn Audio Thread");
 | 
					        tracing::info!("Spawn Audio Thread");
 | 
				
			||||||
        std::thread::spawn(move || {
 | 
					        std::thread::spawn(move || {
 | 
				
			||||||
@@ -105,58 +78,49 @@ fn main() -> Result<()> {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut cycle_count: Cycle = Default::default();
 | 
					    // Set up state for the Immediate-mode GUI
 | 
				
			||||||
 | 
					    let event_loop: EventLoop<Event<()>> = EventLoopBuilder::with_user_event().build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut app = Gui::new(rom_title, &event_loop, &cpu);
 | 
				
			||||||
 | 
					    let mut last_key = gui::unused_key(); // TODO: Fix this awful impl
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // used for egui animations
 | 
				
			||||||
 | 
					    let start_time = Instant::now();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    event_loop.run(move |event, _, control_flow| {
 | 
					    event_loop.run(move |event, _, control_flow| {
 | 
				
			||||||
        if let Event::RedrawRequested(_) = event {
 | 
					        app.handle_event(&event);
 | 
				
			||||||
            if pixels.render().is_err() {
 | 
					 | 
				
			||||||
                emu.try_write_sav().expect("Write game save if need be");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                *control_flow = ControlFlow::Exit;
 | 
					        match event {
 | 
				
			||||||
                return;
 | 
					            Event::MainEventsCleared => {
 | 
				
			||||||
 | 
					                app.maybe_quit(&cpu, control_flow);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                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);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					                    _ => {}
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if input.update(&event) {
 | 
					                // Input has been consumed, reset it
 | 
				
			||||||
            if input.key_pressed(VirtualKeyCode::Escape) || input.quit() {
 | 
					                last_key = gui::unused_key();
 | 
				
			||||||
                emu.try_write_sav().expect("Write game save if need be");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                *control_flow = ControlFlow::Exit;
 | 
					                app.request_redraw();
 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            Event::RedrawRequested(..) => {
 | 
				
			||||||
            if let Some(size) = input.window_resized() {
 | 
					                app.update_time(start_time.elapsed().as_secs_f64());
 | 
				
			||||||
                pixels.resize_surface(size.width, size.height);
 | 
					                app.paint(&cpu);
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            cycle_count += gb::emu::run_frame(&mut emu, &mut gamepad, &input);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if cycle_count >= CYCLES_IN_FRAME {
 | 
					 | 
				
			||||||
                cycle_count %= 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::WindowEvent { event, .. } => match event {
 | 
				
			||||||
 | 
					                WindowEvent::Resized(size) => app.resize(size),
 | 
				
			||||||
 | 
					                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)?)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										206
									
								
								src/ppu.rs
									
									
									
									
									
								
							
							
						
						
									
										206
									
								
								src/ppu.rs
									
									
									
									
									
								
							@@ -10,6 +10,8 @@ use types::{
 | 
				
			|||||||
    ObjectPaletteKind, ObjectSize, Pixels, RenderPriority, TileDataAddress,
 | 
					    ObjectPaletteKind, ObjectSize, Pixels, RenderPriority, TileDataAddress,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use once_cell::sync::Lazy;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mod dma;
 | 
					mod dma;
 | 
				
			||||||
mod types;
 | 
					mod types;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -32,6 +34,14 @@ const LIGHT_GRAY: [u8; 4] = 0xAEBA89FFu32.to_be_bytes();
 | 
				
			|||||||
const DARK_GRAY: [u8; 4] = 0x5E6745FFu32.to_be_bytes();
 | 
					const DARK_GRAY: [u8; 4] = 0x5E6745FFu32.to_be_bytes();
 | 
				
			||||||
const BLACK: [u8; 4] = 0x202020FFu32.to_be_bytes();
 | 
					const BLACK: [u8; 4] = 0x202020FFu32.to_be_bytes();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static BLANK_SCREEN: Lazy<Box<[u8; (GB_WIDTH * 4) * GB_HEIGHT]>> = Lazy::new(|| {
 | 
				
			||||||
 | 
					    WHITE
 | 
				
			||||||
 | 
					        .repeat(GB_WIDTH * GB_HEIGHT)
 | 
				
			||||||
 | 
					        .into_boxed_slice()
 | 
				
			||||||
 | 
					        .try_into()
 | 
				
			||||||
 | 
					        .unwrap()
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug)]
 | 
					#[derive(Debug)]
 | 
				
			||||||
pub struct Ppu {
 | 
					pub struct Ppu {
 | 
				
			||||||
    pub(crate) int: Interrupt,
 | 
					    pub(crate) int: Interrupt,
 | 
				
			||||||
@@ -42,13 +52,13 @@ pub struct Ppu {
 | 
				
			|||||||
    pub(crate) monochrome: Monochrome,
 | 
					    pub(crate) monochrome: Monochrome,
 | 
				
			||||||
    pub(crate) pos: ScreenPosition,
 | 
					    pub(crate) pos: ScreenPosition,
 | 
				
			||||||
    vram: Box<[u8; VRAM_SIZE]>,
 | 
					    vram: Box<[u8; VRAM_SIZE]>,
 | 
				
			||||||
    pub(crate) oam: ObjectAttributeTable,
 | 
					    pub(crate) oam: ObjectAttrTable,
 | 
				
			||||||
    pub(crate) dma: DirectMemoryAccess,
 | 
					    pub(crate) dma: DirectMemoryAccess,
 | 
				
			||||||
    scan_dot: Cycle,
 | 
					    scan_dot: Cycle,
 | 
				
			||||||
    fetch: PixelFetcher,
 | 
					    fetch: PixelFetcher,
 | 
				
			||||||
    fifo: PixelFifo,
 | 
					    fifo: PixelFifo,
 | 
				
			||||||
    obj_buffer: ObjectBuffer,
 | 
					    obj_buffer: ObjectBuffer,
 | 
				
			||||||
    frame_buf: Box<[u8; GB_WIDTH * GB_HEIGHT * 4]>,
 | 
					    pub(crate) frame_buf: FrameBuffer,
 | 
				
			||||||
    win_stat: WindowStatus,
 | 
					    win_stat: WindowStatus,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    scanline_start: bool,
 | 
					    scanline_start: bool,
 | 
				
			||||||
@@ -70,15 +80,26 @@ impl BusIo for Ppu {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
impl Ppu {
 | 
					impl Ppu {
 | 
				
			||||||
    pub(crate) fn tick(&mut self) {
 | 
					    pub(crate) fn tick(&mut self) {
 | 
				
			||||||
        self.dot += 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if !self.ctrl.lcd_enabled() {
 | 
					        if !self.ctrl.lcd_enabled() {
 | 
				
			||||||
 | 
					            if self.dot > 0 {
 | 
				
			||||||
 | 
					                // Check ensures this expensive operation only happens once
 | 
				
			||||||
 | 
					                self.frame_buf
 | 
				
			||||||
 | 
					                    .get_mut(Device::Guest)
 | 
				
			||||||
 | 
					                    .copy_from_slice(BLANK_SCREEN.as_ref());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.stat.set_mode(PpuMode::HBlank);
 | 
				
			||||||
 | 
					            self.pos.line_y = 0;
 | 
				
			||||||
 | 
					            self.dot = 0;
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.dot += 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        match self.stat.mode() {
 | 
					        match self.stat.mode() {
 | 
				
			||||||
            PpuMode::OamScan => {
 | 
					            PpuMode::OamScan => {
 | 
				
			||||||
                // Cycles 1 -> 80
 | 
					                // Cycles 1 -> 80
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if self.dot >= 80 {
 | 
					                if self.dot >= 80 {
 | 
				
			||||||
                    self.x_pos = 0;
 | 
					                    self.x_pos = 0;
 | 
				
			||||||
                    self.scanline_start = true;
 | 
					                    self.scanline_start = true;
 | 
				
			||||||
@@ -104,12 +125,7 @@ impl Ppu {
 | 
				
			|||||||
                self.scan_oam();
 | 
					                self.scan_oam();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            PpuMode::Drawing => {
 | 
					            PpuMode::Drawing => {
 | 
				
			||||||
                if self.ctrl.lcd_enabled() {
 | 
					 | 
				
			||||||
                    // Only Draw when the LCD Is Enabled
 | 
					 | 
				
			||||||
                self.draw();
 | 
					                self.draw();
 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    self.reset();
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if self.x_pos == 160 {
 | 
					                if self.x_pos == 160 {
 | 
				
			||||||
                    if self.stat.hblank_int() {
 | 
					                    if self.stat.hblank_int() {
 | 
				
			||||||
@@ -164,6 +180,9 @@ impl Ppu {
 | 
				
			|||||||
                            self.int.set_lcd_stat(true);
 | 
					                            self.int.set_lcd_stat(true);
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // Screen is done drawing
 | 
				
			||||||
 | 
					                        self.frame_buf.swap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        PpuMode::VBlank
 | 
					                        PpuMode::VBlank
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        if self.stat.oam_int() {
 | 
					                        if self.stat.oam_int() {
 | 
				
			||||||
@@ -265,7 +284,7 @@ impl Ppu {
 | 
				
			|||||||
                TileLowB => {
 | 
					                TileLowB => {
 | 
				
			||||||
                    let obj_size = self.ctrl.obj_size();
 | 
					                    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);
 | 
					                    let byte = self.read_byte(addr);
 | 
				
			||||||
                    self.fetch.obj.tile.with_low(byte);
 | 
					                    self.fetch.obj.tile.with_low(byte);
 | 
				
			||||||
@@ -276,7 +295,7 @@ impl Ppu {
 | 
				
			|||||||
                TileHighB => {
 | 
					                TileHighB => {
 | 
				
			||||||
                    let obj_size = self.ctrl.obj_size();
 | 
					                    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);
 | 
					                    let byte = self.read_byte(addr + 1);
 | 
				
			||||||
                    self.fetch.obj.tile.with_high(byte);
 | 
					                    self.fetch.obj.tile.with_high(byte);
 | 
				
			||||||
@@ -397,8 +416,8 @@ impl Ppu {
 | 
				
			|||||||
                let x = self.x_pos as usize;
 | 
					                let x = self.x_pos as usize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                let i = (GB_WIDTH * 4) * y + (x * 4);
 | 
					                let i = (GB_WIDTH * 4) * y + (x * 4);
 | 
				
			||||||
                self.frame_buf[i..(i + rgba.len())].copy_from_slice(&rgba);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                self.frame_buf.get_mut(Device::Guest)[i..(i + rgba.len())].copy_from_slice(&rgba);
 | 
				
			||||||
                self.x_pos += 1;
 | 
					                self.x_pos += 1;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -415,19 +434,6 @@ impl Ppu {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn reset(&mut self) {
 | 
					 | 
				
			||||||
        self.pos.line_y = 0;
 | 
					 | 
				
			||||||
        self.stat.set_mode(PpuMode::HBlank);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let mut blank = WHITE.repeat(self.frame_buf.len() / 4);
 | 
					 | 
				
			||||||
        self.frame_buf.swap_with_slice(&mut blank);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[inline]
 | 
					 | 
				
			||||||
    pub(crate) fn frame_buf(&self) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] {
 | 
					 | 
				
			||||||
        &self.frame_buf
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn clock_fifo(&mut self) -> Option<GrayShade> {
 | 
					    fn clock_fifo(&mut self) -> Option<GrayShade> {
 | 
				
			||||||
        use RenderPriority::*;
 | 
					        use RenderPriority::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -446,7 +452,7 @@ impl Ppu {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    fn obj_pixel(&self, obj: ObjPixelProperty) -> GrayShade {
 | 
					    fn obj_pixel(&self, obj: ObjPixelProperty) -> GrayShade {
 | 
				
			||||||
        use ObjectPaletteKind::*;
 | 
					        use ObjectPaletteKind::*;
 | 
				
			||||||
        assert!(obj.shade_id != 0);
 | 
					        assert_ne!(obj.shade_id, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let p0 = &self.monochrome.obj_palette_0;
 | 
					        let p0 = &self.monochrome.obj_palette_0;
 | 
				
			||||||
        let p1 = &self.monochrome.obj_palette_1;
 | 
					        let p1 = &self.monochrome.obj_palette_1;
 | 
				
			||||||
@@ -473,12 +479,12 @@ impl Default for Ppu {
 | 
				
			|||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            vram: Box::new([0u8; VRAM_SIZE]),
 | 
					            vram: Box::new([0u8; VRAM_SIZE]),
 | 
				
			||||||
            dot: Default::default(),
 | 
					            dot: Default::default(),
 | 
				
			||||||
            frame_buf: Box::new([0; GB_WIDTH * GB_HEIGHT * 4]),
 | 
					            frame_buf: FrameBuffer::new().expect("create frame buffers"),
 | 
				
			||||||
            int: Default::default(),
 | 
					            int: Default::default(),
 | 
				
			||||||
            ctrl: Default::default(),
 | 
					            ctrl: LCDControl(0),
 | 
				
			||||||
            monochrome: Default::default(),
 | 
					            monochrome: Default::default(),
 | 
				
			||||||
            pos: Default::default(),
 | 
					            pos: Default::default(),
 | 
				
			||||||
            stat: Default::default(),
 | 
					            stat: LCDStatus(0x80), // bit 7 is always 1
 | 
				
			||||||
            oam: Default::default(),
 | 
					            oam: Default::default(),
 | 
				
			||||||
            scan_dot: Default::default(),
 | 
					            scan_dot: Default::default(),
 | 
				
			||||||
            fetch: Default::default(),
 | 
					            fetch: Default::default(),
 | 
				
			||||||
@@ -533,7 +539,7 @@ pub(crate) struct ScreenPosition {
 | 
				
			|||||||
    pub(crate) window_x: u8,
 | 
					    pub(crate) window_x: u8,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Default)]
 | 
					#[derive(Debug)]
 | 
				
			||||||
pub(crate) struct Monochrome {
 | 
					pub(crate) struct Monochrome {
 | 
				
			||||||
    /// 0xFF47 | BGP - Background Palette Data
 | 
					    /// 0xFF47 | BGP - Background Palette Data
 | 
				
			||||||
    pub(crate) bg_palette: BackgroundPalette,
 | 
					    pub(crate) bg_palette: BackgroundPalette,
 | 
				
			||||||
@@ -543,12 +549,22 @@ pub(crate) struct Monochrome {
 | 
				
			|||||||
    pub(crate) obj_palette_1: ObjectPalette,
 | 
					    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)]
 | 
					#[derive(Debug)]
 | 
				
			||||||
pub(crate) struct ObjectAttributeTable {
 | 
					pub(crate) struct ObjectAttrTable {
 | 
				
			||||||
    buf: Box<[u8; OAM_SIZE]>,
 | 
					    buf: Box<[u8; OAM_SIZE]>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl BusIo for ObjectAttributeTable {
 | 
					impl BusIo for ObjectAttrTable {
 | 
				
			||||||
    fn read_byte(&self, addr: u16) -> u8 {
 | 
					    fn read_byte(&self, addr: u16) -> u8 {
 | 
				
			||||||
        let index = (addr - 0xFE00) as usize;
 | 
					        let index = (addr - 0xFE00) as usize;
 | 
				
			||||||
        self.buf[index]
 | 
					        self.buf[index]
 | 
				
			||||||
@@ -560,8 +576,8 @@ impl BusIo for ObjectAttributeTable {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl ObjectAttributeTable {
 | 
					impl ObjectAttrTable {
 | 
				
			||||||
    fn attribute(&self, index: usize) -> ObjectAttribute {
 | 
					    fn attribute(&self, index: usize) -> ObjectAttr {
 | 
				
			||||||
        let start = index * 4;
 | 
					        let start = index * 4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let slice: &[u8; 4] = self.buf[start..(start + 4)]
 | 
					        let slice: &[u8; 4] = self.buf[start..(start + 4)]
 | 
				
			||||||
@@ -572,7 +588,7 @@ impl ObjectAttributeTable {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for ObjectAttributeTable {
 | 
					impl Default for ObjectAttrTable {
 | 
				
			||||||
    fn default() -> Self {
 | 
					    fn default() -> Self {
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            buf: Box::new([0; OAM_SIZE]),
 | 
					            buf: Box::new([0; OAM_SIZE]),
 | 
				
			||||||
@@ -580,15 +596,15 @@ impl Default for ObjectAttributeTable {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Copy, Default)]
 | 
					#[derive(Debug, Clone, Copy)]
 | 
				
			||||||
struct ObjectAttribute {
 | 
					struct ObjectAttr {
 | 
				
			||||||
    y: u8,
 | 
					    y: u8,
 | 
				
			||||||
    x: u8,
 | 
					    x: u8,
 | 
				
			||||||
    tile_index: u8,
 | 
					    tile_index: u8,
 | 
				
			||||||
    flags: ObjectFlags,
 | 
					    flags: ObjectFlags,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl From<[u8; 4]> for ObjectAttribute {
 | 
					impl From<[u8; 4]> for ObjectAttr {
 | 
				
			||||||
    fn from(bytes: [u8; 4]) -> Self {
 | 
					    fn from(bytes: [u8; 4]) -> Self {
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            y: bytes[0],
 | 
					            y: bytes[0],
 | 
				
			||||||
@@ -599,7 +615,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 {
 | 
					    fn from(bytes: &'a [u8; 4]) -> Self {
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            y: bytes[0],
 | 
					            y: bytes[0],
 | 
				
			||||||
@@ -612,7 +628,7 @@ impl<'a> From<&'a [u8; 4]> for ObjectAttribute {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#[derive(Debug)]
 | 
					#[derive(Debug)]
 | 
				
			||||||
struct ObjectBuffer {
 | 
					struct ObjectBuffer {
 | 
				
			||||||
    inner: [Option<ObjectAttribute>; OBJECT_LIMIT],
 | 
					    inner: [Option<ObjectAttr>; OBJECT_LIMIT],
 | 
				
			||||||
    len: usize,
 | 
					    len: usize,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -626,7 +642,7 @@ impl ObjectBuffer {
 | 
				
			|||||||
        self.len = 0;
 | 
					        self.len = 0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn add(&mut self, attr: ObjectAttribute) {
 | 
					    fn add(&mut self, attr: ObjectAttr) {
 | 
				
			||||||
        self.inner[self.len] = Some(attr);
 | 
					        self.inner[self.len] = Some(attr);
 | 
				
			||||||
        self.len += 1;
 | 
					        self.len += 1;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -678,7 +694,7 @@ impl PixelFetcher {
 | 
				
			|||||||
        Ok(())
 | 
					        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;
 | 
					        let line_y = pos.line_y;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // TODO: Why is the offset 14 and 30 respectively?
 | 
					        // TODO: Why is the offset 14 and 30 respectively?
 | 
				
			||||||
@@ -792,21 +808,12 @@ impl Default for BackgroundFetcher {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug)]
 | 
					#[derive(Debug, Default)]
 | 
				
			||||||
struct ObjectFetcher {
 | 
					struct ObjectFetcher {
 | 
				
			||||||
    state: FetcherState,
 | 
					    state: FetcherState,
 | 
				
			||||||
    tile: TileBuilder,
 | 
					    tile: TileBuilder,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for ObjectFetcher {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self {
 | 
					 | 
				
			||||||
            state: Default::default(),
 | 
					 | 
				
			||||||
            tile: Default::default(),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Fetcher for ObjectFetcher {
 | 
					impl Fetcher for ObjectFetcher {
 | 
				
			||||||
    fn reset(&mut self) {
 | 
					    fn reset(&mut self) {
 | 
				
			||||||
        self.state = Default::default();
 | 
					        self.state = Default::default();
 | 
				
			||||||
@@ -841,7 +848,7 @@ struct BgPixelProperty {
 | 
				
			|||||||
    shade_id: u8,
 | 
					    shade_id: u8,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Default)]
 | 
					#[derive(Debug)]
 | 
				
			||||||
struct ObjPixelProperty {
 | 
					struct ObjPixelProperty {
 | 
				
			||||||
    shade_id: u8,
 | 
					    shade_id: u8,
 | 
				
			||||||
    palette_kind: ObjectPaletteKind,
 | 
					    palette_kind: ObjectPaletteKind,
 | 
				
			||||||
@@ -914,3 +921,96 @@ struct WindowStatus {
 | 
				
			|||||||
    /// drawing from the window tile map is true
 | 
					    /// drawing from the window tile map is true
 | 
				
			||||||
    enabled: bool,
 | 
					    enabled: bool,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub(crate) mod dbg {
 | 
				
			||||||
 | 
					    use super::{Ppu, PpuMode};
 | 
				
			||||||
 | 
					    use crate::Cycle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub(crate) fn ly(ppu: &Ppu) -> u8 {
 | 
				
			||||||
 | 
					        ppu.pos.line_y
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub(crate) fn scx(ppu: &Ppu) -> u8 {
 | 
				
			||||||
 | 
					        ppu.pos.scroll_x
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub(crate) fn scy(ppu: &Ppu) -> u8 {
 | 
				
			||||||
 | 
					        ppu.pos.scroll_y
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub(crate) fn mode(ppu: &Ppu) -> PpuMode {
 | 
				
			||||||
 | 
					        ppu.stat.mode()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub(crate) fn wx(ppu: &Ppu) -> i16 {
 | 
				
			||||||
 | 
					        ppu.pos.window_x as i16
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub(crate) fn wy(ppu: &Ppu) -> i16 {
 | 
				
			||||||
 | 
					        ppu.pos.window_y as i16
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub(crate) fn dot(ppu: &Ppu) -> Cycle {
 | 
				
			||||||
 | 
					        ppu.dot
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
 | 
					pub struct FrameBuffer {
 | 
				
			||||||
 | 
					    buf: [Box<[u8; Self::FRAME_LEN]>; 2],
 | 
				
			||||||
 | 
					    current: bool,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(PartialEq)]
 | 
				
			||||||
 | 
					pub enum Device {
 | 
				
			||||||
 | 
					    Guest,
 | 
				
			||||||
 | 
					    Host,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl FrameBuffer {
 | 
				
			||||||
 | 
					    const FRAME_LEN: usize = GB_WIDTH * std::mem::size_of::<u32>() * GB_HEIGHT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn new() -> Result<Self, FrameBufferError> {
 | 
				
			||||||
 | 
					        Ok(Self {
 | 
				
			||||||
 | 
					            buf: [
 | 
				
			||||||
 | 
					                vec![0; Self::FRAME_LEN]
 | 
				
			||||||
 | 
					                    .into_boxed_slice()
 | 
				
			||||||
 | 
					                    .try_into()
 | 
				
			||||||
 | 
					                    .map_err(|_| FrameBufferError::TryFrom)?,
 | 
				
			||||||
 | 
					                vec![0; Self::FRAME_LEN]
 | 
				
			||||||
 | 
					                    .into_boxed_slice()
 | 
				
			||||||
 | 
					                    .try_into()
 | 
				
			||||||
 | 
					                    .map_err(|_| FrameBufferError::TryFrom)?,
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            current: false,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn swap(&mut self) {
 | 
				
			||||||
 | 
					        self.current = !self.current;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get_mut(&mut self, device: Device) -> &mut [u8; Self::FRAME_LEN] {
 | 
				
			||||||
 | 
					        let idx = match device {
 | 
				
			||||||
 | 
					            Device::Guest => self.current,
 | 
				
			||||||
 | 
					            Device::Host => !self.current,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        &mut *self.buf[idx as usize]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get(&self, device: Device) -> &[u8; Self::FRAME_LEN] {
 | 
				
			||||||
 | 
					        let idx = match device {
 | 
				
			||||||
 | 
					            Device::Guest => self.current,
 | 
				
			||||||
 | 
					            Device::Host => !self.current,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        &*self.buf[idx as usize]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, thiserror::Error)]
 | 
				
			||||||
 | 
					pub enum FrameBufferError {
 | 
				
			||||||
 | 
					    #[error("Failed to coerce boxed slice to boxed array")]
 | 
				
			||||||
 | 
					    TryFrom,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
use crate::Cycle;
 | 
					use crate::Cycle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Default)]
 | 
					#[derive(Debug)]
 | 
				
			||||||
pub(crate) struct DirectMemoryAccess {
 | 
					pub(crate) struct DirectMemoryAccess {
 | 
				
			||||||
    pub(crate) state: DmaState,
 | 
					    pub(crate) state: DmaState,
 | 
				
			||||||
    cycle: Cycle,
 | 
					    cycle: Cycle,
 | 
				
			||||||
@@ -8,6 +8,16 @@ pub(crate) struct DirectMemoryAccess {
 | 
				
			|||||||
    pub(crate) start: DmaAddress,
 | 
					    pub(crate) start: DmaAddress,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Default for DirectMemoryAccess {
 | 
				
			||||||
 | 
					    fn default() -> Self {
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            state: DmaState::Disabled,
 | 
				
			||||||
 | 
					            cycle: Default::default(),
 | 
				
			||||||
 | 
					            start: Default::default(),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl DirectMemoryAccess {
 | 
					impl DirectMemoryAccess {
 | 
				
			||||||
    pub(crate) fn tick(&mut self) -> Option<(u16, u16)> {
 | 
					    pub(crate) fn tick(&mut self) -> Option<(u16, u16)> {
 | 
				
			||||||
        match self.state {
 | 
					        match self.state {
 | 
				
			||||||
@@ -69,12 +79,6 @@ pub(crate) enum DmaState {
 | 
				
			|||||||
    Transferring,
 | 
					    Transferring,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for DmaState {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self::Disabled
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, Copy, Default)]
 | 
					#[derive(Debug, Clone, Copy, Default)]
 | 
				
			||||||
pub(crate) struct DmaAddress(Option<u16>);
 | 
					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 {
 | 
					impl From<LCDStatus> for u8 {
 | 
				
			||||||
    fn from(status: LCDStatus) -> Self {
 | 
					    fn from(status: LCDStatus) -> Self {
 | 
				
			||||||
        status.0
 | 
					        status.0
 | 
				
			||||||
@@ -67,12 +61,6 @@ impl From<PpuMode> for u8 {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for PpuMode {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self::HBlank
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bitfield! {
 | 
					bitfield! {
 | 
				
			||||||
    pub struct LCDControl(u8);
 | 
					    pub struct LCDControl(u8);
 | 
				
			||||||
    impl Debug;
 | 
					    impl Debug;
 | 
				
			||||||
@@ -93,12 +81,6 @@ impl Clone for LCDControl {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for LCDControl {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self(0)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<u8> for LCDControl {
 | 
					impl From<u8> for LCDControl {
 | 
				
			||||||
    fn from(byte: u8) -> Self {
 | 
					    fn from(byte: u8) -> Self {
 | 
				
			||||||
        Self(byte)
 | 
					        Self(byte)
 | 
				
			||||||
@@ -142,12 +124,6 @@ impl From<TileMapAddress> for u8 {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for TileMapAddress {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self::X9800
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, Copy)]
 | 
					#[derive(Debug, Clone, Copy)]
 | 
				
			||||||
pub enum TileDataAddress {
 | 
					pub enum TileDataAddress {
 | 
				
			||||||
    X8800 = 0,
 | 
					    X8800 = 0,
 | 
				
			||||||
@@ -170,12 +146,6 @@ impl From<TileDataAddress> for u8 {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for TileDataAddress {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self::X8800
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, Copy)]
 | 
					#[derive(Debug, Clone, Copy)]
 | 
				
			||||||
pub enum ObjectSize {
 | 
					pub enum ObjectSize {
 | 
				
			||||||
    Eight = 0,
 | 
					    Eight = 0,
 | 
				
			||||||
@@ -209,12 +179,6 @@ impl From<ObjectSize> for u8 {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for ObjectSize {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self::Eight
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bitfield! {
 | 
					bitfield! {
 | 
				
			||||||
    pub struct BackgroundPalette(u8);
 | 
					    pub struct BackgroundPalette(u8);
 | 
				
			||||||
    impl Debug;
 | 
					    impl Debug;
 | 
				
			||||||
@@ -243,12 +207,6 @@ impl Clone for BackgroundPalette {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for BackgroundPalette {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self(0)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<u8> for BackgroundPalette {
 | 
					impl From<u8> for BackgroundPalette {
 | 
				
			||||||
    fn from(byte: u8) -> Self {
 | 
					    fn from(byte: u8) -> Self {
 | 
				
			||||||
        Self(byte)
 | 
					        Self(byte)
 | 
				
			||||||
@@ -288,12 +246,6 @@ impl Clone for ObjectPalette {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for ObjectPalette {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self(0)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<u8> for ObjectPalette {
 | 
					impl From<u8> for ObjectPalette {
 | 
				
			||||||
    fn from(byte: u8) -> Self {
 | 
					    fn from(byte: u8) -> Self {
 | 
				
			||||||
        Self(byte)
 | 
					        Self(byte)
 | 
				
			||||||
@@ -361,24 +313,12 @@ impl From<ObjectFlags> for u8 {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for ObjectFlags {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self(0)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, Copy)]
 | 
					#[derive(Debug, Clone, Copy)]
 | 
				
			||||||
pub enum ObjectPaletteKind {
 | 
					pub enum ObjectPaletteKind {
 | 
				
			||||||
    Zero = 0,
 | 
					    Zero = 0,
 | 
				
			||||||
    One = 1,
 | 
					    One = 1,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for ObjectPaletteKind {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self::Zero
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<u8> for ObjectPaletteKind {
 | 
					impl From<u8> for ObjectPaletteKind {
 | 
				
			||||||
    fn from(byte: u8) -> Self {
 | 
					    fn from(byte: u8) -> Self {
 | 
				
			||||||
        match byte & 0b01 {
 | 
					        match byte & 0b01 {
 | 
				
			||||||
@@ -395,7 +335,7 @@ impl From<ObjectPaletteKind> for u8 {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Copy, PartialEq)]
 | 
					#[derive(Debug, Clone, Copy, Eq, PartialEq)]
 | 
				
			||||||
pub enum RenderPriority {
 | 
					pub enum RenderPriority {
 | 
				
			||||||
    Object = 0,
 | 
					    Object = 0,
 | 
				
			||||||
    BackgroundAndWindow = 1,
 | 
					    BackgroundAndWindow = 1,
 | 
				
			||||||
@@ -417,12 +357,6 @@ impl From<RenderPriority> for u8 {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for RenderPriority {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self::Object
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, Copy)]
 | 
					#[derive(Debug, Clone, Copy)]
 | 
				
			||||||
pub enum GrayShade {
 | 
					pub enum GrayShade {
 | 
				
			||||||
    White = 0,
 | 
					    White = 0,
 | 
				
			||||||
@@ -442,12 +376,6 @@ impl GrayShade {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for GrayShade {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self::White
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<u8> for GrayShade {
 | 
					impl From<u8> for GrayShade {
 | 
				
			||||||
    fn from(byte: u8) -> Self {
 | 
					    fn from(byte: u8) -> Self {
 | 
				
			||||||
        match byte & 0b11 {
 | 
					        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;
 | 
					use bitfield::bitfield;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Default)]
 | 
					#[derive(Debug)]
 | 
				
			||||||
pub(crate) struct Serial {
 | 
					pub(crate) struct Serial {
 | 
				
			||||||
    /// 0xFF01 | SB - Serial Transfer Data
 | 
					    /// 0xFF01 | SB - Serial Transfer Data
 | 
				
			||||||
    pub(crate) next: u8,
 | 
					    pub(crate) next: u8,
 | 
				
			||||||
@@ -8,6 +8,15 @@ pub(crate) struct Serial {
 | 
				
			|||||||
    pub(crate) ctrl: SerialControl,
 | 
					    pub(crate) ctrl: SerialControl,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Default for Serial {
 | 
				
			||||||
 | 
					    fn default() -> Self {
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            next: Default::default(),
 | 
				
			||||||
 | 
					            ctrl: SerialControl(0),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bitfield! {
 | 
					bitfield! {
 | 
				
			||||||
    pub struct SerialControl(u8);
 | 
					    pub struct SerialControl(u8);
 | 
				
			||||||
    impl Debug;
 | 
					    impl Debug;
 | 
				
			||||||
@@ -23,12 +32,6 @@ impl Clone for SerialControl {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for SerialControl {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self(0)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<u8> for SerialControl {
 | 
					impl From<u8> for SerialControl {
 | 
				
			||||||
    fn from(byte: u8) -> Self {
 | 
					    fn from(byte: u8) -> Self {
 | 
				
			||||||
        Self(byte)
 | 
					        Self(byte)
 | 
				
			||||||
@@ -47,12 +50,6 @@ enum ShiftClock {
 | 
				
			|||||||
    Internal = 1,
 | 
					    Internal = 1,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for ShiftClock {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self::External
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<u8> for ShiftClock {
 | 
					impl From<u8> for ShiftClock {
 | 
				
			||||||
    fn from(byte: u8) -> Self {
 | 
					    fn from(byte: u8) -> Self {
 | 
				
			||||||
        match byte & 0b01 {
 | 
					        match byte & 0b01 {
 | 
				
			||||||
@@ -69,12 +66,6 @@ enum ClockSpeed {
 | 
				
			|||||||
    Fast = 1,
 | 
					    Fast = 1,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for ClockSpeed {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self::Normal
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<u8> for ClockSpeed {
 | 
					impl From<u8> for ClockSpeed {
 | 
				
			||||||
    fn from(byte: u8) -> Self {
 | 
					    fn from(byte: u8) -> Self {
 | 
				
			||||||
        match byte & 0b01 {
 | 
					        match byte & 0b01 {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -106,7 +106,7 @@ impl Timer {
 | 
				
			|||||||
impl Default for Timer {
 | 
					impl Default for Timer {
 | 
				
			||||||
    fn default() -> Self {
 | 
					    fn default() -> Self {
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            ctrl: Default::default(),
 | 
					            ctrl: TimerControl(0),
 | 
				
			||||||
            counter: 0,
 | 
					            counter: 0,
 | 
				
			||||||
            modulo: 0,
 | 
					            modulo: 0,
 | 
				
			||||||
            divider: 0,
 | 
					            divider: 0,
 | 
				
			||||||
@@ -157,12 +157,6 @@ impl Clone for TimerControl {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for TimerControl {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self(0)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<u8> for TimerControl {
 | 
					impl From<u8> for TimerControl {
 | 
				
			||||||
    fn from(byte: u8) -> Self {
 | 
					    fn from(byte: u8) -> Self {
 | 
				
			||||||
        Self(byte)
 | 
					        Self(byte)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,13 +11,13 @@ pub(crate) struct WorkRam {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl BusIo for 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 {
 | 
					    fn read_byte(&self, addr: u16) -> u8 {
 | 
				
			||||||
        self.bank[addr as usize - WORK_RAM_START_ADDRESS]
 | 
					        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 {
 | 
					impl Default for WorkRam {
 | 
				
			||||||
@@ -42,11 +42,11 @@ impl Default for VariableWorkRam {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl BusIo 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 {
 | 
					    fn read_byte(&self, addr: u16) -> u8 {
 | 
				
			||||||
        self.buf[addr as usize - VARIABLE_WORK_RAM_START_ADDRESS]
 | 
					        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