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