diff options
author | Astatin <[email protected]> | 2024-08-29 22:32:10 +0900 |
---|---|---|
committer | Astatin <astatin@redacted> | 2024-08-29 22:32:10 +0900 |
commit | 09104524ed468442bb4957d24e65395955b59ddd (patch) | |
tree | 0aaad5952df29e0a920660c422e1aaf8acea0123 /src |
I FINALLY COMMITTED IT ! AND YOU WILL HAVE NO GIT HISTORY BC IT'S TO HARD NOT TO DOX MYSELF
Diffstat (limited to 'src')
-rw-r--r-- | src/audio.rs | 561 | ||||
-rw-r--r-- | src/consts.rs | 12 | ||||
-rw-r--r-- | src/display.rs | 359 | ||||
-rw-r--r-- | src/gamepad.rs | 89 | ||||
-rw-r--r-- | src/interrupts_timers.rs | 67 | ||||
-rw-r--r-- | src/io.rs | 257 | ||||
-rw-r--r-- | src/main.rs | 142 | ||||
-rw-r--r-- | src/opcodes.rs | 825 | ||||
-rw-r--r-- | src/state.rs | 377 |
9 files changed, 2689 insertions, 0 deletions
diff --git a/src/audio.rs b/src/audio.rs new file mode 100644 index 0000000..07f7c85 --- /dev/null +++ b/src/audio.rs @@ -0,0 +1,561 @@ +// You are entering a very scary territory of magic numbers and arbitrary math operations. +// I don't remember why I did all of this but it works I guess :3 + +use rodio::{OutputStream, Sink, Source}; + +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +const SAMPLE_RATE: u32 = 65536; + +const SAMPLE_AVERAGING: usize = 5; //20; + +const SQUARE_WAVE_PATTERN_DUTY_0: [u8; 32] = [ + 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, + 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0, 0, 0, 0, +]; + +const SQUARE_WAVE_PATTERN_DUTY_1: [u8; 32] = [ + 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, + 0xf, 0xf, 0xf, 0xf, 0xf, 0, 0, 0, 0, 0, 0, 0, 0, +]; + +const SQUARE_WAVE_PATTERN_DUTY_2: [u8; 32] = [ + 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; + +const SQUARE_WAVE_PATTERN_DUTY_3: [u8; 32] = [ + 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, +]; + +/* + * Sometimes, you have to abandon the idea of doing things the right way and just do the thing. + * + * The Gameboy noise wave generation function is stateful and the sample averaging feature (a + * low pass filter to filter out some weird noises created by the "too squared" nature of our + * sound wave) needs a pure function (and fast). It's too hard so I just computed the result + * of the noise function and put it here. It's O(1) :3 + */ + +const NOISE_WAVE: [u16; 1023] = [ + 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1 +]; + +const SQUARE_WAVE_PATTERNS: [[u8; 32]; 4] = [ + SQUARE_WAVE_PATTERN_DUTY_0, + SQUARE_WAVE_PATTERN_DUTY_1, + SQUARE_WAVE_PATTERN_DUTY_2, + SQUARE_WAVE_PATTERN_DUTY_3, +]; + +#[derive(Clone, Debug)] +pub struct Wave { + period_value: u16, + num_sample: usize, + wave_pattern: [u8; 32], + length_timer: u8, + length_timer_enabled: bool, + + env_initial_volume: f32, + env_direction: f32, + env_sweep_pace: u8, + + period_sweep_pace: u8, + period_sweep_direction: u8, + period_sweep_slope: u8, +} + +impl Wave { + pub fn new( + period_value: u16, + wave_pattern: [u8; 32], + env_initial_volume: u8, + env_direction: u8, + env_sweep_pace: u8, + length_timer: u8, + length_timer_enabled: bool, + period_sweep_pace: u8, + period_sweep_direction: u8, + period_sweep_slope: u8, + ) -> Wave { + Wave { + period_value, + num_sample: 0, + wave_pattern, + env_initial_volume: env_initial_volume as f32, + env_direction: if env_direction == 0 { -1. } else { 1. }, + env_sweep_pace, + length_timer, + length_timer_enabled, + period_sweep_pace, + period_sweep_direction, + period_sweep_slope, + } + } +} + +impl Iterator for Wave { + type Item = f32; + + fn next(&mut self) -> Option<f32> { + self.num_sample = self.num_sample.wrapping_add(1); + + let mut period_value = self.period_value; + + if period_value == 0 { + return None; + } + + if self.length_timer_enabled + && self.length_timer < 64 + && SAMPLE_RATE * (64 - self.length_timer as u32) / 256 < self.num_sample as u32 + { + return None; + } + + if self.period_sweep_slope != 0 && self.period_sweep_pace != 0 { + let sweep_i = ((self.num_sample as f32 * (32768 as f32 / SAMPLE_RATE as f32)) as u32 + / 256) + / self.period_sweep_pace as u32; + + if self.period_sweep_direction == 0 { + period_value = 2048 + - ((2048 - period_value) as f32 + * f32::powf( + f32::powf(2., -(self.period_sweep_slope as f32)) + 1., + sweep_i as f32, + )) as u16; + } else { + period_value = 2048 + - ((2048 - period_value) as f32 + * f32::powf( + -f32::powf(2., -(self.period_sweep_slope as f32)) + 1., + sweep_i as f32, + )) as u16; + } + + if period_value > 2048 { + return None; + } + } + + let envelope_time = if self.env_sweep_pace != 0 { + (self.num_sample as f32 / SAMPLE_RATE as f32) * 64. / self.env_sweep_pace as f32 + } else { + 0. + }; + + let envelope = self.env_initial_volume + (self.env_direction * envelope_time); + + let envelope_boundaries = if envelope > 16. { + 16. + } else if envelope < 0. { + 0. + } else { + envelope + }; + + let mut avg = 0.; + + for n in 0..SAMPLE_AVERAGING { + if self.num_sample as i32 + n as i32 - SAMPLE_AVERAGING as i32 >= 0 { + avg += (self.wave_pattern[(((8. * 32768. / (SAMPLE_RATE as f32) + * (self.num_sample + n - (SAMPLE_AVERAGING / 2)) as f32 + / period_value as f32) + * 16.) + % 32.) as u8 as usize] as f32 + * 2. + - 16.) + / 16.; // Before you ask, no I don't remember why it's so complicated :3 + } + } + + Some((avg / SAMPLE_AVERAGING as f32) * envelope_boundaries / 64.) + } +} + +#[derive(Clone, Debug)] +pub struct NoiseWave { + num_sample: usize, + length_timer: u8, + length_timer_enabled: bool, + + env_initial_volume: f32, + env_direction: f32, + env_sweep_pace: u8, + + clock_shift: u8, + lsfr_width: u8, + clock_divider: u8, +} + +impl NoiseWave { + pub fn new( + env_initial_volume: u8, + env_direction: u8, + env_sweep_pace: u8, + length_timer: u8, + length_timer_enabled: bool, + clock_shift: u8, + lsfr_width: u8, + clock_divider: u8, + ) -> NoiseWave { + NoiseWave { + num_sample: 0, + env_initial_volume: env_initial_volume as f32, + env_direction: if env_direction == 0 { -1. } else { 1. }, + env_sweep_pace, + length_timer, + length_timer_enabled, + clock_shift, + lsfr_width, + clock_divider, + } + } +} + +impl Iterator for NoiseWave { + type Item = f32; + + fn next(&mut self) -> Option<f32> { + self.num_sample = self.num_sample.wrapping_add(1); + + let clock_divider = if self.clock_divider == 0 { + 0.5 + } else { + self.clock_divider as f32 + }; + + if self.length_timer_enabled + && self.length_timer < 64 + && SAMPLE_RATE * (64 - self.length_timer as u32) / 256 < self.num_sample as u32 + { + return None; + } + + let envelope_time = if self.env_sweep_pace != 0 { + (self.num_sample as f32 / SAMPLE_RATE as f32) * 64. / self.env_sweep_pace as f32 + } else { + 0. + }; + + let envelope = self.env_initial_volume + (self.env_direction * envelope_time); + + let envelope_boundaries = if envelope > 16. { + 16. + } else if envelope < 0. { + 0. + } else { + envelope + }; + + let mut avg = 0.; + + for n in 0..SAMPLE_AVERAGING { + if self.num_sample as i32 + n as i32 - SAMPLE_AVERAGING as i32 >= 0 { + let ns = ((262144. / ((clock_divider) * (2 << self.clock_shift) as f32)) / 32768.) + * (self.num_sample + n - (SAMPLE_AVERAGING / 2)) as f32; + + let i = (ns as f32 * (32768 as f32 / SAMPLE_RATE as f32)) as usize; + + let up = if self.lsfr_width == 1 { + NOISE_WAVE[i % 63] + } else { + NOISE_WAVE[i % 1023] + }; + + avg += up as f32 * 2. - 1.; + } + } + + Some((avg / SAMPLE_AVERAGING as f32) * envelope_boundaries / 64.) + } +} + +#[derive(Clone, Debug)] +struct MutableWave { + wave_ch1: Arc<Mutex<Option<Wave>>>, + wave_ch2: Arc<Mutex<Option<Wave>>>, + wave_ch3: Arc<Mutex<Option<Wave>>>, + wave_ch4: Arc<Mutex<Option<NoiseWave>>>, +} + +impl MutableWave { + pub fn new( + wave_ch1: Arc<Mutex<Option<Wave>>>, + wave_ch2: Arc<Mutex<Option<Wave>>>, + wave_ch3: Arc<Mutex<Option<Wave>>>, + wave_ch4: Arc<Mutex<Option<NoiseWave>>>, + ) -> Self { + Self { + wave_ch1, + wave_ch2, + wave_ch3, + wave_ch4, + } + } +} + +impl Iterator for MutableWave { + type Item = f32; + + fn next(&mut self) -> Option<f32> { + let mut res = 0.; + + // Imagine using an Arc<Mutex<>> in a sound wave generation function that needs to reliably + // run 65536 times a second. Couldn't be me :3 + if let Ok(mut wave_o) = self.wave_ch1.lock() { + if let Some(wave) = wave_o.as_mut() { + if let Some(result) = wave.next() { + res += result / 4.; + } else { + *wave_o = None; + } + } + } + + if let Ok(mut wave_o) = self.wave_ch2.lock() { + if let Some(wave) = wave_o.as_mut() { + if let Some(result) = wave.next() { + res += result / 4.; + } else { + *wave_o = None; + } + } + } + + if let Ok(mut wave_o) = self.wave_ch3.lock() { + if let Some(wave) = wave_o.as_mut() { + if let Some(result) = wave.next() { + res += result / 4.; + } else { + *wave_o = None; + } + } + } + + if let Ok(mut wave_o) = self.wave_ch4.lock() { + if let Some(wave) = wave_o.as_mut() { + if let Some(result) = wave.next() { + res += result / 4.; + } else { + *wave_o = None; + } + } + } + + Some(res) + } +} + +impl Source for MutableWave { + fn current_frame_len(&self) -> Option<usize> { + None + } + + fn channels(&self) -> u16 { + 1 + } + + fn sample_rate(&self) -> u32 { + SAMPLE_RATE + } + + fn total_duration(&self) -> Option<Duration> { + None + } +} + +pub struct AudioSquareChannel { + wave: Arc<Mutex<Option<Wave>>>, + + pub length_timer: u8, + pub length_timer_enabled: bool, + pub on: bool, + pub period_value: u16, + pub duty: u8, + pub initial_volume: u8, + pub env_direction: u8, + pub sweep: u8, + pub period_sweep_pace: u8, + pub period_sweep_direction: u8, + pub period_sweep_slope: u8, +} + +impl AudioSquareChannel { + pub fn new(wave: Arc<Mutex<Option<Wave>>>) -> Self { + Self { + on: true, + period_value: 0, + duty: 0, + initial_volume: 0, + env_direction: 0, + sweep: 0, + wave, + length_timer: 0, + length_timer_enabled: false, + period_sweep_pace: 0, + period_sweep_direction: 0, + period_sweep_slope: 0, + } + } + + pub fn update(&mut self) { + if let Ok(mut wave) = self.wave.lock() { + if self.on { + *wave = Some(Wave::new( + 2048 - self.period_value, + SQUARE_WAVE_PATTERNS[self.duty as usize], + self.initial_volume, + self.env_direction, + self.sweep, + self.length_timer, + self.length_timer_enabled, + self.period_sweep_pace, + self.period_sweep_direction, + self.period_sweep_slope, + )); + } else { + *wave = None; + } + } + } +} + +pub struct AudioCustomChannel { + wave: Arc<Mutex<Option<Wave>>>, + + pub length_timer: u8, + pub length_timer_enabled: bool, + pub wave_pattern: [u8; 32], + pub on: bool, + pub period_value: u16, + pub initial_volume: u8, +} + +impl AudioCustomChannel { + pub fn new(wave: Arc<Mutex<Option<Wave>>>) -> Self { + Self { + wave_pattern: [0; 32], + on: true, + period_value: 0, + initial_volume: 0, + wave, + length_timer: 0, + length_timer_enabled: false, + } + } + + pub fn update(&mut self) { + if let Ok(mut wave) = self.wave.lock() { + if self.on { + *wave = Some(Wave::new( + 2 * (2048 - (self.period_value * 2)), + self.wave_pattern, + self.initial_volume, + 0, + 0, + self.length_timer, + self.length_timer_enabled, + 0, + 0, + 0, + )); + } else { + *wave = None; + } + } + } +} + +pub struct AudioNoiseChannel { + wave: Arc<Mutex<Option<NoiseWave>>>, + + pub length_timer: u8, + pub length_timer_enabled: bool, + pub on: bool, + pub initial_volume: u8, + pub env_direction: u8, + pub sweep: u8, + pub clock_shift: u8, + pub lsfr_width: u8, + pub clock_divider: u8, +} + +impl AudioNoiseChannel { + pub fn new(wave: Arc<Mutex<Option<NoiseWave>>>) -> Self { + Self { + on: true, + initial_volume: 0, + env_direction: 0, + sweep: 0, + wave, + length_timer: 0, + length_timer_enabled: false, + clock_shift: 0, + lsfr_width: 0, + clock_divider: 0, + } + } + + pub fn update(&mut self) { + if let Ok(mut wave) = self.wave.lock() { + if self.on { + *wave = Some(NoiseWave::new( + self.initial_volume, + self.env_direction, + self.sweep, + self.length_timer, + self.length_timer_enabled, + self.clock_shift, + self.lsfr_width, + self.clock_divider, + )); + } else { + *wave = None; + } + } + } +} + +pub struct Audio { + _stream: OutputStream, + _sink: Sink, + + pub ch1: AudioSquareChannel, + pub ch2: AudioSquareChannel, + pub ch3: AudioCustomChannel, + pub ch4: AudioNoiseChannel, +} + +impl Audio { + pub fn new() -> Self { + let (stream, stream_handle) = OutputStream::try_default().unwrap(); + + let sink = Sink::try_new(&stream_handle).unwrap(); + + let wave_ch1 = Arc::new(Mutex::new(None)); + let wave_ch2 = Arc::new(Mutex::new(None)); + let wave_ch3 = Arc::new(Mutex::new(None)); + let wave_ch4 = Arc::new(Mutex::new(None)); + + sink.append(MutableWave::new( + wave_ch1.clone(), + wave_ch2.clone(), + wave_ch3.clone(), + wave_ch4.clone(), + )); + + Self { + _stream: stream, + _sink: sink, + + ch1: AudioSquareChannel::new(wave_ch1), + ch2: AudioSquareChannel::new(wave_ch2), + ch3: AudioCustomChannel::new(wave_ch3), + ch4: AudioNoiseChannel::new(wave_ch4), + } + } +} diff --git a/src/consts.rs b/src/consts.rs new file mode 100644 index 0000000..dd06796 --- /dev/null +++ b/src/consts.rs @@ -0,0 +1,12 @@ +pub const PROGRAM_START_ADDRESS: u16 = 0x0; +pub const STACK_START_ADDRESS: u16 = 0x0; + +pub const SPEEDUP_FACTOR: f64 = 1.0; + +pub const DISPLAY_UPDATE_RATE: u64 = 60; // Hertz +pub const DISPLAY_UPDATE_SLEEP_TIME_MICROS: u64 = + ((1000000 / DISPLAY_UPDATE_RATE) as f64 / SPEEDUP_FACTOR) as u64; + +pub const CPU_CLOCK_SPEED: u64 = 4_194_304; +pub const CPU_CYCLE_LENGTH_NANOS: u64 = + ((1_000_000_000 / CPU_CLOCK_SPEED) as f64 / SPEEDUP_FACTOR) as u64; diff --git a/src/display.rs b/src/display.rs new file mode 100644 index 0000000..8db432f --- /dev/null +++ b/src/display.rs @@ -0,0 +1,359 @@ +// Very readable, much clean wow. + +use crate::consts::DISPLAY_UPDATE_SLEEP_TIME_MICROS; +use crate::state::MemError; +use minifb::{Window, WindowOptions}; +use std::time::SystemTime; + +const COLORS: [u32; 4] = [0x00e0f8d0, 0x0088c070, 0x346856, 0x00081820]; + +const LINE_DOTS: u64 = 456; + +mod lcdc_flags { + pub const _BG_PRIORITY: u8 = 0b1; + pub const OBJ_ENABLE: u8 = 0b10; + pub const OBJ_SIZE: u8 = 0b100; + pub const BG_TILEMAP_AREA: u8 = 0b1000; + pub const BG_TILEDATA_AREA: u8 = 0b10000; + pub const WIN_ENABLE: u8 = 0b100000; + pub const WIN_TILEMAP_AREA: u8 = 0b1000000; + pub const LCD_ENABLE: u8 = 0b10000000; +} + +pub enum DisplayInterrupt { + Vblank, + Stat, + Both, + None, +} + +#[derive(Debug)] +pub struct Display { + window: Window, + framebuffer: [u32; 160 * 144], + bg_buffer: [u8; 160 * 144], + + tiledata: [u8; 0x3000], + bg_map_attr: [u8; 0x400], + tilemaps: [u8; 0x800], + oam: [u8; 0xa0], + + pub cram: [u8; 0x80], + pub bg_palette: u8, + pub obj_palettes: [u8; 2], + pub viewport_y: u8, + pub viewport_x: u8, + pub lcdc: u8, + pub ly: u8, + pub lyc: u8, + pub lcd_interrupt_mode: u8, + pub vram_bank: u8, + + pub cgb_mode: bool, + + pub window_x: u8, + pub window_y: u8, + + last_dt: SystemTime, + + pub stat: u64, +} + +impl Display { + pub fn new() -> Self { + Self { + window: Window::new( + "Gameboy Emulator", + 512, 461, + /* 1200, 1080, */ + WindowOptions::default(), + ) + .unwrap(), + framebuffer: [0; 160 * 144], + bg_buffer: [0; 160 * 144], + tiledata: [0; 0x3000], + bg_map_attr: [0; 0x400], + cram: [0; 0x80], + tilemaps: [0; 0x800], + oam: [0; 0xa0], + bg_palette: 0, + vram_bank: 0, + obj_palettes: [0; 2], + viewport_y: 0, + viewport_x: 0, + lcdc: 0, + ly: 0, + window_x: 0, + window_y: 0, + last_dt: SystemTime::now(), + stat: 0, + lyc: 0, + cgb_mode: false, + lcd_interrupt_mode: 0xff, + } + } + + pub fn cls(&mut self) { + self.framebuffer = [COLORS[0]; 160 * 144]; + } + + pub fn update(&mut self) { + self.window + .update_with_buffer(&self.framebuffer, 160, 144) + .unwrap(); + } + + pub fn color_palette(&self, color_byte: u8, palette: u8, cgb_mode: bool) -> u32 { + if cgb_mode { + let color_pointer = palette * 8 + color_byte * 2; + let color16b: u16 = (self.cram[color_pointer as usize] as u16) + | ((self.cram[color_pointer as usize + 1] as u16) << 8); + + let red = ((0b11111 & color16b) << 3) as u32; + let green = (((color16b >> 5) & 0b11111) << 3) as u32; + let blue = (((color16b >> 10) & 0b11111) << 3) as u32; + + (red << 16) | (green << 8) | blue + } else { + COLORS[((palette >> (color_byte << 1)) & 0b11) as usize] + } + } + + pub fn print_tile(&mut self, tile: u8, x: u8, y: u8, l: usize, bg_map_attr: u8) { + let tile_pointer = if self.lcdc & lcdc_flags::BG_TILEDATA_AREA != 0 { + ((tile as u16) << 4) as usize + } else { + ((tile as i8 as i32) * 16) as usize + 0x1000 + } + if bg_map_attr & 0b1000 != 0 { 0x1800 } else { 0 }; + + for b in (0..8).rev() { + let data = (((self.tiledata[tile_pointer + l * 2] as u8) >> b) & 1) + | ((((self.tiledata[tile_pointer + l * 2 + 1] as u8) >> b) & 1) << 1); + + let pxx = (x as i32 + 7 - b as i32) as u8; + let pxy = y as i32; + + if pxy < 144 && pxx < 160 { + self.framebuffer[pxy as usize * 160 + pxx as usize] = self.color_palette( + data, + if self.cgb_mode { + bg_map_attr & 0b111 + } else { + self.bg_palette + }, + self.cgb_mode, + ); + self.bg_buffer[pxy as usize * 160 + pxx as usize] = data; + } + } + } + pub fn print_all_tiles(&mut self) { + for i in 0..=255 { + for l in 0..8 { + self.print_tile(i, (i % 20) * 8, (i / 20) * 8, l, 0); + } + } + } + + pub fn w(&mut self, addr: u16, value: u8) -> Result<(), MemError> { + if self.vram_bank == 0 { + if addr < 0x1800 { + self.tiledata[addr as usize] = value; + } else if addr >= 0x7e00 { + self.oam[addr as usize - 0x7e00] = value; + } else { + self.tilemaps[addr as usize - 0x1800] = value; + } + } else { + if addr < 0x1800 { + self.tiledata[addr as usize + 0x1800] = value; + } else if addr < 0x1c00 { + self.bg_map_attr[addr as usize - 0x1800] = value; + } + } + Ok(()) + } + + pub fn r(&self, addr: u16) -> Result<u8, MemError> { + if self.vram_bank == 0 { + if addr < 0x1800 { + Ok(self.tiledata[addr as usize]) + } else if addr >= 0x7e00 { + Ok(self.oam[addr as usize - 0x7e00]) + } else { + Ok(self.tilemaps[addr as usize - 0x1800]) + } + } else { + if addr < 0x1800 { + Ok(self.tiledata[addr as usize + 0x1800]) + } else if addr < 0x1c00 { + Ok(self.bg_map_attr[addr as usize - 0x1800]) + } else { + Ok(0) + } + } + } + + pub fn print_bg(&mut self) { + let tilemap_pointer = if self.lcdc & lcdc_flags::BG_TILEMAP_AREA != 0 { + 0x400 + } else { + 0 + }; + + let y_tile = (self.ly + self.viewport_y) as usize; + + for x in 0..32 { + let tile = self.tilemaps[tilemap_pointer + (y_tile / 8) * 32 + x]; + let bg_map_attr = self.bg_map_attr[(y_tile / 8) * 32 + x]; + self.print_tile( + tile, + x as u8 * 8 - self.viewport_x, + self.ly, + (y_tile % 8) as usize, + bg_map_attr, + ); + } + } + + pub fn print_win(&mut self) { + if self.lcdc & lcdc_flags::WIN_ENABLE == 0 { + return; + } + + let tilemap_pointer = if self.lcdc & lcdc_flags::WIN_TILEMAP_AREA != 0 { + 0x400 + } else { + 0 + }; + + let y_tile = (self.ly - self.window_y) as usize; + + for x in 0..32 { + if tilemap_pointer + (y_tile / 8) * 32 + x >= 2048 { + return; + } + let tile = self.tilemaps[tilemap_pointer + (y_tile / 8) * 32 + x]; + if x * 8 + self.window_x as usize - 7 < 160 && self.ly >= self.window_y { + self.print_tile( + tile, + x as u8 * 8 + self.window_x - 7, + self.ly, + (y_tile % 8) as usize, + 0, + ); + } + } + } + + pub fn print_obj(&mut self) { + if self.lcdc & lcdc_flags::OBJ_ENABLE == 0 { + return; + } + + // Making unreadable magic code is my brand + for o in (0..40).rev() { + let mut y = self.oam[o * 4] - 9; + let x = self.oam[o * 4 + 1]; + let mut tile = self.oam[o * 4 + 2]; + let opts = self.oam[o * 4 + 3]; + let bg_priority_flag = opts & 0b10000000 != 0; + let x_flip = opts & 0b100000 != 0; + let y_flip = opts & 0b1000000 != 0; + let palette = (opts >> 4) & 1; + let tile_vram = if self.cgb_mode && opts & 0b1000 != 0 { + 0x1800 + } else { + 0 + }; + let cgb_palette = opts & 0b111; + let obj_size = if self.lcdc & lcdc_flags::OBJ_SIZE != 0 { + tile &= 0xfe; + y += 8; + 16 + } else { + 8 + }; + + let tile_pointer = ((tile as u16) << 4) as usize + tile_vram; + + if y < self.ly || y >= self.ly + obj_size { + continue; + } + + let l = if y_flip { + y - self.ly + } else { + obj_size - 1 - (y - self.ly) + }; + + for b in 0..8 { + let pxx = if x_flip { + x as i32 + b as i32 - 8 as u8 as i32 + } else { + x as i32 + 7 - b as i32 - 8 as u8 as i32 + }; + let pxy = self.ly as i32; + + let data = (((self.tiledata[tile_pointer + l as usize * 2] as u8) >> b) & 1) + | ((((self.tiledata[tile_pointer + l as usize * 2 + 1] as u8) >> b) & 1) << 1); + + if pxy < 144 && pxx < 160 && pxy >= 0 && pxx >= 0 { + if data != 0 + && !((bg_priority_flag/* && self.lcdc & lcdc_flags::BG_PRIORITY != 0 */) + && self.bg_buffer[pxy as usize * 160 + pxx as usize] != 0) + { + self.framebuffer[pxy as usize * 160 + pxx as usize] = self.color_palette( + data, + if self.cgb_mode { + cgb_palette + 8 + } else { + self.obj_palettes[palette as usize] + }, + self.cgb_mode, + ); + } + } + } + } + } + + pub fn update_display(&mut self, cycles: u64) -> DisplayInterrupt { + let mut ret_interrupt = DisplayInterrupt::None; + self.stat += cycles; + if self.lcdc & lcdc_flags::LCD_ENABLE != 0 && self.stat >= LINE_DOTS { + self.print_bg(); + self.print_win(); + self.print_obj(); + self.ly = (self.ly + 1) % 154; + self.stat %= LINE_DOTS; + if self.ly == 0x90 { + ret_interrupt = DisplayInterrupt::Vblank; + if self.lcd_interrupt_mode == 1 { + ret_interrupt = DisplayInterrupt::Both; + } + if SystemTime::now() + .duration_since(self.last_dt) + .unwrap() + .as_micros() + > DISPLAY_UPDATE_SLEEP_TIME_MICROS as u128 + { + self.update(); + self.last_dt = SystemTime::now(); + } + } + if self.ly < 0x90 && (self.lcd_interrupt_mode == 0 || self.lcd_interrupt_mode == 2) { + ret_interrupt = DisplayInterrupt::Stat; + } + + if self.lcd_interrupt_mode == 3 && self.ly == self.lyc + 1 { + ret_interrupt = DisplayInterrupt::Stat; + } + } + if self.lcdc & lcdc_flags::LCD_ENABLE == 0 { + self.ly = 0; + } + + return ret_interrupt; + } +} diff --git a/src/gamepad.rs b/src/gamepad.rs new file mode 100644 index 0000000..8f05333 --- /dev/null +++ b/src/gamepad.rs @@ -0,0 +1,89 @@ +use crate::state::GBState; +use gilrs::{Button, GamepadId, Gilrs, Event}; + +pub struct Gamepad { + gilrs: Gilrs, + gamepad_id: Option<GamepadId>, +} + +impl Gamepad { + pub fn new() -> Self { + let mut gilrs = Gilrs::new().unwrap(); + + let gamepad_id = if let Some((gamepad_id, _gamepad)) = gilrs.gamepads().next() { + println!("Found Gamepad id: {:?}", gamepad_id); + Some(gamepad_id) + } else { + println!("No gamepad found"); + None + }; + + + Self { gilrs, gamepad_id } + } + + pub fn update_events(&mut self) { + while let Some(_) = self.gilrs.next_event() {} + } + + pub fn check_special_actions(&self, state: &mut GBState) { + if let Some(gamepad_id) = self.gamepad_id { + if let Some(gamepad) = self.gilrs.connected_gamepad(gamepad_id) { + state.is_debug = gamepad.is_pressed(Button::West); + } + } + } + + pub fn get_action_gamepad_reg(&self) -> u8 { + + let mut res = 0xf; + + if let Some(gamepad_id) = self.gamepad_id { + if let Some(gamepad) = self.gilrs.connected_gamepad(gamepad_id) { + if gamepad.is_pressed(Button::East) { + res &= 0b1110; + } + + if gamepad.is_pressed(Button::South) { + res &= 0b1101; + } + + if gamepad.is_pressed(Button::Select) { + res &= 0b1011; + } + + if gamepad.is_pressed(Button::Start) { + res &= 0b0111; + } + } + } + + res + } + + pub fn get_direction_gamepad_reg(&self) -> u8 { + let mut res = 0xf; + + if let Some(gamepad_id) = self.gamepad_id { + if let Some(gamepad) = self.gilrs.connected_gamepad(gamepad_id) { + if gamepad.is_pressed(Button::DPadRight) { + res &= 0b1110; + } + + if gamepad.is_pressed(Button::DPadLeft) { + res &= 0b1101; + } + + if gamepad.is_pressed(Button::DPadUp) { + res &= 0b1011; + } + + if gamepad.is_pressed(Button::DPadDown) { + res &= 0b0111; + } + } + } + + res + } +} diff --git a/src/interrupts_timers.rs b/src/interrupts_timers.rs new file mode 100644 index 0000000..c339ce8 --- /dev/null +++ b/src/interrupts_timers.rs @@ -0,0 +1,67 @@ +use crate::display::DisplayInterrupt; +use crate::opcodes; +use crate::state::{GBState, MemError}; + +const TIMA_TIMER_SPEEDS: [u64; 4] = [1024, 16, 64, 256]; + +impl GBState { + pub fn check_interrupts(&mut self) -> Result<(), MemError> { + if self.mem.ime { + let interrupts = self.mem.io[0x0f] & self.mem.interrupts_register & 0b11111; + for i in 0..5 { + if interrupts & (1 << i) != 0 { + opcodes::push(self, self.cpu.pc)?; + + self.mem.ime = false; + self.cpu.pc = 0x40 + (i << 3); + self.mem.halt = false; + + self.mem.io[0x0f] &= !(1 << i); + break; + } + } + } + Ok(()) + } + + pub fn tima_timer(&mut self, c: u64) { + if self.mem.timer_enabled + && self.tima_cycles >= TIMA_TIMER_SPEEDS[self.mem.timer_speed as usize] + { + if self.mem.tima == 0xff { + self.mem.io[0x0f] |= 0b100; + self.mem.tima = self.mem.tma; + } else { + self.mem.tima += 1; + } + self.tima_cycles %= TIMA_TIMER_SPEEDS[self.mem.timer_speed as usize]; + } + self.tima_cycles += c; + } + + pub fn update_display_interrupts(&mut self, c: u64) { + let interrupt = self.mem.display.update_display(c); + + match interrupt { + DisplayInterrupt::Vblank => { + self.mem.io[0x0f] |= 1; + } + DisplayInterrupt::Stat => { + self.mem.io[0xf] |= 2; + } + DisplayInterrupt::Both => { + self.mem.io[0xf] |= 3; + } + _ => {} + } + } + + pub fn div_timer(&mut self, c: u64) { + if self.div_cycles >= 256 { + self.mem.div += 1; + + self.div_cycles = 0; + } + self.div_cycles += c; + } +} diff --git a/src/io.rs b/src/io.rs new file mode 100644 index 0000000..df50c51 --- /dev/null +++ b/src/io.rs @@ -0,0 +1,257 @@ +use crate::state::{MemError, Memory}; + +impl Memory { + pub fn r_io(&self, addr: u8) -> u8 { + if addr > 0x50 { + println!("Reading from 0xff{:02x} not implemented yet", addr); + } + match addr { + 0x00 => { + if self.joypad_is_action { + (self.joypad_reg >> 4) | 0b11010000 + } else { + (self.joypad_reg & 0xf) | 0b11100000 + } + } + 0x04 => self.div, + 0x40 => self.display.lcdc, + 0x42 => self.display.viewport_y, + 0x43 => self.display.viewport_x, + 0x41 => { + let mut ret = match self.display.lcd_interrupt_mode { + 3 => 0b01000000, + 2 => 0b00100000, + 1 => 0b00010000, + 0 => 0b00001000, + _ => 0, + }; + + ret |= if self.display.ly > 0x90 { + 1 + } else if self.display.stat < 80 { + 2 + } else if self.display.stat < 280 { + 3 + } else { + 0 + }; + + if self.display.ly == self.display.lyc + 1 { + ret |= 0b100; + } + + ret + } + 0x44 => self.display.ly, + 0x45 => self.display.lyc, + 0x47 => self.display.bg_palette, + 0x48 => self.display.obj_palettes[0], + 0x49 => self.display.obj_palettes[1], + 0x4a => self.display.window_y, + 0x4b => self.display.window_x, + 0x50 => { + if self.boot_rom_on { + 0xfe + } else { + 0xff + } + } + _ => { + // println!("Reading from 0xff{:02x} not implemented yet", addr); + self.io[addr as usize] + } + } + } + + pub fn w_io(&mut self, addr: u8, value: u8) -> Result<(), MemError> { + match addr { + 0x00 => { + self.joypad_is_action = !value & 0b00100000 != 0; + } + 0x04 => { + self.div = 0; + } + 0x05 => { + self.tima = value; + } + 0x06 => { + self.tma = value; + } + 0x07 => { + self.timer_enabled = value & 0b100 != 0; + self.timer_speed = value & 0b11; + } + 0x0f => { + self.io[0x0f] = value; + } + 0x10 => { + self.audio.ch1.period_sweep_pace = (0b1110000 & value) >> 4; + self.audio.ch1.period_sweep_direction = (0b1000 & value) >> 3; + self.audio.ch1.period_sweep_slope = 0b111 & value; + } + 0x11 => { + self.audio.ch1.duty = value >> 6; + self.audio.ch1.length_timer = value & 0b111111; + } + 0x12 => { + self.audio.ch1.initial_volume = value >> 4; + self.audio.ch1.env_direction = (value & 0xf) >> 3; + self.audio.ch1.sweep = value & 0b111; + } + 0x13 => { + self.audio.ch1.period_value &= 0xff00; + self.audio.ch1.period_value |= value as u16; + } + 0x14 => { + self.audio.ch1.period_value &= 0xff; + self.audio.ch1.period_value |= ((value & 0b111) as u16) << 8; + self.audio.ch1.length_timer_enabled = value & 0b01000000 != 0; + if value >> 7 == 1 { + self.audio.ch1.update(); + } + } + 0x16 => { + self.audio.ch2.duty = value >> 6; + self.audio.ch2.length_timer = value & 0b111111; + } + 0x17 => { + self.audio.ch2.initial_volume = value >> 4; + self.audio.ch2.env_direction = (value & 0xf) >> 3; + self.audio.ch2.sweep = value & 0b111; + } + 0x18 => { + self.audio.ch2.period_value &= 0xff00; + self.audio.ch2.period_value |= value as u16; + } + 0x19 => { + self.audio.ch2.period_value &= 0xff; + self.audio.ch2.period_value |= ((value & 0b111) as u16) << 8; + self.audio.ch2.length_timer_enabled = value & 0b01000000 != 0; + if value >> 7 == 1 { + self.audio.ch2.update(); + } + } + 0x1a => { + if value & 0b10000000 != 0 { + self.audio.ch3.on = true; + } else { + self.audio.ch3.on = false; + } + self.audio.ch3.update(); + } + 0x1b => { + self.audio.ch3.length_timer = value & 0b111111; + } + 0x1c => { + let s = (value >> 5) & 0b11; + if s == 0 { + self.audio.ch3.initial_volume = 0; + } else { + self.audio.ch3.initial_volume = 0xf >> (s - 1); + } + } + 0x1d => { + self.audio.ch3.period_value &= 0xff00; + self.audio.ch3.period_value |= value as u16; + } + 0x1e => { + self.audio.ch3.period_value &= 0xff; + self.audio.ch3.period_value |= ((value & 0b111) as u16) << 8; + self.audio.ch3.period_value /= 2; + self.audio.ch3.length_timer_enabled = value & 0b01000000 != 0; + if value >> 7 == 1 { + self.audio.ch3.update(); + } + } + 0x20 => { + self.audio.ch4.length_timer = value & 0b111111; + } + 0x21 => { + self.audio.ch4.initial_volume = value >> 4; + self.audio.ch4.env_direction = (value & 0xf) >> 3; + self.audio.ch4.sweep = value & 0b111; + } + 0x22 => { + self.audio.ch4.clock_shift = value >> 4; + self.audio.ch4.lsfr_width = (value & 0xf) >> 3; + self.audio.ch4.clock_divider = value & 0b111; + } + 0x23 => { + self.audio.ch4.length_timer_enabled = value & 0b01000000 != 0; + if value >> 7 == 1 { + self.audio.ch4.update(); + } + } + 0x40 => self.display.lcdc = value, + 0x41 => { + if value & 0b01000000 != 0 { + self.display.lcd_interrupt_mode = 3; + } else if value & 0b00100000 != 0 { + self.display.lcd_interrupt_mode = 2; + } else if value & 0b00010000 != 0 { + self.display.lcd_interrupt_mode = 1; + } else if value & 0b00001000 != 0 { + self.display.lcd_interrupt_mode = 0; + } + } + 0x45 => self.display.lyc = value, + 0x42 => self.display.viewport_y = value, + 0x43 => self.display.viewport_x = value, + 0x46 => { + if value < 0xe0 { + let addr = (value as u16) << 8; + + for i in 0..0xa0 { + self.w(0xfe00 | i, self.r(addr | i)?)?; + } + } + } + 0x47 => self.display.bg_palette = value, + 0x48 => self.display.obj_palettes[0] = value, + 0x49 => self.display.obj_palettes[1] = value, + 0x4a => self.display.window_y = value, + 0x4b => self.display.window_x = value, + 0x4f => self.display.vram_bank = value & 1, + 0x50 => self.boot_rom_on = value & 1 == 0 && self.boot_rom_on, + 0x68 => { + self.bgcram_pointer = 0b111111 & value; + self.bgcram_pointer_autoincrement = value & 0b10000000 != 0; + } + 0x69 => { + self.display.cram[self.bgcram_pointer as usize] = value; + if self.bgcram_pointer_autoincrement { + self.bgcram_pointer += 1; + self.bgcram_pointer &= 0b111111; + } + } + 0x6a => { + self.obcram_pointer = 0b111111 & value; + self.obcram_pointer_autoincrement = value & 0b10000000 != 0; + } + 0x6b => { + self.display.cram[self.obcram_pointer as usize + 0x40] = value; + if self.obcram_pointer_autoincrement { + self.obcram_pointer += 1; + self.obcram_pointer &= 0b111111; + } + } + _ => { + if addr >= 0x4d { + println!( + "Writing to 0xff{:02x} not implemented yet ({:02x})", + addr, value + ); + } + } + } + self.io[addr as usize] = value; + + if addr >= 0x30 && addr <= 0x3f { + let i = (addr - 0x30) as usize; + self.audio.ch3.wave_pattern[i * 2] = value >> 4; + self.audio.ch3.wave_pattern[i * 2 + 1] = value & 0xf; + } + + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..3d18789 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,142 @@ +pub mod audio; +pub mod consts; +pub mod display; +pub mod gamepad; +pub mod interrupts_timers; +pub mod io; +pub mod opcodes; +pub mod state; + +use crate::gamepad::Gamepad; +use crate::state::{GBState, MemError}; +use clap::Parser; +use std::time::SystemTime; +use std::{thread, time}; + +pub fn exec_opcode(state: &mut GBState) -> Result<u64, MemError> { + let opcode = state.mem.r(state.cpu.pc)?; + + if state.is_debug { + println!( + "{:02x}:{:04x} = {:02x} (IME: {})", + state.mem.rom_bank, state.cpu.pc, opcode, state.mem.ime + ); + } + + state.cpu.pc += 1; + + let n1 = (opcode >> 3) & 0b111; + let n2 = opcode & 0b111; + + match opcode >> 6 { + 0b00 => opcodes::op00(state, n1, n2), + 0b01 => opcodes::op01(state, n1, n2), + 0b10 => opcodes::op10(state, n1, n2), + 0b11 => opcodes::op11(state, n1, n2), + _ => panic!(), + } +} + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +struct Cli { + /// The gameboy rom file + rom: String, + + /// Setting this saves battery by using thread::sleep instead of spin_sleeping. It can result in lag and inconsistent timing. + #[arg(long)] + thread_sleep: bool, + + #[arg(short, long, default_value_t = 1.0)] + speed: f32, +} + +fn main() { + let cli = Cli::parse(); + + println!("Initializing Gamepad..."); + + let mut gamepad = Gamepad::new(); + + println!("Starting {:?}...", &cli.rom); + + let mut state = GBState::new(); + + let save_file = format!("{}.sav", &cli.rom); + + state.mem.load_rom(&cli.rom).unwrap(); + + if let Err(_) = state.mem.load_external_ram(&save_file) { + println!( + "\"{}\" not found. Initializing new external ram.", + save_file + ); + } + + let mut nanos_sleep: i128 = 0; + let mut halt_time = 0; + let mut was_previously_halted = false; + + let mut last_ram_bank_enabled = false; + + loop { + if was_previously_halted && !state.mem.halt { + println!("Halt cycles {}", halt_time); + halt_time = 0; + } + was_previously_halted = state.mem.halt; + let now = SystemTime::now(); + let c = if !state.mem.halt { + exec_opcode(&mut state).unwrap() + } else { + halt_time += 4; + 4 + }; + + state.div_timer(c); + state.tima_timer(c); + state.update_display_interrupts(c); + state.check_interrupts().unwrap(); + + nanos_sleep += c as i128 * (consts::CPU_CYCLE_LENGTH_NANOS as f32 / cli.speed) as i128; + if nanos_sleep > 0 { + gamepad.update_events(); + + let action_button_reg = gamepad.get_action_gamepad_reg(); + let direction_button_reg = gamepad.get_direction_gamepad_reg(); + gamepad.check_special_actions(&mut state); + + if state.mem.joypad_is_action + && (action_button_reg & (state.mem.joypad_reg >> 4)) + != (state.mem.joypad_reg >> 4) + || (!state.mem.joypad_is_action + && (direction_button_reg & state.mem.joypad_reg & 0b1111) + != (state.mem.joypad_reg & 0b1111)) + { + state.mem.io[0x0f] |= 0b10000; + } + + state.mem.joypad_reg = direction_button_reg | (action_button_reg << 4); + + if cli.thread_sleep { + thread::sleep(time::Duration::from_nanos(nanos_sleep as u64 / 10)); + } else { + while SystemTime::now().duration_since(now).unwrap().as_nanos() + < nanos_sleep as u128 + {} + } + + nanos_sleep = + nanos_sleep - SystemTime::now().duration_since(now).unwrap().as_nanos() as i128; + + if last_ram_bank_enabled && !state.mem.ram_bank_enabled { + println!("Saving to \"{}\"...", save_file); + + if let Err(_) = state.mem.save_external_ram(&save_file) { + println!("Failed to save external RAM"); + } + } + last_ram_bank_enabled = state.mem.ram_bank_enabled; + } + } +} diff --git a/src/opcodes.rs b/src/opcodes.rs new file mode 100644 index 0000000..f238239 --- /dev/null +++ b/src/opcodes.rs @@ -0,0 +1,825 @@ +use crate::state::{flag, reg, GBState, MemError}; + +// The opcodes functions are returning the number of cycles used. + +pub fn r_16b_from_pc(state: &mut GBState) -> Result<u16, MemError> { + let p: u16 = state.mem.r(state.cpu.pc)? as u16 | ((state.mem.r(state.cpu.pc + 1)? as u16) << 8); + state.cpu.pc += 2; + + Ok(p) +} + +pub fn r_8b_from_pc(state: &mut GBState) -> Result<u8, MemError> { + let p = state.mem.r(state.cpu.pc)?; + state.cpu.pc += 1; + + Ok(p) +} + +pub fn ldrr(state: &mut GBState, n1: u8, n2: u8) -> Result<(), MemError> { + // Load a register into another register + // LD r, r + state.w_reg(n1, state.r_reg(n2)?) +} + +pub fn ldr8(state: &mut GBState, n1: u8) -> Result<u64, MemError> { + // Load an raw 8b value into a register + let p = r_8b_from_pc(state)?; + + state.w_reg(n1, p)?; + Ok(8) +} + +pub fn ldrr16(state: &mut GBState, rr: u8, x: u16) { + // Load a raw 16b value into a register + state.cpu.w16(rr, x); +} + +pub fn ldnnsp(state: &mut GBState) -> Result<u64, MemError> { + // Load SP into an arbitrary position in memory + let p = r_16b_from_pc(state)?; + + state.mem.w(p, (state.cpu.sp & 0xff) as u8)?; + state.mem.w(p + 1, (state.cpu.sp >> 8) as u8)?; + Ok(20) +} + +pub fn ldsphl(state: &mut GBState) -> u64 { + state.cpu.sp = state.cpu.r16(reg::HL); + 8 +} + +pub fn ldnna(state: &mut GBState, nn: u16) -> Result<(), MemError> { + // Load A into an arbitrary position in memory + state.mem.w(nn, state.cpu.r[reg::A as usize])?; + Ok(()) +} + +pub fn ldann(state: &mut GBState, nn: u16) -> Result<(), MemError> { + // Load A from an arbitrary position in memory + state.cpu.r[reg::A as usize] = state.mem.r(nn)?; + Ok(()) +} + +pub fn push(state: &mut GBState, x: u16) -> Result<(), MemError> { + state.cpu.sp -= 2; + + state.mem.w(state.cpu.sp, (x & 0xff) as u8)?; + + state.mem.w(state.cpu.sp + 1, (x >> 8) as u8)?; + + Ok(()) +} + +pub fn pop(state: &mut GBState) -> Result<u16, MemError> { + let res = state.mem.r(state.cpu.sp)? as u16 | ((state.mem.r(state.cpu.sp + 1)? as u16) << 8); + + state.cpu.sp += 2; + + Ok(res) +} + +pub fn jr8(state: &mut GBState) -> Result<u64, MemError> { + // Unconditional relative jump + let p = r_8b_from_pc(state)?; + + state.cpu.pc = (state.cpu.pc as i16 + p as i8 as i16) as u16; + + Ok(12) +} + +pub fn jrcc8(state: &mut GBState, n1: u8) -> Result<u64, MemError> { + // Conditional relative jump + let p = r_8b_from_pc(state)?; + let mut cycles = 8; + + if state.cpu.check_flag(n1 & 0b11) { + cycles += 4; + state.cpu.pc = (state.cpu.pc as i16 + p as i8 as i16) as u16; + } + + Ok(cycles) +} + +pub fn jp16(state: &mut GBState) -> Result<u64, MemError> { + // Unconditional absolute jump + let p = r_16b_from_pc(state)?; + + state.cpu.pc = p; + + Ok(16) +} + +pub fn jphl(state: &mut GBState) -> u64 { + // Unconditional absolute jump to HL + state.cpu.pc = state.cpu.r16(reg::HL); + + 4 +} + +pub fn jpcc16(state: &mut GBState, n1: u8) -> Result<u64, MemError> { + // Conditional absolute jump + let p = r_16b_from_pc(state)?; + let mut cycles = 8; + + if state.cpu.check_flag(n1 & 0b11) { + cycles += 4; + state.cpu.pc = p; + } + + Ok(cycles) +} + +pub fn call(state: &mut GBState) -> Result<u64, MemError> { + // Unconditional function call + let p = r_16b_from_pc(state)?; + + push(state, state.cpu.pc)?; + state.cpu.pc = p; + + Ok(24) +} + +pub fn callcc(state: &mut GBState, n1: u8) -> Result<u64, MemError> { + // Conditional function call + let p = r_16b_from_pc(state)?; + let mut cycles = 12; + + if state.cpu.check_flag(n1 & 0b11) { + cycles += 12; + push(state, state.cpu.pc)?; + state.cpu.pc = p; + } + + Ok(cycles) +} + +pub fn ret(state: &mut GBState) -> Result<u64, MemError> { + let res = pop(state)?; + + if res == 0 { + println!("DEBUG: {:?}", state.cpu); + panic!("RET to start"); + } + + state.cpu.pc = res; + + Ok(16) +} + +pub fn retcc(state: &mut GBState, n1: u8) -> Result<u64, MemError> { + let mut cycles = 8; + if state.cpu.check_flag(n1 & 0b11) { + cycles += 12; + state.cpu.pc = pop(state)?; + } + + Ok(cycles) +} + +pub fn ld00a(state: &mut GBState, n1: u8) -> Result<u64, MemError> { + // Load register A into or from memory pointed by rr (BC, DE or HL(+/-)) + // LD (rr), A + // LD A, (rr) + let ptr_reg = match n1 & 0b110 { + 0b000 => reg::B, + 0b010 => reg::C, + _ => reg::HL, + }; + + if n1 & 0b001 == 1 { + state.cpu.r[reg::A as usize] = state.mem.r(state.cpu.r16(ptr_reg))?; + } else { + state + .mem + .w(state.cpu.r16(ptr_reg), state.cpu.r[reg::A as usize])?; + } + + if n1 & 0b110 == 0b100 { + state.cpu.w16(reg::HL, state.cpu.r16(reg::HL) + 1); // (HL+) + } + + if n1 & 0b110 == 0b110 { + state.cpu.w16(reg::HL, state.cpu.r16(reg::HL) - 1); // (HL-) + } + + Ok(8) +} + +pub fn inc8(state: &mut GBState, n1: u8) -> Result<u64, MemError> { + // Increment 8 bit register + state.w_reg(n1, state.r_reg(n1)? + 1)?; + state.cpu.r[reg::F as usize] &= !(flag::N | flag::ZF | flag::H); + if state.r_reg(n1)? == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } + + if state.r_reg(n1)? & 0xf == 0x0 { + state.cpu.r[reg::F as usize] |= flag::H; + } + + Ok(4) +} + +pub fn dec8(state: &mut GBState, n1: u8) -> Result<u64, MemError> { + // Decrement 8 bit register + state.w_reg(n1, state.r_reg(n1)? - 1)?; + state.cpu.r[reg::F as usize] |= flag::N; + + state.cpu.r[reg::F as usize] &= !(flag::ZF | flag::H); + if state.r_reg(n1)? == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } + + if state.r_reg(n1)? & 0xf == 0xf { + state.cpu.r[reg::F as usize] |= flag::H; + } + + Ok(4) +} + +pub fn inc16(state: &mut GBState, rr: u8) -> u64 { + // Increment 16 bit register + state.cpu.w16(rr, state.cpu.r16(rr) + 1); + 8 +} + +pub fn dec16(state: &mut GBState, rr: u8) -> u64 { + // Decrement 16 bit register + state.cpu.w16(rr, state.cpu.r16(rr) - 1); + 8 +} + +pub fn ccf(state: &mut GBState) { + // Flip carry flag + state.cpu.r[reg::F as usize] = (state.cpu.r[reg::F as usize] & 0b10011111) ^ 0b00010000 +} + +pub fn scf(state: &mut GBState) { + // Set carry flag + state.cpu.r[reg::F as usize] = (state.cpu.r[reg::F as usize] & 0b10011111) | 0b00010000 +} + +pub fn daa(state: &mut GBState) { + // Decimal Adjust Accumulator + // Adjust the A register after a addition or substraction to keep valid BCD representation + let nibble_low = state.cpu.r[reg::A as usize] & 0b1111; + let sub_flag = (state.cpu.r[reg::F as usize] & flag::N) != 0; + let half_carry_flag = (state.cpu.r[reg::F as usize] & flag::H) != 0; + + if (half_carry_flag || nibble_low > 9) && !sub_flag { + state.cpu.r[reg::A as usize] += 0x06; + } + if (half_carry_flag || nibble_low > 9) && sub_flag { + state.cpu.r[reg::A as usize] -= 0x06; + } + + let nibble_high = state.cpu.r[reg::A as usize] >> 4; + + state.cpu.r[reg::F as usize] &= !(flag::CY | flag::ZF); + + if nibble_high > 9 && !sub_flag { + state.cpu.r[reg::A as usize] += 0x60; + state.cpu.r[reg::F as usize] |= flag::CY; + } + if nibble_high > 9 && sub_flag { + state.cpu.r[reg::A as usize] -= 0x60; + state.cpu.r[reg::F as usize] |= flag::CY; + } + + if state.cpu.r[reg::A as usize] == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } + + state.cpu.r[reg::F as usize] &= !flag::H; +} + +pub fn cpl(state: &mut GBState) { + // Flip all bits in register A + state.cpu.r[reg::F as usize] = state.cpu.r[reg::F as usize] | flag::N | flag::H; + state.cpu.r[reg::A as usize] ^= 0xff; +} + +pub fn addsp8(state: &mut GBState) -> Result<u64, MemError> { + let n = r_8b_from_pc(state)? as i8; + + state.cpu.sp = (state.cpu.sp as i32 + n as i32) as u16; + + state.cpu.r[reg::F as usize] &= !(flag::N | flag::H | flag::CY); + + if (state.cpu.sp & 0xff) as i32 + n as i32 & !0xff != 0 { + state.cpu.r[reg::F as usize] |= flag::H; + } + + if (state.cpu.sp as i32 + n as i32) & !0xffff != 0 { + state.cpu.r[reg::F as usize] |= flag::CY; + } + Ok(16) +} + +pub fn add(state: &mut GBState, x: u8) { + // ADD a number to A and store the result in A + let res = x as u16 + state.cpu.r[reg::A as usize] as u16; + + state.cpu.r[reg::F as usize] = 0; + + if (x & 0xf) + (state.cpu.r[reg::A as usize] & 0xf) > 0xf { + state.cpu.r[reg::F as usize] |= flag::H; + } + + if res > 0xff { + state.cpu.r[reg::F as usize] |= flag::CY; + } + + state.cpu.r[reg::A as usize] = res as u8; + + if state.cpu.r[reg::A as usize] == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } +} + +pub fn addhlrr(state: &mut GBState, rr: u8) -> u64 { + let n = state.cpu.r16(rr); + let hl = state.cpu.r16(reg::HL); + + state.cpu.w16(reg::HL, (hl as i32 + n as i32) as u16); + + state.cpu.r[reg::F as usize] &= !(flag::N | flag::H | flag::CY); + + if (hl & 0xff) as i32 + n as i32 & !0xff != 0 { + state.cpu.r[reg::F as usize] |= flag::H; + } + + if (hl as i32 + n as i32) & !0xffff != 0 { + state.cpu.r[reg::F as usize] |= flag::CY; + } + + 8 +} + +pub fn adc(state: &mut GBState, x: u8) { + // ADD a number and the carry flag to A and store the result in A + let carry = (state.cpu.r[reg::F as usize] & flag::CY) >> 4; + let res = x as u16 + state.cpu.r[reg::A as usize] as u16 + carry as u16; + + state.cpu.r[reg::F as usize] = 0; + + if (x & 0xf) + ((state.cpu.r[reg::A as usize] & 0xf) + carry) > 0xf { + state.cpu.r[reg::F as usize] |= flag::H; + } + + if res > 0xff { + state.cpu.r[reg::F as usize] |= flag::CY; + } + + state.cpu.r[reg::A as usize] = res as u8; + + if state.cpu.r[reg::A as usize] == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } +} + +pub fn sub(state: &mut GBState, x: u8) { + // SUB a number to A and store the result in A + state.cpu.r[reg::F as usize] = flag::N; + + if (x & 0xf) > (state.cpu.r[reg::A as usize] & 0xf) { + state.cpu.r[reg::F as usize] |= flag::H; + } + + if x > state.cpu.r[reg::A as usize] { + state.cpu.r[reg::F as usize] |= flag::CY; + } + + state.cpu.r[reg::A as usize] = state.cpu.r[reg::A as usize] - x; + + if state.cpu.r[reg::A as usize] == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } +} + +pub fn sbc(state: &mut GBState, x: u8) { + // SUB a number and the carry flag to A and store the result in A + let carry = (state.cpu.r[reg::F as usize] & flag::CY) >> 4; + state.cpu.r[reg::F as usize] = flag::N; + + if (x & 0xf) > (state.cpu.r[reg::A as usize] & 0xf) - carry { + state.cpu.r[reg::F as usize] |= flag::H; + } + + if x as i32 > state.cpu.r[reg::A as usize] as i32 - carry as i32 { + state.cpu.r[reg::F as usize] |= flag::CY; + } + + state.cpu.r[reg::A as usize] = state.cpu.r[reg::A as usize] - x - carry; + + if state.cpu.r[reg::A as usize] == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } +} + +pub fn and(state: &mut GBState, x: u8) { + // AND a number to A and store the result in A + state.cpu.r[reg::A as usize] &= x; + + state.cpu.r[reg::F as usize] = flag::H; + + if state.cpu.r[reg::A as usize] == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } +} + +pub fn xor(state: &mut GBState, x: u8) { + // XOR a number to A and store the result in A + state.cpu.r[reg::A as usize] ^= x; + + state.cpu.r[reg::F as usize] = 0; + + if state.cpu.r[reg::A as usize] == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } +} + +pub fn or(state: &mut GBState, x: u8) { + // OR a number to A and store the result in A + state.cpu.r[reg::A as usize] |= x; + + state.cpu.r[reg::F as usize] = 0; + + if state.cpu.r[reg::A as usize] == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } +} + +pub fn cp(state: &mut GBState, x: u8) { + // SUB a number to A and update the flags accordingly without updating A + state.cpu.r[reg::F as usize] &= !(flag::H | flag::N | flag::ZF | flag::CY); + + state.cpu.r[reg::F as usize] |= flag::N; + + if x & 0xf > state.cpu.r[reg::A as usize] & 0xf { + state.cpu.r[reg::F as usize] |= flag::H; + } + + if x > state.cpu.r[reg::A as usize] { + state.cpu.r[reg::F as usize] |= flag::CY; + } + + let res = state.cpu.r[reg::A as usize] - x; + + if res == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } +} + +pub fn rlc(state: &mut GBState, r_i: u8) -> Result<(), MemError> { + // ROTATE LEFT the input register + let mut n = state.r_reg(r_i)?; + state.cpu.r[reg::F as usize] &= !(flag::H | flag::N | flag::ZF | flag::CY); + state.cpu.r[reg::F as usize] |= (n >> 7) << 4; + n <<= 1; + n |= (state.cpu.r[reg::F as usize] & flag::CY) >> 4; + state.w_reg(r_i, n) +} + +pub fn rrc(state: &mut GBState, r_i: u8) -> Result<(), MemError> { + // ROTATE RIGHT the input register + let mut n = state.r_reg(r_i)?; + state.cpu.r[reg::F as usize] &= !(flag::H | flag::N | flag::ZF | flag::CY); + state.cpu.r[reg::F as usize] |= (n & 1) << 4; + n >>= 1; + n |= ((state.cpu.r[reg::F as usize] & flag::CY) >> 4) << 7; + state.w_reg(r_i, n) +} + +pub fn rl(state: &mut GBState, r_i: u8) -> Result<(), MemError> { + // ROTATE LEFT THROUGH CARRY the input register + // (RLC IS ROTATE AND RL IS ROTATE THROUGH CARRY ! IT DOESN'T MAKE ANY SENSE !!) + let mut n = state.r_reg(r_i)?; + let carry = (state.cpu.r[reg::F as usize] & flag::CY) >> 4; + + state.cpu.r[reg::F as usize] &= !(flag::H | flag::N | flag::ZF | flag::CY); + state.cpu.r[reg::F as usize] |= (n >> 7) << 4; + n <<= 1; + n |= carry; + state.w_reg(r_i, n) +} + +pub fn rr(state: &mut GBState, r_i: u8) -> Result<(), MemError> { + // ROTATE RIGHT THROUGH CARRY the input register + let mut n = state.r_reg(r_i)?; + let carry = (state.cpu.r[reg::F as usize] & flag::CY) >> 4; + + state.cpu.r[reg::F as usize] &= !(flag::H | flag::N | flag::ZF | flag::CY); + state.cpu.r[reg::F as usize] |= (n & 1) << 4; + n >>= 1; + n |= carry << 7; + state.w_reg(r_i, n) +} + +pub fn sla(state: &mut GBState, r_i: u8) -> Result<(), MemError> { + // Shift left Arithmetic (b0=0) the input register + let mut n = state.r_reg(r_i)?; + + state.cpu.r[reg::F as usize] &= !(flag::H | flag::N | flag::ZF | flag::CY); + state.cpu.r[reg::F as usize] |= (n >> 7) << 4; + n <<= 1; + + if n == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } + + state.w_reg(r_i, n) +} + +pub fn sra(state: &mut GBState, r_i: u8) -> Result<(), MemError> { + // Shift right Arithmetic (b7=b7) the input register + let mut n = state.r_reg(r_i)?; + + state.cpu.r[reg::F as usize] &= !(flag::H | flag::N | flag::ZF | flag::CY); + state.cpu.r[reg::F as usize] |= (n & 0b1) << 4; + let b7 = n & 0b10000000; + n >>= 1; + n |= b7; + + if n == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } + + state.w_reg(r_i, n) +} + +pub fn swap(state: &mut GBState, r_i: u8) -> Result<(), MemError> { + // Swap the high nibble and low nibble + let mut n = state.r_reg(r_i)?; + + let nibble_low = n & 0b1111; + let nibble_high = n >> 4; + + state.cpu.r[reg::F as usize] &= !(flag::H | flag::N | flag::ZF | flag::CY); + + n = nibble_high | (nibble_low << 4); + + if n == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } + + state.w_reg(r_i, n) +} + +pub fn srl(state: &mut GBState, r_i: u8) -> Result<(), MemError> { + // Shift right Logical (b7=0) the input register + let mut n = state.r_reg(r_i)?; + + state.cpu.r[reg::F as usize] &= !(flag::H | flag::N | flag::ZF | flag::CY); + state.cpu.r[reg::F as usize] |= (n & 0b1) << 4; + n >>= 1; + + if n == 0 { + state.cpu.r[reg::F as usize] |= flag::ZF; + } + + state.w_reg(r_i, n) +} + +pub fn bit(state: &mut GBState, n1: u8, n2: u8) -> Result<(), MemError> { + let z = (((state.r_reg(n2)? >> n1) & 1) ^ 1) << 7; + + state.cpu.r[reg::F as usize] &= !(flag::N | flag::ZF); + state.cpu.r[reg::F as usize] |= flag::H | z; + Ok(()) +} + +pub fn set(state: &mut GBState, n1: u8, n2: u8) -> Result<(), MemError> { + state.w_reg(n2, state.r_reg(n2)? | (1 << n1)) +} + +pub fn res(state: &mut GBState, n1: u8, n2: u8) -> Result<(), MemError> { + state.w_reg(n2, state.r_reg(n2)? & !(1 << n1)) +} + +// I don't remember why I separated op00, op01, op10 and op11 AND I'M NOT GOING TO CHANGE IT +// BECAUSE I LOVE CHAOS + +pub fn op00(state: &mut GBState, n1: u8, n2: u8) -> Result<u64, MemError> { + // Dispatcher for the instructions starting with 0b00 based on their 3 LSB + match n2 { + 0b000 => match n1 { + 0b000 => Ok(4), + 0b001 => ldnnsp(state), + 0b010 => todo!("STOP"), + 0b011 => jr8(state), + _ => jrcc8(state, n1), + }, + 0b001 => match n1 { + 0b001 | 0b011 | 0b101 | 0b111 => Ok(addhlrr(state, n1 >> 1)), + 0b000 | 0b010 | 0b100 | 0b110 => { + let p = r_16b_from_pc(state)?; + ldrr16(state, n1 >> 1, p); + Ok(12) + } + _ => panic!(), + }, + 0b010 => ld00a(state, n1), + 0b011 => match n1 { + 0b001 | 0b011 | 0b101 | 0b111 => Ok(dec16(state, n1 >> 1)), + 0b000 | 0b010 | 0b100 | 0b110 => Ok(inc16(state, n1 >> 1)), + _ => panic!(), + }, + 0b100 => inc8(state, n1), + 0b101 => dec8(state, n1), + 0b110 => ldr8(state, n1), + 0b111 => { + match n1 { + 0b000 => rlc(state, 7)?, + 0b001 => rrc(state, 7)?, + 0b010 => rl(state, 7)?, + 0b011 => rr(state, 7)?, + 0b100 => daa(state), + 0b101 => cpl(state), + 0b110 => scf(state), + 0b111 => ccf(state), + _ => panic!(), + }; + Ok(4) + } + _ => panic!(), + } +} + +pub fn op01(state: &mut GBState, n1: u8, n2: u8) -> Result<u64, MemError> { + // Dispatcher for the instructions starting with 0b01 (LD r,r and HALT) + if n1 == 0b110 && n2 == 0b110 { + state.mem.halt = true; + Ok(4) + } else { + ldrr(state, n1, n2)?; + + if n1 == 0b110 || n2 == 0b110 { + Ok(8) + } else { + Ok(4) + } + } +} + +pub fn op10(state: &mut GBState, n1: u8, n2: u8) -> Result<u64, MemError> { + // Dispatcher for the instructions starting with 0b10 (Arithmetic) + match n1 { + 0b000 => add(state, state.r_reg(n2)?), + 0b001 => adc(state, state.r_reg(n2)?), + 0b010 => sub(state, state.r_reg(n2)?), + 0b011 => sbc(state, state.r_reg(n2)?), + 0b100 => and(state, state.r_reg(n2)?), + 0b101 => xor(state, state.r_reg(n2)?), + 0b110 => or(state, state.r_reg(n2)?), + 0b111 => cp(state, state.r_reg(n2)?), + _ => panic!(), + } + + if n2 == 0b110 { + Ok(8) + } else { + Ok(4) + } +} + +pub fn op11(state: &mut GBState, n1: u8, n2: u8) -> Result<u64, MemError> { + match n2 { + 0b000 => match n1 { + 0b100 => { + let n = r_8b_from_pc(state)?; + ldnna(state, n as u16 | 0xff00)?; + Ok(12) + } + 0b101 => addsp8(state), + 0b110 => { + let n = r_8b_from_pc(state)?; + ldann(state, n as u16 | 0xff00)?; + Ok(12) + } + 0b111 => { + let n = r_8b_from_pc(state)?; + ldrr16(state, reg::HL, n as u16 + state.cpu.sp); + Ok(12) + } + _ => retcc(state, n1 & 0b11), + }, + 0b001 => match n1 { + 0b001 => ret(state), + 0b011 => { + state.mem.ime = true; + + ret(state) + } + 0b101 => Ok(jphl(state)), + 0b111 => Ok(ldsphl(state)), + _ => { + let p = pop(state)?; + state.cpu.r[(n1 >> 1) as usize * 2 + 1] = (p & 0xff) as u8; + state.cpu.r[(n1 >> 1) as usize * 2] = (p >> 8) as u8; + Ok(12) + } + }, + 0b010 => match n1 { + 0b100 => { + ldnna(state, state.cpu.r[reg::C as usize] as u16 | 0xff00)?; + Ok(8) + } + 0b101 => { + let nn = r_16b_from_pc(state)?; + ldnna(state, nn)?; + Ok(16) + } + 0b110 => { + ldann(state, state.cpu.r[reg::C as usize] as u16 | 0xff00)?; + Ok(8) + } + 0b111 => { + let nn = r_16b_from_pc(state)?; + ldann(state, nn)?; + Ok(16) + } + _ => jpcc16(state, n1 & 0b11), + }, + 0b011 => match n1 { + 0b000 => jp16(state), + 0b001 => op_bitwise(state), // Bitwise operations + 0b010 | 0b011 | 0b100 | 0b101 => unimplemented!(), + 0b110 => { + state.mem.ime = false; + Ok(4) + } + 0b111 => { + state.mem.ime = true; + Ok(4) + } + _ => panic!(), + }, + 0b100 => callcc(state, n1 & 0b11), + 0b101 => match n1 { + 0b001 => call(state), + 0b011 | 0b101 | 0b111 => unimplemented!(), + _ => { + let value = state.cpu.r[(n1 >> 1) as usize * 2 + 1] as u16 + | ((state.cpu.r[(n1 >> 1) as usize * 2] as u16) << 8); + push(state, value)?; + Ok(16) + } + }, + 0b110 => { + let p = r_8b_from_pc(state)?; + + match n1 { + 0b000 => add(state, p), + 0b001 => adc(state, p), + 0b010 => sub(state, p), + 0b011 => sbc(state, p), + 0b100 => and(state, p), + 0b101 => xor(state, p), + 0b110 => or(state, p), + 0b111 => cp(state, p), + _ => panic!(), + } + Ok(8) + } + 0b111 => { + let p = n1 << 3; + + push(state, state.cpu.pc)?; + state.cpu.pc = p as u16; + Ok(16) + } // RST + _ => panic!(), + } +} + +pub fn op_bitwise(state: &mut GBState) -> Result<u64, MemError> { + let p = r_8b_from_pc(state)?; + let opcode = p >> 6; + let n1 = p >> 3 & 0b111; + let n2 = p & 0b111; + + match opcode { + 0b00 => match n1 { + 0b000 => rlc(state, n2), + 0b001 => rrc(state, n2), + 0b010 => rl(state, n2), + 0b011 => rr(state, n2), + 0b100 => sla(state, n2), + 0b101 => sra(state, n2), + 0b110 => swap(state, n2), + 0b111 => srl(state, n2), + _ => panic!(), + }, + 0b01 => bit(state, n1, n2), + 0b10 => res(state, n1, n2), + 0b11 => set(state, n1, n2), + _ => panic!(), + }?; + if n2 == 0b110 { + Ok(16) + } else { + Ok(8) + } +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..d2f93d9 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,377 @@ +use crate::audio::Audio; +use crate::consts::{PROGRAM_START_ADDRESS, STACK_START_ADDRESS}; +use crate::display::Display; +use std::fs::File; +use std::io::{Read, Write}; + +pub mod reg { + pub const B: u8 = 0; + pub const C: u8 = 1; + pub const D: u8 = 2; + pub const E: u8 = 3; + pub const H: u8 = 4; + pub const L: u8 = 5; + pub const A: u8 = 6; + pub const F: u8 = 7; + + pub const BC: u8 = 0; + pub const DE: u8 = 1; + pub const HL: u8 = 2; + pub const SP: u8 = 3; +} + +pub mod flag { + pub const NZ: u8 = 0; + pub const Z: u8 = 1; + pub const NC: u8 = 2; + pub const C: u8 = 3; + + pub const CY: u8 = 1 << 4; + pub const H: u8 = 1 << 5; + pub const N: u8 = 1 << 6; + pub const ZF: u8 = 1 << 7; +} + +#[derive(Debug)] +pub struct CPU { + /* B, C, D, E, H, L, A, F registers. + * A is usually represented by 111 even though it's in index 6. + * (HL) usually takes the 110 representation. + * F isn't usually used by the 8bits register operations. */ + pub r: [u8; 8], + + pub pc: u16, // program counter + pub sp: u16, // stack pointer +} + +impl CPU { + pub fn new() -> Self { + Self { + r: [0; 8], + + pc: PROGRAM_START_ADDRESS, + sp: STACK_START_ADDRESS, + } + } + + pub fn r16(&self, r: u8) -> u16 { + if r == reg::SP { + return self.sp; + } else { + return self.r[r as usize * 2 + 1] as u16 | ((self.r[r as usize * 2] as u16) << 8); + } + } + + pub fn w16(&mut self, r: u8, value: u16) { + if r == reg::SP { + self.sp = value; + } else { + self.r[r as usize * 2 + 1] = (value & 0xff) as u8; + self.r[r as usize * 2] = (value >> 8) as u8; + } + } + + pub fn check_flag(&self, flag: u8) -> bool { + let f = self.r[reg::F as usize]; + + match flag { + flag::NZ => f >> 7 == 0, + flag::Z => f >> 7 == 1, + flag::NC => (f >> 4) & 1 == 0, + flag::C => (f >> 4) & 1 == 1, + _ => unimplemented!(), + } + } +} + +pub struct Memory { + boot_rom: [u8; 0x900], + + pub cgb_mode: bool, + + pub bgcram_pointer: u8, + + pub bgcram_pointer_autoincrement: bool, + + pub obcram_pointer: u8, + + pub obcram_pointer_autoincrement: bool, + + pub boot_rom_on: bool, + + pub rom_bank: u8, + + pub ram_bank: u8, + + pub ram_bank_enabled: bool, + + // 32 KiB ROM bank 00 + rom: [u8; 0x200000], + + // 4 KiB Work RAM 00 + wram_00: [u8; 0x1000], + + // 4 KiB Work RAM 00 + wram_01: [u8; 0x1000], + + // External RAM + external_ram: [u8; 0x8000], + + // 8 KiB Video RAM + pub display: Display, + + pub io: [u8; 0x80], + + // High RAM + hram: [u8; 0x7f], + + pub audio: Audio, + + pub ime: bool, + + pub div: u8, + + pub joypad_reg: u8, + + pub joypad_is_action: bool, + + pub interrupts_register: u8, + + pub halt: bool, + + pub tima: u8, + + pub tma: u8, + + pub timer_enabled: bool, + + pub timer_speed: u8, +} + +#[derive(Debug)] +pub enum MemError { + WritingToROM, + Unimplemented, + NotUsable, +} + +impl Memory { + pub fn new() -> Self { + let mut display = Display::new(); + + display.cls(); + + Self { + boot_rom: [0; 0x900], + boot_rom_on: true, + cgb_mode: false, + bgcram_pointer: 0, + bgcram_pointer_autoincrement: false, + obcram_pointer: 0, + obcram_pointer_autoincrement: false, + rom_bank: 1, + ram_bank: 0, + ram_bank_enabled: false, + rom: [0; 0x200000], + wram_00: [0; 0x1000], + wram_01: [0; 0x1000], + external_ram: [0; 0x8000], + display, + io: [0; 0x80], + hram: [0; 0x7f], + audio: Audio::new(), + ime: false, + interrupts_register: 0, + joypad_is_action: false, + joypad_reg: 0, + div: 0, + halt: false, + tima: 0, + tma: 0, + timer_enabled: false, + timer_speed: 0, + } + } + + pub fn load_dmg_boot_rom(&mut self) { + let bytes = include_bytes!("../assets/dmg_boot.bin"); + + self.boot_rom[..0x100].copy_from_slice(bytes); + } + + pub fn load_cgb_boot_rom(&mut self) { + let bytes = include_bytes!("../assets/cgb_boot.bin"); + + self.boot_rom[..0x900].copy_from_slice(bytes); + } + + pub fn load_rom(&mut self, file: &str) -> Result<(), std::io::Error> { + let mut f = File::open(file)?; + + f.read(&mut self.rom)?; + + println!("MBC: {:02x}", self.rom[0x147]); + println!("CGB: {:02x}", self.rom[0x143]); + + if self.rom[0x143] == 0x80 || self.rom[0x143] == 0xc0 { + self.load_cgb_boot_rom(); + self.cgb_mode = true; + self.display.cgb_mode = true; + } else { + self.load_dmg_boot_rom(); + } + + Ok(()) + } + + pub fn load_external_ram(&mut self, file: &str) -> Result<(), std::io::Error> { + let mut f = File::open(file)?; + + f.read(&mut self.external_ram)?; + + println!("Save file loaded from \"{}\"!", file); + + Ok(()) + } + + pub fn save_external_ram(&self, file: &str) -> Result<(), std::io::Error> { + let mut f = File::create(file)?; + + f.write_all(&self.external_ram)?; + + println!("Save written to \"{}\"!", file); + + Ok(()) + } + + pub fn r(&self, addr: u16) -> Result<u8, MemError> { + if (addr < 0x100 || (addr >= 0x200 && addr < 0x900)) && self.boot_rom_on { + Ok(self.boot_rom[addr as usize]) + } else if addr < 0x4000 { + Ok(self.rom[addr as usize]) + } else if addr < 0x8000 { + Ok(self.rom[self.rom_bank as usize * 0x4000 + addr as usize - 0x4000 as usize]) + } else if addr >= 0xa000 && addr < 0xc000 { + if self.ram_bank_enabled { + Ok(self.external_ram[self.ram_bank as usize * 0x2000 + addr as usize - 0xa000]) + } else { + Ok(0xff) + } + } else if addr >= 0xc000 && addr < 0xd000 { + Ok(self.wram_00[addr as usize - 0xc000]) + } else if addr >= 0xd000 && addr < 0xe000 { + Ok(self.wram_01[addr as usize - 0xd000]) + } else if (addr >= 0x8000 && addr < 0xa000) || (addr >= 0xfe00 && addr < 0xfea0) { + self.display.r(addr & !0x8000) + } else if addr >= 0xff00 && addr < 0xff80 { + Ok(self.r_io((addr & 0xff) as u8)) + } else if addr >= 0xff80 && addr < 0xffff { + Ok(self.hram[addr as usize - 0xff80]) + } else if addr == 0xffff { + Ok(self.interrupts_register) + } else { + println!( + "Trying to read at address 0x{:04x} which is unimplemented", + addr + ); + Ok(0) //Err(MemError::Unimplemented) + } + } + + pub fn w(&mut self, addr: u16, value: u8) -> Result<(), MemError> { + if addr < 0x2000 { + self.ram_bank_enabled = value == 0x0a; + Ok(()) + } else if addr >= 0x2000 && addr < 0x4000 { + if value == 0 { + self.rom_bank = 1 + } else { + self.rom_bank = value & 0b1111111; + } + Ok(()) + } else if addr >= 0x4000 && addr < 0x6000 { + self.ram_bank = value & 0b11; + Ok(()) + } else if addr >= 0xa000 && addr < 0xc000 { + self.external_ram[self.ram_bank as usize * 0x2000 + addr as usize - 0xa000] = value; + Ok(()) + } else if addr >= 0xc000 && addr < 0xd000 { + self.wram_00[addr as usize - 0xc000] = value; + Ok(()) + } else if addr >= 0xd000 && addr < 0xe000 { + self.wram_01[addr as usize - 0xd000] = value; + Ok(()) + } else if (addr >= 0x8000 && addr < 0xa000) || (addr >= 0xfe00 && addr < 0xfea0) { + self.display.w(addr & !0x8000, value) + } else if addr >= 0xff00 && addr < 0xff80 { + Ok(self.w_io((addr & 0xff) as u8, value)?) + } else if addr >= 0xff80 && addr < 0xffff { + self.hram[addr as usize - 0xff80] = value; + Ok(()) + } else if addr == 0xffff { + self.interrupts_register = value; + Ok(()) + } else { + println!( + "Trying to write at address 0x{:04x} which is unimplemented (value: {:02x})", + addr, value + ); + Ok(()) //Err(MemError::Unimplemented) + } + } +} + +pub struct GBState { + pub cpu: CPU, + pub mem: Memory, + pub is_debug: bool, + + pub div_cycles: u64, + pub tima_cycles: u64, +} + +impl GBState { + pub fn new() -> Self { + let mem = Memory::new(); + + Self { + cpu: CPU::new(), + mem, + is_debug: false, + + div_cycles: 0, + tima_cycles: 0, + } + } + + pub fn r_reg(&self, r_i: u8) -> Result<u8, MemError> { + if r_i < 6 { + Ok(self.cpu.r[r_i as usize]) + } else if r_i == 7 { + Ok(self.cpu.r[6]) + } else if r_i == 6 { + self.mem.r(self.cpu.r16(reg::HL)) + } else { + panic!("r_i must be a 3 bits register input number") + } + } + + pub fn w_reg(&mut self, r_i: u8, value: u8) -> Result<(), MemError> { + if r_i < 6 { + self.cpu.r[r_i as usize] = value; + } else if r_i == 7 { + self.cpu.r[6] = value; + } else if r_i == 6 { + self.mem.w(self.cpu.r16(reg::HL), value)?; + } else { + panic!("r_i must be a 3 bits register input number") + } + Ok(()) + } + + pub fn debug(&self, s: &str) -> () { + if self.is_debug { + println!("{}", s); + } + } +} |