aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAstatin <[email protected]>2024-08-29 22:32:10 +0900
committerAstatin <astatin@redacted>2024-08-29 22:32:10 +0900
commit09104524ed468442bb4957d24e65395955b59ddd (patch)
tree0aaad5952df29e0a920660c422e1aaf8acea0123 /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.rs561
-rw-r--r--src/consts.rs12
-rw-r--r--src/display.rs359
-rw-r--r--src/gamepad.rs89
-rw-r--r--src/interrupts_timers.rs67
-rw-r--r--src/io.rs257
-rw-r--r--src/main.rs142
-rw-r--r--src/opcodes.rs825
-rw-r--r--src/state.rs377
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);
+ }
+ }
+}