diff --git a/Cargo.toml b/Cargo.toml index 67e39d7..e58b243 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,6 @@ + [workspace] +resolver = "2" members = [ "nes_ui_sfml", "nes_ui_gtk", @@ -6,6 +8,7 @@ members = [ "nes_ui_native_windows", "plastic_core", ] +default-members = ["nes_ui_sfml"] [profile.dev] opt-level = 2 @@ -13,5 +16,3 @@ opt-level = 2 [profile.release] opt-level = 3 - - diff --git a/nes_ui_gtk/Cargo.toml b/nes_ui_gtk/Cargo.toml index 85e6059..690ff39 100644 --- a/nes_ui_gtk/Cargo.toml +++ b/nes_ui_gtk/Cargo.toml @@ -2,7 +2,7 @@ name = "nes_ui_gtk" version = "0.1.0" authors = ["Amjad Alsharafi "] -edition = "2018" +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/nes_ui_gtk/src/ui.rs b/nes_ui_gtk/src/ui.rs index 5501b71..4f7666a 100644 --- a/nes_ui_gtk/src/ui.rs +++ b/nes_ui_gtk/src/ui.rs @@ -320,13 +320,13 @@ impl UiProvider for GtkProvider { let label = format!("_{} {}", i + 1, if label { "saved" } else { "" }); - if let Some(item) = save_state_menu_list.get_children().get(i as usize) + if let Some(item) = save_state_menu_list.get_children().get(i ) { if let Some(item) = item.downcast_ref::() { item.set_label(&label); } } - if let Some(item) = load_state_menu_list.get_children().get(i as usize) + if let Some(item) = load_state_menu_list.get_children().get(i ) { if let Some(item) = item.downcast_ref::() { item.set_label(&label); diff --git a/nes_ui_native_windows/Cargo.toml b/nes_ui_native_windows/Cargo.toml index 2dfa8af..47bd132 100644 --- a/nes_ui_native_windows/Cargo.toml +++ b/nes_ui_native_windows/Cargo.toml @@ -2,7 +2,7 @@ name = "nes_ui_native_windows" version = "0.1.0" authors = ["Amjad Alsharafi "] -edition = "2018" +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/nes_ui_sfml/Cargo.toml b/nes_ui_sfml/Cargo.toml index 0eaa84c..a92b5ad 100644 --- a/nes_ui_sfml/Cargo.toml +++ b/nes_ui_sfml/Cargo.toml @@ -2,7 +2,7 @@ name = "nes_ui_sfml" version = "0.1.0" authors = ["Amjad Alsharafi "] -edition = "2018" +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/nes_ui_tui/Cargo.toml b/nes_ui_tui/Cargo.toml index 8c393ff..93e3c78 100644 --- a/nes_ui_tui/Cargo.toml +++ b/nes_ui_tui/Cargo.toml @@ -2,7 +2,7 @@ name = "nes_ui_tui" version = "0.1.0" authors = ["Amjad Alsharafi "] -edition = "2018" +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/nes_ui_tui/src/ui.rs b/nes_ui_tui/src/ui.rs index 7c240ee..c9a8518 100644 --- a/nes_ui_tui/src/ui.rs +++ b/nes_ui_tui/src/ui.rs @@ -38,7 +38,7 @@ impl Shape for ImageView { for x in 0..TV_WIDTH { for y in 0..TV_HEIGHT { - let index = ((TV_HEIGHT - y - 1) * TV_WIDTH + x) as usize; + let index = (TV_HEIGHT - y - 1) * TV_WIDTH + x; if let Some((x, y)) = painter.get_point(x as f64, y as f64) { let pixel = data.get(index * 4..(index + 1) * 4).unwrap(); painter.paint(x, y, Color::Rgb(pixel[0], pixel[1], pixel[2])); @@ -171,7 +171,7 @@ impl UiProvider for TuiProvider { active_gamepad = None; } } - keyboard_events.set_stopped_state(active_gamepad != None); + keyboard_events.set_stopped_state(active_gamepad.is_some()); if let Some(gamepad) = active_gamepad.map(|id| gilrs.gamepad(id)) { for (controller_button, nes_button) in &[ diff --git a/plastic_core/Cargo.toml b/plastic_core/Cargo.toml index 3e72a49..6e02eb3 100644 --- a/plastic_core/Cargo.toml +++ b/plastic_core/Cargo.toml @@ -2,7 +2,7 @@ name = "plastic_core" version = "0.1.0" authors = ["Amjad Alsharafi "] -edition = "2018" +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/plastic_core/src/apu2a03/apu2a03.rs b/plastic_core/src/apu2a03/apu2a03.rs deleted file mode 100644 index dd19f5c..0000000 --- a/plastic_core/src/apu2a03/apu2a03.rs +++ /dev/null @@ -1,629 +0,0 @@ -use super::apu2a03_registers::Register; -use super::channels::{Dmc, NoiseWave, SquarePulse, TriangleWave}; -use super::envelope::EnvelopedChannel; -use super::length_counter::LengthCountedChannel; -use super::tone_source::{APUChannel, APUChannelPlayer, BufferedChannel, TimedAPUChannel}; -use crate::common::{ - interconnection::{APUCPUConnection, CPUIrqProvider}, - save_state::{Savable, SaveError}, - CPU_FREQ, -}; -use serde::{Deserialize, Serialize}; -use std::cell::Cell; -use std::sync::{Arc, Mutex}; - -use rodio::DeviceTrait; - -// after how many apu clocks a sample should be recorded -// APU, is clocked on every CPU clock -const SAMPLES_EVERY_N_APU_CLOCK: f64 = CPU_FREQ / (super::SAMPLE_RATE as f64); - -mod buffered_channel_serde { - use super::BufferedChannel; - use serde::{ser::Error, Deserialize, Deserializer, Serialize, Serializer}; - use std::sync::{Arc, Mutex}; - - pub fn serialize( - value: &Arc>, - serializer: S, - ) -> Result - where - S: Serializer, - { - if let Ok(value) = value.lock() { - value.serialize(serializer) - } else { - Err(S::Error::custom("")) - } - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result>, D::Error> - where - D: Deserializer<'de>, - { - BufferedChannel::deserialize(deserializer).map(|channel| Arc::new(Mutex::new(channel))) - } -} - -#[derive(Serialize, Deserialize)] -pub struct APU2A03 { - square_pulse_1: LengthCountedChannel, - square_pulse_2: LengthCountedChannel, - triangle: LengthCountedChannel, - noise: LengthCountedChannel, - dmc: Dmc, - - #[serde(with = "buffered_channel_serde")] - buffered_channel: Arc>, - - is_4_step_squence_mode_hold_value: bool, - is_4_step_squence_mode: bool, - interrupt_inhibit_flag: bool, - - cycle: u16, - - wait_reset: i8, - - sample_counter: f64, - - offset: f64, - - interrupt_flag: Cell, - request_interrupt_flag_change: Cell, - - #[serde(skip)] - player: Option, -} - -impl APU2A03 { - pub fn new() -> Self { - let buffered_channel = Arc::new(Mutex::new(BufferedChannel::new())); - - Self { - square_pulse_1: LengthCountedChannel::new(SquarePulse::new(true)), - square_pulse_2: LengthCountedChannel::new(SquarePulse::new(false)), - - triangle: LengthCountedChannel::new(TriangleWave::new()), - - noise: LengthCountedChannel::new(NoiseWave::new()), - - dmc: Dmc::new(), - - buffered_channel: buffered_channel.clone(), - - is_4_step_squence_mode_hold_value: false, - is_4_step_squence_mode: false, - interrupt_inhibit_flag: false, - - cycle: 0, - - sample_counter: 0., - - offset: 0., - - wait_reset: 0, - - interrupt_flag: Cell::new(false), - request_interrupt_flag_change: Cell::new(false), - - player: Self::get_player(buffered_channel), - } - } - - fn get_player(channel: Arc>) -> Option { - let device = rodio::default_output_device()?; - - // bug in rodio, that it panics if the device does not support any format - // it is fixed now in github, not sure when is the release coming - let formats = device.supported_output_formats().ok()?; - if formats.count() > 0 { - let sink = rodio::Sink::new(&device); - - let low_pass_player = - rodio::source::Source::low_pass(APUChannelPlayer::from_clone(channel), 10000); - - sink.append(low_pass_player); - sink.set_volume(0.15); - - sink.pause(); - - Some(sink) - } else { - None - } - } - - pub(crate) fn read_register(&self, register: Register) -> u8 { - match register { - Register::Status => { - let sqr1_length_counter = - (self.square_pulse_1.length_counter().counter() != 0) as u8; - - let sqr2_length_counter = - (self.square_pulse_2.length_counter().counter() != 0) as u8; - - let triangle_length_counter = (self.triangle.length_counter().counter() != 0) as u8; - - let noise_length_counter = (self.noise.length_counter().counter() != 0) as u8; - - let dmc_active = self.dmc.sample_remaining_bytes_more_than_0() as u8; - let dmc_interrupt = self.dmc.get_irq_pin_state() as u8; - - let frame_interrupt = self.interrupt_flag.get() as u8; - self.interrupt_flag.set(false); - self.request_interrupt_flag_change.set(true); - - dmc_interrupt << 7 - | frame_interrupt << 6 - | dmc_active << 4 - | noise_length_counter << 3 - | triangle_length_counter << 2 - | sqr2_length_counter << 1 - | sqr1_length_counter - } - _ => { - // unreadable - 0 - } - } - } - - #[allow(clippy::identity_op)] - pub(crate) fn write_register(&mut self, register: Register, data: u8) { - match register { - Register::Pulse1_1 => { - let duty_cycle_index = data >> 6; - let volume = data & 0xF; - let use_volume = data & 0x10 != 0; - let halt = data & 0x20 != 0; - - self.square_pulse_1 - .channel_mut() - .set_duty_cycle_index(duty_cycle_index); - self.square_pulse_1 - .channel_mut() - .envelope_generator_mut() - .set_volume(volume, use_volume); - - self.square_pulse_1.length_counter_mut().set_halt(halt); - self.square_pulse_1 - .channel_mut() - .envelope_generator_mut() - .set_loop_flag(halt); - - self.square_pulse_1 - .channel_mut() - .envelope_generator_mut() - .set_start_flag(true); - } - Register::Pulse1_2 => { - // sweep - self.square_pulse_1.channel_mut().set_sweeper_data(data); - } - Register::Pulse1_3 => { - let period = self.square_pulse_1.channel().get_period(); - - // lower timer bits - self.square_pulse_1 - .channel_mut() - .set_period((period & 0xFF00) | data as u16); - } - Register::Pulse1_4 => { - self.square_pulse_1 - .length_counter_mut() - .reload_counter(data >> 3); - - let period = self.square_pulse_1.channel().get_period(); - - // high timer bits - self.square_pulse_1 - .channel_mut() - .set_period((period & 0xFF) | ((data as u16 & 0b111) << 8)); - - self.square_pulse_1 - .channel_mut() - .envelope_generator_mut() - .set_start_flag(true); - - // reset pulse - self.square_pulse_1.channel_mut().reset(); - } - Register::Pulse2_1 => { - let duty_cycle_index = data >> 6; - let volume = data & 0xF; - let use_volume = data & 0x10 != 0; - let halt = data & 0x20 != 0; - - self.square_pulse_2 - .channel_mut() - .set_duty_cycle_index(duty_cycle_index); - self.square_pulse_2 - .channel_mut() - .envelope_generator_mut() - .set_volume(volume, use_volume); - - self.square_pulse_2.length_counter_mut().set_halt(halt); - self.square_pulse_2 - .channel_mut() - .envelope_generator_mut() - .set_loop_flag(halt); - self.square_pulse_2 - .channel_mut() - .envelope_generator_mut() - .set_start_flag(true); - } - Register::Pulse2_2 => { - // sweep - self.square_pulse_2.channel_mut().set_sweeper_data(data); - } - Register::Pulse2_3 => { - let period = self.square_pulse_2.channel().get_period(); - - // lower timer bits - self.square_pulse_2 - .channel_mut() - .set_period((period & 0xFF00) | data as u16); - } - Register::Pulse2_4 => { - self.square_pulse_2 - .length_counter_mut() - .reload_counter(data >> 3); - - let period = self.square_pulse_2.channel().get_period(); - - // high timer bits - self.square_pulse_2 - .channel_mut() - .set_period((period & 0xFF) | ((data as u16 & 0b111) << 8)); - - self.square_pulse_2 - .channel_mut() - .envelope_generator_mut() - .set_start_flag(true); - - // reset pulse - self.square_pulse_2.channel_mut().reset(); - } - Register::Triangle1 => { - self.triangle - .channel_mut() - .set_linear_counter_reload_value(data & 0x7F); - self.triangle - .channel_mut() - .set_linear_counter_control_flag(data & 0x80 != 0); - - self.triangle - .length_counter_mut() - .set_halt(data & 0x80 != 0); - } - Register::Triangle2 => { - // unused - } - Register::Triangle3 => { - let period = self.triangle.channel().get_period(); - - // lower timer bits - self.triangle - .channel_mut() - .set_period((period & 0xFF00) | data as u16); - } - Register::Triangle4 => { - self.triangle.length_counter_mut().reload_counter(data >> 3); - - let period = self.triangle.channel().get_period(); - - // high timer bits - self.triangle - .channel_mut() - .set_period((period & 0xFF) | ((data as u16 & 0b111) << 8)); - - self.triangle - .channel_mut() - .set_linear_counter_reload_flag(true); - } - Register::Noise1 => { - let volume = data & 0xF; - let use_volume = data & 0x10 != 0; - let halt = data & 0x20 != 0; - - self.noise - .channel_mut() - .envelope_generator_mut() - .set_volume(volume, use_volume); - self.noise.length_counter_mut().set_halt(halt); - self.noise - .channel_mut() - .envelope_generator_mut() - .set_loop_flag(halt); - self.noise - .channel_mut() - .envelope_generator_mut() - .set_start_flag(true); - } - Register::Noise2 => { - // unused - } - Register::Noise3 => { - self.noise.channel_mut().set_mode_flag(data & 0x80 != 0); - self.noise.channel_mut().set_period(data & 0xF); - } - Register::Noise4 => { - self.noise.length_counter_mut().reload_counter(data >> 3); - } - Register::DMC1 => { - let rate_index = data & 0xF; - let loop_flag = data & 0x40 != 0; - let irq_enabled = data & 0x80 != 0; - - self.dmc.set_rate_index(rate_index); - self.dmc.set_loop_flag(loop_flag); - self.dmc.set_irq_enabled_flag(irq_enabled); - } - Register::DMC2 => { - self.dmc.set_direct_output_level_load(data & 0x7F); - } - Register::DMC3 => { - self.dmc.set_samples_address(data); - } - Register::DMC4 => { - self.dmc.set_samples_length(data); - } - Register::Status => { - // enable and disable length counters - self.square_pulse_1 - .length_counter_mut() - .set_enabled((data >> 0 & 1) != 0); - - self.square_pulse_2 - .length_counter_mut() - .set_enabled((data >> 1 & 1) != 0); - - self.triangle - .length_counter_mut() - .set_enabled((data >> 2 & 1) != 0); - - self.noise - .length_counter_mut() - .set_enabled((data >> 3 & 1) != 0); - - if data >> 4 & 1 == 0 { - self.dmc.clear_sample_remaining_bytes_and_silence(); - } else if !self.dmc.sample_remaining_bytes_more_than_0() { - self.dmc.restart_sample(); - } - - self.dmc.clear_interrupt_flag(); - } - Register::FrameCounter => { - self.is_4_step_squence_mode_hold_value = data & 0x80 == 0; - self.interrupt_inhibit_flag = data & 0x40 != 0; - - if self.interrupt_inhibit_flag { - self.interrupt_flag.set(false); - self.request_interrupt_flag_change.set(true); - } - - self.wait_reset = if self.cycle % 2 == 0 { 4 } else { 3 }; - } - } - } - - pub fn play(&self) { - if let Some(ref player) = self.player { - player.play(); - } - } - - pub fn pause(&self) { - if let Some(ref player) = self.player { - player.pause(); - } - } - - fn generate_quarter_frame_clock(&mut self) { - self.square_pulse_1.clock_envlope(); - self.square_pulse_2.clock_envlope(); - self.noise.clock_envlope(); - self.triangle.channel_mut().clock_linear_counter(); - } - - fn generate_half_frame_clock(&mut self) { - self.square_pulse_1.length_counter_mut().decrement(); - self.square_pulse_1.channel_mut().clock_sweeper(); - self.square_pulse_2.length_counter_mut().decrement(); - self.square_pulse_2.channel_mut().clock_sweeper(); - self.triangle.length_counter_mut().decrement(); - self.noise.length_counter_mut().decrement(); - } - - fn update_irq_pin(&mut self) { - if !self.interrupt_inhibit_flag { - self.interrupt_flag.set(true); - self.request_interrupt_flag_change.set(true); - } - } - - fn get_mixer_output(&mut self) -> f32 { - let square_pulse_1 = self.square_pulse_1.get_output(); - let square_pulse_2 = self.square_pulse_2.get_output(); - let triangle = self.triangle.get_output(); - let noise = self.noise.get_output(); - let dmc = self.dmc.get_output(); - - let pulse_out = if square_pulse_1 == 0. && square_pulse_2 == 0. { - 0. - } else { - 95.88 / ((8128. / (square_pulse_1 + square_pulse_2)) + 100.) - }; - - let tnd_out = if triangle == 0. && noise == 0. && dmc == 0. { - 0. - } else { - 159.79 / ((1. / ((triangle / 8227.) + (noise / 12241.) + (dmc / 22638.))) + 100.) - }; - - pulse_out + tnd_out - } - - pub fn empty_queue(&mut self) { - if let Ok(mut buffer) = self.buffered_channel.lock() { - buffer.clear_buffer(); - } - } - - /// clock the APU **at** CPU clock rate, the clocks are handled correctly - /// as it should be - pub fn clock(&mut self) { - match self.wait_reset.cmp(&0) { - std::cmp::Ordering::Less => {} - std::cmp::Ordering::Equal => { - self.cycle = 0; - self.wait_reset = -1; - - self.is_4_step_squence_mode = self.is_4_step_squence_mode_hold_value; - - if !self.is_4_step_squence_mode { - self.generate_quarter_frame_clock(); - self.generate_half_frame_clock(); - } - } - std::cmp::Ordering::Greater => self.wait_reset -= 1, - } - - // after how many apu clocks a sample should be recorded - let samples_every_n_apu_clock = SAMPLES_EVERY_N_APU_CLOCK + self.offset; - - self.sample_counter += 1.; - if self.sample_counter >= samples_every_n_apu_clock { - let output = self.get_mixer_output(); - - if let Ok(mut buffered_channel) = self.buffered_channel.lock() { - buffered_channel.recored_sample(output); - - // check for needed change in offset - let change = if buffered_channel.get_is_overusing() { - -0.001 - } else if buffered_channel.get_is_underusing() { - 0.001 - } else { - 0. - }; - - self.offset += change; - buffered_channel.clear_using_flags(); - } - - self.sample_counter -= samples_every_n_apu_clock; - } - - // clocked on every CPU cycle - self.triangle.timer_clock(); - - if self.cycle % 2 == 0 { - self.square_pulse_1.timer_clock(); - self.square_pulse_2.timer_clock(); - self.noise.timer_clock(); - self.dmc.timer_clock(); - } - - self.cycle += 1; - - // this is clocked in every CPU cycle, so the numbers are multiplied by 2 - match self.cycle { - 7455 => { - self.generate_quarter_frame_clock(); - } - 14913 => { - self.generate_quarter_frame_clock(); - self.generate_half_frame_clock(); - } - 22371 => { - self.generate_quarter_frame_clock(); - } - 29828 if self.is_4_step_squence_mode => { - self.update_irq_pin(); - } - 29829 if self.is_4_step_squence_mode => { - self.generate_quarter_frame_clock(); - self.generate_half_frame_clock(); - - self.update_irq_pin(); - } - 29830 if self.is_4_step_squence_mode => { - self.update_irq_pin(); - - self.cycle = 0; - } - 37281 if !self.is_4_step_squence_mode => { - self.generate_quarter_frame_clock(); - self.generate_half_frame_clock(); - } - 37282 if !self.is_4_step_squence_mode => { - self.cycle = 0; - } - _ => { - // ignore - } - } - } -} - -impl CPUIrqProvider for APU2A03 { - fn is_irq_change_requested(&self) -> bool { - let dmc_irq_request = self.dmc.is_irq_change_requested(); - - self.request_interrupt_flag_change.get() || dmc_irq_request - } - - fn irq_pin_state(&self) -> bool { - let dmc_irq = self.dmc.get_irq_pin_state(); - - self.interrupt_flag.get() || dmc_irq - } - - fn clear_irq_request_pin(&mut self) { - self.request_interrupt_flag_change.set(false); - - self.dmc.clear_irq_request_pin(); - } -} - -impl Default for APU2A03 { - fn default() -> Self { - Self::new() - } -} - -impl APUCPUConnection for APU2A03 { - fn request_dmc_reader_read(&self) -> Option { - self.dmc.request_dmc_reader_read() - } - - fn submit_dmc_buffer_byte(&mut self, byte: u8) { - self.dmc.submit_buffer_byte(byte); - } -} - -impl Savable for APU2A03 { - fn save(&self, writer: &mut W) -> Result<(), SaveError> { - bincode::serialize_into(writer, self).map_err(|err| match *err { - bincode::ErrorKind::Io(err) => SaveError::IoError(err), - _ => SaveError::Others, - })?; - - Ok(()) - } - - fn load(&mut self, reader: &mut R) -> Result<(), SaveError> { - let state: APU2A03 = bincode::deserialize_from(reader).map_err(|err| match *err { - bincode::ErrorKind::Io(err) => SaveError::IoError(err), - _ => SaveError::Others, - })?; - - let _ = std::mem::replace(self, state); - - self.player = Self::get_player(self.buffered_channel.clone()); - - Ok(()) - } -} diff --git a/plastic_core/src/apu2a03/apu2a03_registers.rs b/plastic_core/src/apu2a03/apu2a03_registers.rs index 2a4ea42..e3528bf 100644 --- a/plastic_core/src/apu2a03/apu2a03_registers.rs +++ b/plastic_core/src/apu2a03/apu2a03_registers.rs @@ -38,7 +38,7 @@ memory_mapped_registers! { impl Bus for APU2A03 { fn read(&self, address: u16, device: Device) -> u8 { // only the CPU is allowed to read from PPU registers - if device == Device::CPU { + if device == Device::Cpu { if let Ok(register) = address.try_into() { self.read_register(register) } else { @@ -51,7 +51,7 @@ impl Bus for APU2A03 { fn write(&mut self, address: u16, data: u8, device: Device) { // only the CPU is allowed to write to PPU registers - if device == Device::CPU { + if device == Device::Cpu { if let Ok(register) = address.try_into() { self.write_register(register, data); } else { diff --git a/plastic_core/src/apu2a03/mod.rs b/plastic_core/src/apu2a03/mod.rs index 35b83d2..4e68bca 100644 --- a/plastic_core/src/apu2a03/mod.rs +++ b/plastic_core/src/apu2a03/mod.rs @@ -1,4 +1,3 @@ -mod apu2a03; mod apu2a03_registers; mod channels; mod envelope; @@ -6,7 +5,635 @@ mod length_counter; mod sequencer; mod tone_source; -pub use apu2a03::APU2A03; +use crate::common::{ + interconnection::{APUCPUConnection, CPUIrqProvider}, + save_state::{Savable, SaveError}, + CPU_FREQ, +}; +use apu2a03_registers::Register; +use channels::{Dmc, NoiseWave, SquarePulse, TriangleWave}; +use envelope::EnvelopedChannel; +use length_counter::LengthCountedChannel; +use serde::{Deserialize, Serialize}; +use std::cell::Cell; +use std::sync::{Arc, Mutex}; +use tone_source::{APUChannel, APUChannelPlayer, BufferedChannel, TimedAPUChannel}; + +use rodio::DeviceTrait; // for performance pub const SAMPLE_RATE: u32 = 22050; + +// after how many apu clocks a sample should be recorded +// APU, is clocked on every CPU clock +const SAMPLES_EVERY_N_APU_CLOCK: f64 = CPU_FREQ / (SAMPLE_RATE as f64); + +mod buffered_channel_serde { + use super::BufferedChannel; + use serde::{ser::Error, Deserialize, Deserializer, Serialize, Serializer}; + use std::sync::{Arc, Mutex}; + + pub fn serialize( + value: &Arc>, + serializer: S, + ) -> Result + where + S: Serializer, + { + if let Ok(value) = value.lock() { + value.serialize(serializer) + } else { + Err(S::Error::custom("")) + } + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result>, D::Error> + where + D: Deserializer<'de>, + { + BufferedChannel::deserialize(deserializer).map(|channel| Arc::new(Mutex::new(channel))) + } +} + +#[derive(Serialize, Deserialize)] +pub struct APU2A03 { + square_pulse_1: LengthCountedChannel, + square_pulse_2: LengthCountedChannel, + triangle: LengthCountedChannel, + noise: LengthCountedChannel, + dmc: Dmc, + + #[serde(with = "buffered_channel_serde")] + buffered_channel: Arc>, + + is_4_step_squence_mode_hold_value: bool, + is_4_step_squence_mode: bool, + interrupt_inhibit_flag: bool, + + cycle: u16, + + wait_reset: i8, + + sample_counter: f64, + + offset: f64, + + interrupt_flag: Cell, + request_interrupt_flag_change: Cell, + + #[serde(skip)] + player: Option, +} + +impl APU2A03 { + pub fn new() -> Self { + let buffered_channel = Arc::new(Mutex::new(BufferedChannel::new())); + + Self { + square_pulse_1: LengthCountedChannel::new(SquarePulse::new(true)), + square_pulse_2: LengthCountedChannel::new(SquarePulse::new(false)), + + triangle: LengthCountedChannel::new(TriangleWave::new()), + + noise: LengthCountedChannel::new(NoiseWave::new()), + + dmc: Dmc::new(), + + buffered_channel: buffered_channel.clone(), + + is_4_step_squence_mode_hold_value: false, + is_4_step_squence_mode: false, + interrupt_inhibit_flag: false, + + cycle: 0, + + sample_counter: 0., + + offset: 0., + + wait_reset: 0, + + interrupt_flag: Cell::new(false), + request_interrupt_flag_change: Cell::new(false), + + player: Self::get_player(buffered_channel), + } + } + + fn get_player(channel: Arc>) -> Option { + let device = rodio::default_output_device()?; + + // bug in rodio, that it panics if the device does not support any format + // it is fixed now in github, not sure when is the release coming + let formats = device.supported_output_formats().ok()?; + if formats.count() > 0 { + let sink = rodio::Sink::new(&device); + + let low_pass_player = + rodio::source::Source::low_pass(APUChannelPlayer::from_clone(channel), 10000); + + sink.append(low_pass_player); + sink.set_volume(0.15); + + sink.pause(); + + Some(sink) + } else { + None + } + } + + pub(crate) fn read_register(&self, register: Register) -> u8 { + match register { + Register::Status => { + let sqr1_length_counter = + (self.square_pulse_1.length_counter().counter() != 0) as u8; + + let sqr2_length_counter = + (self.square_pulse_2.length_counter().counter() != 0) as u8; + + let triangle_length_counter = (self.triangle.length_counter().counter() != 0) as u8; + + let noise_length_counter = (self.noise.length_counter().counter() != 0) as u8; + + let dmc_active = self.dmc.sample_remaining_bytes_more_than_0() as u8; + let dmc_interrupt = self.dmc.get_irq_pin_state() as u8; + + let frame_interrupt = self.interrupt_flag.get() as u8; + self.interrupt_flag.set(false); + self.request_interrupt_flag_change.set(true); + + dmc_interrupt << 7 + | frame_interrupt << 6 + | dmc_active << 4 + | noise_length_counter << 3 + | triangle_length_counter << 2 + | sqr2_length_counter << 1 + | sqr1_length_counter + } + _ => { + // unreadable + 0 + } + } + } + + #[allow(clippy::identity_op)] + pub(crate) fn write_register(&mut self, register: Register, data: u8) { + match register { + Register::Pulse1_1 => { + let duty_cycle_index = data >> 6; + let volume = data & 0xF; + let use_volume = data & 0x10 != 0; + let halt = data & 0x20 != 0; + + self.square_pulse_1 + .channel_mut() + .set_duty_cycle_index(duty_cycle_index); + self.square_pulse_1 + .channel_mut() + .envelope_generator_mut() + .set_volume(volume, use_volume); + + self.square_pulse_1.length_counter_mut().set_halt(halt); + self.square_pulse_1 + .channel_mut() + .envelope_generator_mut() + .set_loop_flag(halt); + + self.square_pulse_1 + .channel_mut() + .envelope_generator_mut() + .set_start_flag(true); + } + Register::Pulse1_2 => { + // sweep + self.square_pulse_1.channel_mut().set_sweeper_data(data); + } + Register::Pulse1_3 => { + let period = self.square_pulse_1.channel().get_period(); + + // lower timer bits + self.square_pulse_1 + .channel_mut() + .set_period((period & 0xFF00) | data as u16); + } + Register::Pulse1_4 => { + self.square_pulse_1 + .length_counter_mut() + .reload_counter(data >> 3); + + let period = self.square_pulse_1.channel().get_period(); + + // high timer bits + self.square_pulse_1 + .channel_mut() + .set_period((period & 0xFF) | ((data as u16 & 0b111) << 8)); + + self.square_pulse_1 + .channel_mut() + .envelope_generator_mut() + .set_start_flag(true); + + // reset pulse + self.square_pulse_1.channel_mut().reset(); + } + Register::Pulse2_1 => { + let duty_cycle_index = data >> 6; + let volume = data & 0xF; + let use_volume = data & 0x10 != 0; + let halt = data & 0x20 != 0; + + self.square_pulse_2 + .channel_mut() + .set_duty_cycle_index(duty_cycle_index); + self.square_pulse_2 + .channel_mut() + .envelope_generator_mut() + .set_volume(volume, use_volume); + + self.square_pulse_2.length_counter_mut().set_halt(halt); + self.square_pulse_2 + .channel_mut() + .envelope_generator_mut() + .set_loop_flag(halt); + self.square_pulse_2 + .channel_mut() + .envelope_generator_mut() + .set_start_flag(true); + } + Register::Pulse2_2 => { + // sweep + self.square_pulse_2.channel_mut().set_sweeper_data(data); + } + Register::Pulse2_3 => { + let period = self.square_pulse_2.channel().get_period(); + + // lower timer bits + self.square_pulse_2 + .channel_mut() + .set_period((period & 0xFF00) | data as u16); + } + Register::Pulse2_4 => { + self.square_pulse_2 + .length_counter_mut() + .reload_counter(data >> 3); + + let period = self.square_pulse_2.channel().get_period(); + + // high timer bits + self.square_pulse_2 + .channel_mut() + .set_period((period & 0xFF) | ((data as u16 & 0b111) << 8)); + + self.square_pulse_2 + .channel_mut() + .envelope_generator_mut() + .set_start_flag(true); + + // reset pulse + self.square_pulse_2.channel_mut().reset(); + } + Register::Triangle1 => { + self.triangle + .channel_mut() + .set_linear_counter_reload_value(data & 0x7F); + self.triangle + .channel_mut() + .set_linear_counter_control_flag(data & 0x80 != 0); + + self.triangle + .length_counter_mut() + .set_halt(data & 0x80 != 0); + } + Register::Triangle2 => { + // unused + } + Register::Triangle3 => { + let period = self.triangle.channel().get_period(); + + // lower timer bits + self.triangle + .channel_mut() + .set_period((period & 0xFF00) | data as u16); + } + Register::Triangle4 => { + self.triangle.length_counter_mut().reload_counter(data >> 3); + + let period = self.triangle.channel().get_period(); + + // high timer bits + self.triangle + .channel_mut() + .set_period((period & 0xFF) | ((data as u16 & 0b111) << 8)); + + self.triangle + .channel_mut() + .set_linear_counter_reload_flag(true); + } + Register::Noise1 => { + let volume = data & 0xF; + let use_volume = data & 0x10 != 0; + let halt = data & 0x20 != 0; + + self.noise + .channel_mut() + .envelope_generator_mut() + .set_volume(volume, use_volume); + self.noise.length_counter_mut().set_halt(halt); + self.noise + .channel_mut() + .envelope_generator_mut() + .set_loop_flag(halt); + self.noise + .channel_mut() + .envelope_generator_mut() + .set_start_flag(true); + } + Register::Noise2 => { + // unused + } + Register::Noise3 => { + self.noise.channel_mut().set_mode_flag(data & 0x80 != 0); + self.noise.channel_mut().set_period(data & 0xF); + } + Register::Noise4 => { + self.noise.length_counter_mut().reload_counter(data >> 3); + } + Register::DMC1 => { + let rate_index = data & 0xF; + let loop_flag = data & 0x40 != 0; + let irq_enabled = data & 0x80 != 0; + + self.dmc.set_rate_index(rate_index); + self.dmc.set_loop_flag(loop_flag); + self.dmc.set_irq_enabled_flag(irq_enabled); + } + Register::DMC2 => { + self.dmc.set_direct_output_level_load(data & 0x7F); + } + Register::DMC3 => { + self.dmc.set_samples_address(data); + } + Register::DMC4 => { + self.dmc.set_samples_length(data); + } + Register::Status => { + // enable and disable length counters + self.square_pulse_1 + .length_counter_mut() + .set_enabled((data >> 0 & 1) != 0); + + self.square_pulse_2 + .length_counter_mut() + .set_enabled((data >> 1 & 1) != 0); + + self.triangle + .length_counter_mut() + .set_enabled((data >> 2 & 1) != 0); + + self.noise + .length_counter_mut() + .set_enabled((data >> 3 & 1) != 0); + + if data >> 4 & 1 == 0 { + self.dmc.clear_sample_remaining_bytes_and_silence(); + } else if !self.dmc.sample_remaining_bytes_more_than_0() { + self.dmc.restart_sample(); + } + + self.dmc.clear_interrupt_flag(); + } + Register::FrameCounter => { + self.is_4_step_squence_mode_hold_value = data & 0x80 == 0; + self.interrupt_inhibit_flag = data & 0x40 != 0; + + if self.interrupt_inhibit_flag { + self.interrupt_flag.set(false); + self.request_interrupt_flag_change.set(true); + } + + self.wait_reset = if self.cycle % 2 == 0 { 4 } else { 3 }; + } + } + } + + pub fn play(&self) { + if let Some(ref player) = self.player { + player.play(); + } + } + + pub fn pause(&self) { + if let Some(ref player) = self.player { + player.pause(); + } + } + + fn generate_quarter_frame_clock(&mut self) { + self.square_pulse_1.clock_envlope(); + self.square_pulse_2.clock_envlope(); + self.noise.clock_envlope(); + self.triangle.channel_mut().clock_linear_counter(); + } + + fn generate_half_frame_clock(&mut self) { + self.square_pulse_1.length_counter_mut().decrement(); + self.square_pulse_1.channel_mut().clock_sweeper(); + self.square_pulse_2.length_counter_mut().decrement(); + self.square_pulse_2.channel_mut().clock_sweeper(); + self.triangle.length_counter_mut().decrement(); + self.noise.length_counter_mut().decrement(); + } + + fn update_irq_pin(&mut self) { + if !self.interrupt_inhibit_flag { + self.interrupt_flag.set(true); + self.request_interrupt_flag_change.set(true); + } + } + + fn get_mixer_output(&mut self) -> f32 { + let square_pulse_1 = self.square_pulse_1.get_output(); + let square_pulse_2 = self.square_pulse_2.get_output(); + let triangle = self.triangle.get_output(); + let noise = self.noise.get_output(); + let dmc = self.dmc.get_output(); + + let pulse_out = if square_pulse_1 == 0. && square_pulse_2 == 0. { + 0. + } else { + 95.88 / ((8128. / (square_pulse_1 + square_pulse_2)) + 100.) + }; + + let tnd_out = if triangle == 0. && noise == 0. && dmc == 0. { + 0. + } else { + 159.79 / ((1. / ((triangle / 8227.) + (noise / 12241.) + (dmc / 22638.))) + 100.) + }; + + pulse_out + tnd_out + } + + pub fn empty_queue(&mut self) { + if let Ok(mut buffer) = self.buffered_channel.lock() { + buffer.clear_buffer(); + } + } + + /// clock the APU **at** CPU clock rate, the clocks are handled correctly + /// as it should be + pub fn clock(&mut self) { + match self.wait_reset.cmp(&0) { + std::cmp::Ordering::Less => {} + std::cmp::Ordering::Equal => { + self.cycle = 0; + self.wait_reset = -1; + + self.is_4_step_squence_mode = self.is_4_step_squence_mode_hold_value; + + if !self.is_4_step_squence_mode { + self.generate_quarter_frame_clock(); + self.generate_half_frame_clock(); + } + } + std::cmp::Ordering::Greater => self.wait_reset -= 1, + } + + // after how many apu clocks a sample should be recorded + let samples_every_n_apu_clock = SAMPLES_EVERY_N_APU_CLOCK + self.offset; + + self.sample_counter += 1.; + if self.sample_counter >= samples_every_n_apu_clock { + let output = self.get_mixer_output(); + + if let Ok(mut buffered_channel) = self.buffered_channel.lock() { + buffered_channel.recored_sample(output); + + // check for needed change in offset + let change = if buffered_channel.get_is_overusing() { + -0.001 + } else if buffered_channel.get_is_underusing() { + 0.001 + } else { + 0. + }; + + self.offset += change; + buffered_channel.clear_using_flags(); + } + + self.sample_counter -= samples_every_n_apu_clock; + } + + // clocked on every CPU cycle + self.triangle.timer_clock(); + + if self.cycle % 2 == 0 { + self.square_pulse_1.timer_clock(); + self.square_pulse_2.timer_clock(); + self.noise.timer_clock(); + self.dmc.timer_clock(); + } + + self.cycle += 1; + + // this is clocked in every CPU cycle, so the numbers are multiplied by 2 + match self.cycle { + 7455 => { + self.generate_quarter_frame_clock(); + } + 14913 => { + self.generate_quarter_frame_clock(); + self.generate_half_frame_clock(); + } + 22371 => { + self.generate_quarter_frame_clock(); + } + 29828 if self.is_4_step_squence_mode => { + self.update_irq_pin(); + } + 29829 if self.is_4_step_squence_mode => { + self.generate_quarter_frame_clock(); + self.generate_half_frame_clock(); + + self.update_irq_pin(); + } + 29830 if self.is_4_step_squence_mode => { + self.update_irq_pin(); + + self.cycle = 0; + } + 37281 if !self.is_4_step_squence_mode => { + self.generate_quarter_frame_clock(); + self.generate_half_frame_clock(); + } + 37282 if !self.is_4_step_squence_mode => { + self.cycle = 0; + } + _ => { + // ignore + } + } + } +} + +impl CPUIrqProvider for APU2A03 { + fn is_irq_change_requested(&self) -> bool { + let dmc_irq_request = self.dmc.is_irq_change_requested(); + + self.request_interrupt_flag_change.get() || dmc_irq_request + } + + fn irq_pin_state(&self) -> bool { + let dmc_irq = self.dmc.get_irq_pin_state(); + + self.interrupt_flag.get() || dmc_irq + } + + fn clear_irq_request_pin(&mut self) { + self.request_interrupt_flag_change.set(false); + + self.dmc.clear_irq_request_pin(); + } +} + +impl Default for APU2A03 { + fn default() -> Self { + Self::new() + } +} + +impl APUCPUConnection for APU2A03 { + fn request_dmc_reader_read(&self) -> Option { + self.dmc.request_dmc_reader_read() + } + + fn submit_dmc_buffer_byte(&mut self, byte: u8) { + self.dmc.submit_buffer_byte(byte); + } +} + +impl Savable for APU2A03 { + fn save(&self, writer: &mut W) -> Result<(), SaveError> { + bincode::serialize_into(writer, self).map_err(|err| match *err { + bincode::ErrorKind::Io(err) => SaveError::IoError(err), + _ => SaveError::Others, + })?; + + Ok(()) + } + + fn load(&mut self, reader: &mut R) -> Result<(), SaveError> { + let state: APU2A03 = bincode::deserialize_from(reader).map_err(|err| match *err { + bincode::ErrorKind::Io(err) => SaveError::IoError(err), + _ => SaveError::Others, + })?; + + let _ = std::mem::replace(self, state); + + self.player = Self::get_player(self.buffered_channel.clone()); + + Ok(()) + } +} diff --git a/plastic_core/src/apu2a03/tone_source.rs b/plastic_core/src/apu2a03/tone_source.rs index 1589d7c..a21ba37 100644 --- a/plastic_core/src/apu2a03/tone_source.rs +++ b/plastic_core/src/apu2a03/tone_source.rs @@ -67,10 +67,6 @@ impl BufferedChannel { pub fn clear_buffer(&mut self) { self.buffer.clear(); } - - pub fn len(&self) -> usize { - self.buffer.len() - } } impl APUChannel for BufferedChannel { diff --git a/plastic_core/src/cartridge/cartridge.rs b/plastic_core/src/cartridge/cartridge.rs deleted file mode 100644 index db07e93..0000000 --- a/plastic_core/src/cartridge/cartridge.rs +++ /dev/null @@ -1,503 +0,0 @@ -use super::{ - error::{CartridgeError, SramError}, - mapper::{Mapper, MappingResult}, - mappers::*, -}; -use crate::common::{ - interconnection::CPUIrqProvider, - save_state::{Savable, SaveError}, - Bus, Device, MirroringMode, MirroringProvider, -}; -use std::{ - fs::File, - io::{Read, Seek, SeekFrom, Write}, - path::Path, -}; - -struct INesHeader { - // in 16kb units - prg_rom_size: u16, - // in 8kb units - chr_rom_size: u16, - is_chr_ram: bool, - hardwired_mirroring_vertical: bool, - has_prg_ram_battery: bool, - contain_trainer_data: bool, - use_hardwaired_4_screen_mirroring: bool, - mapper_id: u16, - submapper_id: u8, - prg_wram_size: u32, - prg_sram_size: u32, - chr_wram_size: u32, - chr_sram_size: u32, -} - -impl INesHeader { - fn from_bytes(mut header: [u8; 16]) -> Result { - // decode header - Self::check_magic(&header[0..4])?; - - let prg_size_low = header[4] as u16; - let chr_size_low = header[5] as u16; - let is_chr_ram = chr_size_low == 0; - - let hardwired_mirroring_vertical = header[6] & 1 != 0; - header[6] >>= 1; - let has_prg_ram_battery = header[6] & 1 != 0; - header[6] >>= 1; - let contain_trainer_data = header[6] & 1 != 0; - header[6] >>= 1; - let use_hardwaired_4_screen_mirroring = header[6] & 1 != 0; - header[6] >>= 1; - let mapper_id_low = (header[6] & 0xF) as u16; - - let mut console_type = header[7] & 0x3; - header[7] >>= 2; - let ines_2_ident = header[7] & 0x3; - header[7] >>= 2; - let mut mapper_id_middle = (header[7] & 0xF) as u16; - - if ines_2_ident == 0 { - let mut is_archaic_ines = false; - for item in header.iter().skip(12) { - if *item != 0 { - is_archaic_ines = true; - } - } - - let prg_ram_size; - - if !is_archaic_ines { - prg_ram_size = if header[8] == 0 { 1 } else { header[8] }; - let ntcs_tv_system = header[9] & 1 == 0; - - if header[9] >> 1 != 0 { - return Err(CartridgeError::HeaderError); - } - - let is_prg_ram_present = (header[10] >> 4) & 1 == 0; - let board_has_bus_conflict = (header[10] >> 5) & 1 != 0; - } else { - // ignore `header[7]` data - console_type = 0; - mapper_id_middle = 0; - - prg_ram_size = 1; - } - - Ok(Self { - prg_rom_size: prg_size_low, - chr_rom_size: chr_size_low, - is_chr_ram, - hardwired_mirroring_vertical, - has_prg_ram_battery, - contain_trainer_data, - use_hardwaired_4_screen_mirroring, - mapper_id: mapper_id_middle << 4 | mapper_id_low, - submapper_id: 0, - prg_wram_size: prg_ram_size as u32 * 0x2000, - prg_sram_size: prg_ram_size as u32 * 0x2000, - chr_wram_size: 0x2000, // can only use 8kb - chr_sram_size: 0x2000, - }) - } else { - let mapper_id_high = (header[8] & 0xF) as u16; - header[8] >>= 4; - let submapper_id = header[8] & 0xF; - - let prg_size_high = (header[9] & 0xF) as u16; - let chr_size_high = ((header[9] >> 4) & 0xF) as u16; - - let shift_size = (header[10] & 0xF) as u32; - let prg_wram_size_bytes = if shift_size != 0 { 64 << shift_size } else { 0 }; - header[10] >>= 4; - let shift_size = (header[10] & 0xF) as u32; - let prg_sram_size_bytes = if shift_size != 0 { 64 << shift_size } else { 0 }; - - let shift_size = (header[11] & 0xF) as u32; - let chr_wram_size_bytes = if shift_size != 0 { 64 << shift_size } else { 0 }; - header[11] >>= 4; - let shift_size = (header[11] & 0xF) as u32; - let chr_sram_size_bytes = if shift_size != 0 { 64 << shift_size } else { 0 }; - - // TODO: implement the rest - - Ok(Self { - prg_rom_size: prg_size_high << 8 | prg_size_low, - chr_rom_size: chr_size_high << 8 | chr_size_low, - is_chr_ram, - hardwired_mirroring_vertical, - has_prg_ram_battery, - contain_trainer_data, - use_hardwaired_4_screen_mirroring, - mapper_id: mapper_id_high << 8 | mapper_id_middle << 4 | mapper_id_low, - submapper_id, - prg_wram_size: prg_wram_size_bytes, - prg_sram_size: prg_sram_size_bytes, - chr_wram_size: chr_wram_size_bytes, - chr_sram_size: chr_sram_size_bytes, - }) - } - } - - fn empty() -> Self { - Self::from_bytes([0x4E, 0x45, 0x53, 0x1A, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).unwrap() - } - - fn check_magic(header: &[u8]) -> Result<(), CartridgeError> { - let real = [0x4E, 0x45, 0x53, 0x1A]; - - if header == real { - Ok(()) - } else { - Err(CartridgeError::HeaderError) - } - } -} - -pub struct Cartridge { - file_path: Box, - header: INesHeader, - - _trainer_data: Vec, - pub(crate) prg_data: Vec, - pub(crate) chr_data: Vec, - prg_ram_data: Vec, - - mapper: Box, - - is_empty: bool, -} - -impl Cartridge { - // TODO: not sure if it should consume the file or not - pub fn from_file>(file_path: P) -> Result { - if let Some(extension) = file_path.as_ref().extension() { - if extension == "nes" { - let mut file = File::open(file_path.as_ref())?; - - let mut header = [0; 16]; - file.read_exact(&mut header)?; - - // decode header - let header = INesHeader::from_bytes(header)?; - - let sram_data = if header.has_prg_ram_battery { - // try to load old save data - if let Ok(data) = - Self::load_sram_file(file_path.as_ref(), header.prg_sram_size as usize) - { - data - } else { - vec![0; header.prg_sram_size as usize] - } - } else { - vec![0; header.prg_wram_size as usize] - }; - - println!("mapper {}", header.mapper_id); - - // initialize the mapper first, so that if it is not supported yet, - // panic - let mapper = Self::get_mapper(&header)?; - - let mut trainer_data = Vec::new(); - - // read training data if present - if header.contain_trainer_data { - trainer_data.resize(512, 0); - file.read_exact(&mut trainer_data)?; - } - - // read PRG data - let mut prg_data = vec![0; (header.prg_rom_size as usize) * 16 * 1024]; - file.read_exact(&mut prg_data)?; - - // read CHR data - let chr_data = if !header.is_chr_ram { - let mut data = vec![0; (header.chr_rom_size as usize) * 8 * 1024]; - file.read_exact(&mut data)?; - - data - } else { - // TODO: there is no way of knowing if we are using CHR WRAM or SRAM - let ram_size = header.chr_wram_size; - - vec![0; ram_size as usize] - }; - - // there are missing parts - let current = file.seek(SeekFrom::Current(0))?; - let end = file.seek(SeekFrom::End(0))?; - if current != end { - Err(CartridgeError::TooLargeFile(end - current)) - } else { - Ok(Self { - file_path: file_path.as_ref().to_path_buf().into_boxed_path(), - header, - _trainer_data: trainer_data, - prg_data, - chr_data, - prg_ram_data: sram_data, - mapper, - - is_empty: false, - }) - } - } else { - Err(CartridgeError::ExtensionError) - } - } else { - Err(CartridgeError::ExtensionError) - } - } - - pub fn new_without_file() -> Self { - Self { - // should not be used - file_path: Path::new("").to_path_buf().into_boxed_path(), - header: INesHeader::empty(), - _trainer_data: Vec::new(), - prg_data: Vec::new(), - chr_data: Vec::new(), - prg_ram_data: Vec::new(), - mapper: Box::new(Mapper0::new()), - - is_empty: true, - } - } - - fn get_mapper(header: &INesHeader) -> Result, CartridgeError> { - let mut mapper: Box = match header.mapper_id { - 0 => Box::new(Mapper0::new()), - 1 => Box::new(Mapper1::new()), - 2 => Box::new(Mapper2::new()), - 3 => Box::new(Mapper3::new()), - 4 => Box::new(Mapper4::new()), - 7 => Box::new(Mapper7::new()), - 9 => Box::new(Mapper9::new()), - 10 => Box::new(Mapper10::new()), - 11 => Box::new(Mapper11::new()), - 12 => Box::new(Mapper12::new()), - 66 => Box::new(Mapper66::new()), - _ => { - return Err(CartridgeError::MapperNotImplemented(header.mapper_id)); - } - }; - - // FIXME: fix parameters types to support INES2.0 - // should always call init in a new mapper, as it is the only way - // they share a constructor - mapper.init( - header.prg_rom_size as u8, - header.is_chr_ram, - if !header.is_chr_ram { - header.chr_rom_size as u8 - } else { - (header.chr_wram_size / 0x2000) as u8 - }, - if header.has_prg_ram_battery { - header.prg_sram_size / 0x2000 - } else { - header.prg_wram_size / 0x2000 - } as u8, - ); - - Ok(mapper) - } - - fn load_sram_file>(path: P, sram_size: usize) -> Result, SramError> { - let path = path.as_ref().with_extension("nes.sav"); - println!("Loading SRAM file data from {:?}", path); - - let mut file = File::open(path)?; - let mut result = vec![0; sram_size]; - - file.read_exact(&mut result) - .map_err(|_| SramError::SramFileSizeDoesNotMatch)?; - - Ok(result) - } - - fn save_sram_file(&self) -> Result<(), SramError> { - let path = self.file_path.with_extension("nes.sav"); - println!("Writing SRAM file data to {:?}", path); - - let mut file = File::create(&path)?; - - let size = file.write(&self.prg_ram_data)?; - - if size != self.header.prg_sram_size as usize { - file.sync_all()?; - // remove the file so it will not be loaded next time the game is run - std::fs::remove_file(path).expect("Could not remove `nes.sav` file"); - Err(SramError::FailedToSaveSramFile) - } else { - Ok(()) - } - } - - pub fn is_empty(&self) -> bool { - self.is_empty - } - - pub fn cartridge_path(&self) -> &Path { - &self.file_path - } -} - -impl Bus for Cartridge { - fn read(&self, address: u16, device: Device) -> u8 { - if self.is_empty { - return match device { - Device::CPU => 0xEA, // NOP instruction just in case, this - Device::PPU => 0x00, // should not be called - }; - } - - let result = self.mapper.map_read(address, device); - - if let MappingResult::Allowed(new_address) = result { - match device { - Device::CPU => match address { - 0x6000..=0x7FFF => *self - .prg_ram_data - .get(new_address) - .expect("SRAM out of bounds"), - 0x8000..=0xFFFF => *self.prg_data.get(new_address).expect("PRG out of bounds"), - _ => { - unreachable!(); - } - }, - Device::PPU => { - if address <= 0x1FFF { - *self.chr_data.get(new_address).expect("CHR out of bounds") - } else { - unreachable!(); - } - } - } - } else { - 0 - } - } - fn write(&mut self, address: u16, data: u8, device: Device) { - if self.is_empty { - return; - } - - // send the write signal, this might trigger bank change - let result = self.mapper.map_write(address, data, device); - - if let MappingResult::Allowed(new_address) = result { - match device { - Device::CPU => match address { - 0x6000..=0x7FFF => { - *self - .prg_ram_data - .get_mut(new_address) - .expect("SRAM out of bounds") = data; - } - 0x8000..=0xFFFF => { - *self - .prg_data - .get_mut(new_address) - .expect("PRG out of bounds") = data; - } - _ => { - unreachable!(); - } - }, - Device::PPU => { - if address <= 0x1FFF { - *self - .chr_data - .get_mut(new_address) - .expect("CHR out of bounds") = data; - } else { - unreachable!(); - } - } - } - } - } -} - -impl MirroringProvider for Cartridge { - fn mirroring_mode(&self) -> MirroringMode { - if self.is_empty { - //anything - return MirroringMode::Vertical; - } - - if self.header.use_hardwaired_4_screen_mirroring { - MirroringMode::FourScreen - } else if self.mapper.is_hardwired_mirrored() { - if self.header.hardwired_mirroring_vertical { - MirroringMode::Vertical - } else { - MirroringMode::Horizontal - } - } else { - self.mapper.nametable_mirroring() - } - } -} - -impl Drop for Cartridge { - fn drop(&mut self) { - if !self.is_empty && self.header.has_prg_ram_battery { - self.save_sram_file().unwrap(); - } - } -} - -impl CPUIrqProvider for Cartridge { - fn is_irq_change_requested(&self) -> bool { - if self.is_empty { - return false; - } - - self.mapper.is_irq_pin_state_changed_requested() - } - - fn irq_pin_state(&self) -> bool { - self.mapper.irq_pin_state() - } - - fn clear_irq_request_pin(&mut self) { - self.mapper.clear_irq_request_pin(); - } -} - -impl Savable for Cartridge { - fn save(&self, writer: &mut W) -> Result<(), SaveError> { - let mapper_saved_state = self.mapper.save_state(); - writer.write_all(&mapper_saved_state)?; - - writer.write_all(&self.prg_ram_data)?; - - writer.write_all(&[self.header.is_chr_ram as u8])?; - if self.header.is_chr_ram { - writer.write_all(&self.chr_data)?; - } - - Ok(()) - } - - fn load(&mut self, reader: &mut R) -> Result<(), SaveError> { - let mut mapper_load_data = vec![0; self.mapper.save_state_size()]; - reader.read_exact(&mut mapper_load_data)?; - self.mapper.load_state(mapper_load_data); - - reader.read_exact(&mut self.prg_ram_data)?; - - let mut is_chr_ram = [0u8; 1]; - reader.read_exact(&mut is_chr_ram)?; - if is_chr_ram[0] != 0 { - reader.read_exact(&mut self.chr_data)?; - } - - Ok(()) - } -} diff --git a/plastic_core/src/cartridge/mappers/mapper0.rs b/plastic_core/src/cartridge/mappers/mapper0.rs index 20f618d..e66e17c 100644 --- a/plastic_core/src/cartridge/mappers/mapper0.rs +++ b/plastic_core/src/cartridge/mappers/mapper0.rs @@ -26,7 +26,7 @@ impl Mapper for Mapper0 { fn map_read(&self, address: u16, device: Device) -> MappingResult { match device { - Device::CPU => { + Device::Cpu => { match address { 0x6000..=0x7FFF => MappingResult::Denied, 0x8000..=0xFFFF => { @@ -48,7 +48,7 @@ impl Mapper for Mapper0 { _ => unreachable!(), } } - Device::PPU => { + Device::Ppu => { // it does not matter if its a ram or rom, same array location if address < 0x2000 { // only one fixed memory @@ -64,8 +64,8 @@ impl Mapper for Mapper0 { // only for RAMs match device { - Device::CPU => MappingResult::Denied, - Device::PPU => { + Device::Cpu => MappingResult::Denied, + Device::Ppu => { if self.is_chr_ram && address <= 0x1FFF { MappingResult::Allowed(address as usize) } else { diff --git a/plastic_core/src/cartridge/mappers/mapper1.rs b/plastic_core/src/cartridge/mappers/mapper1.rs index 3009bf3..d393d92 100644 --- a/plastic_core/src/cartridge/mappers/mapper1.rs +++ b/plastic_core/src/cartridge/mappers/mapper1.rs @@ -168,7 +168,7 @@ impl Mapper1 { self.chr_0_bank & 0b11110 } else if address <= 0x0FFF { self.chr_0_bank - } else if address >= 0x1000 && address <= 0x1FFF { + } else if (0x1000..=0x1FFF).contains(&address) { self.chr_1_bank } else { unreachable!() @@ -222,14 +222,14 @@ impl Mapper for Mapper1 { fn map_read(&self, address: u16, device: Device) -> MappingResult { match device { - Device::CPU => { + Device::Cpu => { match address { 0x6000..=0x7FFF => self.map_prg_ram(address), 0x8000..=0xFFFF => { let mut bank = if self.is_prg_32kb_mode() { // ignore last bit self.get_prg_bank() & 0b11110 - } else if address >= 0x8000 && address <= 0xBFFF { + } else if (0x8000..=0xBFFF).contains(&address) { if self.is_first_prg_chunk_fixed() { 0 } else { @@ -279,7 +279,7 @@ impl Mapper for Mapper1 { _ => unreachable!(), } } - Device::PPU => { + Device::Ppu => { if address < 0x2000 { self.map_ppu(address) } else { @@ -291,7 +291,7 @@ impl Mapper for Mapper1 { fn map_write(&mut self, address: u16, data: u8, device: Device) -> MappingResult { match device { - Device::CPU => { + Device::Cpu => { match address { 0x6000..=0x7FFF => self.map_prg_ram(address), 0x8000..=0xFFFF => { @@ -328,7 +328,7 @@ impl Mapper for Mapper1 { _ => unreachable!(), } } - Device::PPU => { + Device::Ppu => { // CHR RAM if self.is_chr_ram && address <= 0x1FFF { self.map_ppu(address) diff --git a/plastic_core/src/cartridge/mappers/mapper10.rs b/plastic_core/src/cartridge/mappers/mapper10.rs index 8eafd6c..dab4bb9 100644 --- a/plastic_core/src/cartridge/mappers/mapper10.rs +++ b/plastic_core/src/cartridge/mappers/mapper10.rs @@ -148,7 +148,7 @@ impl Mapper for Mapper10 { fn map_read(&self, address: u16, device: Device) -> MappingResult { match device { - Device::CPU => match address { + Device::Cpu => match address { 0x6000..=0x7FFF => MappingResult::Allowed(address as usize & 0x1FFF), 0x8000..=0xFFFF => { let mut bank = match address { @@ -166,7 +166,7 @@ impl Mapper for Mapper10 { 0x4020..=0x5FFF => MappingResult::Denied, _ => unreachable!(), }, - Device::PPU => { + Device::Ppu => { if address < 0x2000 { self.map_ppu(address) } else { @@ -178,7 +178,7 @@ impl Mapper for Mapper10 { fn map_write(&mut self, address: u16, data: u8, device: Device) -> MappingResult { match device { - Device::CPU => match address { + Device::Cpu => match address { 0x6000..=0x7FFF => MappingResult::Allowed(address as usize & 0x1FFF), 0x8000..=0xFFFF => { match address { @@ -196,7 +196,7 @@ impl Mapper for Mapper10 { 0x4020..=0x5FFF => MappingResult::Denied, _ => unreachable!(), }, - Device::PPU => { + Device::Ppu => { // CHR RAM if self.is_chr_ram && address <= 0x1FFF { self.map_ppu(address) diff --git a/plastic_core/src/cartridge/mappers/mapper11.rs b/plastic_core/src/cartridge/mappers/mapper11.rs index f9a68bf..7a37f32 100644 --- a/plastic_core/src/cartridge/mappers/mapper11.rs +++ b/plastic_core/src/cartridge/mappers/mapper11.rs @@ -50,7 +50,7 @@ impl Mapper for Mapper11 { fn map_read(&self, address: u16, device: Device) -> MappingResult { match device { - Device::CPU => match address { + Device::Cpu => match address { 0x6000..=0x7FFF => MappingResult::Denied, 0x8000..=0xFFFF => { let bank = self.prg_bank % self.prg_count; @@ -62,7 +62,7 @@ impl Mapper for Mapper11 { 0x4020..=0x5FFF => MappingResult::Denied, _ => unreachable!(), }, - Device::PPU => { + Device::Ppu => { if address < 0x2000 { self.map_ppu(address) } else { @@ -74,7 +74,7 @@ impl Mapper for Mapper11 { fn map_write(&mut self, address: u16, data: u8, device: Device) -> MappingResult { match device { - Device::CPU => match address { + Device::Cpu => match address { 0x6000..=0x7FFF => MappingResult::Denied, 0x8000..=0xFFFF => { self.prg_bank = data & 0x3; @@ -85,7 +85,7 @@ impl Mapper for Mapper11 { 0x4020..=0x5FFF => MappingResult::Denied, _ => unreachable!(), }, - Device::PPU => { + Device::Ppu => { // CHR RAM if self.is_chr_ram && address <= 0x1FFF { self.map_ppu(address) diff --git a/plastic_core/src/cartridge/mappers/mapper12.rs b/plastic_core/src/cartridge/mappers/mapper12.rs index f3bf31c..67789ac 100644 --- a/plastic_core/src/cartridge/mappers/mapper12.rs +++ b/plastic_core/src/cartridge/mappers/mapper12.rs @@ -250,7 +250,7 @@ impl Mapper for Mapper12 { fn map_read(&self, address: u16, device: Device) -> MappingResult { match device { - Device::CPU => { + Device::Cpu => { match address { 0x6000..=0x7FFF => { if self.prg_ram_enabled && self.has_prg_ram { @@ -292,7 +292,7 @@ impl Mapper for Mapper12 { _ => unreachable!(), } } - Device::PPU => { + Device::Ppu => { if address < 0x2000 { self.map_ppu(address) } else { @@ -304,7 +304,7 @@ impl Mapper for Mapper12 { fn map_write(&mut self, address: u16, data: u8, device: Device) -> MappingResult { match device { - Device::CPU => { + Device::Cpu => { match address { 0x6000..=0x7FFF => { if self.prg_ram_enabled && self.prg_ram_allow_writes && self.has_prg_ram { @@ -384,7 +384,7 @@ impl Mapper for Mapper12 { _ => unreachable!(), } } - Device::PPU => { + Device::Ppu => { // CHR RAM if self.is_chr_ram && address <= 0x1FFF { self.map_ppu(address) diff --git a/plastic_core/src/cartridge/mappers/mapper2.rs b/plastic_core/src/cartridge/mappers/mapper2.rs index c9c8ed5..fa559b7 100644 --- a/plastic_core/src/cartridge/mappers/mapper2.rs +++ b/plastic_core/src/cartridge/mappers/mapper2.rs @@ -28,11 +28,11 @@ impl Mapper for Mapper2 { fn map_read(&self, address: u16, device: Device) -> MappingResult { match device { - Device::CPU => { + Device::Cpu => { match address { 0x6000..=0x7FFF => MappingResult::Denied, 0x8000..=0xFFFF => { - let mut bank = if address >= 0x8000 && address <= 0xBFFF { + let mut bank = if (0x8000..=0xBFFF).contains(&address) { self.prg_top_bank & 0xF } else if address >= 0xC000 { self.prg_count - 1 @@ -51,7 +51,7 @@ impl Mapper for Mapper2 { _ => unreachable!(), } } - Device::PPU => { + Device::Ppu => { // it does not matter if its a ram or rom, same array location if address < 0x2000 { // only one fixed memory @@ -65,7 +65,7 @@ impl Mapper for Mapper2 { fn map_write(&mut self, address: u16, data: u8, device: Device) -> MappingResult { match device { - Device::CPU => match address { + Device::Cpu => match address { 0x6000..=0x7FFF => MappingResult::Denied, 0x8000..=0xFFFF => { self.prg_top_bank = data; @@ -74,7 +74,7 @@ impl Mapper for Mapper2 { 0x4020..=0x5FFF => MappingResult::Denied, _ => unreachable!(), }, - Device::PPU => { + Device::Ppu => { // CHR RAM if self.is_chr_ram && address <= 0x1FFF { MappingResult::Allowed(address as usize) diff --git a/plastic_core/src/cartridge/mappers/mapper3.rs b/plastic_core/src/cartridge/mappers/mapper3.rs index d0dfb96..632d859 100644 --- a/plastic_core/src/cartridge/mappers/mapper3.rs +++ b/plastic_core/src/cartridge/mappers/mapper3.rs @@ -41,7 +41,7 @@ impl Mapper for Mapper3 { fn map_read(&self, address: u16, device: Device) -> MappingResult { match device { - Device::CPU => { + Device::Cpu => { match address { 0x6000..=0x7FFF => MappingResult::Denied, 0x8000..=0xFFFF => { @@ -64,7 +64,7 @@ impl Mapper for Mapper3 { _ => unreachable!(), } } - Device::PPU => { + Device::Ppu => { if address < 0x2000 { self.map_ppu(address) } else { @@ -76,7 +76,7 @@ impl Mapper for Mapper3 { fn map_write(&mut self, address: u16, data: u8, device: Device) -> MappingResult { match device { - Device::CPU => match address { + Device::Cpu => match address { 0x6000..=0x7FFF => MappingResult::Denied, 0x8000..=0xFFFF => { if self.chr_count <= 4 { @@ -93,7 +93,7 @@ impl Mapper for Mapper3 { 0x4020..=0x5FFF => MappingResult::Denied, _ => unreachable!(), }, - Device::PPU => { + Device::Ppu => { if self.is_chr_ram && address <= 0x1FFF { self.map_ppu(address) } else { diff --git a/plastic_core/src/cartridge/mappers/mapper4.rs b/plastic_core/src/cartridge/mappers/mapper4.rs index 8da5e25..b7ffff3 100644 --- a/plastic_core/src/cartridge/mappers/mapper4.rs +++ b/plastic_core/src/cartridge/mappers/mapper4.rs @@ -231,7 +231,7 @@ impl Mapper for Mapper4 { fn map_read(&self, address: u16, device: Device) -> MappingResult { match device { - Device::CPU => { + Device::Cpu => { match address { 0x6000..=0x7FFF => { if self.prg_ram_enabled && self.has_prg_ram { @@ -273,7 +273,7 @@ impl Mapper for Mapper4 { _ => unreachable!(), } } - Device::PPU => { + Device::Ppu => { if address < 0x2000 { self.map_ppu(address) } else { @@ -285,7 +285,7 @@ impl Mapper for Mapper4 { fn map_write(&mut self, address: u16, data: u8, device: Device) -> MappingResult { match device { - Device::CPU => { + Device::Cpu => { match address { 0x6000..=0x7FFF => { if self.prg_ram_enabled && self.prg_ram_allow_writes && self.has_prg_ram { @@ -357,7 +357,7 @@ impl Mapper for Mapper4 { _ => unreachable!(), } } - Device::PPU => { + Device::Ppu => { // CHR RAM if self.is_chr_ram && address <= 0x1FFF { self.map_ppu(address) diff --git a/plastic_core/src/cartridge/mappers/mapper66.rs b/plastic_core/src/cartridge/mappers/mapper66.rs index f0effe3..20e0cd5 100644 --- a/plastic_core/src/cartridge/mappers/mapper66.rs +++ b/plastic_core/src/cartridge/mappers/mapper66.rs @@ -61,7 +61,7 @@ impl Mapper for Mapper66 { fn map_read(&self, address: u16, device: Device) -> MappingResult { match device { - Device::CPU => match address { + Device::Cpu => match address { 0x6000..=0x7FFF => MappingResult::Denied, 0x8000..=0xFFFF => { let bank = self.prg_bank % self.prg_count; @@ -73,7 +73,7 @@ impl Mapper for Mapper66 { 0x4020..=0x5FFF => MappingResult::Denied, _ => unreachable!(), }, - Device::PPU => { + Device::Ppu => { if address < 0x2000 { self.map_ppu(address) } else { @@ -85,7 +85,7 @@ impl Mapper for Mapper66 { fn map_write(&mut self, address: u16, data: u8, device: Device) -> MappingResult { match device { - Device::CPU => match address { + Device::Cpu => match address { 0x6000..=0x7FFF => MappingResult::Denied, 0x8000..=0xFFFF => { self.chr_bank = data & 0x3; @@ -96,7 +96,7 @@ impl Mapper for Mapper66 { 0x4020..=0x5FFF => MappingResult::Denied, _ => unreachable!(), }, - Device::PPU => { + Device::Ppu => { if self.is_chr_ram && address <= 0x1FFF { self.map_ppu(address) } else { diff --git a/plastic_core/src/cartridge/mappers/mapper7.rs b/plastic_core/src/cartridge/mappers/mapper7.rs index 617d680..d7dbb81 100644 --- a/plastic_core/src/cartridge/mappers/mapper7.rs +++ b/plastic_core/src/cartridge/mappers/mapper7.rs @@ -39,7 +39,7 @@ impl Mapper for Mapper7 { fn map_read(&self, address: u16, device: Device) -> MappingResult { match device { - Device::CPU => { + Device::Cpu => { match address { 0x6000..=0x7FFF => MappingResult::Denied, 0x8000..=0xFFFF => { @@ -54,7 +54,7 @@ impl Mapper for Mapper7 { _ => unreachable!(), } } - Device::PPU => { + Device::Ppu => { // it does not matter if its a ram or rom, same array location if address < 0x2000 { // only one fixed memory @@ -68,7 +68,7 @@ impl Mapper for Mapper7 { fn map_write(&mut self, address: u16, data: u8, device: Device) -> MappingResult { match device { - Device::CPU => match address { + Device::Cpu => match address { 0x6000..=0x7FFF => MappingResult::Denied, 0x8000..=0xFFFF => { self.prg_bank = data & 0xF; @@ -79,7 +79,7 @@ impl Mapper for Mapper7 { 0x4020..=0x5FFF => MappingResult::Denied, _ => unreachable!(), }, - Device::PPU => { + Device::Ppu => { // CHR RAM if self.is_chr_ram && address <= 0x1FFF { MappingResult::Allowed(address as usize) diff --git a/plastic_core/src/cartridge/mappers/mapper9.rs b/plastic_core/src/cartridge/mappers/mapper9.rs index 31a6530..8164a78 100644 --- a/plastic_core/src/cartridge/mappers/mapper9.rs +++ b/plastic_core/src/cartridge/mappers/mapper9.rs @@ -153,7 +153,7 @@ impl Mapper for Mapper9 { fn map_read(&self, address: u16, device: Device) -> MappingResult { match device { - Device::CPU => match address { + Device::Cpu => match address { 0x6000..=0x7FFF => { if self.has_prg_ram { MappingResult::Allowed(address as usize & 0x1FFF) @@ -183,7 +183,7 @@ impl Mapper for Mapper9 { 0x4020..=0x5FFF => MappingResult::Denied, _ => unreachable!(), }, - Device::PPU => { + Device::Ppu => { if address < 0x2000 { self.map_ppu(address) } else { @@ -195,7 +195,7 @@ impl Mapper for Mapper9 { fn map_write(&mut self, address: u16, data: u8, device: Device) -> MappingResult { match device { - Device::CPU => match address { + Device::Cpu => match address { 0x6000..=0x7FFF => { if self.has_prg_ram { MappingResult::Allowed(address as usize & 0x1FFF) @@ -219,7 +219,7 @@ impl Mapper for Mapper9 { 0x4020..=0x5FFF => MappingResult::Denied, _ => unreachable!(), }, - Device::PPU => { + Device::Ppu => { // CHR RAM if self.is_chr_ram && address <= 0x1FFF { self.map_ppu(address) diff --git a/plastic_core/src/cartridge/mappers/tests.rs b/plastic_core/src/cartridge/mappers/tests.rs index 1c90ae0..8ca7e34 100644 --- a/plastic_core/src/cartridge/mappers/tests.rs +++ b/plastic_core/src/cartridge/mappers/tests.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + #[cfg(test)] mod mappers_tests { use crate::tests::{NesTester, TestError}; diff --git a/plastic_core/src/cartridge/mod.rs b/plastic_core/src/cartridge/mod.rs index c4ccba9..b03a946 100644 --- a/plastic_core/src/cartridge/mod.rs +++ b/plastic_core/src/cartridge/mod.rs @@ -1,9 +1,513 @@ -mod cartridge; mod error; mod mapper; mod mappers; mod tests; -pub use cartridge::Cartridge; pub use error::CartridgeError; +use error::SramError; +use mapper::{Mapper, MappingResult}; +use mappers::{ + Mapper0, Mapper1, Mapper10, Mapper11, Mapper12, Mapper2, Mapper3, Mapper4, Mapper66, Mapper7, + Mapper9, +}; + +use crate::common::{ + interconnection::CPUIrqProvider, + save_state::{Savable, SaveError}, + Bus, Device, MirroringMode, MirroringProvider, +}; +use std::{ + fs::File, + io::{Read, Seek, SeekFrom, Write}, + path::Path, +}; + +#[allow(dead_code)] +struct INesHeader { + // in 16kb units + prg_rom_size: u16, + // in 8kb units + chr_rom_size: u16, + is_chr_ram: bool, + hardwired_mirroring_vertical: bool, + has_prg_ram_battery: bool, + contain_trainer_data: bool, + use_hardwaired_4_screen_mirroring: bool, + mapper_id: u16, + submapper_id: u8, + prg_wram_size: u32, + prg_sram_size: u32, + chr_wram_size: u32, + chr_sram_size: u32, +} + +impl INesHeader { + fn from_bytes(mut header: [u8; 16]) -> Result { + // decode header + Self::check_magic(&header[0..4])?; + + let prg_size_low = header[4] as u16; + let chr_size_low = header[5] as u16; + let is_chr_ram = chr_size_low == 0; + + let hardwired_mirroring_vertical = header[6] & 1 != 0; + header[6] >>= 1; + let has_prg_ram_battery = header[6] & 1 != 0; + header[6] >>= 1; + let contain_trainer_data = header[6] & 1 != 0; + header[6] >>= 1; + let use_hardwaired_4_screen_mirroring = header[6] & 1 != 0; + header[6] >>= 1; + let mapper_id_low = (header[6] & 0xF) as u16; + + // let mut console_type = header[7] & 0x3; + header[7] >>= 2; + let ines_2_ident = header[7] & 0x3; + header[7] >>= 2; + let mut mapper_id_middle = (header[7] & 0xF) as u16; + + if ines_2_ident == 0 { + let mut is_archaic_ines = false; + for item in header.iter().skip(12) { + if *item != 0 { + is_archaic_ines = true; + } + } + + let prg_ram_size; + + if !is_archaic_ines { + prg_ram_size = if header[8] == 0 { 1 } else { header[8] }; + // let ntcs_tv_system = header[9] & 1 == 0; + + if header[9] >> 1 != 0 { + return Err(CartridgeError::HeaderError); + } + + // let is_prg_ram_present = (header[10] >> 4) & 1 == 0; + // let board_has_bus_conflict = (header[10] >> 5) & 1 != 0; + } else { + // ignore `header[7]` data + // console_type = 0; + mapper_id_middle = 0; + + prg_ram_size = 1; + } + + Ok(Self { + prg_rom_size: prg_size_low, + chr_rom_size: chr_size_low, + is_chr_ram, + hardwired_mirroring_vertical, + has_prg_ram_battery, + contain_trainer_data, + use_hardwaired_4_screen_mirroring, + mapper_id: mapper_id_middle << 4 | mapper_id_low, + submapper_id: 0, + prg_wram_size: prg_ram_size as u32 * 0x2000, + prg_sram_size: prg_ram_size as u32 * 0x2000, + chr_wram_size: 0x2000, // can only use 8kb + chr_sram_size: 0x2000, + }) + } else { + let mapper_id_high = (header[8] & 0xF) as u16; + header[8] >>= 4; + let submapper_id = header[8] & 0xF; + + let prg_size_high = (header[9] & 0xF) as u16; + let chr_size_high = ((header[9] >> 4) & 0xF) as u16; + + let shift_size = (header[10] & 0xF) as u32; + let prg_wram_size_bytes = if shift_size != 0 { 64 << shift_size } else { 0 }; + header[10] >>= 4; + let shift_size = (header[10] & 0xF) as u32; + let prg_sram_size_bytes = if shift_size != 0 { 64 << shift_size } else { 0 }; + + let shift_size = (header[11] & 0xF) as u32; + let chr_wram_size_bytes = if shift_size != 0 { 64 << shift_size } else { 0 }; + header[11] >>= 4; + let shift_size = (header[11] & 0xF) as u32; + let chr_sram_size_bytes = if shift_size != 0 { 64 << shift_size } else { 0 }; + + // TODO: implement the rest + + Ok(Self { + prg_rom_size: prg_size_high << 8 | prg_size_low, + chr_rom_size: chr_size_high << 8 | chr_size_low, + is_chr_ram, + hardwired_mirroring_vertical, + has_prg_ram_battery, + contain_trainer_data, + use_hardwaired_4_screen_mirroring, + mapper_id: mapper_id_high << 8 | mapper_id_middle << 4 | mapper_id_low, + submapper_id, + prg_wram_size: prg_wram_size_bytes, + prg_sram_size: prg_sram_size_bytes, + chr_wram_size: chr_wram_size_bytes, + chr_sram_size: chr_sram_size_bytes, + }) + } + } + + fn empty() -> Self { + Self::from_bytes([0x4E, 0x45, 0x53, 0x1A, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).unwrap() + } + + fn check_magic(header: &[u8]) -> Result<(), CartridgeError> { + let real = [0x4E, 0x45, 0x53, 0x1A]; + + if header == real { + Ok(()) + } else { + Err(CartridgeError::HeaderError) + } + } +} + +pub struct Cartridge { + file_path: Box, + header: INesHeader, + + _trainer_data: Vec, + pub(crate) prg_data: Vec, + pub(crate) chr_data: Vec, + prg_ram_data: Vec, + + mapper: Box, + + is_empty: bool, +} + +impl Cartridge { + // TODO: not sure if it should consume the file or not + pub fn from_file>(file_path: P) -> Result { + if let Some(extension) = file_path.as_ref().extension() { + if extension == "nes" { + let mut file = File::open(file_path.as_ref())?; + + let mut header = [0; 16]; + file.read_exact(&mut header)?; + + // decode header + let header = INesHeader::from_bytes(header)?; + + let sram_data = if header.has_prg_ram_battery { + // try to load old save data + if let Ok(data) = + Self::load_sram_file(file_path.as_ref(), header.prg_sram_size as usize) + { + data + } else { + vec![0; header.prg_sram_size as usize] + } + } else { + vec![0; header.prg_wram_size as usize] + }; + + println!("mapper {}", header.mapper_id); + + // initialize the mapper first, so that if it is not supported yet, + // panic + let mapper = Self::get_mapper(&header)?; + + let mut trainer_data = Vec::new(); + + // read training data if present + if header.contain_trainer_data { + trainer_data.resize(512, 0); + file.read_exact(&mut trainer_data)?; + } + + // read PRG data + let mut prg_data = vec![0; (header.prg_rom_size as usize) * 16 * 1024]; + file.read_exact(&mut prg_data)?; + + // read CHR data + let chr_data = if !header.is_chr_ram { + let mut data = vec![0; (header.chr_rom_size as usize) * 8 * 1024]; + file.read_exact(&mut data)?; + + data + } else { + // TODO: there is no way of knowing if we are using CHR WRAM or SRAM + let ram_size = header.chr_wram_size; + + vec![0; ram_size as usize] + }; + + // there are missing parts + let current = file.stream_position()?; + let end = file.seek(SeekFrom::End(0))?; + if current != end { + Err(CartridgeError::TooLargeFile(end - current)) + } else { + Ok(Self { + file_path: file_path.as_ref().to_path_buf().into_boxed_path(), + header, + _trainer_data: trainer_data, + prg_data, + chr_data, + prg_ram_data: sram_data, + mapper, + + is_empty: false, + }) + } + } else { + Err(CartridgeError::ExtensionError) + } + } else { + Err(CartridgeError::ExtensionError) + } + } + + pub fn new_without_file() -> Self { + Self { + // should not be used + file_path: Path::new("").to_path_buf().into_boxed_path(), + header: INesHeader::empty(), + _trainer_data: Vec::new(), + prg_data: Vec::new(), + chr_data: Vec::new(), + prg_ram_data: Vec::new(), + mapper: Box::new(Mapper0::new()), + + is_empty: true, + } + } + + fn get_mapper(header: &INesHeader) -> Result, CartridgeError> { + let mut mapper: Box = match header.mapper_id { + 0 => Box::new(Mapper0::new()), + 1 => Box::new(Mapper1::new()), + 2 => Box::new(Mapper2::new()), + 3 => Box::new(Mapper3::new()), + 4 => Box::new(Mapper4::new()), + 7 => Box::new(Mapper7::new()), + 9 => Box::new(Mapper9::new()), + 10 => Box::new(Mapper10::new()), + 11 => Box::new(Mapper11::new()), + 12 => Box::new(Mapper12::new()), + 66 => Box::new(Mapper66::new()), + _ => { + return Err(CartridgeError::MapperNotImplemented(header.mapper_id)); + } + }; + + // FIXME: fix parameters types to support INES2.0 + // should always call init in a new mapper, as it is the only way + // they share a constructor + mapper.init( + header.prg_rom_size as u8, + header.is_chr_ram, + if !header.is_chr_ram { + header.chr_rom_size as u8 + } else { + (header.chr_wram_size / 0x2000) as u8 + }, + if header.has_prg_ram_battery { + header.prg_sram_size / 0x2000 + } else { + header.prg_wram_size / 0x2000 + } as u8, + ); + + Ok(mapper) + } + + fn load_sram_file>(path: P, sram_size: usize) -> Result, SramError> { + let path = path.as_ref().with_extension("nes.sav"); + println!("Loading SRAM file data from {:?}", path); + + let mut file = File::open(path)?; + let mut result = vec![0; sram_size]; + + file.read_exact(&mut result) + .map_err(|_| SramError::SramFileSizeDoesNotMatch)?; + + Ok(result) + } + + fn save_sram_file(&self) -> Result<(), SramError> { + let path = self.file_path.with_extension("nes.sav"); + println!("Writing SRAM file data to {:?}", path); + + let mut file = File::create(&path)?; + + let size = file.write(&self.prg_ram_data)?; + + if size != self.header.prg_sram_size as usize { + file.sync_all()?; + // remove the file so it will not be loaded next time the game is run + std::fs::remove_file(path).expect("Could not remove `nes.sav` file"); + Err(SramError::FailedToSaveSramFile) + } else { + Ok(()) + } + } + + pub fn is_empty(&self) -> bool { + self.is_empty + } + + pub fn cartridge_path(&self) -> &Path { + &self.file_path + } +} + +impl Bus for Cartridge { + fn read(&self, address: u16, device: Device) -> u8 { + if self.is_empty { + return match device { + Device::Cpu => 0xEA, // NOP instruction just in case, this + Device::Ppu => 0x00, // should not be called + }; + } + + let result = self.mapper.map_read(address, device); + + if let MappingResult::Allowed(new_address) = result { + match device { + Device::Cpu => match address { + 0x6000..=0x7FFF => *self + .prg_ram_data + .get(new_address) + .expect("SRAM out of bounds"), + 0x8000..=0xFFFF => *self.prg_data.get(new_address).expect("PRG out of bounds"), + _ => { + unreachable!(); + } + }, + Device::Ppu => { + if address <= 0x1FFF { + *self.chr_data.get(new_address).expect("CHR out of bounds") + } else { + unreachable!(); + } + } + } + } else { + 0 + } + } + fn write(&mut self, address: u16, data: u8, device: Device) { + if self.is_empty { + return; + } + + // send the write signal, this might trigger bank change + let result = self.mapper.map_write(address, data, device); + + if let MappingResult::Allowed(new_address) = result { + match device { + Device::Cpu => match address { + 0x6000..=0x7FFF => { + *self + .prg_ram_data + .get_mut(new_address) + .expect("SRAM out of bounds") = data; + } + 0x8000..=0xFFFF => { + *self + .prg_data + .get_mut(new_address) + .expect("PRG out of bounds") = data; + } + _ => { + unreachable!(); + } + }, + Device::Ppu => { + if address <= 0x1FFF { + *self + .chr_data + .get_mut(new_address) + .expect("CHR out of bounds") = data; + } else { + unreachable!(); + } + } + } + } + } +} + +impl MirroringProvider for Cartridge { + fn mirroring_mode(&self) -> MirroringMode { + if self.is_empty { + //anything + return MirroringMode::Vertical; + } + + if self.header.use_hardwaired_4_screen_mirroring { + MirroringMode::FourScreen + } else if self.mapper.is_hardwired_mirrored() { + if self.header.hardwired_mirroring_vertical { + MirroringMode::Vertical + } else { + MirroringMode::Horizontal + } + } else { + self.mapper.nametable_mirroring() + } + } +} + +impl Drop for Cartridge { + fn drop(&mut self) { + if !self.is_empty && self.header.has_prg_ram_battery { + self.save_sram_file().unwrap(); + } + } +} + +impl CPUIrqProvider for Cartridge { + fn is_irq_change_requested(&self) -> bool { + if self.is_empty { + return false; + } + + self.mapper.is_irq_pin_state_changed_requested() + } + + fn irq_pin_state(&self) -> bool { + self.mapper.irq_pin_state() + } + + fn clear_irq_request_pin(&mut self) { + self.mapper.clear_irq_request_pin(); + } +} + +impl Savable for Cartridge { + fn save(&self, writer: &mut W) -> Result<(), SaveError> { + let mapper_saved_state = self.mapper.save_state(); + writer.write_all(&mapper_saved_state)?; + + writer.write_all(&self.prg_ram_data)?; + + writer.write_all(&[self.header.is_chr_ram as u8])?; + if self.header.is_chr_ram { + writer.write_all(&self.chr_data)?; + } + + Ok(()) + } + + fn load(&mut self, reader: &mut R) -> Result<(), SaveError> { + let mut mapper_load_data = vec![0; self.mapper.save_state_size()]; + reader.read_exact(&mut mapper_load_data)?; + self.mapper.load_state(mapper_load_data); + + reader.read_exact(&mut self.prg_ram_data)?; + + let mut is_chr_ram = [0u8; 1]; + reader.read_exact(&mut is_chr_ram)?; + if is_chr_ram[0] != 0 { + reader.read_exact(&mut self.chr_data)?; + } + + Ok(()) + } +} diff --git a/plastic_core/src/common/bus.rs b/plastic_core/src/common/bus.rs index 0efc074..5a63ca5 100644 --- a/plastic_core/src/common/bus.rs +++ b/plastic_core/src/common/bus.rs @@ -1,7 +1,7 @@ #[derive(PartialEq, Clone, Copy)] pub enum Device { - CPU, - PPU, + Cpu, + Ppu, } pub trait Bus { diff --git a/plastic_core/src/controller/controller.rs b/plastic_core/src/controller/controller.rs deleted file mode 100644 index 47ebf3e..0000000 --- a/plastic_core/src/controller/controller.rs +++ /dev/null @@ -1,90 +0,0 @@ -use crate::common::{Bus, Device}; -use bitflags::bitflags; -use std::cell::Cell; -use std::sync::{Arc, Mutex}; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum StandardNESKey { - A = 1 << 0, - B = 1 << 1, - Select = 1 << 2, - Start = 1 << 3, - Up = 1 << 4, - Down = 1 << 5, - Left = 1 << 6, - Right = 1 << 7, -} - -bitflags! { - pub struct StandardNESControllerState : u8{ - const A = 1 << 0; - const B = 1 << 1; - const SELECT = 1 << 2; - const START = 1 << 3; - const UP = 1 << 4; - const DOWN = 1 << 5; - const LEFT = 1 << 6; - const RIGHT = 1 << 7; - } -} - -impl StandardNESControllerState { - pub fn press(&mut self, key: StandardNESKey) { - self.insert(StandardNESControllerState::from_bits(key as u8).unwrap()); - } - - pub fn release(&mut self, key: StandardNESKey) { - self.remove(StandardNESControllerState::from_bits(key as u8).unwrap()); - } -} - -pub struct Controller { - primary_state: Arc>, - polled_state: Cell, - - polling: bool, -} - -impl Controller { - pub fn new() -> Self { - Self { - primary_state: Arc::new(Mutex::new(StandardNESControllerState::empty())), - polled_state: Cell::new(0), - - polling: false, - } - } - - pub fn get_primary_controller_state(&self) -> Arc> { - self.primary_state.clone() - } -} - -impl Bus for Controller { - fn read(&self, _address: u16, _device: Device) -> u8 { - // refresh polled here - if self.polling { - if let Ok(primary_state) = self.primary_state.lock() { - self.polled_state.set(primary_state.bits); - } - } - let result = self.polled_state.get() & 1; - - self.polled_state.set(self.polled_state.get() >> 1); - - result - } - - fn write(&mut self, _address: u16, data: u8, _device: Device) { - let new_polling = data & 1 == 1; - - // if the state changed, then refresh - if self.polling ^ new_polling { - if let Ok(primary_state) = self.primary_state.lock() { - self.polled_state.set(primary_state.bits); - } - } - - self.polling = new_polling; - } -} diff --git a/plastic_core/src/controller/mod.rs b/plastic_core/src/controller/mod.rs index a85a509..47ebf3e 100644 --- a/plastic_core/src/controller/mod.rs +++ b/plastic_core/src/controller/mod.rs @@ -1,5 +1,90 @@ -mod controller; +use crate::common::{Bus, Device}; +use bitflags::bitflags; +use std::cell::Cell; +use std::sync::{Arc, Mutex}; -pub use controller::Controller; -pub use controller::StandardNESControllerState; -pub use controller::StandardNESKey; +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum StandardNESKey { + A = 1 << 0, + B = 1 << 1, + Select = 1 << 2, + Start = 1 << 3, + Up = 1 << 4, + Down = 1 << 5, + Left = 1 << 6, + Right = 1 << 7, +} + +bitflags! { + pub struct StandardNESControllerState : u8{ + const A = 1 << 0; + const B = 1 << 1; + const SELECT = 1 << 2; + const START = 1 << 3; + const UP = 1 << 4; + const DOWN = 1 << 5; + const LEFT = 1 << 6; + const RIGHT = 1 << 7; + } +} + +impl StandardNESControllerState { + pub fn press(&mut self, key: StandardNESKey) { + self.insert(StandardNESControllerState::from_bits(key as u8).unwrap()); + } + + pub fn release(&mut self, key: StandardNESKey) { + self.remove(StandardNESControllerState::from_bits(key as u8).unwrap()); + } +} + +pub struct Controller { + primary_state: Arc>, + polled_state: Cell, + + polling: bool, +} + +impl Controller { + pub fn new() -> Self { + Self { + primary_state: Arc::new(Mutex::new(StandardNESControllerState::empty())), + polled_state: Cell::new(0), + + polling: false, + } + } + + pub fn get_primary_controller_state(&self) -> Arc> { + self.primary_state.clone() + } +} + +impl Bus for Controller { + fn read(&self, _address: u16, _device: Device) -> u8 { + // refresh polled here + if self.polling { + if let Ok(primary_state) = self.primary_state.lock() { + self.polled_state.set(primary_state.bits); + } + } + let result = self.polled_state.get() & 1; + + self.polled_state.set(self.polled_state.get() >> 1); + + result + } + + fn write(&mut self, _address: u16, data: u8, _device: Device) { + let new_polling = data & 1 == 1; + + // if the state changed, then refresh + if self.polling ^ new_polling { + if let Ok(primary_state) = self.primary_state.lock() { + self.polled_state.set(primary_state.bits); + } + } + + self.polling = new_polling; + } +} diff --git a/plastic_core/src/cpu6502/cpu6502.rs b/plastic_core/src/cpu6502/cpu6502.rs deleted file mode 100644 index 4c3ec75..0000000 --- a/plastic_core/src/cpu6502/cpu6502.rs +++ /dev/null @@ -1,1462 +0,0 @@ -use super::instruction::{AddressingMode, Instruction, Opcode}; -use super::CPUBusTrait; -use crate::common::save_state::{Savable, SaveError}; -use serde::{Deserialize, Serialize}; -use std::io::{Read, Write}; - -const NMI_VECTOR_ADDRESS: u16 = 0xFFFA; -const RESET_VECTOR_ADDRESS: u16 = 0xFFFC; -const IRQ_VECTOR_ADDRESS: u16 = 0xFFFE; - -#[derive(PartialEq)] -pub enum CPURunState { - DmaTransfere, - Waiting, - InfiniteLoop(u16), - StartingInterrupt, - NormalInstructionExecution, -} - -// helper function -fn is_on_same_page(address1: u16, address2: u16) -> bool { - address1 & 0xff00 == address2 & 0xff00 -} - -// flags: [N, V, _, B, D, I, Z, C] -enum StatusFlag { - Carry = 1 << 0, - Zero = 1 << 1, - InterruptDisable = 1 << 2, - DecimalMode = 1 << 3, - BreakCommand = 1 << 4, - Overflow = 1 << 6, - Negative = 1 << 7, -} - -// TODO: this CPU does not support BCD mode yet -pub struct CPU6502 { - reg_pc: u16, - reg_sp: u8, - reg_a: u8, - reg_x: u8, - reg_y: u8, - reg_status: u8, - - nmi_pin_status: bool, - irq_pin_status: bool, - - cycles_to_wait: u8, - - dma_remaining: u16, - dma_address: u8, - - /// a buffer to hold the next_instruction before execution, - /// check `run_next` for more info - next_instruction: Option<(Instruction, u8)>, - - bus: T, -} - -// public -impl CPU6502 -where - T: CPUBusTrait, -{ - pub fn new(bus: T) -> Self { - CPU6502 { - reg_pc: 0, - reg_sp: 0, - reg_a: 0, - reg_x: 0, - reg_y: 0, - reg_status: 0, - - nmi_pin_status: false, - irq_pin_status: false, - - cycles_to_wait: 0, - - dma_remaining: 0, - dma_address: 0, - - next_instruction: None, - - bus, - } - } - - pub fn reset(&mut self) { - // reset registers and other variables - self.reg_pc = 0; - self.reg_sp = 0; - self.reg_a = 0; - self.reg_x = 0; - self.reg_y = 0; - self.reg_status = 0; - - self.nmi_pin_status = false; - self.irq_pin_status = false; - - self.cycles_to_wait = 0; - - self.dma_remaining = 0; - self.dma_address = 0; - - self.set_flag(StatusFlag::InterruptDisable); - self.reg_sp = 0xFD; //reset - - let low = self.read_bus(RESET_VECTOR_ADDRESS) as u16; - let high = self.read_bus(RESET_VECTOR_ADDRESS + 1) as u16; - - let pc = high << 8 | low; - self.reg_pc = pc; - - self.cycles_to_wait += 7; - } - - pub fn reset_bus(&mut self) { - self.bus.reset() - } - - pub fn bus(&self) -> &T { - &self.bus - } - - pub fn run_next(&mut self) -> CPURunState { - self.check_and_run_dmc_transfer(); - - if self.cycles_to_wait == 0 && self.next_instruction.is_none() { - // are we still executing the DMA transfer instruction? - if self.dma_remaining > 0 { - self.dma_remaining -= 1; - { - // send one byte at a time - let oma_address = (255 - self.dma_remaining) & 0xFF; - let cpu_address = (self.dma_address as u16) << 8 | oma_address; - - let data = self.read_bus(cpu_address); - - self.bus.send_oam_data(oma_address as u8, data); - } - - // since it should read in one cycle and write in the other cycle - self.cycles_to_wait = 1; - CPURunState::DmaTransfere - } else if self.nmi_pin_status - || (self.irq_pin_status - && self.reg_status & StatusFlag::InterruptDisable as u8 == 0) - { - // execute interrupt - // hardware side interrupt - self.execute_interrupt(false, self.nmi_pin_status); - CPURunState::StartingInterrupt - } else { - // check for NMI and DMA and apply them only after the next - // instruction - self.check_for_nmi_dma(); - // check if there is pending IRQs from cartridge - if self.bus.is_irq_change_requested() { - self.irq_pin_status = self.bus.irq_pin_state(); - self.bus.clear_irq_request_pin() - } - - // reload the next instruction in `the next_instruction` buffer - let instruction = self.fetch_next_instruction(); - let (_, mut cycle_time, _) = self.decode_operand(&instruction); - - // only the JMP instruction has lesser time than the base time - if instruction.opcode == Opcode::Jmp { - // this instruction has only `Absolute` and `Relative` as adressing modes - cycle_time = if instruction.addressing_mode == AddressingMode::Absolute { - 3 - } else { - 5 - }; - } - - // wait for the base time minus this cycle - self.cycles_to_wait += cycle_time - 1; - - self.next_instruction = Some((instruction, cycle_time)); - - CPURunState::Waiting - } - } else if self.cycles_to_wait == 1 && self.next_instruction.is_some() { - // on the last clock, run the instruction that was saved - - // this can be seen as `self.cycles_to_wait -= 1;` because we - // know its 1 in this stage - self.cycles_to_wait = 0; - - let (instruction, cycle_time) = self.next_instruction.take().unwrap(); - - let return_state = self.run_instruction(&instruction); - - // `run_instruction` will set `self.cycles_to_wait` to the amount - // of cycles to wait minus 1, but before we have already waited - // `cycle_time` cycles (excluding this one), so subtract that - // and if anything remains then wait for it - self.cycles_to_wait -= cycle_time - 1; - - return_state - } else { - self.cycles_to_wait -= 1; - CPURunState::Waiting - } - } -} - -// private -impl CPU6502 -where - T: CPUBusTrait, -{ - fn set_flag(&mut self, flag: StatusFlag) { - self.reg_status |= flag as u8; - } - - fn unset_flag(&mut self, flag: StatusFlag) { - self.reg_status &= !(flag as u8); - } - - fn set_flag_status(&mut self, flag: StatusFlag, status: bool) { - if status { - self.set_flag(flag) - } else { - self.unset_flag(flag) - } - } - - fn read_bus(&self, address: u16) -> u8 { - self.bus.read(address) - } - - fn write_bus(&mut self, address: u16, data: u8) { - self.bus.write(address, data); - } - - /// decods the operand of an instruction and returnrs - /// (the decoded_operand, base cycle time for the instruction, has crossed page) - fn decode_operand(&self, instruction: &Instruction) -> (u16, u8, bool) { - match instruction.addressing_mode { - AddressingMode::ZeroPage => ( - instruction.operand & 0xff, - instruction.get_base_cycle_time(), - false, - ), - AddressingMode::ZeroPageIndexX => ( - (instruction.operand + self.reg_x as u16) & 0xff, - instruction.get_base_cycle_time(), - false, - ), - - AddressingMode::ZeroPageIndexY => ( - (instruction.operand + self.reg_y as u16) & 0xff, - instruction.get_base_cycle_time(), - false, - ), - - AddressingMode::Indirect => { - let low = self.read_bus(instruction.operand) as u16; - // if the indirect vector is at the last of the page (0xff) then - // wrap around on the same page - let high = self.read_bus(if instruction.operand & 0xff == 0xff { - instruction.operand & 0xff00 - } else { - instruction.operand + 1 - }) as u16; - - (high << 8 | low, instruction.get_base_cycle_time(), false) - } - AddressingMode::XIndirect => { - let location_indirect = instruction.operand.wrapping_add(self.reg_x as u16) & 0xff; - let low = self.read_bus(location_indirect) as u16; - let high = self.read_bus((location_indirect + 1) & 0xFF) as u16; - (high << 8 | low, instruction.get_base_cycle_time(), false) - } - AddressingMode::IndirectY => { - let location_indirect = instruction.operand & 0xff; - let low = self.read_bus(location_indirect) as u16; - let high = self.read_bus((location_indirect + 1) & 0xFF) as u16; - - let unindxed_address = high << 8 | low; - let result = unindxed_address + self.reg_y as u16; - - let page_cross = if is_on_same_page(unindxed_address, result) { - 0 - } else { - 1 - }; - - ( - result, - instruction.get_base_cycle_time() + page_cross, - page_cross == 1, - ) - } - AddressingMode::Absolute => ( - instruction.operand, - instruction.get_base_cycle_time(), - false, - ), - AddressingMode::AbsoluteX => { - let result = instruction.operand + self.reg_x as u16; - let page_cross = if is_on_same_page(instruction.operand, result) { - 0 - } else { - 1 - }; - - ( - result, - instruction.get_base_cycle_time() + page_cross, - page_cross == 1, - ) - } - AddressingMode::AbsoluteY => { - let result = instruction.operand + self.reg_y as u16; - let page_cross = if is_on_same_page(instruction.operand, result) { - 0 - } else { - 1 - }; - - ( - result, - instruction.get_base_cycle_time() + page_cross, - page_cross == 1, - ) - } - AddressingMode::Relative => { - let sign_extended_operand = instruction.operand - | if instruction.operand & 0x80 != 0 { - 0xFF00 - } else { - 0x0000 - }; - ( - self.reg_pc.wrapping_add(sign_extended_operand), - instruction.get_base_cycle_time(), - false, - ) - } - AddressingMode::Immediate | AddressingMode::Accumulator | AddressingMode::Implied => ( - instruction.operand, - instruction.get_base_cycle_time(), - false, - ), - } - } - - fn update_zero_negative_flags(&mut self, result: u8) { - self.set_flag_status(StatusFlag::Zero, result == 0); - self.set_flag_status(StatusFlag::Negative, result & 0x80 != 0); - } - - fn run_bitwise_operation(&mut self, decoded_operand: u16, is_operand_address: bool, f: F) - where - F: Fn(u8, u8) -> u8, - { - let operand = if is_operand_address { - self.read_bus(decoded_operand) - } else { - decoded_operand as u8 - }; - - let result = f(operand, self.reg_a); - - self.update_zero_negative_flags(result); - - self.reg_a = result; - } - - fn run_cmp_operation(&mut self, decoded_operand: u16, is_operand_address: bool, register: u8) { - let operand = if is_operand_address { - self.read_bus(decoded_operand) - } else { - decoded_operand as u8 - }; - - let result = (register as u16).wrapping_sub(operand as u16); - - self.update_zero_negative_flags(result as u8); - self.set_flag_status(StatusFlag::Carry, result & 0xff00 == 0); - } - - fn run_branch_condition(&mut self, decoded_operand: u16, condition: bool) -> (u8, CPURunState) { - let mut cycle_time = 0; - - // all branch instructions are 2 bytes, its hardcoded number - // not sure if its good or not - let pc = self.reg_pc.wrapping_sub(2); - - if condition { - cycle_time = if is_on_same_page(self.reg_pc, decoded_operand) { - 1 - } else { - 2 - }; - - self.reg_pc = decoded_operand; - } - - ( - cycle_time, - if condition && decoded_operand == pc { - CPURunState::InfiniteLoop(pc) - } else { - CPURunState::NormalInstructionExecution - }, - ) - } - - fn run_load_instruction(&mut self, decoded_operand: u16, is_operand_address: bool) -> u8 { - let operand = if is_operand_address { - self.read_bus(decoded_operand) - } else { - decoded_operand as u8 - }; - - self.update_zero_negative_flags(operand); - - operand - } - - fn push_stack(&mut self, data: u8) { - self.write_bus(0x0100 | self.reg_sp as u16, data); - self.reg_sp = self.reg_sp.wrapping_sub(1); - } - - fn pull_stack(&mut self) -> u8 { - self.reg_sp = self.reg_sp.wrapping_add(1); - self.read_bus(0x0100 | self.reg_sp as u16) - } - - // is_soft should be only from BRK - fn execute_interrupt(&mut self, is_soft: bool, is_nmi: bool) { - let pc = self.reg_pc; - - let low = pc as u8; - let high = (pc >> 8) as u8; - - self.push_stack(high); - self.push_stack(low); - - self.set_flag_status(StatusFlag::BreakCommand, is_soft); - - self.push_stack(self.reg_status); - - let jump_vector_address = if is_nmi { - NMI_VECTOR_ADDRESS - } else { - IRQ_VECTOR_ADDRESS - }; - - if is_nmi { - // disable after execution, not to stuck in a infinite loop here - self.nmi_pin_status = false; - } else { - self.irq_pin_status = false; - } - - self.set_flag(StatusFlag::InterruptDisable); - - let low = self.read_bus(jump_vector_address) as u16; - let high = self.read_bus(jump_vector_address + 1) as u16; - - let pc = high << 8 | low; - self.reg_pc = pc; - - // delay of interrupt - self.cycles_to_wait += 7; - } - - fn check_for_nmi_dma(&mut self) { - // check if the PPU is setting the NMI pin - if self.bus.is_nmi_pin_set() { - self.nmi_pin_status = true; - self.bus.clear_nmi_pin(); - } - // check if PPU is requesting DMA - if self.bus.is_dma_request() { - self.dma_address = self.bus.dma_address(); - self.dma_remaining = 256; - self.bus.clear_dma_request(); - } - } - - fn check_and_run_dmc_transfer(&mut self) { - let request = self.bus.request_dmc_reader_read(); - - if let Some(addr) = request { - let data = self.read_bus(addr); - - self.bus.submit_dmc_buffer_byte(data); - - // FIXME: respect different clock delay for respective positions to - // steal the clock - self.cycles_to_wait += 3; - } - } - - fn fetch_next_instruction(&mut self) -> Instruction { - let opcode = self.read_bus(self.reg_pc); - self.reg_pc += 1; - - let mut instruction = Instruction::from_byte(opcode); - let len = instruction.get_instruction_len(); - - let mut operand = 0; - - match len { - 2 => { - operand |= self.read_bus(self.reg_pc) as u16; - } - 3 => { - operand |= self.read_bus(self.reg_pc) as u16; - operand |= (self.read_bus(self.reg_pc + 1) as u16) << 8; - } - _ => {} - } - - // 1 => ( +0 ), 2 => ( +1 ), 3 => ( +2 ) - self.reg_pc += (len - 1) as u16; - - instruction.operand = operand; - - instruction - } - - fn run_instruction(&mut self, instruction: &Instruction) -> CPURunState { - let (decoded_operand, cycle_time, did_page_cross) = self.decode_operand(instruction); - let mut cycle_time = cycle_time; - - let is_operand_address = instruction.is_operand_address(); - - let mut state = CPURunState::NormalInstructionExecution; - - match instruction.opcode { - // TODO: Add support for BCD mode - Opcode::Adc => { - let operand = if is_operand_address { - self.read_bus(decoded_operand) - } else { - decoded_operand as u8 - }; - let carry = if self.reg_status & (StatusFlag::Carry as u8) == 0 { - 0 - } else { - 1 - }; - let result = (self.reg_a as u16) - .wrapping_add(operand as u16) - .wrapping_add(carry); - // overflow = result is negative ^ (reg_A is negative | operand is negative) - // meaning, that if the operands are positive but the result is negative, then something - // is not right, and the same way vise versa - self.set_flag_status( - StatusFlag::Overflow, - (((result as u8 ^ self.reg_a) & 0x80) != 0) - && (((operand ^ self.reg_a) & 0x80) == 0), - ); - self.update_zero_negative_flags(result as u8); - self.set_flag_status(StatusFlag::Carry, result & 0xff00 != 0); - self.reg_a = result as u8; - } - Opcode::Asl => { - let mut operand = if is_operand_address { - self.read_bus(decoded_operand) - } else { - // if its not address, then its Accumulator for this instruction - self.reg_a - }; - - // There is a bit at the leftmost position, it will be moved to the carry - self.set_flag_status(StatusFlag::Carry, operand & 0x80 != 0); - - // modify the value - operand <<= 1; - - self.update_zero_negative_flags(operand); - - if is_operand_address { - // save back - self.write_bus(decoded_operand, operand); - - if instruction.addressing_mode == AddressingMode::AbsoluteX { - cycle_time = 7; // special case - } else { - cycle_time += 2; - } - } else { - self.reg_a = operand; - } - } - Opcode::Lsr => { - let mut operand = if is_operand_address { - self.read_bus(decoded_operand) - } else { - // if its not address, then its Accumulator for this instruction - self.reg_a - }; - - // There is a bit at the leftmost position, it will be moved to the carry - self.set_flag_status(StatusFlag::Carry, operand & 0x01 != 0); - - // modify the value - operand >>= 1; - - self.update_zero_negative_flags(operand); - - if is_operand_address { - // save back - self.write_bus(decoded_operand, operand); - - if instruction.addressing_mode == AddressingMode::AbsoluteX { - cycle_time = 7; // special case - } else { - cycle_time += 2; - } - } else { - self.reg_a = operand; - } - } - Opcode::Rol => { - let mut operand = if is_operand_address { - self.read_bus(decoded_operand) - } else { - // if its not address, then its Accumulator for this instruction - self.reg_a - }; - let old_carry = if self.reg_status & (StatusFlag::Carry as u8) == 0 { - 0 - } else { - 1 - }; - // There is a bit at the leftmost position, it will be moved to the carry - self.set_flag_status(StatusFlag::Carry, operand & 0x80 != 0); - // modify the value - operand <<= 1; - operand |= old_carry; - - self.update_zero_negative_flags(operand); - - if is_operand_address { - // save back - self.write_bus(decoded_operand, operand); - - if instruction.addressing_mode == AddressingMode::AbsoluteX { - cycle_time = 7; // special case - } else { - cycle_time += 2; - } - } else { - self.reg_a = operand; - } - } - Opcode::Ror => { - let mut operand = if is_operand_address { - self.read_bus(decoded_operand) - } else { - // if its not address, then its Accumulator for this instruction - self.reg_a - }; - let old_carry = if self.reg_status & (StatusFlag::Carry as u8) == 0 { - 0 - } else { - 1 - }; - // There is a bit at the leftmost position, it will be moved to the carry - self.set_flag_status(StatusFlag::Carry, operand & 0x01 != 0); - // modify the value - operand >>= 1; - operand |= old_carry << 7; - - self.update_zero_negative_flags(operand); - - if is_operand_address { - // save back - self.write_bus(decoded_operand, operand); - - if instruction.addressing_mode == AddressingMode::AbsoluteX { - cycle_time = 7; // special case - } else { - cycle_time += 2; - } - } else { - self.reg_a = operand; - } - } - Opcode::And => { - self.run_bitwise_operation(decoded_operand, is_operand_address, |a, b| a & b); - } - Opcode::Eor => { - self.run_bitwise_operation(decoded_operand, is_operand_address, |a, b| a ^ b); - } - Opcode::Ora => { - self.run_bitwise_operation(decoded_operand, is_operand_address, |a, b| a | b); - } - // TODO: Add support for BCD mode - Opcode::Sbc => { - let operand = if is_operand_address { - self.read_bus(decoded_operand) - } else { - decoded_operand as u8 - }; - // inverse the carry - let carry = if self.reg_status & (StatusFlag::Carry as u8) != 0 { - 0 - } else { - 1 - }; - let result = (self.reg_a as u16) - .wrapping_sub(operand as u16) - .wrapping_sub(carry); - // overflow = (result's sign) & (2nd operand's sign) & !(1st operand's sign) - // this was obtained from binary table - self.set_flag_status( - StatusFlag::Overflow, - ((result as u8 ^ self.reg_a) & 0x80 != 0) - && ((operand ^ self.reg_a) & 0x80 != 0), - ); - self.set_flag_status(StatusFlag::Carry, result & 0xff00 == 0); - self.update_zero_negative_flags(result as u8); - - self.reg_a = result as u8; - } - Opcode::Bit => { - // only Absolute and Zero page - assert!(is_operand_address); - let operand = self.read_bus(decoded_operand); - // move the negative and overflow flags to the status register - self.set_flag_status( - StatusFlag::Negative, - operand & StatusFlag::Negative as u8 != 0, - ); - self.set_flag_status( - StatusFlag::Overflow, - operand & StatusFlag::Overflow as u8 != 0, - ); - - self.set_flag_status(StatusFlag::Zero, operand & self.reg_a == 0); - } - Opcode::Cmp => { - self.run_cmp_operation(decoded_operand, is_operand_address, self.reg_a); - } - Opcode::Cpx => { - self.run_cmp_operation(decoded_operand, is_operand_address, self.reg_x); - } - Opcode::Cpy => { - self.run_cmp_operation(decoded_operand, is_operand_address, self.reg_y); - } - Opcode::Brk => { - // increment the PC for saving - self.reg_pc += 1; - self.execute_interrupt(true, self.nmi_pin_status); - // execute_interrupt will add 7 and this instruction is implied so 2 - // but this instruction only takes 7 not 9, so minus 2 - self.cycles_to_wait -= 2; - } - Opcode::Bcc => { - let (time, run_state) = self.run_branch_condition( - decoded_operand, - self.reg_status & (StatusFlag::Carry as u8) == 0, - ); - cycle_time += time; - state = run_state; - } - Opcode::Bcs => { - let (time, run_state) = self.run_branch_condition( - decoded_operand, - self.reg_status & (StatusFlag::Carry as u8) != 0, - ); - cycle_time += time; - state = run_state; - } - Opcode::Beq => { - let (time, run_state) = self.run_branch_condition( - decoded_operand, - self.reg_status & (StatusFlag::Zero as u8) != 0, - ); - cycle_time += time; - state = run_state; - } - Opcode::Bmi => { - let (time, run_state) = self.run_branch_condition( - decoded_operand, - self.reg_status & (StatusFlag::Negative as u8) != 0, - ); - cycle_time += time; - state = run_state; - } - Opcode::Bne => { - let (time, run_state) = self.run_branch_condition( - decoded_operand, - self.reg_status & (StatusFlag::Zero as u8) == 0, - ); - cycle_time += time; - state = run_state; - } - Opcode::Bpl => { - let (time, run_state) = self.run_branch_condition( - decoded_operand, - self.reg_status & (StatusFlag::Negative as u8) == 0, - ); - cycle_time += time; - state = run_state; - } - Opcode::Bvc => { - let (time, run_state) = self.run_branch_condition( - decoded_operand, - self.reg_status & (StatusFlag::Overflow as u8) == 0, - ); - cycle_time += time; - state = run_state; - } - Opcode::Bvs => { - let (time, run_state) = self.run_branch_condition( - decoded_operand, - self.reg_status & (StatusFlag::Overflow as u8) != 0, - ); - cycle_time += time; - state = run_state; - } - Opcode::Dec => { - assert!(is_operand_address); - - let result = self.read_bus(decoded_operand).wrapping_sub(1); - - self.update_zero_negative_flags(result); - - // put back - self.write_bus(decoded_operand, result); - - if instruction.addressing_mode == AddressingMode::AbsoluteX { - cycle_time = 7; // special case - } else { - cycle_time += 2; - }; - } - Opcode::Inc => { - assert!(is_operand_address); - - let result = self.read_bus(decoded_operand).wrapping_add(1); - - self.update_zero_negative_flags(result); - - // put back - self.write_bus(decoded_operand, result); - - if instruction.addressing_mode == AddressingMode::AbsoluteX { - cycle_time = 7; // special case - } else { - cycle_time += 2; - }; - } - Opcode::Clc => { - self.unset_flag(StatusFlag::Carry); - } - Opcode::Cld => { - self.unset_flag(StatusFlag::DecimalMode); - } - Opcode::Cli => { - self.unset_flag(StatusFlag::InterruptDisable); - } - Opcode::Clv => { - self.unset_flag(StatusFlag::Overflow); - } - Opcode::Sec => { - self.set_flag(StatusFlag::Carry); - } - Opcode::Sed => { - self.set_flag(StatusFlag::DecimalMode); - } - Opcode::Sei => { - self.set_flag(StatusFlag::InterruptDisable); - } - Opcode::Jmp => { - assert!(is_operand_address); - - // this instruction is 3 bytes long in both addressing variants - let pc = self.reg_pc.wrapping_sub(3); - - self.reg_pc = decoded_operand; - - if pc == decoded_operand { - state = CPURunState::InfiniteLoop(pc); - } - - // this instruction has only `Absolute` and `Relative` as adressing modes - cycle_time = if instruction.addressing_mode == AddressingMode::Absolute { - 3 - } else { - 5 - }; - } - Opcode::Jsr => { - assert!(is_operand_address); - - let pc = self.reg_pc - 1; - let low = pc as u8; - let high = (pc >> 8) as u8; - - self.push_stack(high); - self.push_stack(low); - - self.reg_pc = decoded_operand; - - cycle_time = 6; - } - Opcode::Rti => { - let old_status = self.reg_status & 0x30; - self.reg_status = self.pull_stack() | old_status; - - let low = self.pull_stack() as u16; - let high = self.pull_stack() as u16; - - let address = high << 8 | low; - - // unlike RTS, this is the actual address - self.reg_pc = address; - - cycle_time = 6; - } - Opcode::Rts => { - let low = self.pull_stack() as u16; - let high = self.pull_stack() as u16; - - let address = high << 8 | low; - - // go to address + 1 - self.reg_pc = address + 1; - - cycle_time = 6; - } - Opcode::Lda => { - self.reg_a = self.run_load_instruction(decoded_operand, is_operand_address); - } - Opcode::Ldx => { - self.reg_x = self.run_load_instruction(decoded_operand, is_operand_address); - } - Opcode::Ldy => { - self.reg_y = self.run_load_instruction(decoded_operand, is_operand_address); - } - Opcode::Nop => { - // NOTHING - } - Opcode::Dex => { - let result = self.reg_x.wrapping_sub(1); - - self.update_zero_negative_flags(result); - - self.reg_x = result; - } - Opcode::Dey => { - let result = self.reg_y.wrapping_sub(1); - - self.update_zero_negative_flags(result); - - self.reg_y = result; - } - Opcode::Inx => { - let result = self.reg_x.wrapping_add(1); - - self.update_zero_negative_flags(result); - - self.reg_x = result; - } - Opcode::Iny => { - let result = self.reg_y.wrapping_add(1); - - self.update_zero_negative_flags(result); - - self.reg_y = result; - } - Opcode::Tax => { - let result = self.reg_a; - - self.update_zero_negative_flags(result); - - self.reg_x = result; - } - Opcode::Tay => { - let result = self.reg_a; - - self.update_zero_negative_flags(result); - - self.reg_y = result; - } - Opcode::Txa => { - let result = self.reg_x; - - self.update_zero_negative_flags(result); - - self.reg_a = result; - } - Opcode::Tya => { - let result = self.reg_y; - - self.update_zero_negative_flags(result); - - self.reg_a = result; - } - Opcode::Pha => { - self.push_stack(self.reg_a); - - cycle_time = 3; - } - Opcode::Php => { - // bit 4 and 5 must be set - let status = self.reg_status | 0x30; - self.push_stack(status); - - cycle_time = 3; - } - Opcode::Pla => { - let result = self.pull_stack(); - - self.update_zero_negative_flags(result); - - self.reg_a = result; - - cycle_time = 4; - } - Opcode::Plp => { - // Bits 4 and 5 should not be edited - let old_status = self.reg_status & 0x30; - self.reg_status = self.pull_stack() | old_status; - - cycle_time = 4; - } - Opcode::Sta => { - assert!(is_operand_address); - - // STA has a special timing, these addressing modes add one cycle - // in case of page cross, but if its STA, it will always add 1 - if instruction.addressing_mode.can_cross_page() && !did_page_cross { - cycle_time += 1; - } - - self.write_bus(decoded_operand, self.reg_a); - } - Opcode::Stx => { - assert!(is_operand_address); - self.write_bus(decoded_operand, self.reg_x); - } - Opcode::Sty => { - assert!(is_operand_address); - self.write_bus(decoded_operand, self.reg_y); - } - Opcode::Tsx => { - let result = self.reg_sp; - - self.update_zero_negative_flags(result); - - self.reg_x = result; - } - Opcode::Txs => { - // no need to set flags - self.reg_sp = self.reg_x; - } - - // Unofficial instructions - Opcode::Slo => { - let old_cycles_to_wait = self.cycles_to_wait; - self.run_instruction(&Instruction { - opcode_byte: 0, - operand: instruction.operand, - opcode: Opcode::Asl, - addressing_mode: instruction.addressing_mode, - }); - self.run_instruction(&Instruction { - opcode_byte: 0, - operand: instruction.operand, - opcode: Opcode::Ora, - addressing_mode: instruction.addressing_mode, - }); - self.cycles_to_wait = old_cycles_to_wait; - - // its as if the page crossed, even if it did not - let page_cross_increment = - (instruction.addressing_mode.can_cross_page() && !did_page_cross) as u8; - cycle_time += 2 + page_cross_increment; - } - Opcode::Sre => { - let old_cycles_to_wait = self.cycles_to_wait; - self.run_instruction(&Instruction { - opcode_byte: 0, - operand: instruction.operand, - opcode: Opcode::Lsr, - addressing_mode: instruction.addressing_mode, - }); - self.run_instruction(&Instruction { - opcode_byte: 0, - operand: instruction.operand, - opcode: Opcode::Eor, - addressing_mode: instruction.addressing_mode, - }); - self.cycles_to_wait = old_cycles_to_wait; - - // its as if the page crossed, even if it did not - let page_cross_increment = - (instruction.addressing_mode.can_cross_page() && !did_page_cross) as u8; - cycle_time += 2 + page_cross_increment; - } - Opcode::Rla => { - let old_cycles_to_wait = self.cycles_to_wait; - self.run_instruction(&Instruction { - opcode_byte: 0, - operand: instruction.operand, - opcode: Opcode::Rol, - addressing_mode: instruction.addressing_mode, - }); - self.run_instruction(&Instruction { - opcode_byte: 0, - operand: instruction.operand, - opcode: Opcode::And, - addressing_mode: instruction.addressing_mode, - }); - self.cycles_to_wait = old_cycles_to_wait; - - // its as if the page crossed, even if it did not - let page_cross_increment = - (instruction.addressing_mode.can_cross_page() && !did_page_cross) as u8; - cycle_time += 2 + page_cross_increment; - } - Opcode::Rra => { - let old_cycles_to_wait = self.cycles_to_wait; - self.run_instruction(&Instruction { - opcode_byte: 0, - operand: instruction.operand, - opcode: Opcode::Ror, - addressing_mode: instruction.addressing_mode, - }); - self.run_instruction(&Instruction { - opcode_byte: 0, - operand: instruction.operand, - opcode: Opcode::Adc, - addressing_mode: instruction.addressing_mode, - }); - self.cycles_to_wait = old_cycles_to_wait; - - // its as if the page crossed, even if it did not - let page_cross_increment = - (instruction.addressing_mode.can_cross_page() && !did_page_cross) as u8; - cycle_time += 2 + page_cross_increment; - } - Opcode::Isc => { - let old_cycles_to_wait = self.cycles_to_wait; - self.run_instruction(&Instruction { - opcode_byte: 0, - operand: instruction.operand, - opcode: Opcode::Inc, - addressing_mode: instruction.addressing_mode, - }); - self.run_instruction(&Instruction { - opcode_byte: 0, - operand: instruction.operand, - opcode: Opcode::Sbc, - addressing_mode: instruction.addressing_mode, - }); - self.cycles_to_wait = old_cycles_to_wait; - - // its as if the page crossed, even if it did not - let page_cross_increment = - (instruction.addressing_mode.can_cross_page() && !did_page_cross) as u8; - cycle_time += 2 + page_cross_increment; - } - Opcode::Dcp => { - let old_cycles_to_wait = self.cycles_to_wait; - self.run_instruction(&Instruction { - opcode_byte: 0, - operand: instruction.operand, - opcode: Opcode::Dec, - addressing_mode: instruction.addressing_mode, - }); - self.run_instruction(&Instruction { - opcode_byte: 0, - operand: instruction.operand, - opcode: Opcode::Cmp, - addressing_mode: instruction.addressing_mode, - }); - self.cycles_to_wait = old_cycles_to_wait; - - // its as if the page crossed, even if it did not - let page_cross_increment = - (instruction.addressing_mode.can_cross_page() && !did_page_cross) as u8; - cycle_time += 2 + page_cross_increment; - } - Opcode::Sax => { - assert!(is_operand_address); - self.write_bus(decoded_operand, self.reg_x & self.reg_a); - } - Opcode::Lax => { - let old_cycles_to_wait = self.cycles_to_wait; - self.run_instruction(&Instruction { - opcode_byte: 0, - operand: instruction.operand, - opcode: Opcode::Lda, - addressing_mode: instruction.addressing_mode, - }); - self.run_instruction(&Instruction { - opcode_byte: 0, - operand: instruction.operand, - opcode: Opcode::Ldx, - addressing_mode: instruction.addressing_mode, - }); - self.cycles_to_wait = old_cycles_to_wait; - } - Opcode::Anc => { - assert!(instruction.addressing_mode == AddressingMode::Immediate); - - let old_cycles_to_wait = self.cycles_to_wait; - self.run_instruction(&Instruction { - opcode_byte: 0, - operand: instruction.operand, - opcode: Opcode::And, - addressing_mode: instruction.addressing_mode, - }); - self.cycles_to_wait = old_cycles_to_wait; - - self.set_flag_status( - StatusFlag::Carry, - self.reg_status & StatusFlag::Negative as u8 != 0, - ); - } - Opcode::Alr => { - assert!(instruction.addressing_mode == AddressingMode::Immediate); - - let old_cycles_to_wait = self.cycles_to_wait; - self.run_instruction(&Instruction { - opcode_byte: 0, - operand: instruction.operand, - opcode: Opcode::And, - addressing_mode: instruction.addressing_mode, - }); - self.run_instruction(&Instruction { - opcode_byte: 0, - operand: 0, // unused - opcode: Opcode::Lsr, - addressing_mode: AddressingMode::Accumulator, - }); - self.cycles_to_wait = old_cycles_to_wait; - } - Opcode::Arr => { - assert!(instruction.addressing_mode == AddressingMode::Immediate); - - let old_cycles_to_wait = self.cycles_to_wait; - self.run_instruction(&Instruction { - opcode_byte: 0, - operand: instruction.operand, - opcode: Opcode::And, - addressing_mode: instruction.addressing_mode, - }); - self.run_instruction(&Instruction { - opcode_byte: 0, - operand: 0, // unused - opcode: Opcode::Ror, - addressing_mode: AddressingMode::Accumulator, - }); - self.cycles_to_wait = old_cycles_to_wait; - - self.set_flag_status(StatusFlag::Carry, (self.reg_a >> 6) & 1 != 0); - self.set_flag_status( - StatusFlag::Overflow, - ((self.reg_a >> 6) & 1) ^ ((self.reg_a >> 5) & 1) != 0, - ); - } - Opcode::Axs => { - assert!(instruction.addressing_mode == AddressingMode::Immediate); - - let (result, overflow) = - (self.reg_x & self.reg_a).overflowing_sub(decoded_operand as u8); - - self.reg_x = self.run_load_instruction(result as u16, false); - - self.set_flag_status(StatusFlag::Carry, !overflow); - } - Opcode::Xaa => { - assert!(instruction.addressing_mode == AddressingMode::Immediate); - - let old_cycles_to_wait = self.cycles_to_wait; - self.run_instruction(&Instruction { - opcode_byte: 0, - operand: 0, // unused - opcode: Opcode::Txa, - addressing_mode: AddressingMode::Implied, - }); - self.run_instruction(&Instruction { - opcode_byte: 0, - operand: instruction.operand, - opcode: Opcode::And, - addressing_mode: instruction.addressing_mode, - }); - self.cycles_to_wait = old_cycles_to_wait; - } - Opcode::Ahx => { - assert!(is_operand_address); - - let high_byte = (decoded_operand >> 8) as u8; - - self.write_bus(decoded_operand, self.reg_a & self.reg_x & high_byte); - - cycle_time += !did_page_cross as u8; - } - Opcode::Shy => { - assert!(is_operand_address); - - let low_byte = decoded_operand & 0xFF; - let high_byte = (decoded_operand >> 8) as u8; - - let value = self.reg_y & (high_byte + 1); - - self.write_bus((value as u16) << 8 | low_byte, value); - - cycle_time += !did_page_cross as u8; - } - Opcode::Shx => { - assert!(is_operand_address); - - let low_byte = decoded_operand & 0xFF; - let high_byte = (decoded_operand >> 8) as u8; - - let value = self.reg_x & (high_byte + 1); - - self.write_bus((value as u16) << 8 | low_byte, value); - - cycle_time += !did_page_cross as u8; - } - Opcode::Tas => { - assert!(is_operand_address); - - let high_byte = (decoded_operand >> 8) as u8; - - self.reg_sp = self.reg_x & self.reg_a; - - self.write_bus(decoded_operand, self.reg_sp & high_byte); - - cycle_time += !did_page_cross as u8; - } - Opcode::Las => { - assert!(is_operand_address); - - let value = self.read_bus(decoded_operand); - - //set the flags - let result = self.run_load_instruction((value & self.reg_sp) as u16, false); - - self.reg_a = result; - self.reg_x = result; - self.reg_sp = result; - } - Opcode::Kil => { - // TODO: implement halt - println!("KIL instruction executed, should halt...."); - } - }; - - // minus this cycle - self.cycles_to_wait += cycle_time - 1; - - state - } - - fn load_serialized_state(&mut self, state: SavableCPUState) { - self.reg_pc = state.reg_pc; - self.reg_sp = state.reg_sp; - self.reg_a = state.reg_a; - self.reg_x = state.reg_x; - self.reg_y = state.reg_y; - self.reg_status = state.reg_status; - self.nmi_pin_status = state.nmi_pin_status; - self.irq_pin_status = state.irq_pin_status; - self.cycles_to_wait = state.cycles_to_wait; - self.dma_remaining = state.dma_remaining; - self.dma_address = state.dma_address; - self.next_instruction = state.next_instruction; - } -} - -#[derive(Serialize, Deserialize)] -struct SavableCPUState { - reg_pc: u16, - reg_sp: u8, - reg_a: u8, - reg_x: u8, - reg_y: u8, - reg_status: u8, - - nmi_pin_status: bool, - irq_pin_status: bool, - - cycles_to_wait: u8, - - dma_remaining: u16, - dma_address: u8, - - next_instruction: Option<(Instruction, u8)>, -} - -impl SavableCPUState { - fn from_cpu(cpu: &CPU6502) -> Self { - Self { - reg_pc: cpu.reg_pc, - reg_sp: cpu.reg_sp, - reg_a: cpu.reg_a, - reg_x: cpu.reg_x, - reg_y: cpu.reg_y, - reg_status: cpu.reg_status, - nmi_pin_status: cpu.nmi_pin_status, - irq_pin_status: cpu.irq_pin_status, - cycles_to_wait: cpu.cycles_to_wait, - dma_remaining: cpu.dma_remaining, - dma_address: cpu.dma_address, - next_instruction: cpu.next_instruction, - } - } -} - -/// This is a solution to wrap a reference to reader -struct WrapperReader<'a, R: Read> { - pub inner: &'a mut R, -} - -impl<'a, R: Read> Read for WrapperReader<'a, R> { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - self.inner.read(buf) - } -} - -impl Savable for CPU6502 -where - T: CPUBusTrait, -{ - fn save(&self, writer: &mut W) -> Result<(), SaveError> { - let state = SavableCPUState::from_cpu(self); - - let data = bincode::serialize(&state).map_err(|_| SaveError::Others)?; - writer.write_all(data.as_slice())?; - - self.bus.save(writer)?; - - Ok(()) - } - - fn load(&mut self, reader: &mut R) -> Result<(), SaveError> { - let outer_reader = WrapperReader { inner: reader }; - - { - let state: SavableCPUState = - bincode::deserialize_from(outer_reader).map_err(|err| match *err { - bincode::ErrorKind::Io(err) => SaveError::IoError(err), - _ => SaveError::Others, - })?; - - self.load_serialized_state(state); - } - - self.bus.load(reader)?; - - Ok(()) - } -} diff --git a/plastic_core/src/cpu6502/instruction.rs b/plastic_core/src/cpu6502/instruction.rs index f4bfea2..185e60a 100644 --- a/plastic_core/src/cpu6502/instruction.rs +++ b/plastic_core/src/cpu6502/instruction.rs @@ -263,7 +263,6 @@ impl Instruction { } } -#[cfg(not(tarpaulin_include))] impl Display for Opcode { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { use Opcode::*; @@ -364,7 +363,6 @@ impl Display for Opcode { } } -#[cfg(not(tarpaulin_include))] impl Display for Instruction { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { use AddressingMode::*; diff --git a/plastic_core/src/cpu6502/mod.rs b/plastic_core/src/cpu6502/mod.rs index 84d721a..838c3e7 100644 --- a/plastic_core/src/cpu6502/mod.rs +++ b/plastic_core/src/cpu6502/mod.rs @@ -1,13 +1,7 @@ -use crate::common::interconnection::*; -use crate::common::save_state::Savable; - -mod cpu6502; pub mod instruction; - mod tests; -pub use cpu6502::CPURunState; -pub use cpu6502::CPU6502; +use crate::common::interconnection::{APUCPUConnection, CPUIrqProvider, PPUCPUConnection}; pub trait CPUBusTrait: Savable + PPUCPUConnection + APUCPUConnection + CPUIrqProvider { fn read(&self, address: u16) -> u8; @@ -16,3 +10,1466 @@ pub trait CPUBusTrait: Savable + PPUCPUConnection + APUCPUConnection + CPUIrqPro fn reset(&mut self); } + +use crate::common::save_state::{Savable, SaveError}; +use instruction::{AddressingMode, Instruction, Opcode}; +use serde::{Deserialize, Serialize}; +use std::io::{Read, Write}; + +const NMI_VECTOR_ADDRESS: u16 = 0xFFFA; +const RESET_VECTOR_ADDRESS: u16 = 0xFFFC; +const IRQ_VECTOR_ADDRESS: u16 = 0xFFFE; + +#[derive(PartialEq)] +pub enum CPURunState { + DmaTransfere, + Waiting, + InfiniteLoop(u16), + StartingInterrupt, + NormalInstructionExecution, +} + +// helper function +fn is_on_same_page(address1: u16, address2: u16) -> bool { + address1 & 0xff00 == address2 & 0xff00 +} + +// flags: [N, V, _, B, D, I, Z, C] +enum StatusFlag { + Carry = 1 << 0, + Zero = 1 << 1, + InterruptDisable = 1 << 2, + DecimalMode = 1 << 3, + BreakCommand = 1 << 4, + Overflow = 1 << 6, + Negative = 1 << 7, +} + +// TODO: this CPU does not support BCD mode yet +pub struct CPU6502 { + reg_pc: u16, + reg_sp: u8, + reg_a: u8, + reg_x: u8, + reg_y: u8, + reg_status: u8, + + nmi_pin_status: bool, + irq_pin_status: bool, + + cycles_to_wait: u8, + + dma_remaining: u16, + dma_address: u8, + + /// a buffer to hold the next_instruction before execution, + /// check `run_next` for more info + next_instruction: Option<(Instruction, u8)>, + + bus: T, +} + +// public +impl CPU6502 +where + T: CPUBusTrait, +{ + pub fn new(bus: T) -> Self { + CPU6502 { + reg_pc: 0, + reg_sp: 0, + reg_a: 0, + reg_x: 0, + reg_y: 0, + reg_status: 0, + + nmi_pin_status: false, + irq_pin_status: false, + + cycles_to_wait: 0, + + dma_remaining: 0, + dma_address: 0, + + next_instruction: None, + + bus, + } + } + + pub fn reset(&mut self) { + // reset registers and other variables + self.reg_pc = 0; + self.reg_sp = 0; + self.reg_a = 0; + self.reg_x = 0; + self.reg_y = 0; + self.reg_status = 0; + + self.nmi_pin_status = false; + self.irq_pin_status = false; + + self.cycles_to_wait = 0; + + self.dma_remaining = 0; + self.dma_address = 0; + + self.set_flag(StatusFlag::InterruptDisable); + self.reg_sp = 0xFD; //reset + + let low = self.read_bus(RESET_VECTOR_ADDRESS) as u16; + let high = self.read_bus(RESET_VECTOR_ADDRESS + 1) as u16; + + let pc = high << 8 | low; + self.reg_pc = pc; + + self.cycles_to_wait += 7; + } + + pub fn reset_bus(&mut self) { + self.bus.reset() + } + + #[cfg(test)] + pub fn bus(&self) -> &T { + &self.bus + } + + pub fn run_next(&mut self) -> CPURunState { + self.check_and_run_dmc_transfer(); + + if self.cycles_to_wait == 0 && self.next_instruction.is_none() { + // are we still executing the DMA transfer instruction? + if self.dma_remaining > 0 { + self.dma_remaining -= 1; + { + // send one byte at a time + let oma_address = (255 - self.dma_remaining) & 0xFF; + let cpu_address = (self.dma_address as u16) << 8 | oma_address; + + let data = self.read_bus(cpu_address); + + self.bus.send_oam_data(oma_address as u8, data); + } + + // since it should read in one cycle and write in the other cycle + self.cycles_to_wait = 1; + CPURunState::DmaTransfere + } else if self.nmi_pin_status + || (self.irq_pin_status + && self.reg_status & StatusFlag::InterruptDisable as u8 == 0) + { + // execute interrupt + // hardware side interrupt + self.execute_interrupt(false, self.nmi_pin_status); + CPURunState::StartingInterrupt + } else { + // check for NMI and DMA and apply them only after the next + // instruction + self.check_for_nmi_dma(); + // check if there is pending IRQs from cartridge + if self.bus.is_irq_change_requested() { + self.irq_pin_status = self.bus.irq_pin_state(); + self.bus.clear_irq_request_pin() + } + + // reload the next instruction in `the next_instruction` buffer + let instruction = self.fetch_next_instruction(); + let (_, mut cycle_time, _) = self.decode_operand(&instruction); + + // only the JMP instruction has lesser time than the base time + if instruction.opcode == Opcode::Jmp { + // this instruction has only `Absolute` and `Relative` as adressing modes + cycle_time = if instruction.addressing_mode == AddressingMode::Absolute { + 3 + } else { + 5 + }; + } + + // wait for the base time minus this cycle + self.cycles_to_wait += cycle_time - 1; + + self.next_instruction = Some((instruction, cycle_time)); + + CPURunState::Waiting + } + } else if self.cycles_to_wait == 1 && self.next_instruction.is_some() { + // on the last clock, run the instruction that was saved + + // this can be seen as `self.cycles_to_wait -= 1;` because we + // know its 1 in this stage + self.cycles_to_wait = 0; + + let (instruction, cycle_time) = self.next_instruction.take().unwrap(); + + let return_state = self.run_instruction(&instruction); + + // `run_instruction` will set `self.cycles_to_wait` to the amount + // of cycles to wait minus 1, but before we have already waited + // `cycle_time` cycles (excluding this one), so subtract that + // and if anything remains then wait for it + self.cycles_to_wait -= cycle_time - 1; + + return_state + } else { + self.cycles_to_wait -= 1; + CPURunState::Waiting + } + } +} + +// private +impl CPU6502 +where + T: CPUBusTrait, +{ + fn set_flag(&mut self, flag: StatusFlag) { + self.reg_status |= flag as u8; + } + + fn unset_flag(&mut self, flag: StatusFlag) { + self.reg_status &= !(flag as u8); + } + + fn set_flag_status(&mut self, flag: StatusFlag, status: bool) { + if status { + self.set_flag(flag) + } else { + self.unset_flag(flag) + } + } + + fn read_bus(&self, address: u16) -> u8 { + self.bus.read(address) + } + + fn write_bus(&mut self, address: u16, data: u8) { + self.bus.write(address, data); + } + + /// decods the operand of an instruction and returnrs + /// (the decoded_operand, base cycle time for the instruction, has crossed page) + fn decode_operand(&self, instruction: &Instruction) -> (u16, u8, bool) { + match instruction.addressing_mode { + AddressingMode::ZeroPage => ( + instruction.operand & 0xff, + instruction.get_base_cycle_time(), + false, + ), + AddressingMode::ZeroPageIndexX => ( + (instruction.operand + self.reg_x as u16) & 0xff, + instruction.get_base_cycle_time(), + false, + ), + + AddressingMode::ZeroPageIndexY => ( + (instruction.operand + self.reg_y as u16) & 0xff, + instruction.get_base_cycle_time(), + false, + ), + + AddressingMode::Indirect => { + let low = self.read_bus(instruction.operand) as u16; + // if the indirect vector is at the last of the page (0xff) then + // wrap around on the same page + let high = self.read_bus(if instruction.operand & 0xff == 0xff { + instruction.operand & 0xff00 + } else { + instruction.operand + 1 + }) as u16; + + (high << 8 | low, instruction.get_base_cycle_time(), false) + } + AddressingMode::XIndirect => { + let location_indirect = instruction.operand.wrapping_add(self.reg_x as u16) & 0xff; + let low = self.read_bus(location_indirect) as u16; + let high = self.read_bus((location_indirect + 1) & 0xFF) as u16; + (high << 8 | low, instruction.get_base_cycle_time(), false) + } + AddressingMode::IndirectY => { + let location_indirect = instruction.operand & 0xff; + let low = self.read_bus(location_indirect) as u16; + let high = self.read_bus((location_indirect + 1) & 0xFF) as u16; + + let unindxed_address = high << 8 | low; + let result = unindxed_address + self.reg_y as u16; + + let page_cross = if is_on_same_page(unindxed_address, result) { + 0 + } else { + 1 + }; + + ( + result, + instruction.get_base_cycle_time() + page_cross, + page_cross == 1, + ) + } + AddressingMode::Absolute => ( + instruction.operand, + instruction.get_base_cycle_time(), + false, + ), + AddressingMode::AbsoluteX => { + let result = instruction.operand + self.reg_x as u16; + let page_cross = if is_on_same_page(instruction.operand, result) { + 0 + } else { + 1 + }; + + ( + result, + instruction.get_base_cycle_time() + page_cross, + page_cross == 1, + ) + } + AddressingMode::AbsoluteY => { + let result = instruction.operand + self.reg_y as u16; + let page_cross = if is_on_same_page(instruction.operand, result) { + 0 + } else { + 1 + }; + + ( + result, + instruction.get_base_cycle_time() + page_cross, + page_cross == 1, + ) + } + AddressingMode::Relative => { + let sign_extended_operand = instruction.operand + | if instruction.operand & 0x80 != 0 { + 0xFF00 + } else { + 0x0000 + }; + ( + self.reg_pc.wrapping_add(sign_extended_operand), + instruction.get_base_cycle_time(), + false, + ) + } + AddressingMode::Immediate | AddressingMode::Accumulator | AddressingMode::Implied => ( + instruction.operand, + instruction.get_base_cycle_time(), + false, + ), + } + } + + fn update_zero_negative_flags(&mut self, result: u8) { + self.set_flag_status(StatusFlag::Zero, result == 0); + self.set_flag_status(StatusFlag::Negative, result & 0x80 != 0); + } + + fn run_bitwise_operation(&mut self, decoded_operand: u16, is_operand_address: bool, f: F) + where + F: Fn(u8, u8) -> u8, + { + let operand = if is_operand_address { + self.read_bus(decoded_operand) + } else { + decoded_operand as u8 + }; + + let result = f(operand, self.reg_a); + + self.update_zero_negative_flags(result); + + self.reg_a = result; + } + + fn run_cmp_operation(&mut self, decoded_operand: u16, is_operand_address: bool, register: u8) { + let operand = if is_operand_address { + self.read_bus(decoded_operand) + } else { + decoded_operand as u8 + }; + + let result = (register as u16).wrapping_sub(operand as u16); + + self.update_zero_negative_flags(result as u8); + self.set_flag_status(StatusFlag::Carry, result & 0xff00 == 0); + } + + fn run_branch_condition(&mut self, decoded_operand: u16, condition: bool) -> (u8, CPURunState) { + let mut cycle_time = 0; + + // all branch instructions are 2 bytes, its hardcoded number + // not sure if its good or not + let pc = self.reg_pc.wrapping_sub(2); + + if condition { + cycle_time = if is_on_same_page(self.reg_pc, decoded_operand) { + 1 + } else { + 2 + }; + + self.reg_pc = decoded_operand; + } + + ( + cycle_time, + if condition && decoded_operand == pc { + CPURunState::InfiniteLoop(pc) + } else { + CPURunState::NormalInstructionExecution + }, + ) + } + + fn run_load_instruction(&mut self, decoded_operand: u16, is_operand_address: bool) -> u8 { + let operand = if is_operand_address { + self.read_bus(decoded_operand) + } else { + decoded_operand as u8 + }; + + self.update_zero_negative_flags(operand); + + operand + } + + fn push_stack(&mut self, data: u8) { + self.write_bus(0x0100 | self.reg_sp as u16, data); + self.reg_sp = self.reg_sp.wrapping_sub(1); + } + + fn pull_stack(&mut self) -> u8 { + self.reg_sp = self.reg_sp.wrapping_add(1); + self.read_bus(0x0100 | self.reg_sp as u16) + } + + // is_soft should be only from BRK + fn execute_interrupt(&mut self, is_soft: bool, is_nmi: bool) { + let pc = self.reg_pc; + + let low = pc as u8; + let high = (pc >> 8) as u8; + + self.push_stack(high); + self.push_stack(low); + + self.set_flag_status(StatusFlag::BreakCommand, is_soft); + + self.push_stack(self.reg_status); + + let jump_vector_address = if is_nmi { + NMI_VECTOR_ADDRESS + } else { + IRQ_VECTOR_ADDRESS + }; + + if is_nmi { + // disable after execution, not to stuck in a infinite loop here + self.nmi_pin_status = false; + } else { + self.irq_pin_status = false; + } + + self.set_flag(StatusFlag::InterruptDisable); + + let low = self.read_bus(jump_vector_address) as u16; + let high = self.read_bus(jump_vector_address + 1) as u16; + + let pc = high << 8 | low; + self.reg_pc = pc; + + // delay of interrupt + self.cycles_to_wait += 7; + } + + fn check_for_nmi_dma(&mut self) { + // check if the PPU is setting the NMI pin + if self.bus.is_nmi_pin_set() { + self.nmi_pin_status = true; + self.bus.clear_nmi_pin(); + } + // check if PPU is requesting DMA + if self.bus.is_dma_request() { + self.dma_address = self.bus.dma_address(); + self.dma_remaining = 256; + self.bus.clear_dma_request(); + } + } + + fn check_and_run_dmc_transfer(&mut self) { + let request = self.bus.request_dmc_reader_read(); + + if let Some(addr) = request { + let data = self.read_bus(addr); + + self.bus.submit_dmc_buffer_byte(data); + + // FIXME: respect different clock delay for respective positions to + // steal the clock + self.cycles_to_wait += 3; + } + } + + fn fetch_next_instruction(&mut self) -> Instruction { + let opcode = self.read_bus(self.reg_pc); + self.reg_pc += 1; + + let mut instruction = Instruction::from_byte(opcode); + let len = instruction.get_instruction_len(); + + let mut operand = 0; + + match len { + 2 => { + operand |= self.read_bus(self.reg_pc) as u16; + } + 3 => { + operand |= self.read_bus(self.reg_pc) as u16; + operand |= (self.read_bus(self.reg_pc + 1) as u16) << 8; + } + _ => {} + } + + // 1 => ( +0 ), 2 => ( +1 ), 3 => ( +2 ) + self.reg_pc += (len - 1) as u16; + + instruction.operand = operand; + + instruction + } + + fn run_instruction(&mut self, instruction: &Instruction) -> CPURunState { + let (decoded_operand, cycle_time, did_page_cross) = self.decode_operand(instruction); + let mut cycle_time = cycle_time; + + let is_operand_address = instruction.is_operand_address(); + + let mut state = CPURunState::NormalInstructionExecution; + + match instruction.opcode { + // TODO: Add support for BCD mode + Opcode::Adc => { + let operand = if is_operand_address { + self.read_bus(decoded_operand) + } else { + decoded_operand as u8 + }; + let carry = if self.reg_status & (StatusFlag::Carry as u8) == 0 { + 0 + } else { + 1 + }; + let result = (self.reg_a as u16) + .wrapping_add(operand as u16) + .wrapping_add(carry); + // overflow = result is negative ^ (reg_A is negative | operand is negative) + // meaning, that if the operands are positive but the result is negative, then something + // is not right, and the same way vise versa + self.set_flag_status( + StatusFlag::Overflow, + (((result as u8 ^ self.reg_a) & 0x80) != 0) + && (((operand ^ self.reg_a) & 0x80) == 0), + ); + self.update_zero_negative_flags(result as u8); + self.set_flag_status(StatusFlag::Carry, result & 0xff00 != 0); + self.reg_a = result as u8; + } + Opcode::Asl => { + let mut operand = if is_operand_address { + self.read_bus(decoded_operand) + } else { + // if its not address, then its Accumulator for this instruction + self.reg_a + }; + + // There is a bit at the leftmost position, it will be moved to the carry + self.set_flag_status(StatusFlag::Carry, operand & 0x80 != 0); + + // modify the value + operand <<= 1; + + self.update_zero_negative_flags(operand); + + if is_operand_address { + // save back + self.write_bus(decoded_operand, operand); + + if instruction.addressing_mode == AddressingMode::AbsoluteX { + cycle_time = 7; // special case + } else { + cycle_time += 2; + } + } else { + self.reg_a = operand; + } + } + Opcode::Lsr => { + let mut operand = if is_operand_address { + self.read_bus(decoded_operand) + } else { + // if its not address, then its Accumulator for this instruction + self.reg_a + }; + + // There is a bit at the leftmost position, it will be moved to the carry + self.set_flag_status(StatusFlag::Carry, operand & 0x01 != 0); + + // modify the value + operand >>= 1; + + self.update_zero_negative_flags(operand); + + if is_operand_address { + // save back + self.write_bus(decoded_operand, operand); + + if instruction.addressing_mode == AddressingMode::AbsoluteX { + cycle_time = 7; // special case + } else { + cycle_time += 2; + } + } else { + self.reg_a = operand; + } + } + Opcode::Rol => { + let mut operand = if is_operand_address { + self.read_bus(decoded_operand) + } else { + // if its not address, then its Accumulator for this instruction + self.reg_a + }; + let old_carry = if self.reg_status & (StatusFlag::Carry as u8) == 0 { + 0 + } else { + 1 + }; + // There is a bit at the leftmost position, it will be moved to the carry + self.set_flag_status(StatusFlag::Carry, operand & 0x80 != 0); + // modify the value + operand <<= 1; + operand |= old_carry; + + self.update_zero_negative_flags(operand); + + if is_operand_address { + // save back + self.write_bus(decoded_operand, operand); + + if instruction.addressing_mode == AddressingMode::AbsoluteX { + cycle_time = 7; // special case + } else { + cycle_time += 2; + } + } else { + self.reg_a = operand; + } + } + Opcode::Ror => { + let mut operand = if is_operand_address { + self.read_bus(decoded_operand) + } else { + // if its not address, then its Accumulator for this instruction + self.reg_a + }; + let old_carry = if self.reg_status & (StatusFlag::Carry as u8) == 0 { + 0 + } else { + 1 + }; + // There is a bit at the leftmost position, it will be moved to the carry + self.set_flag_status(StatusFlag::Carry, operand & 0x01 != 0); + // modify the value + operand >>= 1; + operand |= old_carry << 7; + + self.update_zero_negative_flags(operand); + + if is_operand_address { + // save back + self.write_bus(decoded_operand, operand); + + if instruction.addressing_mode == AddressingMode::AbsoluteX { + cycle_time = 7; // special case + } else { + cycle_time += 2; + } + } else { + self.reg_a = operand; + } + } + Opcode::And => { + self.run_bitwise_operation(decoded_operand, is_operand_address, |a, b| a & b); + } + Opcode::Eor => { + self.run_bitwise_operation(decoded_operand, is_operand_address, |a, b| a ^ b); + } + Opcode::Ora => { + self.run_bitwise_operation(decoded_operand, is_operand_address, |a, b| a | b); + } + // TODO: Add support for BCD mode + Opcode::Sbc => { + let operand = if is_operand_address { + self.read_bus(decoded_operand) + } else { + decoded_operand as u8 + }; + // inverse the carry + let carry = if self.reg_status & (StatusFlag::Carry as u8) != 0 { + 0 + } else { + 1 + }; + let result = (self.reg_a as u16) + .wrapping_sub(operand as u16) + .wrapping_sub(carry); + // overflow = (result's sign) & (2nd operand's sign) & !(1st operand's sign) + // this was obtained from binary table + self.set_flag_status( + StatusFlag::Overflow, + ((result as u8 ^ self.reg_a) & 0x80 != 0) + && ((operand ^ self.reg_a) & 0x80 != 0), + ); + self.set_flag_status(StatusFlag::Carry, result & 0xff00 == 0); + self.update_zero_negative_flags(result as u8); + + self.reg_a = result as u8; + } + Opcode::Bit => { + // only Absolute and Zero page + assert!(is_operand_address); + let operand = self.read_bus(decoded_operand); + // move the negative and overflow flags to the status register + self.set_flag_status( + StatusFlag::Negative, + operand & StatusFlag::Negative as u8 != 0, + ); + self.set_flag_status( + StatusFlag::Overflow, + operand & StatusFlag::Overflow as u8 != 0, + ); + + self.set_flag_status(StatusFlag::Zero, operand & self.reg_a == 0); + } + Opcode::Cmp => { + self.run_cmp_operation(decoded_operand, is_operand_address, self.reg_a); + } + Opcode::Cpx => { + self.run_cmp_operation(decoded_operand, is_operand_address, self.reg_x); + } + Opcode::Cpy => { + self.run_cmp_operation(decoded_operand, is_operand_address, self.reg_y); + } + Opcode::Brk => { + // increment the PC for saving + self.reg_pc += 1; + self.execute_interrupt(true, self.nmi_pin_status); + // execute_interrupt will add 7 and this instruction is implied so 2 + // but this instruction only takes 7 not 9, so minus 2 + self.cycles_to_wait -= 2; + } + Opcode::Bcc => { + let (time, run_state) = self.run_branch_condition( + decoded_operand, + self.reg_status & (StatusFlag::Carry as u8) == 0, + ); + cycle_time += time; + state = run_state; + } + Opcode::Bcs => { + let (time, run_state) = self.run_branch_condition( + decoded_operand, + self.reg_status & (StatusFlag::Carry as u8) != 0, + ); + cycle_time += time; + state = run_state; + } + Opcode::Beq => { + let (time, run_state) = self.run_branch_condition( + decoded_operand, + self.reg_status & (StatusFlag::Zero as u8) != 0, + ); + cycle_time += time; + state = run_state; + } + Opcode::Bmi => { + let (time, run_state) = self.run_branch_condition( + decoded_operand, + self.reg_status & (StatusFlag::Negative as u8) != 0, + ); + cycle_time += time; + state = run_state; + } + Opcode::Bne => { + let (time, run_state) = self.run_branch_condition( + decoded_operand, + self.reg_status & (StatusFlag::Zero as u8) == 0, + ); + cycle_time += time; + state = run_state; + } + Opcode::Bpl => { + let (time, run_state) = self.run_branch_condition( + decoded_operand, + self.reg_status & (StatusFlag::Negative as u8) == 0, + ); + cycle_time += time; + state = run_state; + } + Opcode::Bvc => { + let (time, run_state) = self.run_branch_condition( + decoded_operand, + self.reg_status & (StatusFlag::Overflow as u8) == 0, + ); + cycle_time += time; + state = run_state; + } + Opcode::Bvs => { + let (time, run_state) = self.run_branch_condition( + decoded_operand, + self.reg_status & (StatusFlag::Overflow as u8) != 0, + ); + cycle_time += time; + state = run_state; + } + Opcode::Dec => { + assert!(is_operand_address); + + let result = self.read_bus(decoded_operand).wrapping_sub(1); + + self.update_zero_negative_flags(result); + + // put back + self.write_bus(decoded_operand, result); + + if instruction.addressing_mode == AddressingMode::AbsoluteX { + cycle_time = 7; // special case + } else { + cycle_time += 2; + }; + } + Opcode::Inc => { + assert!(is_operand_address); + + let result = self.read_bus(decoded_operand).wrapping_add(1); + + self.update_zero_negative_flags(result); + + // put back + self.write_bus(decoded_operand, result); + + if instruction.addressing_mode == AddressingMode::AbsoluteX { + cycle_time = 7; // special case + } else { + cycle_time += 2; + }; + } + Opcode::Clc => { + self.unset_flag(StatusFlag::Carry); + } + Opcode::Cld => { + self.unset_flag(StatusFlag::DecimalMode); + } + Opcode::Cli => { + self.unset_flag(StatusFlag::InterruptDisable); + } + Opcode::Clv => { + self.unset_flag(StatusFlag::Overflow); + } + Opcode::Sec => { + self.set_flag(StatusFlag::Carry); + } + Opcode::Sed => { + self.set_flag(StatusFlag::DecimalMode); + } + Opcode::Sei => { + self.set_flag(StatusFlag::InterruptDisable); + } + Opcode::Jmp => { + assert!(is_operand_address); + + // this instruction is 3 bytes long in both addressing variants + let pc = self.reg_pc.wrapping_sub(3); + + self.reg_pc = decoded_operand; + + if pc == decoded_operand { + state = CPURunState::InfiniteLoop(pc); + } + + // this instruction has only `Absolute` and `Relative` as adressing modes + cycle_time = if instruction.addressing_mode == AddressingMode::Absolute { + 3 + } else { + 5 + }; + } + Opcode::Jsr => { + assert!(is_operand_address); + + let pc = self.reg_pc - 1; + let low = pc as u8; + let high = (pc >> 8) as u8; + + self.push_stack(high); + self.push_stack(low); + + self.reg_pc = decoded_operand; + + cycle_time = 6; + } + Opcode::Rti => { + let old_status = self.reg_status & 0x30; + self.reg_status = self.pull_stack() | old_status; + + let low = self.pull_stack() as u16; + let high = self.pull_stack() as u16; + + let address = high << 8 | low; + + // unlike RTS, this is the actual address + self.reg_pc = address; + + cycle_time = 6; + } + Opcode::Rts => { + let low = self.pull_stack() as u16; + let high = self.pull_stack() as u16; + + let address = high << 8 | low; + + // go to address + 1 + self.reg_pc = address + 1; + + cycle_time = 6; + } + Opcode::Lda => { + self.reg_a = self.run_load_instruction(decoded_operand, is_operand_address); + } + Opcode::Ldx => { + self.reg_x = self.run_load_instruction(decoded_operand, is_operand_address); + } + Opcode::Ldy => { + self.reg_y = self.run_load_instruction(decoded_operand, is_operand_address); + } + Opcode::Nop => { + // NOTHING + } + Opcode::Dex => { + let result = self.reg_x.wrapping_sub(1); + + self.update_zero_negative_flags(result); + + self.reg_x = result; + } + Opcode::Dey => { + let result = self.reg_y.wrapping_sub(1); + + self.update_zero_negative_flags(result); + + self.reg_y = result; + } + Opcode::Inx => { + let result = self.reg_x.wrapping_add(1); + + self.update_zero_negative_flags(result); + + self.reg_x = result; + } + Opcode::Iny => { + let result = self.reg_y.wrapping_add(1); + + self.update_zero_negative_flags(result); + + self.reg_y = result; + } + Opcode::Tax => { + let result = self.reg_a; + + self.update_zero_negative_flags(result); + + self.reg_x = result; + } + Opcode::Tay => { + let result = self.reg_a; + + self.update_zero_negative_flags(result); + + self.reg_y = result; + } + Opcode::Txa => { + let result = self.reg_x; + + self.update_zero_negative_flags(result); + + self.reg_a = result; + } + Opcode::Tya => { + let result = self.reg_y; + + self.update_zero_negative_flags(result); + + self.reg_a = result; + } + Opcode::Pha => { + self.push_stack(self.reg_a); + + cycle_time = 3; + } + Opcode::Php => { + // bit 4 and 5 must be set + let status = self.reg_status | 0x30; + self.push_stack(status); + + cycle_time = 3; + } + Opcode::Pla => { + let result = self.pull_stack(); + + self.update_zero_negative_flags(result); + + self.reg_a = result; + + cycle_time = 4; + } + Opcode::Plp => { + // Bits 4 and 5 should not be edited + let old_status = self.reg_status & 0x30; + self.reg_status = self.pull_stack() | old_status; + + cycle_time = 4; + } + Opcode::Sta => { + assert!(is_operand_address); + + // STA has a special timing, these addressing modes add one cycle + // in case of page cross, but if its STA, it will always add 1 + if instruction.addressing_mode.can_cross_page() && !did_page_cross { + cycle_time += 1; + } + + self.write_bus(decoded_operand, self.reg_a); + } + Opcode::Stx => { + assert!(is_operand_address); + self.write_bus(decoded_operand, self.reg_x); + } + Opcode::Sty => { + assert!(is_operand_address); + self.write_bus(decoded_operand, self.reg_y); + } + Opcode::Tsx => { + let result = self.reg_sp; + + self.update_zero_negative_flags(result); + + self.reg_x = result; + } + Opcode::Txs => { + // no need to set flags + self.reg_sp = self.reg_x; + } + + // Unofficial instructions + Opcode::Slo => { + let old_cycles_to_wait = self.cycles_to_wait; + self.run_instruction(&Instruction { + opcode_byte: 0, + operand: instruction.operand, + opcode: Opcode::Asl, + addressing_mode: instruction.addressing_mode, + }); + self.run_instruction(&Instruction { + opcode_byte: 0, + operand: instruction.operand, + opcode: Opcode::Ora, + addressing_mode: instruction.addressing_mode, + }); + self.cycles_to_wait = old_cycles_to_wait; + + // its as if the page crossed, even if it did not + let page_cross_increment = + (instruction.addressing_mode.can_cross_page() && !did_page_cross) as u8; + cycle_time += 2 + page_cross_increment; + } + Opcode::Sre => { + let old_cycles_to_wait = self.cycles_to_wait; + self.run_instruction(&Instruction { + opcode_byte: 0, + operand: instruction.operand, + opcode: Opcode::Lsr, + addressing_mode: instruction.addressing_mode, + }); + self.run_instruction(&Instruction { + opcode_byte: 0, + operand: instruction.operand, + opcode: Opcode::Eor, + addressing_mode: instruction.addressing_mode, + }); + self.cycles_to_wait = old_cycles_to_wait; + + // its as if the page crossed, even if it did not + let page_cross_increment = + (instruction.addressing_mode.can_cross_page() && !did_page_cross) as u8; + cycle_time += 2 + page_cross_increment; + } + Opcode::Rla => { + let old_cycles_to_wait = self.cycles_to_wait; + self.run_instruction(&Instruction { + opcode_byte: 0, + operand: instruction.operand, + opcode: Opcode::Rol, + addressing_mode: instruction.addressing_mode, + }); + self.run_instruction(&Instruction { + opcode_byte: 0, + operand: instruction.operand, + opcode: Opcode::And, + addressing_mode: instruction.addressing_mode, + }); + self.cycles_to_wait = old_cycles_to_wait; + + // its as if the page crossed, even if it did not + let page_cross_increment = + (instruction.addressing_mode.can_cross_page() && !did_page_cross) as u8; + cycle_time += 2 + page_cross_increment; + } + Opcode::Rra => { + let old_cycles_to_wait = self.cycles_to_wait; + self.run_instruction(&Instruction { + opcode_byte: 0, + operand: instruction.operand, + opcode: Opcode::Ror, + addressing_mode: instruction.addressing_mode, + }); + self.run_instruction(&Instruction { + opcode_byte: 0, + operand: instruction.operand, + opcode: Opcode::Adc, + addressing_mode: instruction.addressing_mode, + }); + self.cycles_to_wait = old_cycles_to_wait; + + // its as if the page crossed, even if it did not + let page_cross_increment = + (instruction.addressing_mode.can_cross_page() && !did_page_cross) as u8; + cycle_time += 2 + page_cross_increment; + } + Opcode::Isc => { + let old_cycles_to_wait = self.cycles_to_wait; + self.run_instruction(&Instruction { + opcode_byte: 0, + operand: instruction.operand, + opcode: Opcode::Inc, + addressing_mode: instruction.addressing_mode, + }); + self.run_instruction(&Instruction { + opcode_byte: 0, + operand: instruction.operand, + opcode: Opcode::Sbc, + addressing_mode: instruction.addressing_mode, + }); + self.cycles_to_wait = old_cycles_to_wait; + + // its as if the page crossed, even if it did not + let page_cross_increment = + (instruction.addressing_mode.can_cross_page() && !did_page_cross) as u8; + cycle_time += 2 + page_cross_increment; + } + Opcode::Dcp => { + let old_cycles_to_wait = self.cycles_to_wait; + self.run_instruction(&Instruction { + opcode_byte: 0, + operand: instruction.operand, + opcode: Opcode::Dec, + addressing_mode: instruction.addressing_mode, + }); + self.run_instruction(&Instruction { + opcode_byte: 0, + operand: instruction.operand, + opcode: Opcode::Cmp, + addressing_mode: instruction.addressing_mode, + }); + self.cycles_to_wait = old_cycles_to_wait; + + // its as if the page crossed, even if it did not + let page_cross_increment = + (instruction.addressing_mode.can_cross_page() && !did_page_cross) as u8; + cycle_time += 2 + page_cross_increment; + } + Opcode::Sax => { + assert!(is_operand_address); + self.write_bus(decoded_operand, self.reg_x & self.reg_a); + } + Opcode::Lax => { + let old_cycles_to_wait = self.cycles_to_wait; + self.run_instruction(&Instruction { + opcode_byte: 0, + operand: instruction.operand, + opcode: Opcode::Lda, + addressing_mode: instruction.addressing_mode, + }); + self.run_instruction(&Instruction { + opcode_byte: 0, + operand: instruction.operand, + opcode: Opcode::Ldx, + addressing_mode: instruction.addressing_mode, + }); + self.cycles_to_wait = old_cycles_to_wait; + } + Opcode::Anc => { + assert!(instruction.addressing_mode == AddressingMode::Immediate); + + let old_cycles_to_wait = self.cycles_to_wait; + self.run_instruction(&Instruction { + opcode_byte: 0, + operand: instruction.operand, + opcode: Opcode::And, + addressing_mode: instruction.addressing_mode, + }); + self.cycles_to_wait = old_cycles_to_wait; + + self.set_flag_status( + StatusFlag::Carry, + self.reg_status & StatusFlag::Negative as u8 != 0, + ); + } + Opcode::Alr => { + assert!(instruction.addressing_mode == AddressingMode::Immediate); + + let old_cycles_to_wait = self.cycles_to_wait; + self.run_instruction(&Instruction { + opcode_byte: 0, + operand: instruction.operand, + opcode: Opcode::And, + addressing_mode: instruction.addressing_mode, + }); + self.run_instruction(&Instruction { + opcode_byte: 0, + operand: 0, // unused + opcode: Opcode::Lsr, + addressing_mode: AddressingMode::Accumulator, + }); + self.cycles_to_wait = old_cycles_to_wait; + } + Opcode::Arr => { + assert!(instruction.addressing_mode == AddressingMode::Immediate); + + let old_cycles_to_wait = self.cycles_to_wait; + self.run_instruction(&Instruction { + opcode_byte: 0, + operand: instruction.operand, + opcode: Opcode::And, + addressing_mode: instruction.addressing_mode, + }); + self.run_instruction(&Instruction { + opcode_byte: 0, + operand: 0, // unused + opcode: Opcode::Ror, + addressing_mode: AddressingMode::Accumulator, + }); + self.cycles_to_wait = old_cycles_to_wait; + + self.set_flag_status(StatusFlag::Carry, (self.reg_a >> 6) & 1 != 0); + self.set_flag_status( + StatusFlag::Overflow, + ((self.reg_a >> 6) & 1) ^ ((self.reg_a >> 5) & 1) != 0, + ); + } + Opcode::Axs => { + assert!(instruction.addressing_mode == AddressingMode::Immediate); + + let (result, overflow) = + (self.reg_x & self.reg_a).overflowing_sub(decoded_operand as u8); + + self.reg_x = self.run_load_instruction(result as u16, false); + + self.set_flag_status(StatusFlag::Carry, !overflow); + } + Opcode::Xaa => { + assert!(instruction.addressing_mode == AddressingMode::Immediate); + + let old_cycles_to_wait = self.cycles_to_wait; + self.run_instruction(&Instruction { + opcode_byte: 0, + operand: 0, // unused + opcode: Opcode::Txa, + addressing_mode: AddressingMode::Implied, + }); + self.run_instruction(&Instruction { + opcode_byte: 0, + operand: instruction.operand, + opcode: Opcode::And, + addressing_mode: instruction.addressing_mode, + }); + self.cycles_to_wait = old_cycles_to_wait; + } + Opcode::Ahx => { + assert!(is_operand_address); + + let high_byte = (decoded_operand >> 8) as u8; + + self.write_bus(decoded_operand, self.reg_a & self.reg_x & high_byte); + + cycle_time += !did_page_cross as u8; + } + Opcode::Shy => { + assert!(is_operand_address); + + let low_byte = decoded_operand & 0xFF; + let high_byte = (decoded_operand >> 8) as u8; + + let value = self.reg_y & (high_byte + 1); + + self.write_bus((value as u16) << 8 | low_byte, value); + + cycle_time += !did_page_cross as u8; + } + Opcode::Shx => { + assert!(is_operand_address); + + let low_byte = decoded_operand & 0xFF; + let high_byte = (decoded_operand >> 8) as u8; + + let value = self.reg_x & (high_byte + 1); + + self.write_bus((value as u16) << 8 | low_byte, value); + + cycle_time += !did_page_cross as u8; + } + Opcode::Tas => { + assert!(is_operand_address); + + let high_byte = (decoded_operand >> 8) as u8; + + self.reg_sp = self.reg_x & self.reg_a; + + self.write_bus(decoded_operand, self.reg_sp & high_byte); + + cycle_time += !did_page_cross as u8; + } + Opcode::Las => { + assert!(is_operand_address); + + let value = self.read_bus(decoded_operand); + + //set the flags + let result = self.run_load_instruction((value & self.reg_sp) as u16, false); + + self.reg_a = result; + self.reg_x = result; + self.reg_sp = result; + } + Opcode::Kil => { + // TODO: implement halt + println!("KIL instruction executed, should halt...."); + } + }; + + // minus this cycle + self.cycles_to_wait += cycle_time - 1; + + state + } + + fn load_serialized_state(&mut self, state: SavableCPUState) { + self.reg_pc = state.reg_pc; + self.reg_sp = state.reg_sp; + self.reg_a = state.reg_a; + self.reg_x = state.reg_x; + self.reg_y = state.reg_y; + self.reg_status = state.reg_status; + self.nmi_pin_status = state.nmi_pin_status; + self.irq_pin_status = state.irq_pin_status; + self.cycles_to_wait = state.cycles_to_wait; + self.dma_remaining = state.dma_remaining; + self.dma_address = state.dma_address; + self.next_instruction = state.next_instruction; + } +} + +#[derive(Serialize, Deserialize)] +struct SavableCPUState { + reg_pc: u16, + reg_sp: u8, + reg_a: u8, + reg_x: u8, + reg_y: u8, + reg_status: u8, + + nmi_pin_status: bool, + irq_pin_status: bool, + + cycles_to_wait: u8, + + dma_remaining: u16, + dma_address: u8, + + next_instruction: Option<(Instruction, u8)>, +} + +impl SavableCPUState { + fn from_cpu(cpu: &CPU6502) -> Self { + Self { + reg_pc: cpu.reg_pc, + reg_sp: cpu.reg_sp, + reg_a: cpu.reg_a, + reg_x: cpu.reg_x, + reg_y: cpu.reg_y, + reg_status: cpu.reg_status, + nmi_pin_status: cpu.nmi_pin_status, + irq_pin_status: cpu.irq_pin_status, + cycles_to_wait: cpu.cycles_to_wait, + dma_remaining: cpu.dma_remaining, + dma_address: cpu.dma_address, + next_instruction: cpu.next_instruction, + } + } +} + +/// This is a solution to wrap a reference to reader +struct WrapperReader<'a, R: Read> { + pub inner: &'a mut R, +} + +impl<'a, R: Read> Read for WrapperReader<'a, R> { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.inner.read(buf) + } +} + +impl Savable for CPU6502 +where + T: CPUBusTrait, +{ + fn save(&self, writer: &mut W) -> Result<(), SaveError> { + let state = SavableCPUState::from_cpu(self); + + let data = bincode::serialize(&state).map_err(|_| SaveError::Others)?; + writer.write_all(data.as_slice())?; + + self.bus.save(writer)?; + + Ok(()) + } + + fn load(&mut self, reader: &mut R) -> Result<(), SaveError> { + let outer_reader = WrapperReader { inner: reader }; + + { + let state: SavableCPUState = + bincode::deserialize_from(outer_reader).map_err(|err| match *err { + bincode::ErrorKind::Io(err) => SaveError::IoError(err), + _ => SaveError::Others, + })?; + + self.load_serialized_state(state); + } + + self.bus.load(reader)?; + + Ok(()) + } +} diff --git a/plastic_core/src/frame_limiter.rs b/plastic_core/src/frame_limiter.rs index 77070cd..68f3074 100644 --- a/plastic_core/src/frame_limiter.rs +++ b/plastic_core/src/frame_limiter.rs @@ -114,10 +114,8 @@ impl FrameLimiter { self.frame_counter_timestamp = current; self.frame_counter = 0; - if self.fps.saturating_sub(self.target_fps) > 5 { - if self.tolerance_percentage > 2 { - self.tolerance_percentage -= 1; - } + if self.fps.saturating_sub(self.target_fps) > 5 && self.tolerance_percentage > 2 { + self.tolerance_percentage -= 1; } Some(self.fps) diff --git a/plastic_core/src/nes.rs b/plastic_core/src/nes.rs index 56d2175..88c4b30 100644 --- a/plastic_core/src/nes.rs +++ b/plastic_core/src/nes.rs @@ -111,17 +111,17 @@ impl CPUBusTrait for CPUBus { 0x2000..=0x3FFF => self .ppu .borrow() - .read(0x2000 | (address & 0x7), Device::CPU), - 0x4000..=0x4013 => self.apu.borrow().read(address, Device::CPU), - 0x4014 => self.ppu.borrow().read(address, Device::CPU), - 0x4015 => self.apu.borrow().read(address, Device::CPU), - 0x4016 => self.contoller.read(address, Device::CPU), - 0x4017 => self.apu.borrow().read(address, Device::CPU), + .read(0x2000 | (address & 0x7), Device::Cpu), + 0x4000..=0x4013 => self.apu.borrow().read(address, Device::Cpu), + 0x4014 => self.ppu.borrow().read(address, Device::Cpu), + 0x4015 => self.apu.borrow().read(address, Device::Cpu), + 0x4016 => self.contoller.read(address, Device::Cpu), + 0x4017 => self.apu.borrow().read(address, Device::Cpu), 0x4018..=0x401F => { // unused CPU test mode registers 0 } - 0x4020..=0xFFFF => self.cartridge.borrow().read(address, Device::CPU), + 0x4020..=0xFFFF => self.cartridge.borrow().read(address, Device::Cpu), } } @@ -131,20 +131,20 @@ impl CPUBusTrait for CPUBus { 0x2000..=0x3FFF => { self.ppu .borrow_mut() - .write(0x2000 | (address & 0x7), data, Device::CPU) + .write(0x2000 | (address & 0x7), data, Device::Cpu) } - 0x4000..=0x4013 => self.apu.borrow_mut().write(address, data, Device::CPU), - 0x4014 => self.ppu.borrow_mut().write(address, data, Device::CPU), - 0x4015 => self.apu.borrow_mut().write(address, data, Device::CPU), - 0x4016 => self.contoller.write(address, data, Device::CPU), - 0x4017 => self.apu.borrow_mut().write(address, data, Device::CPU), + 0x4000..=0x4013 => self.apu.borrow_mut().write(address, data, Device::Cpu), + 0x4014 => self.ppu.borrow_mut().write(address, data, Device::Cpu), + 0x4015 => self.apu.borrow_mut().write(address, data, Device::Cpu), + 0x4016 => self.contoller.write(address, data, Device::Cpu), + 0x4017 => self.apu.borrow_mut().write(address, data, Device::Cpu), 0x4018..=0x401F => { // unused CPU test mode registers } 0x4020..=0xFFFF => self .cartridge .borrow_mut() - .write(address, data, Device::CPU), + .write(address, data, Device::Cpu), } } @@ -326,19 +326,16 @@ impl NES

{ let cartridge_path = self.cartridge.borrow().cartridge_path().to_path_buf(); - if let Some(base_saved_states_dir) = self.get_base_save_state_folder() { - Some( + self.get_base_save_state_folder() + .map(|base_saved_states_dir| { base_saved_states_dir .join(format!( "{}_{}.pst", cartridge_path.file_stem().unwrap().to_string_lossy(), slot )) - .into_boxed_path(), - ) - } else { - None - } + .into_boxed_path() + }) } fn get_present_save_states(&self) -> Option> { @@ -366,14 +363,12 @@ impl NES

{ } }) .filter_map(|path| { - Some( - saved_states_files_regex - .captures(path.file_name()?.to_str()?)? - .get(1)? - .as_str() - .parse::() - .ok()?, - ) + saved_states_files_regex + .captures(path.file_name()?.to_str()?)? + .get(1)? + .as_str() + .parse::() + .ok() }) .collect::>(), ) diff --git a/plastic_core/src/ppu2c02/mod.rs b/plastic_core/src/ppu2c02/mod.rs index 731ccbb..06b2cbc 100644 --- a/plastic_core/src/ppu2c02/mod.rs +++ b/plastic_core/src/ppu2c02/mod.rs @@ -1,9 +1,1284 @@ mod palette; -mod ppu2c02; mod ppu2c02_registers; mod sprite; mod vram; pub use palette::Palette; -pub use ppu2c02::PPU2C02; pub use vram::VRam; + +use crate::common::{ + interconnection::PPUCPUConnection, + save_state::{Savable, SaveError}, + Bus, Device, +}; +use crate::display::{Color, COLORS, TV}; +use bitflags::bitflags; +use ppu2c02_registers::Register; +use serde::{Deserialize, Serialize}; +use sprite::{Sprite, SpriteAttribute}; +use std::cell::Cell; +use std::cmp::min; + +bitflags! { + pub struct ControlReg: u8 { + const BASE_NAMETABLE = 0b00000011; + const VRAM_INCREMENT = 0b00000100; + const SPRITE_PATTERN_ADDRESS = 0b00001000; + const BACKGROUND_PATTERN_ADDRESS = 0b00010000; + const SPRITE_SIZE = 0b00100000; + const MASTER_SLAVE_SELECT = 0b01000000; + const GENERATE_NMI_ENABLE = 0b10000000; + } +} + +impl ControlReg { + pub fn nametable_selector(&self) -> u8 { + // 0 = $2000; 1 = $2400; 2 = $2800; 3 = $2C00 + self.bits & Self::BASE_NAMETABLE.bits + } + + pub fn vram_increment(&self) -> u16 { + if self.intersects(Self::VRAM_INCREMENT) { + 32 + } else { + 1 + } + } + + pub fn sprite_pattern_address(&self) -> u16 { + ((self.intersects(Self::SPRITE_PATTERN_ADDRESS)) as u16) << 12 + } + + pub fn background_pattern_address(&self) -> u16 { + ((self.intersects(Self::BACKGROUND_PATTERN_ADDRESS)) as u16) << 12 + } + + pub fn nmi_enabled(&self) -> bool { + self.intersects(Self::GENERATE_NMI_ENABLE) + } + + pub fn sprite_height(&self) -> u8 { + // if SPRITE_SIZE is 1, then it will be (8 << 1) == 16, else it will be 8 + 8 << self.intersects(Self::SPRITE_SIZE) as u8 + } +} + +bitflags! { + pub struct MaskReg: u8 { + const GRAYSCALE_ENABLE = 0b00000001; + const SHOW_BACKGROUND_LEFTMOST_8 = 0b00000010; + const SHOW_SPRITES_LEFTMOST_8 = 0b00000100; + const SHOW_BACKGROUND = 0b00001000; + const SHOW_SPRITES = 0b00010000; + const EMPHASIZE_RED = 0b00100000; + const EMPHASIZE_GREEN = 0b01000000; + const EMPHASIZE_BLUE = 0b10000000; + } +} + +impl MaskReg { + pub fn background_enabled(&self) -> bool { + self.intersects(Self::SHOW_BACKGROUND) + } + + pub fn sprites_enabled(&self) -> bool { + self.intersects(Self::SHOW_SPRITES) + } + + pub fn rendering_enabled(&self) -> bool { + self.background_enabled() || self.sprites_enabled() + } + + /// returns true if the background pixels on the left should not + /// be shown, false otherwise + pub fn background_left_clipping_enabled(&self) -> bool { + !self.intersects(Self::SHOW_BACKGROUND_LEFTMOST_8) + } + + /// returns true if the sprite pixels on the left should not + /// be shown, false otherwise + pub fn sprites_left_clipping_enabled(&self) -> bool { + !self.intersects(Self::SHOW_SPRITES_LEFTMOST_8) + } + + pub fn is_grayscale(&self) -> bool { + self.intersects(Self::GRAYSCALE_ENABLE) + } +} + +bitflags! { + pub struct StatusReg: u8 { + const SPRITE_OVERFLOW = 0b00100000; + const SPRITE_0_HIT = 0b01000000; + const VERTICAL_BLANK = 0b10000000; + } +} + +pub struct PPU2C02 { + // memory mapped registers + reg_control: ControlReg, + reg_mask: MaskReg, + reg_status: Cell, + reg_oam_addr: Cell, + + scanline: u16, + cycle: u16, + + /// ## PPU VRAM top 12-bit address ## (v and t) + /// NN YYYYY XXXXX + /// || ||||| +++++-- coarse X scroll + /// || +++++-------- coarse Y scroll + /// ++-------------- nametable select + vram_address_cur: Cell, + vram_address_top_left: u16, + + ppu_data_read_buffer: Cell, + + fine_x_scroll: u8, + + w_toggle: Cell, // this is used for registers that require 2 writes + + bg_pattern_shift_registers: [u16; 2], + bg_palette_shift_registers: [u16; 2], + + nmi_pin_status: Cell, + nmi_occured_in_this_frame: Cell, + + bus: T, + tv: TV, + + primary_oam: [Sprite; 64], + secondary_oam: [Sprite; 8], + rendering_oam: [Sprite; 8], + + rendering_oam_counter: u8, + + sprite_0_present: bool, + next_scanline_sprite_0_present: bool, + + is_dma_request: bool, + dma_request_address: u8, + + is_odd_frame: bool, +} + +impl PPU2C02 +where + T: Bus + Savable, +{ + pub fn new(bus: T, tv: TV) -> Self { + Self { + reg_control: ControlReg::empty(), + reg_mask: MaskReg::empty(), + reg_status: Cell::new(StatusReg::empty()), + reg_oam_addr: Cell::new(0), + + // this would result in it starting from 0,0 next cycle + scanline: 261, // start from -1 scanline + cycle: 340, // last cycle + + vram_address_cur: Cell::new(0), + vram_address_top_left: 0, + + ppu_data_read_buffer: Cell::new(0), + + fine_x_scroll: 0, + + w_toggle: Cell::new(false), + + bg_pattern_shift_registers: [0; 2], + bg_palette_shift_registers: [0; 2], + + nmi_pin_status: Cell::new(false), + nmi_occured_in_this_frame: Cell::new(false), + + bus, + tv, + + primary_oam: [Sprite::empty(); 64], + secondary_oam: [Sprite::empty(); 8], + rendering_oam: [Sprite::empty(); 8], + + rendering_oam_counter: 0, + + sprite_0_present: false, + next_scanline_sprite_0_present: false, + + is_dma_request: false, + dma_request_address: 0, + + is_odd_frame: false, + } + } + + pub(crate) fn read_register(&self, register: Register) -> u8 { + match register { + Register::Status => { + // reset w_mode + self.w_toggle.set(false); + + if self.scanline == 241 { + // Race Condition Warning: Reading PPUSTATUS within two + // cycles of the start of vertical blank will return 0 in bit 7 + // but clear the latch anyway, causing NMI to not occur that frame + if self.cycle <= 2 { + self.reg_status + .set(StatusReg::from_bits(self.reg_status.get().bits & 0x7F).unwrap()); + } + // for NMI it has quite a different range + // source: tests + if self.cycle >= 2 && self.cycle <= 4 { + self.nmi_pin_status.set(false); + self.nmi_occured_in_this_frame.set(true); + } + } + let result = self.reg_status.get().bits; + // reading the status register will clear bit 7 + self.reg_status + .set(StatusReg::from_bits(result & 0x7F).unwrap()); + + result + } + Register::OmaData => self.read_sprite_byte(self.reg_oam_addr.get()), + Register::PPUData => { + let address = self.vram_address_cur.get(); + let data_in_addr = self.read_bus(address); + + // only 0 - 0x2FFF (before palette) is buffered + let result = if address <= 0x3EFF { + let tmp_result = self.ppu_data_read_buffer.get(); + + // fill buffer + self.ppu_data_read_buffer.set(data_in_addr); + + tmp_result + } else { + // reload buffer with VRAM address hidden by palette + // wrap to 0x2FFF rather than 0x3EFF, to avoid the mirror + self.ppu_data_read_buffer + .set(self.read_bus(address & 0x2FFF)); + data_in_addr + }; + + self.increment_vram_readwrite(); + + result + } + _ => { + // unreadable + 0 + } + } + } + + pub(crate) fn write_register(&mut self, register: Register, data: u8) { + match register { + // After power/reset, writes to this register are ignored for about 30,000 cycles + // TODO: not sure, if I should account for that + Register::Control => { + self.reg_control.bits = data; + + // write nametable also in top_left vram address + self.vram_address_top_left &= 0xF3FF; + self.vram_address_top_left |= (self.reg_control.nametable_selector() as u16) << 10; + + // if the NMI flag is set, run immediate NMI to the CPU + // but only run if we are in the VBLANK period and no + // other NMI has occurred so far + if self.reg_control.nmi_enabled() { + if self.reg_status.get().intersects(StatusReg::VERTICAL_BLANK) + && !self.nmi_occured_in_this_frame.get() + { + self.nmi_pin_status.set(true); + self.nmi_occured_in_this_frame.set(true); + } + } else { + // if the NMI is disabled, stop the NMI (if the flag was set) + if self.scanline == 241 && self.cycle <= 4 { + self.nmi_pin_status.set(false); + self.nmi_occured_in_this_frame.set(true); + } else { + // in case if the NMI flag was disabled, then mark as nmi + // never occurred on this frame, even if it has + // meaning, that in some cases 2 NMI can occur + self.nmi_occured_in_this_frame.set(false); + } + } + } + Register::Mask => self.reg_mask.bits = data, + Register::OmaAddress => self.reg_oam_addr.set(data), + Register::OmaData => { + self.write_sprite_byte(self.reg_oam_addr.get(), data); + if self.scanline > 240 || !self.reg_mask.rendering_enabled() { + *self.reg_oam_addr.get_mut() = self.reg_oam_addr.get().wrapping_add(1); + } + } + Register::Scroll => { + if self.w_toggle.get() { + // w == 1 + self.set_top_left_y_scroll(data); + } else { + // w == 0 + self.set_top_left_x_scroll(data); + } + + self.w_toggle.set(!self.w_toggle.get()); + } + Register::PPUAddress => { + if self.w_toggle.get() { + // w == 1 + + // zero out the bottom 8 bits + self.vram_address_top_left &= 0xff00; + // set the data from the parameters + self.vram_address_top_left |= data as u16; + + // a dummy read to the cartridge as some mappers rely + // on PPU address pins for operations + let _ = self.read_bus(self.vram_address_top_left); + + // copy to the current vram address + *self.vram_address_cur.get_mut() = self.vram_address_top_left; + } else { + // w == 0 + + // zero out the top 8 bits + self.vram_address_top_left &= 0x00ff; + // set the data from the parameters + self.vram_address_top_left |= (data as u16) << 8; + + // update nametable + self.reg_control.bits &= !(ControlReg::BASE_NAMETABLE.bits); + self.reg_control.bits |= (data >> 2) & 0b11; + } + + self.w_toggle.set(!self.w_toggle.get()); + } + Register::PPUData => { + self.write_bus(self.vram_address_cur.get(), data); + self.increment_vram_readwrite(); + } + Register::DmaOma => { + self.dma_request_address = data; + self.is_dma_request = true; + } + _ => { + // unwritable + } + }; + } + + /// expose the bus for reading only + #[cfg(test)] + pub fn ppu_bus(&self) -> &T { + &self.bus + } + + fn read_bus(&self, address: u16) -> u8 { + self.bus.read(address, Device::Ppu) + } + + fn write_bus(&mut self, address: u16, data: u8) { + self.bus.write(address, data, Device::Ppu); + } + + fn read_sprite_byte(&self, address: u8) -> u8 { + let sprite_location = address >> 2; + self.primary_oam[sprite_location as usize].read_offset(address & 0b11) + } + + fn write_sprite_byte(&mut self, address: u8, data: u8) { + let sprite_location = address >> 2; + self.primary_oam[sprite_location as usize].write_offset(address & 0b11, data); + } + + // SCROLL attributes START + fn current_coarse_x_scroll(&self) -> u8 { + (self.vram_address_cur.get() & 0b11111) as u8 + } + + fn set_current_coarse_x_scroll(&mut self, coarse_x: u8) { + let vram_cur = self.vram_address_cur.get_mut(); + + // clear first 5 bits + *vram_cur &= 0xFFE0; + // copy new value + *vram_cur |= (coarse_x & 0b11111) as u16; + } + + fn current_coarse_y_scroll(&self) -> u8 { + ((self.vram_address_cur.get() >> 5) & 0b11111) as u8 + } + + fn set_current_coarse_y_scroll(&mut self, coarse_y: u8) { + let vram_cur = self.vram_address_cur.get_mut(); + + // clear second 5 bits + *vram_cur &= 0xFC1F; + // copy new value + *vram_cur |= ((coarse_y & 0b11111) as u16) << 5; + } + + fn current_fine_x_scroll(&self) -> u8 { + self.fine_x_scroll + } + + // this is just for completion, and mostly it will not be ever used + #[allow(unused)] + fn set_current_fine_x_scroll(&mut self, fine_x: u8) { + self.fine_x_scroll = fine_x & 0b111; + } + + fn current_fine_y_scroll(&self) -> u8 { + ((self.vram_address_cur.get() >> 12) & 0b111) as u8 + } + + fn set_current_fine_y_scroll(&mut self, fine_y: u8) { + let vram_cur = self.vram_address_cur.get_mut(); + + // clear fine_y + *vram_cur &= 0x0FFF; + // copy new value + *vram_cur |= ((fine_y & 0b111) as u16) << 12; + } + + fn top_left_coarse_x_scroll(&self) -> u8 { + (self.vram_address_top_left & 0b11111) as u8 + } + + fn set_top_left_coarse_x_scroll(&mut self, coarse_x: u8) { + // clear first 5 bits + self.vram_address_top_left &= 0xFFE0; + // copy new value + self.vram_address_top_left |= (coarse_x & 0b11111) as u16; + } + + fn top_left_coarse_y_scroll(&self) -> u8 { + ((self.vram_address_top_left >> 5) & 0b11111) as u8 + } + + fn set_top_left_coarse_y_scroll(&mut self, coarse_y: u8) { + // clear second 5 bits + self.vram_address_top_left &= 0xFC1F; + // copy new value + self.vram_address_top_left |= ((coarse_y & 0b11111) as u16) << 5; + } + + fn set_top_left_fine_x_scroll(&mut self, fine_x: u8) { + self.fine_x_scroll = fine_x & 0b111; + } + + fn top_left_fine_y_scroll(&self) -> u8 { + ((self.vram_address_top_left >> 12) & 0b111) as u8 + } + + fn set_top_left_fine_y_scroll(&mut self, fine_y: u8) { + // clear fine_y + self.vram_address_top_left &= 0x0FFF; + // copy new value + self.vram_address_top_left |= ((fine_y & 0b111) as u16) << 12; + } + + fn set_top_left_x_scroll(&mut self, x_scroll: u8) { + self.set_top_left_coarse_x_scroll(x_scroll >> 3); + self.set_top_left_fine_x_scroll(x_scroll & 0b111); + } + + fn set_top_left_y_scroll(&mut self, y_scroll: u8) { + self.set_top_left_coarse_y_scroll(y_scroll >> 3); + self.set_top_left_fine_y_scroll(y_scroll & 0b111); + } + + fn increment_y_scroll(&mut self) { + // increment fine scrolling Y on the last dot without carry + let fine_y = self.current_fine_y_scroll() + 1; + + self.set_current_fine_y_scroll(fine_y & 0b111); + + // if the increment resulted in a carry, go to the next tile + // i.e. increment coarse Y + if fine_y & 0x8 != 0 { + // extract coarse_y + let mut coarse_y = self.current_coarse_y_scroll(); + + // in case of overflow, increment nametable vertical address + if coarse_y == 29 { + coarse_y = 0; + self.increment_vram_nametable_vertical(); + } else if coarse_y == 31 { + coarse_y = 0; + } else { + coarse_y += 1; + } + + self.set_current_coarse_y_scroll(coarse_y); + } + } + + fn increment_coarse_x_scroll(&mut self) { + let coarse_x = self.current_coarse_x_scroll() + 1; + + self.set_current_coarse_x_scroll(coarse_x & 0b11111); + + // in case of overflow, increment nametable horizontal address + if coarse_x & 0b100000 != 0 { + self.increment_vram_nametable_horizontal(); + } + } + // SCROLL attributes END + + fn increment_vram_readwrite(&self) { + // only increment if its valid, and increment by the correct ammount + if self.scanline > 240 || !self.reg_mask.rendering_enabled() { + self.vram_address_cur + .set(self.vram_address_cur.get() + self.reg_control.vram_increment()); + + // dummy read to update the cartridge, which mappers rely on some + // address pins from the PPU + let _ = self.read_bus(self.vram_address_cur.get()); + } + } + + fn restore_rendering_scroll_x(&mut self) { + self.set_current_coarse_x_scroll(self.top_left_coarse_x_scroll()); + } + + fn restore_rendering_scroll_y(&mut self) { + self.set_current_fine_y_scroll(self.top_left_fine_y_scroll()); + self.set_current_coarse_y_scroll(self.top_left_coarse_y_scroll()); + } + + fn increment_vram_nametable_horizontal(&mut self) { + *self.vram_address_cur.get_mut() ^= 0b01 << 10; + } + + fn increment_vram_nametable_vertical(&mut self) { + *self.vram_address_cur.get_mut() ^= 0b10 << 10; + } + + // restore from top_left or original nametable selector from `reg_control` + fn restore_nametable(&mut self) { + let vram_cur = self.vram_address_cur.get_mut(); + + // clear old nametable data + *vram_cur &= 0xF3FF; + + *vram_cur |= (self.reg_control.nametable_selector() as u16) << 10; + } + + fn restore_nametable_horizontal(&mut self) { + let vram_cur = self.vram_address_cur.get_mut(); + + // clear horizontal nametable data + *vram_cur &= 0xFBFF; + + *vram_cur |= (self.reg_control.nametable_selector() as u16 & 1) << 10; + } + + fn current_nametable(&self) -> u16 { + (self.vram_address_cur.get() >> 10) & 0b11 + } + + // this should only be called when rendering and a bit after that, + // i.e. when scanline number is in range 0 >= scanline > 255 + fn get_next_scroll_y_render(&self) -> u8 { + if self.scanline == 261 { + 0 + } else if self.scanline < 255 { + (self.scanline + 1) as u8 + } else { + unreachable!() + } + } + + #[allow(clippy::needless_range_loop)] + fn reload_background_shift_registers(&mut self) { + // tile address = 0x2000 | (v & 0x0FFF) + let nametable_tile = self.read_bus(0x2000 | self.vram_address_cur.get() & 0xFFF); + + let tile_pattern = self.fetch_pattern_background(nametable_tile); + + // fetch and prepare the palette + let attribute_byte = self.fetch_attribute_byte(); + + // Each byte controls the palette of a 32×32 pixel or 4×4 tile part of the + // nametable and is divided into four 2-bit areas. Each area covers 16×16 + // pixels or 2×2 tiles. Given palette numbers topleft, topright, + // bottomleft, bottomright, each in the range 0 to 3, the value of + // the byte is + // `value = (bottomright << 6) | (bottomleft << 4) | (topright << 2) | (topleft << 0)` + let coarse_x = self.current_coarse_x_scroll(); + let coarse_y = self.current_coarse_y_scroll(); + let attribute_location_x = (coarse_x >> 1) & 0x1; + let attribute_location_y = (coarse_y >> 1) & 0x1; + + // `attribute_location_x`: 0 => left, 1 => right + // `attribute_location_y`: 0 => top, 1 => bottom + let attribute_location = attribute_location_y << 1 | attribute_location_x; + + // 00: top-left, 01: top-right, 10: bottom-left, 11: bottom-right + // bit-1 is for (top, bottom), bit-0 is for (left, right) + let palette = (attribute_byte >> (attribute_location * 2)) & 0b11; + + // update th shift registers + for i in 0..=1 { + // clear the bottom value + self.bg_pattern_shift_registers[i] &= 0xFF00; + + // in this stage, because we reload in dots (8, 16, 24...) + // the shift registers will be shifted one more time + // meaning, it will be shifted 8 times + self.bg_pattern_shift_registers[i] |= tile_pattern[i] as u16; + + // clear the bottom value + self.bg_palette_shift_registers[i] &= 0xFF00; + + // as palettes are two bits, we store the first bit in index 0 and + // the second bit in index 1 in the array + // + // this is similar to how the patterns are stored in CHR table + self.bg_palette_shift_registers[i] |= 0xFF * ((palette >> i) & 1) as u16; + } + } + + /// ## PPU pattern table addressing ## + /// DCBA98 76543210 + /// --------------- + /// 0HRRRR CCCCPTTT + /// |||||| |||||+++- T: Fine Y offset, the row number within a tile + /// |||||| ||||+---- P: Bit plane (0: "lower"; 1: "upper") + /// |||||| ++++----- C: Tile column + /// ||++++---------- R: Tile row + /// |+-------------- H: Half of sprite table (0: "left"; 1: "right") + /// +--------------- 0: Pattern table is at $0000-$1FFF + #[allow(clippy::identity_op)] + fn fetch_pattern(&self, pattern_table: u16, location: u8, fine_y: u8) -> [u8; 2] { + let fine_y = fine_y as u16; + + let low_plane_pattern = + self.read_bus(pattern_table | (location as u16) << 4 | 0 << 3 | fine_y); + + let high_plane_pattern = + self.read_bus(pattern_table | (location as u16) << 4 | 1 << 3 | fine_y); + + [low_plane_pattern, high_plane_pattern] + } + + fn fetch_pattern_background(&self, location: u8) -> [u8; 2] { + let fine_y = self.current_fine_y_scroll(); + + // for background + let pattern_table = self.reg_control.background_pattern_address(); + + self.fetch_pattern(pattern_table, location, fine_y) + } + + /// ## Attribute address ## + /// NN 1111 YYY XXX + /// || |||| ||| +++-- high 3 bits of coarse X (x/4) + /// || |||| +++------ high 3 bits of coarse Y (y/4) + /// || ++++---------- attribute offset (960 bytes) + /// ++--------------- nametable select + /// + /// or + /// + /// `attribute address = 0x23C0 | (v & 0x0C00) | ((v >> 4) & 0x38) | ((v >> 2) & 0x07)` + /// where x, y and nametable are used from `vram_address_cur` + fn fetch_attribute_byte(&self) -> u8 { + let x = (self.current_coarse_x_scroll() >> 2) as u16; + let y = (self.current_coarse_y_scroll() >> 2) as u16; + + self.read_bus(0x2000 | self.current_nametable() << 10 | 0xF << 6 | y << 3 | x) + } + + fn reload_sprite_shift_registers(&mut self) { + // move sprite_0_present + self.sprite_0_present = self.next_scanline_sprite_0_present; + // reset for scanline after next + self.next_scanline_sprite_0_present = false; + + let next_y = self.get_next_scroll_y_render(); + + let sprite_height = self.reg_control.sprite_height(); + + // loop through all secondary_oam, even the empty ones (0xFF) + // a write to the cartridge MUST be done here even if no sprites + // are drawn + for i in 0..8_usize { + let mut sprite = self.secondary_oam[i]; + let mut fine_y = next_y.wrapping_sub(sprite.get_y()); + + // handle flipping vertically + if sprite.get_attribute().is_flip_vertical() { + fine_y = (sprite_height - 1).wrapping_sub(fine_y); + } + + sprite.set_pattern(self.fetch_pattern_sprite(sprite.get_tile(), fine_y)); + + self.rendering_oam[i] = sprite; + } + } + + fn fetch_pattern_sprite(&self, tile: u8, mut fine_y: u8) -> [u8; 2] { + let mut location = tile; + + // for sprites + let pattern_table = if self.reg_control.sprite_height() == 16 { + // zero the first bit as it is used as a pattern_table selector + location &= !(1); + ((tile & 1) as u16) << 12 + } else { + self.reg_control.sprite_pattern_address() + }; + + if fine_y > 7 { + fine_y -= 8; + location = location.wrapping_add(1); + } + + self.fetch_pattern(pattern_table, location, fine_y) + } + + fn get_background_pixel(&self) -> u8 { + if !self.reg_mask.background_enabled() + || (self.cycle < 8 && self.reg_mask.background_left_clipping_enabled()) + { + return 0; + } + + let fine_x = self.current_fine_x_scroll(); + + // select the bit using `fine_x` from the left + let bit_location = 15 - fine_x; + + let low_plane_bit = + ((self.bg_pattern_shift_registers[0] >> bit_location as u16) & 0x1) as u8; + let high_plane_bit = + ((self.bg_pattern_shift_registers[1] >> bit_location as u16) & 0x1) as u8; + + let color_bit = high_plane_bit << 1 | low_plane_bit; + + if color_bit != 0 { + let low_palette_bit = + ((self.bg_palette_shift_registers[0] >> bit_location as u16) & 0x1) as u8; + let high_palette_bit = + ((self.bg_palette_shift_registers[1] >> bit_location as u16) & 0x1) as u8; + + let palette = high_palette_bit << 1 | low_palette_bit; + + palette << 2 | color_bit + } else { + 0 + } + } + + fn get_sprites_first_non_transparent_pixel(&mut self) -> (u8, bool, bool) { + if !self.reg_mask.sprites_enabled() + || (self.cycle < 8 && self.reg_mask.sprites_left_clipping_enabled()) + || self.cycle == 255 + { + return (0, false, false); + } + + for (i, sprite) in self + .rendering_oam + .iter() + .take(self.rendering_oam_counter as usize) + .enumerate() + { + let current_color_bits = sprite.get_color_bits(self.cycle); + + if current_color_bits != 0 { + let attribute = sprite.get_attribute(); + return ( + // (1 << 4) to select the sprite table + 1 << 4 | attribute.palette() << 2 | current_color_bits, + sprite.get_attribute().is_behind_background(), + i == 0 && self.sprite_0_present, + ); + } + } + + (0, false, false) + } + + /// this method fetches background and sprite pixels, check overflow for + /// sprite_0 and priority, and handles the left 8-pixel clipping + /// and then outputs a color index + /// + /// ## color location offset 0x3F00 ## + /// 43210 + /// ||||| + /// |||++- Pixel value from tile data + /// |++--- Palette number from attribute table or OAM + /// +----- Background/Sprite select + fn generate_pixel(&mut self) -> u8 { + let background_color_location = self.get_background_pixel(); + + let (sprite_color_location, background_priority, is_sprite_0) = + self.get_sprites_first_non_transparent_pixel(); + + // sprite and background multiplexer procedure + let color_location = if sprite_color_location != 0 && background_color_location != 0 { + if is_sprite_0 { + // if sprite and background are not transparent, then there is a collision + self.reg_status.get_mut().insert(StatusReg::SPRITE_0_HIT); + } + // use background priority flag + if background_priority { + background_color_location + } else { + sprite_color_location + } + } else { + sprite_color_location | background_color_location + }; + + // advance the shift registers + for i in 0..=1 { + self.bg_pattern_shift_registers[i] = self.bg_pattern_shift_registers[i].wrapping_shl(1); + self.bg_palette_shift_registers[i] = self.bg_palette_shift_registers[i].wrapping_shl(1); + } + + self.read_bus(0x3F00 | color_location as u16) + } + + fn emphasis_color(&self, color: Color) -> Color { + let is_red_emph = self.reg_mask.intersects(MaskReg::EMPHASIZE_RED); + let is_green_emph = self.reg_mask.intersects(MaskReg::EMPHASIZE_GREEN); + let is_blue_emph = self.reg_mask.intersects(MaskReg::EMPHASIZE_BLUE); + + let mut red = 1.; + let mut green = 1.; + let mut blue = 1.; + + if is_red_emph { + red *= 1.1; + green *= 0.9; + blue *= 0.9; + } + if is_green_emph { + red *= 0.9; + green *= 1.1; + blue *= 0.9; + } + if is_blue_emph { + red *= 0.9; + green *= 0.9; + blue *= 1.1; + } + + Color { + r: min((color.r as f32 * red) as u8, 255), + g: min((color.g as f32 * green) as u8, 255), + b: min((color.b as f32 * blue) as u8, 255), + } + } + + fn render_pixel(&mut self) { + // fix overflowing colors + let mut color = self.generate_pixel() & 0x3F; + + if self.reg_mask.is_grayscale() { + // select from the gray column (0x00, 0x10, 0x20, 0x30) + color &= 0x30; + } + + // render the color + self.tv.set_pixel( + self.cycle as u32, + self.scanline as u32, + &self.emphasis_color(COLORS[color as usize]), + ); + } + + // run one cycle, this should be fed from Master clock + pub fn clock(&mut self) { + // current scanline + match (self.scanline, self.cycle) { + (261, 0) => { + // FIXME: for some reason the test only worked when doing it here + + // clear sprite 0 hit + self.reg_status.get_mut().remove(StatusReg::SPRITE_0_HIT) + } + (261, 2) => { + // reset nmi_occured_in_this_frame + self.nmi_occured_in_this_frame.set(false); + } + (261, 1) => { + // clear sprite overflow + self.reg_status.get_mut().remove(StatusReg::SPRITE_OVERFLOW); + // clear v-blank + self.reg_status.get_mut().remove(StatusReg::VERTICAL_BLANK); + + if self.reg_mask.rendering_enabled() { + self.restore_rendering_scroll_x(); + self.restore_rendering_scroll_y(); + + self.restore_nametable(); + + // load next 2 bytes + for _ in 0..2 { + for i in 0..=1 { + // as this is the first time, shift the registers + // as we are reloading 2 times + self.bg_pattern_shift_registers[i] = + self.bg_pattern_shift_registers[i].wrapping_shl(8); + self.bg_palette_shift_registers[i] = + self.bg_palette_shift_registers[i].wrapping_shl(8); + } + self.reload_background_shift_registers(); + self.increment_coarse_x_scroll(); + } + } + } + (261, 257) => { + // reload all of them in one go + self.reload_sprite_shift_registers(); + } + (0..=239, _) => { + // render only if allowed + if self.reg_mask.rendering_enabled() { + self.run_render_cycle(); + } + } + (240, 1) => { + // post-render + // idle + self.tv.signal_end_of_frame(); + } + (241, 1) => { + // set v-blank + self.reg_status.get_mut().insert(StatusReg::VERTICAL_BLANK); + + // if raising NMI is enabled + if self.reg_control.nmi_enabled() && !self.nmi_occured_in_this_frame.get() { + self.nmi_pin_status.set(true); + self.nmi_occured_in_this_frame.set(true); + } + } + _ => {} + } + + self.cycle += 1; + if self.cycle > 340 + || (self.scanline == 261 + && self.cycle == 340 + && self.is_odd_frame + && self.reg_mask.rendering_enabled()) + { + self.scanline += 1; + self.cycle = 0; + + // next frame + if self.scanline > 261 { + self.scanline = 0; + self.is_odd_frame = !self.is_odd_frame; + } + } + } + + // run one cycle which is part of a scanline execution + fn run_render_cycle(&mut self) { + match self.cycle { + // secondary OAM clear, cycles 1-64, but we do it in one go + // TODO: should it be in multiple times, instead of one go? + 1 => { + self.secondary_oam = [Sprite::filled_ff(); 8]; + } + // fetch and reload shift registers + 8..=256 if self.cycle % 8 == 0 => { + self.reload_background_shift_registers(); + + if self.cycle != 256 { + // increment scrolling X in current VRAM address + self.increment_coarse_x_scroll(); + } else { + // fine and carry to coarse + self.increment_y_scroll(); + } + } + // check all oam memory in one go + 255 => { + let next_y = self.get_next_scroll_y_render() as i16; + + let mut counter = 0; + for (i, sprite) in self.primary_oam.iter().enumerate() { + let sprite_y = sprite.get_y() as i16; + + let diff = next_y - sprite_y; + let height = self.reg_control.sprite_height() as i16; + + if diff >= 0 && diff < height { + // in range + + // sprite 0 + if i == 0 { + self.next_scanline_sprite_0_present = true; + } + + if counter > 7 { + // overflow + self.reg_status.get_mut().insert(StatusReg::SPRITE_OVERFLOW); + break; + } + + self.secondary_oam[counter] = *sprite; + + counter += 1; + } + } + + self.rendering_oam_counter = counter as u8; + } + 257 => { + self.restore_rendering_scroll_x(); + + // to fix nametable wrapping around + self.restore_nametable_horizontal(); + } + 258 => { + // reload them all in one go + self.reload_sprite_shift_registers(); + } + 321 => { + // load next 2 bytes + for _ in 0..2 { + for i in 0..=1 { + // as this is the first time, shift the registers + // as we are reloading 2 times + self.bg_pattern_shift_registers[i] = + self.bg_pattern_shift_registers[i].wrapping_shl(8); + self.bg_palette_shift_registers[i] = + self.bg_palette_shift_registers[i].wrapping_shl(8); + } + self.reload_background_shift_registers(); + self.increment_coarse_x_scroll(); + } + } + _ => {} + } + + // render after reloading + if self.cycle <= 255 { + // main render + self.render_pixel(); + } + } + + pub fn reset(&mut self, bus: T) { + // just as if calling the constructor but without TV, just reset it + self.reg_control = ControlReg::empty(); + self.reg_mask = MaskReg::empty(); + self.reg_status = Cell::new(StatusReg::empty()); + self.reg_oam_addr = Cell::new(0); + + self.scanline = 0; // start from -1 scanline + self.cycle = 0; + + self.vram_address_cur = Cell::new(0); + self.vram_address_top_left = 0; + + self.ppu_data_read_buffer = Cell::new(0); + + self.fine_x_scroll = 0; + + self.w_toggle = Cell::new(false); + + self.bg_pattern_shift_registers = [0; 2]; + self.bg_palette_shift_registers = [0; 2]; + + self.nmi_pin_status = Cell::new(false); + self.nmi_occured_in_this_frame = Cell::new(false); + + self.bus = bus; + + self.primary_oam = [Sprite::empty(); 64]; + self.secondary_oam = [Sprite::empty(); 8]; + self.rendering_oam = [Sprite::empty(); 8]; + + self.rendering_oam_counter = 0; + + self.sprite_0_present = false; + self.next_scanline_sprite_0_present = false; + + self.is_dma_request = false; + self.dma_request_address = 0; + + self.is_odd_frame = false; + + self.tv.reset(); + } + + fn load_serialized_state(&mut self, state: SavablePPUState) { + let mut primary_oam = [Sprite::empty(); 64]; + primary_oam.copy_from_slice(state.primary_oam.as_slice()); + + self.reg_control = ControlReg::from_bits(state.reg_control).unwrap(); + self.reg_mask = MaskReg::from_bits(state.reg_mask).unwrap(); + *self.reg_status.get_mut() = StatusReg::from_bits(state.reg_status).unwrap(); + *self.reg_oam_addr.get_mut() = state.reg_oam_addr; + self.scanline = state.scanline; + self.cycle = state.cycle; + *self.vram_address_cur.get_mut() = state.vram_address_cur; + self.vram_address_top_left = state.vram_address_top_left; + *self.ppu_data_read_buffer.get_mut() = state.ppu_data_read_buffer; + self.fine_x_scroll = state.fine_x_scroll; + *self.w_toggle.get_mut() = state.w_toggle; + self.bg_pattern_shift_registers = state.bg_pattern_shift_registers; + self.bg_palette_shift_registers = state.bg_palette_shift_registers; + *self.nmi_pin_status.get_mut() = state.nmi_pin_status; + *self.nmi_occured_in_this_frame.get_mut() = state.nmi_occured_in_this_frame; + self.primary_oam = primary_oam; + self.secondary_oam = state.secondary_oam; + self.rendering_oam_counter = state.rendering_oam_counter; + self.sprite_0_present = state.sprite_0_present; + self.next_scanline_sprite_0_present = state.next_scanline_sprite_0_present; + self.is_dma_request = state.is_dma_request; + self.dma_request_address = state.dma_request_address; + self.is_odd_frame = state.is_odd_frame; + } +} + +impl PPUCPUConnection for PPU2C02 +where + T: Bus + Savable, +{ + fn is_nmi_pin_set(&self) -> bool { + self.nmi_pin_status.get() + } + + fn clear_nmi_pin(&mut self) { + self.nmi_pin_status.set(false); + } + + fn is_dma_request(&self) -> bool { + self.is_dma_request + } + + fn clear_dma_request(&mut self) { + self.is_dma_request = false; + } + + fn dma_address(&mut self) -> u8 { + self.dma_request_address + } + + fn send_oam_data(&mut self, address: u8, data: u8) { + self.write_sprite_byte(self.reg_oam_addr.get().wrapping_add(address), data); + } +} + +#[derive(Serialize, Deserialize, Debug)] +struct SavablePPUState { + reg_control: u8, + reg_mask: u8, + reg_status: u8, + reg_oam_addr: u8, + + scanline: u16, + cycle: u16, + + vram_address_cur: u16, + vram_address_top_left: u16, + + ppu_data_read_buffer: u8, + + fine_x_scroll: u8, + + w_toggle: bool, + + bg_pattern_shift_registers: [u16; 2], + bg_palette_shift_registers: [u16; 2], + + nmi_pin_status: bool, + nmi_occured_in_this_frame: bool, + + primary_oam: Vec, + // FIXME: add `rendering_oam` + secondary_oam: [Sprite; 8], + + rendering_oam_counter: u8, + + sprite_pattern_shift_registers: [[u8; 2]; 8], + sprite_attribute_registers: [SpriteAttribute; 8], + sprite_counters: [u8; 8], + + sprite_0_present: bool, + next_scanline_sprite_0_present: bool, + + is_dma_request: bool, + dma_request_address: u8, + + is_odd_frame: bool, +} + +impl SavablePPUState { + fn from_ppu(ppu: &PPU2C02) -> Self { + let mut primary_oam = Vec::with_capacity(ppu.primary_oam.len()); + primary_oam.extend_from_slice(&ppu.primary_oam); + + Self { + reg_control: ppu.reg_control.bits(), + reg_mask: ppu.reg_mask.bits(), + reg_status: ppu.reg_status.get().bits(), + reg_oam_addr: ppu.reg_oam_addr.get(), + scanline: ppu.scanline, + cycle: ppu.cycle, + vram_address_cur: ppu.vram_address_cur.get(), + vram_address_top_left: ppu.vram_address_top_left, + ppu_data_read_buffer: ppu.ppu_data_read_buffer.get(), + fine_x_scroll: ppu.fine_x_scroll, + w_toggle: ppu.w_toggle.get(), + bg_pattern_shift_registers: ppu.bg_pattern_shift_registers, + bg_palette_shift_registers: ppu.bg_palette_shift_registers, + nmi_pin_status: ppu.nmi_pin_status.get(), + nmi_occured_in_this_frame: ppu.nmi_occured_in_this_frame.get(), + primary_oam, + secondary_oam: ppu.secondary_oam, + rendering_oam_counter: ppu.rendering_oam_counter, + + // Since these are removed, we keep them in the save just to not corrupt + // the files + // backward compat :( + sprite_pattern_shift_registers: [[0; 2]; 8], + sprite_attribute_registers: [SpriteAttribute::empty(); 8], + sprite_counters: [0; 8], + sprite_0_present: ppu.sprite_0_present, + next_scanline_sprite_0_present: ppu.next_scanline_sprite_0_present, + is_dma_request: ppu.is_dma_request, + dma_request_address: ppu.dma_request_address, + is_odd_frame: ppu.is_odd_frame, + } + } +} + +impl Savable for PPU2C02 { + fn save(&self, writer: &mut W) -> Result<(), SaveError> { + self.bus.save(writer)?; + + let state = SavablePPUState::from_ppu(self); + + bincode::serialize_into(writer, &state).map_err(|err| match *err { + bincode::ErrorKind::Io(err) => SaveError::IoError(err), + _ => SaveError::Others, + })?; + + Ok(()) + } + + fn load(&mut self, reader: &mut R) -> Result<(), SaveError> { + self.bus.load(reader)?; + + let state: SavablePPUState = + bincode::deserialize_from(reader).map_err(|err| match *err { + bincode::ErrorKind::Io(err) => SaveError::IoError(err), + _ => SaveError::Others, + })?; + + self.load_serialized_state(state); + + Ok(()) + } +} diff --git a/plastic_core/src/ppu2c02/palette.rs b/plastic_core/src/ppu2c02/palette.rs index 34256c3..86ee089 100644 --- a/plastic_core/src/ppu2c02/palette.rs +++ b/plastic_core/src/ppu2c02/palette.rs @@ -36,12 +36,12 @@ impl Default for Palette { impl Bus for Palette { fn read(&self, address: u16, device: Device) -> u8 { - assert!(device == Device::PPU && address >= 0x3F00 && address <= 0x3FFF); + assert!(device == Device::Ppu && (0x3F00..=0x3FFF).contains(&address)); self.palette_data[Self::map_address(address) as usize] } fn write(&mut self, address: u16, data: u8, device: Device) { - assert!(device == Device::PPU && address >= 0x3F00 && address <= 0x3FFF); + assert!(device == Device::Ppu && (0x3F00..=0x3FFF).contains(&address)); self.palette_data[Self::map_address(address) as usize] = data; } diff --git a/plastic_core/src/ppu2c02/ppu2c02.rs b/plastic_core/src/ppu2c02/ppu2c02.rs deleted file mode 100644 index ea3412c..0000000 --- a/plastic_core/src/ppu2c02/ppu2c02.rs +++ /dev/null @@ -1,1275 +0,0 @@ -use super::ppu2c02_registers::Register; -use super::sprite::{Sprite, SpriteAttribute}; -use crate::common::{ - interconnection::PPUCPUConnection, - save_state::{Savable, SaveError}, - Bus, Device, -}; -use crate::display::{Color, COLORS, TV}; -use bitflags::bitflags; -use serde::{Deserialize, Serialize}; -use std::cell::Cell; -use std::cmp::min; - -bitflags! { - pub struct ControlReg: u8 { - const BASE_NAMETABLE = 0b00000011; - const VRAM_INCREMENT = 0b00000100; - const SPRITE_PATTERN_ADDRESS = 0b00001000; - const BACKGROUND_PATTERN_ADDRESS = 0b00010000; - const SPRITE_SIZE = 0b00100000; - const MASTER_SLAVE_SELECT = 0b01000000; - const GENERATE_NMI_ENABLE = 0b10000000; - } -} - -impl ControlReg { - pub fn nametable_selector(&self) -> u8 { - // 0 = $2000; 1 = $2400; 2 = $2800; 3 = $2C00 - self.bits & Self::BASE_NAMETABLE.bits - } - - pub fn vram_increment(&self) -> u16 { - if self.intersects(Self::VRAM_INCREMENT) { - 32 - } else { - 1 - } - } - - pub fn sprite_pattern_address(&self) -> u16 { - ((self.intersects(Self::SPRITE_PATTERN_ADDRESS)) as u16) << 12 - } - - pub fn background_pattern_address(&self) -> u16 { - ((self.intersects(Self::BACKGROUND_PATTERN_ADDRESS)) as u16) << 12 - } - - pub fn nmi_enabled(&self) -> bool { - self.intersects(Self::GENERATE_NMI_ENABLE) - } - - pub fn sprite_height(&self) -> u8 { - // if SPRITE_SIZE is 1, then it will be (8 << 1) == 16, else it will be 8 - 8 << self.intersects(Self::SPRITE_SIZE) as u8 - } -} - -bitflags! { - pub struct MaskReg: u8 { - const GRAYSCALE_ENABLE = 0b00000001; - const SHOW_BACKGROUND_LEFTMOST_8 = 0b00000010; - const SHOW_SPRITES_LEFTMOST_8 = 0b00000100; - const SHOW_BACKGROUND = 0b00001000; - const SHOW_SPRITES = 0b00010000; - const EMPHASIZE_RED = 0b00100000; - const EMPHASIZE_GREEN = 0b01000000; - const EMPHASIZE_BLUE = 0b10000000; - } -} - -impl MaskReg { - pub fn background_enabled(&self) -> bool { - self.intersects(Self::SHOW_BACKGROUND) - } - - pub fn sprites_enabled(&self) -> bool { - self.intersects(Self::SHOW_SPRITES) - } - - pub fn rendering_enabled(&self) -> bool { - self.background_enabled() || self.sprites_enabled() - } - - /// returns true if the background pixels on the left should not - /// be shown, false otherwise - pub fn background_left_clipping_enabled(&self) -> bool { - !self.intersects(Self::SHOW_BACKGROUND_LEFTMOST_8) - } - - /// returns true if the sprite pixels on the left should not - /// be shown, false otherwise - pub fn sprites_left_clipping_enabled(&self) -> bool { - !self.intersects(Self::SHOW_SPRITES_LEFTMOST_8) - } - - pub fn is_grayscale(&self) -> bool { - self.intersects(Self::GRAYSCALE_ENABLE) - } -} - -bitflags! { - pub struct StatusReg: u8 { - const SPRITE_OVERFLOW = 0b00100000; - const SPRITE_0_HIT = 0b01000000; - const VERTICAL_BLANK = 0b10000000; - } -} - -pub struct PPU2C02 { - // memory mapped registers - reg_control: ControlReg, - reg_mask: MaskReg, - reg_status: Cell, - reg_oam_addr: Cell, - - scanline: u16, - cycle: u16, - - /// ## PPU VRAM top 12-bit address ## (v and t) - /// NN YYYYY XXXXX - /// || ||||| +++++-- coarse X scroll - /// || +++++-------- coarse Y scroll - /// ++-------------- nametable select - vram_address_cur: Cell, - vram_address_top_left: u16, - - ppu_data_read_buffer: Cell, - - fine_x_scroll: u8, - - w_toggle: Cell, // this is used for registers that require 2 writes - - bg_pattern_shift_registers: [u16; 2], - bg_palette_shift_registers: [u16; 2], - - nmi_pin_status: Cell, - nmi_occured_in_this_frame: Cell, - - bus: T, - tv: TV, - - primary_oam: [Sprite; 64], - secondary_oam: [Sprite; 8], - rendering_oam: [Sprite; 8], - - rendering_oam_counter: u8, - - sprite_0_present: bool, - next_scanline_sprite_0_present: bool, - - is_dma_request: bool, - dma_request_address: u8, - - is_odd_frame: bool, -} - -impl PPU2C02 -where - T: Bus + Savable, -{ - pub fn new(bus: T, tv: TV) -> Self { - Self { - reg_control: ControlReg::empty(), - reg_mask: MaskReg::empty(), - reg_status: Cell::new(StatusReg::empty()), - reg_oam_addr: Cell::new(0), - - // this would result in it starting from 0,0 next cycle - scanline: 261, // start from -1 scanline - cycle: 340, // last cycle - - vram_address_cur: Cell::new(0), - vram_address_top_left: 0, - - ppu_data_read_buffer: Cell::new(0), - - fine_x_scroll: 0, - - w_toggle: Cell::new(false), - - bg_pattern_shift_registers: [0; 2], - bg_palette_shift_registers: [0; 2], - - nmi_pin_status: Cell::new(false), - nmi_occured_in_this_frame: Cell::new(false), - - bus, - tv, - - primary_oam: [Sprite::empty(); 64], - secondary_oam: [Sprite::empty(); 8], - rendering_oam: [Sprite::empty(); 8], - - rendering_oam_counter: 0, - - sprite_0_present: false, - next_scanline_sprite_0_present: false, - - is_dma_request: false, - dma_request_address: 0, - - is_odd_frame: false, - } - } - - pub(crate) fn read_register(&self, register: Register) -> u8 { - match register { - Register::Status => { - // reset w_mode - self.w_toggle.set(false); - - if self.scanline == 241 { - // Race Condition Warning: Reading PPUSTATUS within two - // cycles of the start of vertical blank will return 0 in bit 7 - // but clear the latch anyway, causing NMI to not occur that frame - if self.cycle <= 2 { - self.reg_status - .set(StatusReg::from_bits(self.reg_status.get().bits & 0x7F).unwrap()); - } - // for NMI it has quite a different range - // source: tests - if self.cycle >= 2 && self.cycle <= 4 { - self.nmi_pin_status.set(false); - self.nmi_occured_in_this_frame.set(true); - } - } - let result = self.reg_status.get().bits; - // reading the status register will clear bit 7 - self.reg_status - .set(StatusReg::from_bits(result & 0x7F).unwrap()); - - result - } - Register::OmaData => self.read_sprite_byte(self.reg_oam_addr.get()), - Register::PPUData => { - let address = self.vram_address_cur.get(); - let data_in_addr = self.read_bus(address); - - // only 0 - 0x2FFF (before palette) is buffered - let result = if address <= 0x3EFF { - let tmp_result = self.ppu_data_read_buffer.get(); - - // fill buffer - self.ppu_data_read_buffer.set(data_in_addr); - - tmp_result - } else { - // reload buffer with VRAM address hidden by palette - // wrap to 0x2FFF rather than 0x3EFF, to avoid the mirror - self.ppu_data_read_buffer - .set(self.read_bus(address & 0x2FFF)); - data_in_addr - }; - - self.increment_vram_readwrite(); - - result - } - _ => { - // unreadable - 0 - } - } - } - - pub(crate) fn write_register(&mut self, register: Register, data: u8) { - match register { - // After power/reset, writes to this register are ignored for about 30,000 cycles - // TODO: not sure, if I should account for that - Register::Control => { - self.reg_control.bits = data; - - // write nametable also in top_left vram address - self.vram_address_top_left &= 0xF3FF; - self.vram_address_top_left |= (self.reg_control.nametable_selector() as u16) << 10; - - // if the NMI flag is set, run immediate NMI to the CPU - // but only run if we are in the VBLANK period and no - // other NMI has occurred so far - if self.reg_control.nmi_enabled() { - if self.reg_status.get().intersects(StatusReg::VERTICAL_BLANK) - && !self.nmi_occured_in_this_frame.get() - { - self.nmi_pin_status.set(true); - self.nmi_occured_in_this_frame.set(true); - } - } else { - // if the NMI is disabled, stop the NMI (if the flag was set) - if self.scanline == 241 && self.cycle <= 4 { - self.nmi_pin_status.set(false); - self.nmi_occured_in_this_frame.set(true); - } else { - // in case if the NMI flag was disabled, then mark as nmi - // never occurred on this frame, even if it has - // meaning, that in some cases 2 NMI can occur - self.nmi_occured_in_this_frame.set(false); - } - } - } - Register::Mask => self.reg_mask.bits = data, - Register::OmaAddress => self.reg_oam_addr.set(data), - Register::OmaData => { - self.write_sprite_byte(self.reg_oam_addr.get(), data); - if self.scanline > 240 || !self.reg_mask.rendering_enabled() { - *self.reg_oam_addr.get_mut() = self.reg_oam_addr.get().wrapping_add(1); - } - } - Register::Scroll => { - if self.w_toggle.get() { - // w == 1 - self.set_top_left_y_scroll(data); - } else { - // w == 0 - self.set_top_left_x_scroll(data); - } - - self.w_toggle.set(!self.w_toggle.get()); - } - Register::PPUAddress => { - if self.w_toggle.get() { - // w == 1 - - // zero out the bottom 8 bits - self.vram_address_top_left &= 0xff00; - // set the data from the parameters - self.vram_address_top_left |= data as u16; - - // a dummy read to the cartridge as some mappers rely - // on PPU address pins for operations - let _ = self.read_bus(self.vram_address_top_left); - - // copy to the current vram address - *self.vram_address_cur.get_mut() = self.vram_address_top_left; - } else { - // w == 0 - - // zero out the top 8 bits - self.vram_address_top_left &= 0x00ff; - // set the data from the parameters - self.vram_address_top_left |= (data as u16) << 8; - - // update nametable - self.reg_control.bits &= !(ControlReg::BASE_NAMETABLE.bits); - self.reg_control.bits |= (data >> 2) & 0b11; - } - - self.w_toggle.set(!self.w_toggle.get()); - } - Register::PPUData => { - self.write_bus(self.vram_address_cur.get(), data); - self.increment_vram_readwrite(); - } - Register::DmaOma => { - self.dma_request_address = data; - self.is_dma_request = true; - } - _ => { - // unwritable - } - }; - } - - /// expose the bus for reading only - pub fn ppu_bus(&self) -> &T { - &self.bus - } - - fn read_bus(&self, address: u16) -> u8 { - self.bus.read(address, Device::PPU) - } - - fn write_bus(&mut self, address: u16, data: u8) { - self.bus.write(address, data, Device::PPU); - } - - fn read_sprite_byte(&self, address: u8) -> u8 { - let sprite_location = address >> 2; - self.primary_oam[sprite_location as usize].read_offset(address & 0b11) - } - - fn write_sprite_byte(&mut self, address: u8, data: u8) { - let sprite_location = address >> 2; - self.primary_oam[sprite_location as usize].write_offset(address & 0b11, data); - } - - // SCROLL attributes START - fn current_coarse_x_scroll(&self) -> u8 { - (self.vram_address_cur.get() & 0b11111) as u8 - } - - fn set_current_coarse_x_scroll(&mut self, coarse_x: u8) { - let vram_cur = self.vram_address_cur.get_mut(); - - // clear first 5 bits - *vram_cur &= 0xFFE0; - // copy new value - *vram_cur |= (coarse_x & 0b11111) as u16; - } - - fn current_coarse_y_scroll(&self) -> u8 { - ((self.vram_address_cur.get() >> 5) & 0b11111) as u8 - } - - fn set_current_coarse_y_scroll(&mut self, coarse_y: u8) { - let vram_cur = self.vram_address_cur.get_mut(); - - // clear second 5 bits - *vram_cur &= 0xFC1F; - // copy new value - *vram_cur |= ((coarse_y & 0b11111) as u16) << 5; - } - - fn current_fine_x_scroll(&self) -> u8 { - self.fine_x_scroll - } - - // this is just for completion, and mostly it will not be ever used - #[allow(unused)] - fn set_current_fine_x_scroll(&mut self, fine_x: u8) { - self.fine_x_scroll = fine_x & 0b111; - } - - fn current_fine_y_scroll(&self) -> u8 { - ((self.vram_address_cur.get() >> 12) & 0b111) as u8 - } - - fn set_current_fine_y_scroll(&mut self, fine_y: u8) { - let vram_cur = self.vram_address_cur.get_mut(); - - // clear fine_y - *vram_cur &= 0x0FFF; - // copy new value - *vram_cur |= ((fine_y & 0b111) as u16) << 12; - } - - fn top_left_coarse_x_scroll(&self) -> u8 { - (self.vram_address_top_left & 0b11111) as u8 - } - - fn set_top_left_coarse_x_scroll(&mut self, coarse_x: u8) { - // clear first 5 bits - self.vram_address_top_left &= 0xFFE0; - // copy new value - self.vram_address_top_left |= (coarse_x & 0b11111) as u16; - } - - fn top_left_coarse_y_scroll(&self) -> u8 { - ((self.vram_address_top_left >> 5) & 0b11111) as u8 - } - - fn set_top_left_coarse_y_scroll(&mut self, coarse_y: u8) { - // clear second 5 bits - self.vram_address_top_left &= 0xFC1F; - // copy new value - self.vram_address_top_left |= ((coarse_y & 0b11111) as u16) << 5; - } - - fn set_top_left_fine_x_scroll(&mut self, fine_x: u8) { - self.fine_x_scroll = fine_x & 0b111; - } - - fn top_left_fine_y_scroll(&self) -> u8 { - ((self.vram_address_top_left >> 12) & 0b111) as u8 - } - - fn set_top_left_fine_y_scroll(&mut self, fine_y: u8) { - // clear fine_y - self.vram_address_top_left &= 0x0FFF; - // copy new value - self.vram_address_top_left |= ((fine_y & 0b111) as u16) << 12; - } - - fn set_top_left_x_scroll(&mut self, x_scroll: u8) { - self.set_top_left_coarse_x_scroll(x_scroll >> 3); - self.set_top_left_fine_x_scroll(x_scroll & 0b111); - } - - fn set_top_left_y_scroll(&mut self, y_scroll: u8) { - self.set_top_left_coarse_y_scroll(y_scroll >> 3); - self.set_top_left_fine_y_scroll(y_scroll & 0b111); - } - - fn increment_y_scroll(&mut self) { - // increment fine scrolling Y on the last dot without carry - let fine_y = self.current_fine_y_scroll() + 1; - - self.set_current_fine_y_scroll(fine_y & 0b111); - - // if the increment resulted in a carry, go to the next tile - // i.e. increment coarse Y - if fine_y & 0x8 != 0 { - // extract coarse_y - let mut coarse_y = self.current_coarse_y_scroll(); - - // in case of overflow, increment nametable vertical address - if coarse_y == 29 { - coarse_y = 0; - self.increment_vram_nametable_vertical(); - } else if coarse_y == 31 { - coarse_y = 0; - } else { - coarse_y += 1; - } - - self.set_current_coarse_y_scroll(coarse_y); - } - } - - fn increment_coarse_x_scroll(&mut self) { - let coarse_x = self.current_coarse_x_scroll() + 1; - - self.set_current_coarse_x_scroll(coarse_x & 0b11111); - - // in case of overflow, increment nametable horizontal address - if coarse_x & 0b100000 != 0 { - self.increment_vram_nametable_horizontal(); - } - } - // SCROLL attributes END - - fn increment_vram_readwrite(&self) { - // only increment if its valid, and increment by the correct ammount - if self.scanline > 240 || !self.reg_mask.rendering_enabled() { - self.vram_address_cur - .set(self.vram_address_cur.get() + self.reg_control.vram_increment()); - - // dummy read to update the cartridge, which mappers rely on some - // address pins from the PPU - let _ = self.read_bus(self.vram_address_cur.get()); - } - } - - fn restore_rendering_scroll_x(&mut self) { - self.set_current_coarse_x_scroll(self.top_left_coarse_x_scroll()); - } - - fn restore_rendering_scroll_y(&mut self) { - self.set_current_fine_y_scroll(self.top_left_fine_y_scroll()); - self.set_current_coarse_y_scroll(self.top_left_coarse_y_scroll()); - } - - fn increment_vram_nametable_horizontal(&mut self) { - *self.vram_address_cur.get_mut() ^= 0b01 << 10; - } - - fn increment_vram_nametable_vertical(&mut self) { - *self.vram_address_cur.get_mut() ^= 0b10 << 10; - } - - // restore from top_left or original nametable selector from `reg_control` - fn restore_nametable(&mut self) { - let vram_cur = self.vram_address_cur.get_mut(); - - // clear old nametable data - *vram_cur &= 0xF3FF; - - *vram_cur |= (self.reg_control.nametable_selector() as u16) << 10; - } - - fn restore_nametable_horizontal(&mut self) { - let vram_cur = self.vram_address_cur.get_mut(); - - // clear horizontal nametable data - *vram_cur &= 0xFBFF; - - *vram_cur |= (self.reg_control.nametable_selector() as u16 & 1) << 10; - } - - fn current_nametable(&self) -> u16 { - (self.vram_address_cur.get() >> 10) & 0b11 - } - - // this should only be called when rendering and a bit after that, - // i.e. when scanline number is in range 0 >= scanline > 255 - fn get_next_scroll_y_render(&self) -> u8 { - if self.scanline == 261 { - 0 - } else if self.scanline < 255 { - (self.scanline + 1) as u8 - } else { - unreachable!() - } - } - - #[allow(clippy::needless_range_loop)] - fn reload_background_shift_registers(&mut self) { - // tile address = 0x2000 | (v & 0x0FFF) - let nametable_tile = self.read_bus(0x2000 | self.vram_address_cur.get() & 0xFFF); - - let tile_pattern = self.fetch_pattern_background(nametable_tile); - - // fetch and prepare the palette - let attribute_byte = self.fetch_attribute_byte(); - - // Each byte controls the palette of a 32×32 pixel or 4×4 tile part of the - // nametable and is divided into four 2-bit areas. Each area covers 16×16 - // pixels or 2×2 tiles. Given palette numbers topleft, topright, - // bottomleft, bottomright, each in the range 0 to 3, the value of - // the byte is - // `value = (bottomright << 6) | (bottomleft << 4) | (topright << 2) | (topleft << 0)` - let coarse_x = self.current_coarse_x_scroll(); - let coarse_y = self.current_coarse_y_scroll(); - let attribute_location_x = (coarse_x >> 1) & 0x1; - let attribute_location_y = (coarse_y >> 1) & 0x1; - - // `attribute_location_x`: 0 => left, 1 => right - // `attribute_location_y`: 0 => top, 1 => bottom - let attribute_location = attribute_location_y << 1 | attribute_location_x; - - // 00: top-left, 01: top-right, 10: bottom-left, 11: bottom-right - // bit-1 is for (top, bottom), bit-0 is for (left, right) - let palette = (attribute_byte >> (attribute_location * 2)) & 0b11; - - // update th shift registers - for i in 0..=1 { - // clear the bottom value - self.bg_pattern_shift_registers[i] &= 0xFF00; - - // in this stage, because we reload in dots (8, 16, 24...) - // the shift registers will be shifted one more time - // meaning, it will be shifted 8 times - self.bg_pattern_shift_registers[i] |= tile_pattern[i] as u16; - - // clear the bottom value - self.bg_palette_shift_registers[i] &= 0xFF00; - - // as palettes are two bits, we store the first bit in index 0 and - // the second bit in index 1 in the array - // - // this is similar to how the patterns are stored in CHR table - self.bg_palette_shift_registers[i] |= 0xFF * ((palette >> i) & 1) as u16; - } - } - - /// ## PPU pattern table addressing ## - /// DCBA98 76543210 - /// --------------- - /// 0HRRRR CCCCPTTT - /// |||||| |||||+++- T: Fine Y offset, the row number within a tile - /// |||||| ||||+---- P: Bit plane (0: "lower"; 1: "upper") - /// |||||| ++++----- C: Tile column - /// ||++++---------- R: Tile row - /// |+-------------- H: Half of sprite table (0: "left"; 1: "right") - /// +--------------- 0: Pattern table is at $0000-$1FFF - #[allow(clippy::identity_op)] - fn fetch_pattern(&self, pattern_table: u16, location: u8, fine_y: u8) -> [u8; 2] { - let fine_y = fine_y as u16; - - let low_plane_pattern = - self.read_bus(pattern_table | (location as u16) << 4 | 0 << 3 | fine_y); - - let high_plane_pattern = - self.read_bus(pattern_table | (location as u16) << 4 | 1 << 3 | fine_y); - - [low_plane_pattern, high_plane_pattern] - } - - fn fetch_pattern_background(&self, location: u8) -> [u8; 2] { - let fine_y = self.current_fine_y_scroll(); - - // for background - let pattern_table = self.reg_control.background_pattern_address(); - - self.fetch_pattern(pattern_table, location, fine_y) - } - - /// ## Attribute address ## - /// NN 1111 YYY XXX - /// || |||| ||| +++-- high 3 bits of coarse X (x/4) - /// || |||| +++------ high 3 bits of coarse Y (y/4) - /// || ++++---------- attribute offset (960 bytes) - /// ++--------------- nametable select - /// - /// or - /// - /// `attribute address = 0x23C0 | (v & 0x0C00) | ((v >> 4) & 0x38) | ((v >> 2) & 0x07)` - /// where x, y and nametable are used from `vram_address_cur` - fn fetch_attribute_byte(&self) -> u8 { - let x = (self.current_coarse_x_scroll() >> 2) as u16; - let y = (self.current_coarse_y_scroll() >> 2) as u16; - - self.read_bus(0x2000 | self.current_nametable() << 10 | 0xF << 6 | y << 3 | x) - } - - fn reload_sprite_shift_registers(&mut self) { - // move sprite_0_present - self.sprite_0_present = self.next_scanline_sprite_0_present; - // reset for scanline after next - self.next_scanline_sprite_0_present = false; - - let next_y = self.get_next_scroll_y_render(); - - let sprite_height = self.reg_control.sprite_height(); - - // loop through all secondary_oam, even the empty ones (0xFF) - // a write to the cartridge MUST be done here even if no sprites - // are drawn - for i in 0..8 as usize { - let mut sprite = self.secondary_oam[i]; - let mut fine_y = next_y.wrapping_sub(sprite.get_y()); - - // handle flipping vertically - if sprite.get_attribute().is_flip_vertical() { - fine_y = (sprite_height - 1).wrapping_sub(fine_y); - } - - sprite.set_pattern(self.fetch_pattern_sprite(sprite.get_tile(), fine_y)); - - self.rendering_oam[i] = sprite; - } - } - - fn fetch_pattern_sprite(&self, tile: u8, mut fine_y: u8) -> [u8; 2] { - let mut location = tile; - - // for sprites - let pattern_table = if self.reg_control.sprite_height() == 16 { - // zero the first bit as it is used as a pattern_table selector - location &= !(1); - ((tile & 1) as u16) << 12 - } else { - self.reg_control.sprite_pattern_address() - }; - - if fine_y > 7 { - fine_y -= 8; - location = location.wrapping_add(1); - } - - self.fetch_pattern(pattern_table, location, fine_y) - } - - fn get_background_pixel(&self) -> u8 { - if !self.reg_mask.background_enabled() - || (self.cycle < 8 && self.reg_mask.background_left_clipping_enabled()) - { - return 0; - } - - let fine_x = self.current_fine_x_scroll(); - - // select the bit using `fine_x` from the left - let bit_location = 15 - fine_x; - - let low_plane_bit = - ((self.bg_pattern_shift_registers[0] >> bit_location as u16) & 0x1) as u8; - let high_plane_bit = - ((self.bg_pattern_shift_registers[1] >> bit_location as u16) & 0x1) as u8; - - let color_bit = high_plane_bit << 1 | low_plane_bit; - - if color_bit != 0 { - let low_palette_bit = - ((self.bg_palette_shift_registers[0] >> bit_location as u16) & 0x1) as u8; - let high_palette_bit = - ((self.bg_palette_shift_registers[1] >> bit_location as u16) & 0x1) as u8; - - let palette = high_palette_bit << 1 | low_palette_bit; - - palette << 2 | color_bit - } else { - 0 - } - } - - fn get_sprites_first_non_transparent_pixel(&mut self) -> (u8, bool, bool) { - if !self.reg_mask.sprites_enabled() - || (self.cycle < 8 && self.reg_mask.sprites_left_clipping_enabled()) - || self.cycle == 255 - { - return (0, false, false); - } - - for (i, sprite) in self - .rendering_oam - .iter() - .take(self.rendering_oam_counter as usize) - .enumerate() - { - let current_color_bits = sprite.get_color_bits(self.cycle); - - if current_color_bits != 0 { - let attribute = sprite.get_attribute(); - return ( - // (1 << 4) to select the sprite table - 1 << 4 | attribute.palette() << 2 | current_color_bits, - sprite.get_attribute().is_behind_background(), - i == 0 && self.sprite_0_present, - ); - } - } - - (0, false, false) - } - - /// this method fetches background and sprite pixels, check overflow for - /// sprite_0 and priority, and handles the left 8-pixel clipping - /// and then outputs a color index - /// - /// ## color location offset 0x3F00 ## - /// 43210 - /// ||||| - /// |||++- Pixel value from tile data - /// |++--- Palette number from attribute table or OAM - /// +----- Background/Sprite select - fn generate_pixel(&mut self) -> u8 { - let background_color_location = self.get_background_pixel(); - - let (sprite_color_location, background_priority, is_sprite_0) = - self.get_sprites_first_non_transparent_pixel(); - - // sprite and background multiplexer procedure - let color_location = if sprite_color_location != 0 && background_color_location != 0 { - if is_sprite_0 { - // if sprite and background are not transparent, then there is a collision - self.reg_status.get_mut().insert(StatusReg::SPRITE_0_HIT); - } - // use background priority flag - if background_priority { - background_color_location - } else { - sprite_color_location - } - } else { - sprite_color_location | background_color_location - }; - - // advance the shift registers - for i in 0..=1 { - self.bg_pattern_shift_registers[i] = self.bg_pattern_shift_registers[i].wrapping_shl(1); - self.bg_palette_shift_registers[i] = self.bg_palette_shift_registers[i].wrapping_shl(1); - } - - self.read_bus(0x3F00 | color_location as u16) - } - - fn emphasis_color(&self, color: Color) -> Color { - let is_red_emph = self.reg_mask.intersects(MaskReg::EMPHASIZE_RED); - let is_green_emph = self.reg_mask.intersects(MaskReg::EMPHASIZE_GREEN); - let is_blue_emph = self.reg_mask.intersects(MaskReg::EMPHASIZE_BLUE); - - let mut red = 1.; - let mut green = 1.; - let mut blue = 1.; - - if is_red_emph { - red *= 1.1; - green *= 0.9; - blue *= 0.9; - } - if is_green_emph { - red *= 0.9; - green *= 1.1; - blue *= 0.9; - } - if is_blue_emph { - red *= 0.9; - green *= 0.9; - blue *= 1.1; - } - - Color { - r: min((color.r as f32 * red) as u8, 255), - g: min((color.g as f32 * green) as u8, 255), - b: min((color.b as f32 * blue) as u8, 255), - } - } - - fn render_pixel(&mut self) { - // fix overflowing colors - let mut color = self.generate_pixel() & 0x3F; - - if self.reg_mask.is_grayscale() { - // select from the gray column (0x00, 0x10, 0x20, 0x30) - color &= 0x30; - } - - // render the color - self.tv.set_pixel( - self.cycle as u32, - self.scanline as u32, - &self.emphasis_color(COLORS[color as usize]), - ); - } - - // run one cycle, this should be fed from Master clock - pub fn clock(&mut self) { - // current scanline - match (self.scanline, self.cycle) { - (261, 0) => { - // FIXME: for some reason the test only worked when doing it here - - // clear sprite 0 hit - self.reg_status.get_mut().remove(StatusReg::SPRITE_0_HIT) - } - (261, 2) => { - // reset nmi_occured_in_this_frame - self.nmi_occured_in_this_frame.set(false); - } - (261, 1) => { - // clear sprite overflow - self.reg_status.get_mut().remove(StatusReg::SPRITE_OVERFLOW); - // clear v-blank - self.reg_status.get_mut().remove(StatusReg::VERTICAL_BLANK); - - if self.reg_mask.rendering_enabled() { - self.restore_rendering_scroll_x(); - self.restore_rendering_scroll_y(); - - self.restore_nametable(); - - // load next 2 bytes - for _ in 0..2 { - for i in 0..=1 { - // as this is the first time, shift the registers - // as we are reloading 2 times - self.bg_pattern_shift_registers[i] = - self.bg_pattern_shift_registers[i].wrapping_shl(8); - self.bg_palette_shift_registers[i] = - self.bg_palette_shift_registers[i].wrapping_shl(8); - } - self.reload_background_shift_registers(); - self.increment_coarse_x_scroll(); - } - } - } - (261, 257) => { - // reload all of them in one go - self.reload_sprite_shift_registers(); - } - (0..=239, _) => { - // render only if allowed - if self.reg_mask.rendering_enabled() { - self.run_render_cycle(); - } - } - (240, 1) => { - // post-render - // idle - self.tv.signal_end_of_frame(); - } - (241, 1) => { - // set v-blank - self.reg_status.get_mut().insert(StatusReg::VERTICAL_BLANK); - - // if raising NMI is enabled - if self.reg_control.nmi_enabled() && !self.nmi_occured_in_this_frame.get() { - self.nmi_pin_status.set(true); - self.nmi_occured_in_this_frame.set(true); - } - } - _ => {} - } - - self.cycle += 1; - if self.cycle > 340 - || (self.scanline == 261 - && self.cycle == 340 - && self.is_odd_frame - && self.reg_mask.rendering_enabled()) - { - self.scanline += 1; - self.cycle = 0; - - // next frame - if self.scanline > 261 { - self.scanline = 0; - self.is_odd_frame = !self.is_odd_frame; - } - } - } - - // run one cycle which is part of a scanline execution - fn run_render_cycle(&mut self) { - match self.cycle { - // secondary OAM clear, cycles 1-64, but we do it in one go - // TODO: should it be in multiple times, instead of one go? - 1 => { - self.secondary_oam = [Sprite::filled_ff(); 8]; - } - // fetch and reload shift registers - 8..=256 if self.cycle % 8 == 0 => { - self.reload_background_shift_registers(); - - if self.cycle != 256 { - // increment scrolling X in current VRAM address - self.increment_coarse_x_scroll(); - } else { - // fine and carry to coarse - self.increment_y_scroll(); - } - } - // check all oam memory in one go - 255 => { - let next_y = self.get_next_scroll_y_render() as i16; - - let mut counter = 0; - for (i, sprite) in self.primary_oam.iter().enumerate() { - let sprite_y = sprite.get_y() as i16; - - let diff = next_y - sprite_y; - let height = self.reg_control.sprite_height() as i16; - - if diff >= 0 && diff < height { - // in range - - // sprite 0 - if i == 0 { - self.next_scanline_sprite_0_present = true; - } - - if counter > 7 { - // overflow - self.reg_status.get_mut().insert(StatusReg::SPRITE_OVERFLOW); - break; - } - - self.secondary_oam[counter] = *sprite; - - counter += 1; - } - } - - self.rendering_oam_counter = counter as u8; - } - 257 => { - self.restore_rendering_scroll_x(); - - // to fix nametable wrapping around - self.restore_nametable_horizontal(); - } - 258 => { - // reload them all in one go - self.reload_sprite_shift_registers(); - } - 321 => { - // load next 2 bytes - for _ in 0..2 { - for i in 0..=1 { - // as this is the first time, shift the registers - // as we are reloading 2 times - self.bg_pattern_shift_registers[i] = - self.bg_pattern_shift_registers[i].wrapping_shl(8); - self.bg_palette_shift_registers[i] = - self.bg_palette_shift_registers[i].wrapping_shl(8); - } - self.reload_background_shift_registers(); - self.increment_coarse_x_scroll(); - } - } - _ => {} - } - - // render after reloading - if self.cycle <= 255 { - // main render - self.render_pixel(); - } - } - - pub fn reset(&mut self, bus: T) { - // just as if calling the constructor but without TV, just reset it - self.reg_control = ControlReg::empty(); - self.reg_mask = MaskReg::empty(); - self.reg_status = Cell::new(StatusReg::empty()); - self.reg_oam_addr = Cell::new(0); - - self.scanline = 0; // start from -1 scanline - self.cycle = 0; - - self.vram_address_cur = Cell::new(0); - self.vram_address_top_left = 0; - - self.ppu_data_read_buffer = Cell::new(0); - - self.fine_x_scroll = 0; - - self.w_toggle = Cell::new(false); - - self.bg_pattern_shift_registers = [0; 2]; - self.bg_palette_shift_registers = [0; 2]; - - self.nmi_pin_status = Cell::new(false); - self.nmi_occured_in_this_frame = Cell::new(false); - - self.bus = bus; - - self.primary_oam = [Sprite::empty(); 64]; - self.secondary_oam = [Sprite::empty(); 8]; - self.rendering_oam = [Sprite::empty(); 8]; - - self.rendering_oam_counter = 0; - - self.sprite_0_present = false; - self.next_scanline_sprite_0_present = false; - - self.is_dma_request = false; - self.dma_request_address = 0; - - self.is_odd_frame = false; - - self.tv.reset(); - } - - fn load_serialized_state(&mut self, state: SavablePPUState) { - let mut primary_oam = [Sprite::empty(); 64]; - primary_oam.copy_from_slice(state.primary_oam.as_slice()); - - self.reg_control = ControlReg::from_bits(state.reg_control).unwrap(); - self.reg_mask = MaskReg::from_bits(state.reg_mask).unwrap(); - *self.reg_status.get_mut() = StatusReg::from_bits(state.reg_status).unwrap(); - *self.reg_oam_addr.get_mut() = state.reg_oam_addr; - self.scanline = state.scanline; - self.cycle = state.cycle; - *self.vram_address_cur.get_mut() = state.vram_address_cur; - self.vram_address_top_left = state.vram_address_top_left; - *self.ppu_data_read_buffer.get_mut() = state.ppu_data_read_buffer; - self.fine_x_scroll = state.fine_x_scroll; - *self.w_toggle.get_mut() = state.w_toggle; - self.bg_pattern_shift_registers = state.bg_pattern_shift_registers; - self.bg_palette_shift_registers = state.bg_palette_shift_registers; - *self.nmi_pin_status.get_mut() = state.nmi_pin_status; - *self.nmi_occured_in_this_frame.get_mut() = state.nmi_occured_in_this_frame; - self.primary_oam = primary_oam; - self.secondary_oam = state.secondary_oam; - self.rendering_oam_counter = state.rendering_oam_counter; - self.sprite_0_present = state.sprite_0_present; - self.next_scanline_sprite_0_present = state.next_scanline_sprite_0_present; - self.is_dma_request = state.is_dma_request; - self.dma_request_address = state.dma_request_address; - self.is_odd_frame = state.is_odd_frame; - } -} - -impl PPUCPUConnection for PPU2C02 -where - T: Bus + Savable, -{ - fn is_nmi_pin_set(&self) -> bool { - self.nmi_pin_status.get() - } - - fn clear_nmi_pin(&mut self) { - self.nmi_pin_status.set(false); - } - - fn is_dma_request(&self) -> bool { - self.is_dma_request - } - - fn clear_dma_request(&mut self) { - self.is_dma_request = false; - } - - fn dma_address(&mut self) -> u8 { - self.dma_request_address - } - - fn send_oam_data(&mut self, address: u8, data: u8) { - self.write_sprite_byte(self.reg_oam_addr.get().wrapping_add(address), data); - } -} - -#[derive(Serialize, Deserialize, Debug)] -struct SavablePPUState { - reg_control: u8, - reg_mask: u8, - reg_status: u8, - reg_oam_addr: u8, - - scanline: u16, - cycle: u16, - - vram_address_cur: u16, - vram_address_top_left: u16, - - ppu_data_read_buffer: u8, - - fine_x_scroll: u8, - - w_toggle: bool, - - bg_pattern_shift_registers: [u16; 2], - bg_palette_shift_registers: [u16; 2], - - nmi_pin_status: bool, - nmi_occured_in_this_frame: bool, - - primary_oam: Vec, - // FIXME: add `rendering_oam` - secondary_oam: [Sprite; 8], - - rendering_oam_counter: u8, - - sprite_pattern_shift_registers: [[u8; 2]; 8], - sprite_attribute_registers: [SpriteAttribute; 8], - sprite_counters: [u8; 8], - - sprite_0_present: bool, - next_scanline_sprite_0_present: bool, - - is_dma_request: bool, - dma_request_address: u8, - - is_odd_frame: bool, -} - -impl SavablePPUState { - fn from_ppu(ppu: &PPU2C02) -> Self { - let mut primary_oam = Vec::with_capacity(ppu.primary_oam.len()); - primary_oam.extend_from_slice(&ppu.primary_oam); - - Self { - reg_control: ppu.reg_control.bits(), - reg_mask: ppu.reg_mask.bits(), - reg_status: ppu.reg_status.get().bits(), - reg_oam_addr: ppu.reg_oam_addr.get(), - scanline: ppu.scanline, - cycle: ppu.cycle, - vram_address_cur: ppu.vram_address_cur.get(), - vram_address_top_left: ppu.vram_address_top_left, - ppu_data_read_buffer: ppu.ppu_data_read_buffer.get(), - fine_x_scroll: ppu.fine_x_scroll, - w_toggle: ppu.w_toggle.get(), - bg_pattern_shift_registers: ppu.bg_pattern_shift_registers, - bg_palette_shift_registers: ppu.bg_palette_shift_registers, - nmi_pin_status: ppu.nmi_pin_status.get(), - nmi_occured_in_this_frame: ppu.nmi_occured_in_this_frame.get(), - primary_oam, - secondary_oam: ppu.secondary_oam, - rendering_oam_counter: ppu.rendering_oam_counter, - - // Since these are removed, we keep them in the save just to not corrupt - // the files - // backward compat :( - sprite_pattern_shift_registers: [[0; 2]; 8], - sprite_attribute_registers: [SpriteAttribute::empty(); 8], - sprite_counters: [0; 8], - sprite_0_present: ppu.sprite_0_present, - next_scanline_sprite_0_present: ppu.next_scanline_sprite_0_present, - is_dma_request: ppu.is_dma_request, - dma_request_address: ppu.dma_request_address, - is_odd_frame: ppu.is_odd_frame, - } - } -} - -impl Savable for PPU2C02 { - fn save(&self, writer: &mut W) -> Result<(), SaveError> { - self.bus.save(writer)?; - - let state = SavablePPUState::from_ppu(self); - - bincode::serialize_into(writer, &state).map_err(|err| match *err { - bincode::ErrorKind::Io(err) => SaveError::IoError(err), - _ => SaveError::Others, - })?; - - Ok(()) - } - - fn load(&mut self, reader: &mut R) -> Result<(), SaveError> { - self.bus.load(reader)?; - - let state: SavablePPUState = - bincode::deserialize_from(reader).map_err(|err| match *err { - bincode::ErrorKind::Io(err) => SaveError::IoError(err), - _ => SaveError::Others, - })?; - - self.load_serialized_state(state); - - Ok(()) - } -} diff --git a/plastic_core/src/ppu2c02/ppu2c02_registers.rs b/plastic_core/src/ppu2c02/ppu2c02_registers.rs index 08e5039..2aa03a4 100644 --- a/plastic_core/src/ppu2c02/ppu2c02_registers.rs +++ b/plastic_core/src/ppu2c02/ppu2c02_registers.rs @@ -22,7 +22,7 @@ where { fn read(&self, address: u16, device: Device) -> u8 { // only the CPU is allowed to read from PPU registers - if device == Device::CPU { + if device == Device::Cpu { if let Ok(register) = address.try_into() { self.read_register(register) } else { @@ -35,7 +35,7 @@ where fn write(&mut self, address: u16, data: u8, device: Device) { // only the CPU is allowed to write to PPU registers - if device == Device::CPU { + if device == Device::Cpu { if let Ok(register) = address.try_into() { self.write_register(register, data); } else { diff --git a/plastic_core/src/ppu2c02/vram.rs b/plastic_core/src/ppu2c02/vram.rs index 00c3f73..a5eded7 100644 --- a/plastic_core/src/ppu2c02/vram.rs +++ b/plastic_core/src/ppu2c02/vram.rs @@ -41,18 +41,18 @@ impl VRam { impl Bus for VRam { fn read(&self, address: u16, device: Device) -> u8 { - assert!(device == Device::PPU); + assert!(device == Device::Ppu); let address = self.map_address(address); - self.vram_data[address as usize] + self.vram_data[address] } fn write(&mut self, address: u16, data: u8, device: Device) { - assert!(device == Device::PPU); + assert!(device == Device::Ppu); let address = self.map_address(address); - self.vram_data[address as usize] = data; + self.vram_data[address] = data; } } diff --git a/plastic_core/src/tests/blargg_tests.rs b/plastic_core/src/tests/blargg_tests.rs index 5086daa..93b9859 100644 --- a/plastic_core/src/tests/blargg_tests.rs +++ b/plastic_core/src/tests/blargg_tests.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + use super::{NesTester, TestError}; fn run_sprite_hit_test(filename: &str) -> Result<(), TestError> { diff --git a/plastic_core/src/tests/mod.rs b/plastic_core/src/tests/mod.rs index 0cbe554..9730a6e 100644 --- a/plastic_core/src/tests/mod.rs +++ b/plastic_core/src/tests/mod.rs @@ -1,4 +1,3 @@ -#![cfg(test)] use crate::apu2a03::APU2A03; use crate::cartridge::{Cartridge, CartridgeError}; use crate::common::{ @@ -22,6 +21,7 @@ mod blargg_tests; // FIXME: used constants hosted in TV const TV_WIDTH: u32 = 256; +#[allow(dead_code)] const TV_HEIGHT: u32 = 240; pub enum TestError { @@ -136,20 +136,20 @@ impl CPUBusTrait for CPUBus { 0x2000..=0x3FFF => self .ppu .borrow() - .read(0x2000 | (address & 0x7), Device::CPU), - 0x4000..=0x4013 => self.apu.borrow().read(address, Device::CPU), - 0x4014 => self.ppu.borrow().read(address, Device::CPU), - 0x4015 => self.apu.borrow().read(address, Device::CPU), + .read(0x2000 | (address & 0x7), Device::Cpu), + 0x4000..=0x4013 => self.apu.borrow().read(address, Device::Cpu), + 0x4014 => self.ppu.borrow().read(address, Device::Cpu), + 0x4015 => self.apu.borrow().read(address, Device::Cpu), 0x4016 => { // controller 0 } - 0x4017 => self.apu.borrow().read(address, Device::CPU), + 0x4017 => self.apu.borrow().read(address, Device::Cpu), 0x4018..=0x401F => { // unused CPU test mode registers 0 } - 0x4020..=0xFFFF => self.cartridge.borrow().read(address, Device::CPU), + 0x4020..=0xFFFF => self.cartridge.borrow().read(address, Device::Cpu), } } fn write(&mut self, address: u16, data: u8) { @@ -158,22 +158,22 @@ impl CPUBusTrait for CPUBus { 0x2000..=0x3FFF => { self.ppu .borrow_mut() - .write(0x2000 | (address & 0x7), data, Device::CPU) + .write(0x2000 | (address & 0x7), data, Device::Cpu) } - 0x4000..=0x4013 => self.apu.borrow_mut().write(address, data, Device::CPU), - 0x4014 => self.ppu.borrow_mut().write(address, data, Device::CPU), - 0x4015 => self.apu.borrow_mut().write(address, data, Device::CPU), + 0x4000..=0x4013 => self.apu.borrow_mut().write(address, data, Device::Cpu), + 0x4014 => self.ppu.borrow_mut().write(address, data, Device::Cpu), + 0x4015 => self.apu.borrow_mut().write(address, data, Device::Cpu), 0x4016 => { // controller } - 0x4017 => self.apu.borrow_mut().write(address, data, Device::CPU), + 0x4017 => self.apu.borrow_mut().write(address, data, Device::Cpu), 0x4018..=0x401F => { // unused CPU test mode registers } 0x4020..=0xFFFF => self .cartridge .borrow_mut() - .write(address, data, Device::CPU), + .write(address, data, Device::Cpu), }; } @@ -297,7 +297,7 @@ impl NesTester { } pub fn ppu_read_address(&self, address: u16) -> u8 { - self.ppu.borrow().ppu_bus().read(address, Device::PPU) + self.ppu.borrow().ppu_bus().read(address, Device::Ppu) } pub fn clock(&mut self) -> CPURunState {