diff --git a/CHANGELOG.md b/CHANGELOG.md index 664dfeed..6bbb1c33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,15 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -* +* State management support for: `pad`, `ppu` and `cpu` ### Changed -* +* Structure of the BOS save file format, breaking change! ### Fixed -* +* Major bug related to OAM masking ## [0.10.14] - 2024-10-21 diff --git a/crates/common/src/error.rs b/crates/common/src/error.rs index 3ccd253a..2c88299d 100644 --- a/crates/common/src/error.rs +++ b/crates/common/src/error.rs @@ -24,6 +24,7 @@ pub enum Error { NotImplemented, MissingOption(String), IoError(String), + DataError(String), InvalidParameter(String), CustomError(String), } @@ -38,6 +39,7 @@ impl Error { Error::NotImplemented => String::from("Not implemented"), Error::MissingOption(option) => format!("Missing option: {option}"), Error::IoError(message) => format!("IO error: {message}"), + Error::DataError(message) => format!("Data error: {message}"), Error::InvalidParameter(message) => format!("Invalid parameter: {message}"), Error::CustomError(message) => String::from(message), } @@ -54,6 +56,7 @@ impl error::Error for Error { Error::NotImplemented => "Not implemented", Error::MissingOption(_) => "Missing option", Error::IoError(_) => "IO error", + Error::DataError(_) => "Data error", Error::InvalidParameter(_) => "Invalid parameter", Error::CustomError(_) => "Custom error", } diff --git a/src/apu.rs b/src/apu.rs index 159f299b..a258c17d 100644 --- a/src/apu.rs +++ b/src/apu.rs @@ -18,7 +18,7 @@ use crate::{ }, gb::GameBoy, mmu::BusComponent, - state::StateComponent, + state::{StateComponent, StateFormat}, warnln, }; @@ -1168,7 +1168,7 @@ impl BusComponent for Apu { } impl StateComponent for Apu { - fn state(&self) -> Result, Error> { + fn state(&self, _format: Option) -> Result, Error> { let mut cursor = Cursor::new(vec![]); write_i16(&mut cursor, self.ch1_timer)?; @@ -1255,7 +1255,7 @@ impl StateComponent for Apu { Ok(cursor.into_inner()) } - fn set_state(&mut self, data: &[u8]) -> Result<(), Error> { + fn set_state(&mut self, data: &[u8], _format: Option) -> Result<(), Error> { let mut cursor = Cursor::new(data); self.ch1_timer = read_i16(&mut cursor)?; @@ -1408,171 +1408,166 @@ mod tests { #[test] fn test_state_and_set_state() { - let mut apu = Apu::default(); - apu.ch1_timer = 1234; - apu.ch1_sequence = 5; - apu.ch1_envelope_sequence = 3; - apu.ch1_envelope_enabled = true; - apu.ch1_sweep_sequence = 2; - apu.ch1_output = 10; - apu.ch1_dac = true; - apu.ch1_sweep_slope = 1; - apu.ch1_sweep_increase = true; - apu.ch1_sweep_pace = 4; - apu.ch1_length_timer = 20; - apu.ch1_wave_duty = 2; - apu.ch1_pace = 3; - apu.ch1_direction = 1; - apu.ch1_volume = 15; - apu.ch1_wave_length = 2048; - apu.ch1_length_enabled = true; - apu.ch1_enabled = true; - - apu.ch2_timer = 5678; - apu.ch2_sequence = 6; - apu.ch2_envelope_sequence = 4; - apu.ch2_envelope_enabled = true; - apu.ch2_output = 20; - apu.ch2_dac = true; - apu.ch2_length_timer = 30; - apu.ch2_wave_duty = 3; - apu.ch2_pace = 5; - apu.ch2_direction = 0; - apu.ch2_volume = 10; - apu.ch2_wave_length = 1024; - apu.ch2_length_enabled = true; - apu.ch2_enabled = true; - - apu.ch3_timer = 9111; - apu.ch3_position = 7; - apu.ch3_output = 30; - apu.ch3_dac = true; - apu.ch3_length_timer = 40; - apu.ch3_output_level = 2; - apu.ch3_wave_length = 512; - apu.ch3_length_enabled = true; - apu.ch3_enabled = true; - - apu.ch4_timer = 121314; - apu.ch4_envelope_sequence = 5; - apu.ch4_envelope_enabled = true; - apu.ch4_output = 40; - apu.ch4_dac = true; - apu.ch4_length_timer = 50; - apu.ch4_pace = 6; - apu.ch4_direction = 1; - apu.ch4_volume = 5; - apu.ch4_divisor = 2; - apu.ch4_width_mode = true; - apu.ch4_clock_shift = 3; - apu.ch4_lfsr = 0x7ff1; - apu.ch4_length_enabled = true; - apu.ch4_enabled = true; - - apu.master = 0x77; - apu.glob_panning = 0x88; - - apu.right_enabled = true; - apu.left_enabled = true; - apu.sound_enabled = true; - - apu.ch1_out_enabled = true; - apu.ch2_out_enabled = true; - apu.ch3_out_enabled = true; - apu.ch4_out_enabled = true; - - apu.wave_ram = [0x12; 16]; - - apu.sampling_rate = 44100; - apu.channels = 2; - - apu.sequencer = 12345; - apu.sequencer_step = 6; - apu.output_timer = 789; - - let state = apu.state().unwrap(); + let apu = Apu { + ch1_timer: 1234, + ch1_sequence: 5, + ch1_envelope_sequence: 3, + ch1_envelope_enabled: true, + ch1_sweep_sequence: 2, + ch1_output: 10, + ch1_dac: true, + ch1_sweep_slope: 1, + ch1_sweep_increase: true, + ch1_sweep_pace: 4, + ch1_length_timer: 20, + ch1_wave_duty: 2, + ch1_pace: 3, + ch1_direction: 1, + ch1_volume: 15, + ch1_wave_length: 2048, + ch1_length_enabled: true, + ch1_enabled: true, + ch2_timer: 5678, + ch2_sequence: 6, + ch2_envelope_sequence: 4, + ch2_envelope_enabled: true, + ch2_output: 20, + ch2_dac: true, + ch2_length_timer: 30, + ch2_wave_duty: 3, + ch2_pace: 5, + ch2_direction: 0, + ch2_volume: 10, + ch2_wave_length: 1024, + ch2_length_enabled: true, + ch2_enabled: true, + ch3_timer: 9111, + ch3_position: 7, + ch3_output: 30, + ch3_dac: true, + ch3_length_timer: 40, + ch3_output_level: 2, + ch3_wave_length: 512, + ch3_length_enabled: true, + ch3_enabled: true, + ch4_timer: 121314, + ch4_envelope_sequence: 5, + ch4_envelope_enabled: true, + ch4_output: 40, + ch4_dac: true, + ch4_length_timer: 50, + ch4_pace: 6, + ch4_direction: 1, + ch4_volume: 5, + ch4_divisor: 2, + ch4_width_mode: true, + ch4_clock_shift: 3, + ch4_lfsr: 0x7ff1, + ch4_length_enabled: true, + ch4_enabled: true, + master: 0x77, + glob_panning: 0x88, + right_enabled: true, + left_enabled: true, + sound_enabled: true, + ch1_out_enabled: true, + ch2_out_enabled: true, + ch3_out_enabled: true, + ch4_out_enabled: true, + wave_ram: [0x12; 16], + sampling_rate: 44100, + channels: 2, + sequencer: 12345, + sequencer_step: 6, + output_timer: 789, + ..Default::default() + }; + + let state = apu.state(None).unwrap(); + assert_eq!(state.len(), 100); + let mut new_apu = Apu::default(); - new_apu.set_state(&state).unwrap(); - - assert_eq!(apu.ch1_timer, new_apu.ch1_timer); - assert_eq!(apu.ch1_sequence, new_apu.ch1_sequence); - assert_eq!(apu.ch1_envelope_sequence, new_apu.ch1_envelope_sequence); - assert_eq!(apu.ch1_envelope_enabled, new_apu.ch1_envelope_enabled); - assert_eq!(apu.ch1_sweep_sequence, new_apu.ch1_sweep_sequence); - assert_eq!(apu.ch1_output, new_apu.ch1_output); - assert_eq!(apu.ch1_dac, new_apu.ch1_dac); - assert_eq!(apu.ch1_sweep_slope, new_apu.ch1_sweep_slope); - assert_eq!(apu.ch1_sweep_increase, new_apu.ch1_sweep_increase); - assert_eq!(apu.ch1_sweep_pace, new_apu.ch1_sweep_pace); - assert_eq!(apu.ch1_length_timer, new_apu.ch1_length_timer); - assert_eq!(apu.ch1_wave_duty, new_apu.ch1_wave_duty); - assert_eq!(apu.ch1_pace, new_apu.ch1_pace); - assert_eq!(apu.ch1_direction, new_apu.ch1_direction); - assert_eq!(apu.ch1_volume, new_apu.ch1_volume); - assert_eq!(apu.ch1_wave_length, new_apu.ch1_wave_length); - assert_eq!(apu.ch1_length_enabled, new_apu.ch1_length_enabled); - assert_eq!(apu.ch1_enabled, new_apu.ch1_enabled); - - assert_eq!(apu.ch2_timer, new_apu.ch2_timer); - assert_eq!(apu.ch2_sequence, new_apu.ch2_sequence); - assert_eq!(apu.ch2_envelope_sequence, new_apu.ch2_envelope_sequence); - assert_eq!(apu.ch2_envelope_enabled, new_apu.ch2_envelope_enabled); - assert_eq!(apu.ch2_output, new_apu.ch2_output); - assert_eq!(apu.ch2_dac, new_apu.ch2_dac); - assert_eq!(apu.ch2_length_timer, new_apu.ch2_length_timer); - assert_eq!(apu.ch2_wave_duty, new_apu.ch2_wave_duty); - assert_eq!(apu.ch2_pace, new_apu.ch2_pace); - assert_eq!(apu.ch2_direction, new_apu.ch2_direction); - assert_eq!(apu.ch2_volume, new_apu.ch2_volume); - assert_eq!(apu.ch2_wave_length, new_apu.ch2_wave_length); - assert_eq!(apu.ch2_length_enabled, new_apu.ch2_length_enabled); - assert_eq!(apu.ch2_enabled, new_apu.ch2_enabled); - - assert_eq!(apu.ch3_timer, new_apu.ch3_timer); - assert_eq!(apu.ch3_position, new_apu.ch3_position); - assert_eq!(apu.ch3_output, new_apu.ch3_output); - assert_eq!(apu.ch3_dac, new_apu.ch3_dac); - assert_eq!(apu.ch3_length_timer, new_apu.ch3_length_timer); - assert_eq!(apu.ch3_output_level, new_apu.ch3_output_level); - assert_eq!(apu.ch3_wave_length, new_apu.ch3_wave_length); - assert_eq!(apu.ch3_length_enabled, new_apu.ch3_length_enabled); - assert_eq!(apu.ch3_enabled, new_apu.ch3_enabled); - - assert_eq!(apu.ch4_timer, new_apu.ch4_timer); - assert_eq!(apu.ch4_envelope_sequence, new_apu.ch4_envelope_sequence); - assert_eq!(apu.ch4_envelope_enabled, new_apu.ch4_envelope_enabled); - assert_eq!(apu.ch4_output, new_apu.ch4_output); - assert_eq!(apu.ch4_dac, new_apu.ch4_dac); - assert_eq!(apu.ch4_length_timer, new_apu.ch4_length_timer); - assert_eq!(apu.ch4_pace, new_apu.ch4_pace); - assert_eq!(apu.ch4_direction, new_apu.ch4_direction); - assert_eq!(apu.ch4_volume, new_apu.ch4_volume); - assert_eq!(apu.ch4_divisor, new_apu.ch4_divisor); - assert_eq!(apu.ch4_width_mode, new_apu.ch4_width_mode); - assert_eq!(apu.ch4_clock_shift, new_apu.ch4_clock_shift); - assert_eq!(apu.ch4_lfsr, new_apu.ch4_lfsr); - assert_eq!(apu.ch4_length_enabled, new_apu.ch4_length_enabled); - assert_eq!(apu.ch4_enabled, new_apu.ch4_enabled); - - assert_eq!(apu.master, new_apu.master); - assert_eq!(apu.glob_panning, new_apu.glob_panning); - - assert_eq!(apu.right_enabled, new_apu.right_enabled); - assert_eq!(apu.left_enabled, new_apu.left_enabled); - assert_eq!(apu.sound_enabled, new_apu.sound_enabled); - - assert_eq!(apu.ch1_out_enabled, new_apu.ch1_out_enabled); - assert_eq!(apu.ch2_out_enabled, new_apu.ch2_out_enabled); - assert_eq!(apu.ch3_out_enabled, new_apu.ch3_out_enabled); - assert_eq!(apu.ch4_out_enabled, new_apu.ch4_out_enabled); - - assert_eq!(apu.wave_ram, new_apu.wave_ram); - - assert_eq!(apu.sampling_rate, new_apu.sampling_rate); - assert_eq!(apu.channels, new_apu.channels); - - assert_eq!(apu.sequencer, new_apu.sequencer); - assert_eq!(apu.sequencer_step, new_apu.sequencer_step); - assert_eq!(apu.output_timer, new_apu.output_timer); + new_apu.set_state(&state, None).unwrap(); + + assert_eq!(new_apu.ch1_timer, 1234); + assert_eq!(new_apu.ch1_sequence, 5); + assert_eq!(new_apu.ch1_envelope_sequence, 3); + assert!(new_apu.ch1_envelope_enabled); + assert_eq!(new_apu.ch1_sweep_sequence, 2); + assert_eq!(new_apu.ch1_output, 10); + assert!(new_apu.ch1_dac); + assert_eq!(new_apu.ch1_sweep_slope, 1); + assert!(new_apu.ch1_sweep_increase); + assert_eq!(new_apu.ch1_sweep_pace, 4); + assert_eq!(new_apu.ch1_length_timer, 20); + assert_eq!(new_apu.ch1_wave_duty, 2); + assert_eq!(new_apu.ch1_pace, 3); + assert_eq!(new_apu.ch1_direction, 1); + assert_eq!(new_apu.ch1_volume, 15); + assert_eq!(new_apu.ch1_wave_length, 2048); + assert!(new_apu.ch1_length_enabled); + assert!(new_apu.ch1_enabled); + + assert_eq!(new_apu.ch2_timer, 5678); + assert_eq!(new_apu.ch2_sequence, 6); + assert_eq!(new_apu.ch2_envelope_sequence, 4); + assert!(new_apu.ch2_envelope_enabled); + assert_eq!(new_apu.ch2_output, 20); + assert!(new_apu.ch2_dac); + assert_eq!(new_apu.ch2_length_timer, 30); + assert_eq!(new_apu.ch2_wave_duty, 3); + assert_eq!(new_apu.ch2_pace, 5); + assert_eq!(new_apu.ch2_direction, 0); + assert_eq!(new_apu.ch2_volume, 10); + assert_eq!(new_apu.ch2_wave_length, 1024); + assert!(new_apu.ch2_length_enabled); + assert!(new_apu.ch2_enabled); + + assert_eq!(new_apu.ch3_timer, 9111); + assert_eq!(new_apu.ch3_position, 7); + assert_eq!(new_apu.ch3_output, 30); + assert!(new_apu.ch3_dac); + assert_eq!(new_apu.ch3_length_timer, 40); + assert_eq!(new_apu.ch3_output_level, 2); + assert_eq!(new_apu.ch3_wave_length, 512); + assert!(new_apu.ch3_length_enabled); + assert!(new_apu.ch3_enabled); + + assert_eq!(new_apu.ch4_timer, 121314); + assert_eq!(new_apu.ch4_envelope_sequence, 5); + assert!(new_apu.ch4_envelope_enabled); + assert_eq!(new_apu.ch4_output, 40); + assert!(new_apu.ch4_dac); + assert_eq!(new_apu.ch4_length_timer, 50); + assert_eq!(new_apu.ch4_pace, 6); + assert_eq!(new_apu.ch4_direction, 1); + assert_eq!(new_apu.ch4_volume, 5); + assert_eq!(new_apu.ch4_divisor, 2); + assert!(new_apu.ch4_width_mode); + assert_eq!(new_apu.ch4_clock_shift, 3); + assert_eq!(new_apu.ch4_lfsr, 0x7ff1); + assert!(new_apu.ch4_length_enabled); + assert!(new_apu.ch4_enabled); + + assert_eq!(new_apu.master, 0x77); + assert_eq!(new_apu.glob_panning, 0x88); + + assert!(new_apu.right_enabled); + assert!(new_apu.left_enabled); + assert!(new_apu.sound_enabled); + + assert!(new_apu.ch1_out_enabled); + assert!(new_apu.ch2_out_enabled); + assert!(new_apu.ch3_out_enabled); + assert!(new_apu.ch4_out_enabled); + + assert_eq!(new_apu.wave_ram, [0x12; 16]); + + assert_eq!(new_apu.sampling_rate, 44100); + assert_eq!(new_apu.channels, 2); + + assert_eq!(new_apu.sequencer, 12345); + assert_eq!(new_apu.sequencer_step, 6); + assert_eq!(new_apu.output_timer, 789); } } diff --git a/src/cpu.rs b/src/cpu.rs index 21b1eb9c..7d550a8e 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -5,9 +5,14 @@ //! //! Most of the core CPU logic is implemented in the [`Cpu::clock`] method. -use boytacean_common::util::SharedThread; +use boytacean_common::{ + data::{read_u16, read_u8, write_u16, write_u8}, + error::Error, + util::SharedThread, +}; use std::{ fmt::{self, Display, Formatter}, + io::Cursor, sync::Mutex, }; @@ -23,6 +28,7 @@ use crate::{ pad::Pad, ppu::Ppu, serial::Serial, + state::{StateComponent, StateFormat}, timer::Timer, }; @@ -660,6 +666,52 @@ impl Cpu { } } +impl StateComponent for Cpu { + fn state(&self, _format: Option) -> Result, Error> { + let mut cursor = Cursor::new(vec![]); + write_u16(&mut cursor, self.pc)?; + write_u16(&mut cursor, self.sp)?; + write_u8(&mut cursor, self.a)?; + write_u8(&mut cursor, self.b)?; + write_u8(&mut cursor, self.c)?; + write_u8(&mut cursor, self.d)?; + write_u8(&mut cursor, self.e)?; + write_u8(&mut cursor, self.h)?; + write_u8(&mut cursor, self.l)?; + write_u8(&mut cursor, self.ime as u8)?; + write_u8(&mut cursor, self.zero as u8)?; + write_u8(&mut cursor, self.sub as u8)?; + write_u8(&mut cursor, self.half_carry as u8)?; + write_u8(&mut cursor, self.carry as u8)?; + write_u8(&mut cursor, self.halted as u8)?; + write_u8(&mut cursor, self.cycles)?; + write_u16(&mut cursor, self.ppc)?; + Ok(cursor.into_inner()) + } + + fn set_state(&mut self, data: &[u8], _format: Option) -> Result<(), Error> { + let mut cursor = Cursor::new(data); + self.pc = read_u16(&mut cursor)?; + self.sp = read_u16(&mut cursor)?; + self.a = read_u8(&mut cursor)?; + self.b = read_u8(&mut cursor)?; + self.c = read_u8(&mut cursor)?; + self.d = read_u8(&mut cursor)?; + self.e = read_u8(&mut cursor)?; + self.h = read_u8(&mut cursor)?; + self.l = read_u8(&mut cursor)?; + self.ime = read_u8(&mut cursor)? != 0; + self.zero = read_u8(&mut cursor)? != 0; + self.sub = read_u8(&mut cursor)? != 0; + self.half_carry = read_u8(&mut cursor)? != 0; + self.carry = read_u8(&mut cursor)? != 0; + self.halted = read_u8(&mut cursor)? != 0; + self.cycles = read_u8(&mut cursor)?; + self.ppc = read_u16(&mut cursor)?; + Ok(()) + } +} + impl Default for Cpu { fn default() -> Self { let gbc = SharedThread::new(Mutex::new(GameBoyConfig::default())); @@ -675,6 +727,12 @@ impl Display for Cpu { #[cfg(test)] mod tests { + use std::sync::Mutex; + + use boytacean_common::util::SharedThread; + + use crate::{gb::GameBoyConfig, mmu::Mmu, state::StateComponent}; + use super::Cpu; #[test] @@ -807,4 +865,53 @@ mod tests { assert_eq!(cpu.pc, 0xc002); assert_eq!(cpu.a, 0x0a ^ 0x0f); } + + #[test] + fn test_state_and_set_state() { + let cpu = Cpu { + pc: 0x1234, + sp: 0x5678, + a: 0x9a, + b: 0xbc, + c: 0xde, + d: 0xf0, + e: 0x12, + h: 0x34, + l: 0x56, + ime: true, + zero: true, + sub: false, + half_carry: true, + carry: false, + halted: true, + mmu: Mmu::default(), + cycles: 0x78, + ppc: 0x9abc, + gbc: SharedThread::new(Mutex::new(GameBoyConfig::default())), + }; + + let state = cpu.state(None).unwrap(); + assert_eq!(state.len(), 20); + + let mut new_cpu = Cpu::default(); + new_cpu.set_state(&state, None).unwrap(); + + assert_eq!(new_cpu.pc, 0x1234); + assert_eq!(new_cpu.sp, 0x5678); + assert_eq!(new_cpu.a, 0x9a); + assert_eq!(new_cpu.b, 0xbc); + assert_eq!(new_cpu.c, 0xde); + assert_eq!(new_cpu.d, 0xf0); + assert_eq!(new_cpu.e, 0x12); + assert_eq!(new_cpu.h, 0x34); + assert_eq!(new_cpu.l, 0x56); + assert!(new_cpu.ime); + assert!(new_cpu.zero); + assert!(!new_cpu.sub); + assert!(new_cpu.half_carry); + assert!(!new_cpu.carry); + assert!(new_cpu.halted); + assert_eq!(new_cpu.cycles, 0x78); + assert_eq!(new_cpu.ppc, 0x9abc); + } } diff --git a/src/dma.rs b/src/dma.rs index 0304aed3..ff839ab5 100644 --- a/src/dma.rs +++ b/src/dma.rs @@ -14,7 +14,7 @@ use crate::{ consts::{DMA_ADDR, HDMA1_ADDR, HDMA2_ADDR, HDMA3_ADDR, HDMA4_ADDR, HDMA5_ADDR}, mmu::BusComponent, panic_gb, - state::StateComponent, + state::{StateComponent, StateFormat}, warnln, }; @@ -277,7 +277,7 @@ impl BusComponent for Dma { } impl StateComponent for Dma { - fn state(&self) -> Result, Error> { + fn state(&self, _format: Option) -> Result, Error> { let mut cursor = Cursor::new(vec![]); write_u16(&mut cursor, self.source)?; write_u16(&mut cursor, self.destination)?; @@ -291,7 +291,7 @@ impl StateComponent for Dma { Ok(cursor.into_inner()) } - fn set_state(&mut self, data: &[u8]) -> Result<(), Error> { + fn set_state(&mut self, data: &[u8], _format: Option) -> Result<(), Error> { let mut cursor = Cursor::new(data); self.source = read_u16(&mut cursor)?; self.destination = read_u16(&mut cursor)?; @@ -300,8 +300,8 @@ impl StateComponent for Dma { self.mode = read_u8(&mut cursor)?.into(); self.value_dma = read_u8(&mut cursor)?; self.cycles_dma = read_u16(&mut cursor)?; - self.active_dma = read_u8(&mut cursor)? == 1; - self.active_hdma = read_u8(&mut cursor)? == 1; + self.active_dma = read_u8(&mut cursor)? != 0; + self.active_hdma = read_u8(&mut cursor)? != 0; Ok(()) } } @@ -368,31 +368,32 @@ mod tests { #[test] fn test_state_and_set_state() { - let mut dma = Dma::new(); - dma.source = 0x1234; - dma.destination = 0x5678; - dma.length = 0x9abc; - dma.pending = 0xdef0; - dma.mode = DmaMode::HBlank; - dma.value_dma = 0xff; - dma.cycles_dma = 0x0012; - dma.active_dma = true; - dma.active_hdma = true; - - let state = dma.state().unwrap(); + let dma = Dma { + source: 0x1234, + destination: 0x5678, + length: 0x9abc, + pending: 0xdef0, + mode: DmaMode::HBlank, + value_dma: 0xff, + cycles_dma: 0x0012, + active_dma: true, + active_hdma: true, + }; + + let state = dma.state(None).unwrap(); assert_eq!(state.len(), 14); let mut new_dma = Dma::new(); - new_dma.set_state(&state).unwrap(); - - assert_eq!(dma.source, new_dma.source); - assert_eq!(dma.destination, new_dma.destination); - assert_eq!(dma.length, new_dma.length); - assert_eq!(dma.pending, new_dma.pending); - assert_eq!(dma.mode, new_dma.mode); - assert_eq!(dma.value_dma, new_dma.value_dma); - assert_eq!(dma.cycles_dma, new_dma.cycles_dma); - assert_eq!(dma.active_dma, new_dma.active_dma); - assert_eq!(dma.active_hdma, new_dma.active_hdma); + new_dma.set_state(&state, None).unwrap(); + + assert_eq!(new_dma.source, 0x1234); + assert_eq!(new_dma.destination, 0x5678); + assert_eq!(new_dma.length, 0x9abc); + assert_eq!(new_dma.pending, 0xdef0); + assert_eq!(new_dma.mode, DmaMode::HBlank); + assert_eq!(new_dma.value_dma, 0xff); + assert_eq!(new_dma.cycles_dma, 0x0012); + assert!(new_dma.active_dma); + assert!(new_dma.active_hdma); } } diff --git a/src/mmu.rs b/src/mmu.rs index d9588ef9..c5505f6c 100644 --- a/src/mmu.rs +++ b/src/mmu.rs @@ -571,7 +571,7 @@ impl Mmu { } pub fn read_many(&mut self, addr: u16, count: u16) -> Vec { - let mut data: Vec = vec![]; + let mut data: Vec = Vec::with_capacity(count as usize); for index in 0..count { let byte = self.read(addr + index); @@ -588,7 +588,7 @@ impl Mmu { } pub fn read_many_raw(&mut self, addr: u16, count: u16) -> Vec { - let mut data: Vec = vec![]; + let mut data: Vec = Vec::with_capacity(count as usize); for index in 0..count { let byte = self.read_raw(addr + index); diff --git a/src/pad.rs b/src/pad.rs index 55fcd0e8..80286b90 100644 --- a/src/pad.rs +++ b/src/pad.rs @@ -1,17 +1,75 @@ //! Gamepad related functions and structures. -use crate::{mmu::BusComponent, warnln}; +use std::{ + fmt::{self, Display, Formatter}, + io::Cursor, +}; +use crate::{ + mmu::BusComponent, + state::{StateComponent, StateFormat}, + warnln, +}; + +use boytacean_common::{ + data::{read_u8, write_u8}, + error::Error, +}; #[cfg(feature = "wasm")] use wasm_bindgen::prelude::*; -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum PadSelection { None, Action, Direction, } +impl PadSelection { + pub fn description(&self) -> &'static str { + match self { + PadSelection::None => "None", + PadSelection::Action => "Action", + PadSelection::Direction => "Direction", + } + } + + pub fn from_u8(value: u8) -> Self { + match value { + 0x00 => PadSelection::None, + 0x01 => PadSelection::Action, + 0x02 => PadSelection::Direction, + _ => panic!("Invalid pad selection value: {value}"), + } + } + + pub fn into_u8(self) -> u8 { + match self { + PadSelection::None => 0x00, + PadSelection::Action => 0x01, + PadSelection::Direction => 0x02, + } + } +} + +impl Display for PadSelection { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.description()) + } +} + +impl From for PadSelection { + fn from(value: u8) -> Self { + Self::from_u8(value) + } +} + +impl From for u8 { + fn from(value: PadSelection) -> Self { + value.into_u8() + } +} + #[cfg_attr(feature = "wasm", wasm_bindgen)] pub enum PadKey { Up, @@ -184,8 +242,80 @@ impl BusComponent for Pad { } } +impl StateComponent for Pad { + fn state(&self, _format: Option) -> Result, Error> { + let mut cursor = Cursor::new(vec![]); + write_u8(&mut cursor, self.down as u8)?; + write_u8(&mut cursor, self.up as u8)?; + write_u8(&mut cursor, self.left as u8)?; + write_u8(&mut cursor, self.right as u8)?; + write_u8(&mut cursor, self.start as u8)?; + write_u8(&mut cursor, self.select as u8)?; + write_u8(&mut cursor, self.b as u8)?; + write_u8(&mut cursor, self.a as u8)?; + write_u8(&mut cursor, self.selection.into())?; + write_u8(&mut cursor, self.int_pad as u8)?; + Ok(cursor.into_inner()) + } + + fn set_state(&mut self, data: &[u8], _format: Option) -> Result<(), Error> { + let mut cursor: Cursor<&[u8]> = Cursor::new(data); + self.down = read_u8(&mut cursor)? != 0; + self.up = read_u8(&mut cursor)? != 0; + self.left = read_u8(&mut cursor)? != 0; + self.right = read_u8(&mut cursor)? != 0; + self.start = read_u8(&mut cursor)? != 0; + self.select = read_u8(&mut cursor)? != 0; + self.b = read_u8(&mut cursor)? != 0; + self.a = read_u8(&mut cursor)? != 0; + self.selection = read_u8(&mut cursor)?.into(); + self.int_pad = read_u8(&mut cursor)? != 0; + Ok(()) + } +} + impl Default for Pad { fn default() -> Self { Self::new() } } + +#[cfg(test)] +mod tests { + use crate::state::StateComponent; + + use super::{Pad, PadSelection}; + + #[test] + fn test_state_and_set_state() { + let pad = Pad { + down: true, + up: false, + left: true, + right: false, + start: true, + select: false, + b: true, + a: false, + selection: PadSelection::Action, + int_pad: true, + }; + + let state = pad.state(None).unwrap(); + assert_eq!(state.len(), 10); + + let mut new_pad = Pad::new(); + new_pad.set_state(&state, None).unwrap(); + + assert!(new_pad.down); + assert!(!new_pad.up); + assert!(new_pad.left); + assert!(!new_pad.right); + assert!(new_pad.start); + assert!(!new_pad.select); + assert!(new_pad.b); + assert!(!new_pad.a); + assert_eq!(new_pad.selection, PadSelection::Action); + assert!(new_pad.int_pad); + } +} diff --git a/src/ppu.rs b/src/ppu.rs index aea4b898..80367a59 100644 --- a/src/ppu.rs +++ b/src/ppu.rs @@ -1,12 +1,17 @@ //! PPU (Picture Processing Unit) functions and structures. -use boytacean_common::util::SharedThread; +use boytacean_common::{ + data::{read_into, read_u16, read_u8, write_bytes, write_u16, write_u8}, + error::Error, + util::SharedThread, +}; use core::fmt; use std::{ borrow::BorrowMut, cmp::max, convert::TryInto, fmt::{Display, Formatter}, + io::Cursor, sync::{Arc, Mutex}, }; @@ -23,7 +28,9 @@ use crate::{ }, gb::{GameBoyConfig, GameBoyMode}, mmu::BusComponent, - panic_gb, warnln, + panic_gb, + state::{StateComponent, StateFormat}, + warnln, }; #[cfg(feature = "wasm")] @@ -33,7 +40,7 @@ pub const VRAM_SIZE_DMG: usize = 8192; pub const VRAM_SIZE_CGB: usize = 16384; pub const VRAM_SIZE: usize = VRAM_SIZE_CGB; pub const HRAM_SIZE: usize = 128; -pub const OAM_SIZE: usize = 260; +pub const OAM_SIZE: usize = 160; pub const PALETTE_SIZE: usize = 4; pub const TILE_WIDTH: usize = 8; pub const TILE_HEIGHT: usize = 8; @@ -185,7 +192,7 @@ impl PaletteInfo { /// should contain the pixel buffer of the tile. /// The tiles are always 8x8 pixels in size. #[cfg_attr(feature = "wasm", wasm_bindgen)] -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Tile { /// The buffer for the tile, should contain a byte /// per each pixel of the tile with values ranging @@ -195,6 +202,10 @@ pub struct Tile { #[cfg_attr(feature = "wasm", wasm_bindgen)] impl Tile { + pub fn new() -> Self { + Self { buffer: [0u8; 64] } + } + pub fn get(&self, x: usize, y: usize) -> u8 { self.buffer[y * TILE_WIDTH + x] } @@ -218,9 +229,7 @@ impl Tile { pub fn get_row(&self, y: usize) -> &[u8] { &self.buffer[y * TILE_WIDTH..(y + 1) * TILE_WIDTH] } -} -impl Tile { pub fn palette_buffer(&self, palette: Palette) -> Vec { self.buffer .iter() @@ -229,6 +238,12 @@ impl Tile { } } +impl Default for Tile { + fn default() -> Self { + Self::new() + } +} + impl Display for Tile { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut buffer = String::new(); @@ -242,8 +257,24 @@ impl Display for Tile { } } +impl From<&[u8]> for Tile { + fn from(value: &[u8]) -> Self { + let mut object = Tile::new(); + object.buffer.copy_from_slice(value); + object + } +} + +impl From for Vec { + fn from(value: Tile) -> Self { + let mut buffer = Vec::with_capacity(64); + buffer.extend_from_slice(&value.buffer); + buffer + } +} + #[cfg_attr(feature = "wasm", wasm_bindgen)] -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct ObjectData { x: i16, y: i16, @@ -290,6 +321,40 @@ impl Display for ObjectData { } } +impl From<&[u8]> for ObjectData { + fn from(value: &[u8]) -> Self { + let mut object = ObjectData::new(); + object.x = i16::from_le_bytes([value[0], value[1]]); + object.y = i16::from_le_bytes([value[2], value[3]]); + object.tile = value[4]; + object.palette_cgb = value[5]; + object.tile_bank = value[6]; + object.palette = value[7]; + object.xflip = value[8] != 0; + object.yflip = value[9] != 0; + object.bg_over = value[10] != 0; + object.index = value[11]; + object + } +} + +impl From for Vec { + fn from(value: ObjectData) -> Self { + let mut buffer = Vec::with_capacity(12); + buffer.extend_from_slice(&value.x.to_le_bytes()); + buffer.extend_from_slice(&value.y.to_le_bytes()); + buffer.push(value.tile); + buffer.push(value.palette_cgb); + buffer.push(value.tile_bank); + buffer.push(value.palette); + buffer.push(if value.xflip { 1 } else { 0 }); + buffer.push(if value.yflip { 1 } else { 0 }); + buffer.push(if value.bg_over { 1 } else { 0 }); + buffer.push(value.index); + buffer + } +} + #[cfg_attr(feature = "wasm", wasm_bindgen)] #[derive(Clone, Copy, PartialEq, Eq)] pub struct TileData { @@ -328,6 +393,30 @@ impl Display for TileData { } } +impl From<&[u8]> for TileData { + fn from(value: &[u8]) -> Self { + let mut object = TileData::new(); + object.palette = value[0]; + object.vram_bank = value[1]; + object.xflip = value[2] != 0; + object.yflip = value[3] != 0; + object.priority = value[4] != 0; + object + } +} + +impl From for Vec { + fn from(value: TileData) -> Self { + let mut buffer = Vec::with_capacity(5); + buffer.push(value.palette); + buffer.push(value.vram_bank); + buffer.push(if value.xflip { 1 } else { 0 }); + buffer.push(if value.yflip { 1 } else { 0 }); + buffer.push(if value.priority { 1 } else { 0 }); + buffer + } +} + pub struct PpuRegisters { pub scy: u8, pub scx: u8, @@ -393,7 +482,7 @@ pub struct Ppu { /// that is currently selected (CGB only). vram_offset: u16, - /// The current set of processed tiles that are store in the + /// The current set of processed tiles that are stored in the /// PPU related structures. tiles: [Tile; TILE_COUNT], @@ -408,30 +497,39 @@ pub struct Ppu { palette_colors: Palette, /// The palette of colors that is currently loaded in Game Boy - /// and used for background (tiles) and window. + /// and used for background (tiles) and window. The value of + /// thi field can be computed value from [`Self::palettes[0]`]. palette_bg: Palette, /// The palette that is going to be used for sprites/objects #0. + /// The value of this field can be computed value from [`Self::palettes[1]`]. palette_obj_0: Palette, /// The palette that is going to be used for sprites/objects #1. + /// The value of this field can be computed value from [`Self::palettes[2]`]. palette_obj_1: Palette, /// The complete set of background palettes that are going to be /// used in CGB emulation to provide the full set of colors (CGB only). + /// The value of this field can be computed value from [`Self::palettes_color[0]`]. palettes_color_bg: [Palette; 8], /// The complete set of object/sprite palettes that are going to be /// used in CGB emulation to provide the full set of colors (CGB only). + /// The value of this field can be computed value from [`Self::palettes_color[1]`]. palettes_color_obj: [Palette; 8], - /// The complete set of palettes in binary data so that they can + /// The complete set of palettes in raw binary data so that they can /// be re-read if required by the system. + /// This field contains the values required to recompute [`Self::palette_bg`], + /// [`Self::palette_obj_0`] and [`Self::palette_obj_1`]. palettes: [u8; 3], /// The raw binary information (64 bytes) for the color palettes, /// contains binary information for both the background and /// the objects palettes (CGB only). + /// This field contains the values required to recompute [`Self::palettes_color_bg`], + /// and [`Self::palettes_color_obj`] . palettes_color: [[u8; 64]; 2], /// The complete list of attributes for the first background @@ -578,7 +676,7 @@ pub struct Ppu { } #[cfg_attr(feature = "wasm", wasm_bindgen)] -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum PpuMode { HBlank = 0, VBlank = 1, @@ -586,11 +684,29 @@ pub enum PpuMode { VramRead = 3, } +impl From for PpuMode { + fn from(value: u8) -> Self { + match value { + 0 => PpuMode::HBlank, + 1 => PpuMode::VBlank, + 2 => PpuMode::OamRead, + 3 => PpuMode::VramRead, + _ => PpuMode::HBlank, + } + } +} + +impl From for u8 { + fn from(value: PpuMode) -> Self { + value as u8 + } +} + impl Ppu { pub fn new(mode: GameBoyMode, gbc: SharedThread) -> Self { Self { color_buffer: Box::new([0u8; COLOR_BUFFER_SIZE]), - shade_buffer: Box::new([0u8; COLOR_BUFFER_SIZE]), + shade_buffer: Box::new([0u8; SHADE_BUFFER_SIZE]), frame_buffer: Box::new([0u8; FRAME_BUFFER_SIZE]), priority_buffer: Box::new([false; COLOR_BUFFER_SIZE]), vram: [0u8; VRAM_SIZE], @@ -808,10 +924,13 @@ impl Ppu { pub fn read(&self, addr: u16) -> u8 { match addr { + // 0x8000-0x9FFF - Graphics: VRAM (8 KB) 0x8000..=0x9fff => self.vram[(self.vram_offset + (addr & 0x1fff)) as usize], - 0xfe00..=0xfe9f => self.oam[(addr & 0x009f) as usize], - // Not Usable + // 0xFE00-0xFE9F - Object attribute memory (OAM) + 0xfe00..=0xfe9f => self.oam[(addr & 0x00ff) as usize], + // 0xFEA0-0xFEFF - Not Usable 0xfea0..=0xfeff => 0xff, + // 0xFF80-0xFFFE - High RAM (HRAM) 0xff80..=0xfffe => self.hram[(addr & 0x007f) as usize], LCDC_ADDR => { @@ -834,7 +953,9 @@ impl Ppu { | (self.mode as u8 & 0x03) | 0x80) } + // 0xFF42 — SCY: Background Y position SCY_ADDR => self.scy, + // 0xFF43 — SCX: Background X position SCX_ADDR => self.scx, // 0xFF44 — LY: LCD Y coordinate LY_ADDR => self.ly, @@ -872,6 +993,7 @@ impl Ppu { pub fn write(&mut self, addr: u16, value: u8) { match addr { + // 0x8000-0x9FFF - Graphics: VRAM (8 KB) 0x8000..=0x9fff => { self.vram[(self.vram_offset + (addr & 0x1fff)) as usize] = value; if addr < 0x9800 { @@ -880,12 +1002,14 @@ impl Ppu { self.update_bg_map_attrs(addr, value); } } + // 0xFE00-0xFE9F - Object attribute memory (OAM) 0xfe00..=0xfe9f => { - self.oam[(addr & 0x009f) as usize] = value; + self.oam[(addr & 0x00ff) as usize] = value; self.update_object(addr, value); } - // Not Usable + // 0xFEA0-0xFEFF - Not Usable 0xfea0..=0xfeff => (), + // 0xFF80-0xFFFE - High RAM (HRAM) 0xff80..=0xfffe => self.hram[(addr & 0x007f) as usize] = value, LCDC_ADDR => { self.switch_bg = value & 0x01 == 0x01; @@ -910,7 +1034,9 @@ impl Ppu { self.stat_oam = value & 0x20 == 0x20; self.stat_lyc = value & 0x40 == 0x40; } + // 0xFF42 — SCY: Background Y position SCY_ADDR => self.scy = value, + // 0xFF43 — SCX: Background X position SCX_ADDR => self.scx = value, // 0xFF45 — LYC: LY compare LYC_ADDR => self.lyc = value, @@ -1168,12 +1294,21 @@ impl Ppu { self.update_vram(); } + pub fn oam(&self) -> &[u8; OAM_SIZE] { + &self.oam + } + + pub fn set_oam(&mut self, value: &[u8]) { + self.oam[0..value.len()].copy_from_slice(value); + self.update_oam(); + } + pub fn hram(&self) -> &[u8; HRAM_SIZE] { &self.hram } - pub fn set_hram(&mut self, value: [u8; HRAM_SIZE]) { - self.hram = value; + pub fn set_hram(&mut self, value: &[u8]) { + self.hram[0..value.len()].copy_from_slice(value); } pub fn tiles(&self) -> &[Tile; TILE_COUNT] { @@ -1308,7 +1443,7 @@ impl Ppu { /// Updates the internal PPU state (calculated values) according /// to the VRAM values, this should be called whenever the VRAM - /// is replaced. + /// data is replaced (eg: state loading). pub fn update_vram(&mut self) { // "saves" the old values of the VRAM bank and offset // as they are going to be needed later, this is required @@ -1345,6 +1480,16 @@ impl Ppu { (self.vram_bank, self.vram_offset) = (vram_bank_old, vram_offset_old); } + /// Updates the internal PPU state (calculated values) according + /// to the OAM values, this should be called whenever the OAM + /// data is replaced (eg: state loading). + fn update_oam(&mut self) { + for addr in 0xfe00..=0xfe9f { + let value = self.oam[(addr & 0x00ff) as usize]; + self.update_object(addr, value); + } + } + /// Updates the tile structure with the value that has /// just been written to a location on the VRAM associated /// with tiles. @@ -1959,7 +2104,8 @@ impl Ppu { /// Computes the values for all of the palettes, this method /// is useful to "flush" color computation whenever the base /// palette colors are changed. - /// Notice that this is only applicable to the DMG running mode + /// + /// Notice that this is only applicable to the DMG running mode - /// either in the original DMG or in CGB with DMG compatibility. fn compute_palettes(&mut self) { if self.dmg_compat { @@ -2001,6 +2147,7 @@ impl Ppu { /// Static method used for the base logic of computation of RGB /// based palettes from the internal Game Boy color indexes. + /// /// This method should be called whenever the palette indexes /// are changed. fn compute_palette(palette: &mut Palette, palette_colors: &Palette, value: u8) { @@ -2075,6 +2222,166 @@ impl BusComponent for Ppu { } } +impl StateComponent for Ppu { + fn state(&self, format: Option) -> Result, Error> { + let format = format.unwrap_or(StateFormat::Minimal); + + let mut cursor = Cursor::new(vec![]); + + if format == StateFormat::Full { + write_bytes(&mut cursor, &self.color_buffer[..])?; + write_bytes(&mut cursor, &self.shade_buffer[..])?; + write_bytes(&mut cursor, &self.frame_buffer[..])?; + write_bytes( + &mut cursor, + &self + .priority_buffer + .iter() + .map(|&b| if b { 1 } else { 0 }) + .collect::>(), + )?; + write_bytes(&mut cursor, &self.vram)?; + write_bytes(&mut cursor, &self.hram)?; + write_bytes(&mut cursor, &self.oam)?; + } + + write_u8(&mut cursor, self.vram_bank)?; + write_u16(&mut cursor, self.vram_offset)?; + + if format == StateFormat::Full { + for tile in &self.tiles { + let tile = &Into::>::into(*tile)[..]; + write_bytes(&mut cursor, tile)?; + } + for obj_data in &self.obj_data { + let obj_data = &Into::>::into(*obj_data)[..]; + write_bytes(&mut cursor, obj_data)?; + } + write_bytes(&mut cursor, &self.palettes)?; + for palette_color in &self.palettes_color { + write_bytes(&mut cursor, palette_color)?; + } + } + + write_u8(&mut cursor, self.obj_priority as u8)?; + write_u8(&mut cursor, self.scy)?; + write_u8(&mut cursor, self.scx)?; + write_u8(&mut cursor, self.wy)?; + write_u8(&mut cursor, self.wx)?; + write_u8(&mut cursor, self.ly)?; + write_u8(&mut cursor, self.lyc)?; + write_u8(&mut cursor, self.mode as u8)?; + write_u16(&mut cursor, self.mode_clock)?; + write_u8(&mut cursor, self.switch_bg as u8)?; + write_u8(&mut cursor, self.switch_obj as u8)?; + write_u8(&mut cursor, self.obj_size as u8)?; + write_u8(&mut cursor, self.bg_map as u8)?; + write_u8(&mut cursor, self.bg_tile as u8)?; + write_u8(&mut cursor, self.switch_window as u8)?; + write_u8(&mut cursor, self.window_map as u8)?; + write_u8(&mut cursor, self.switch_lcd as u8)?; + write_u8(&mut cursor, self.window_counter)?; + write_u8(&mut cursor, self.auto_increment_bg as u8)?; + write_u8(&mut cursor, self.palette_address_bg)?; + write_u8(&mut cursor, self.auto_increment_obj as u8)?; + write_u8(&mut cursor, self.palette_address_obj)?; + write_u8(&mut cursor, self.first_frame as u8)?; + write_u16(&mut cursor, self.frame_index)?; + write_u16(&mut cursor, self.frame_buffer_index)?; + write_u8(&mut cursor, self.stat_hblank as u8)?; + write_u8(&mut cursor, self.stat_vblank as u8)?; + write_u8(&mut cursor, self.stat_oam as u8)?; + write_u8(&mut cursor, self.stat_lyc as u8)?; + write_u8(&mut cursor, self.int_vblank as u8)?; + write_u8(&mut cursor, self.int_stat as u8)?; + write_u8(&mut cursor, self.dmg_compat as u8)?; + write_u8(&mut cursor, self.gb_mode as u8)?; + + Ok(cursor.into_inner()) + } + + fn set_state(&mut self, data: &[u8], format: Option) -> Result<(), Error> { + let format: StateFormat = format.unwrap_or(StateFormat::Minimal); + + let mut cursor: Cursor<&[u8]> = Cursor::new(data); + + if format == StateFormat::Full { + let mut priority_buffer = [0u8; COLOR_BUFFER_SIZE]; + read_into(&mut cursor, &mut self.color_buffer[..])?; + read_into(&mut cursor, &mut self.shade_buffer[..])?; + read_into(&mut cursor, &mut self.frame_buffer[..])?; + read_into(&mut cursor, &mut priority_buffer)?; + self.priority_buffer.copy_from_slice( + &priority_buffer + .iter() + .map(|&b| b != 0) + .collect::>(), + ); + read_into(&mut cursor, &mut self.vram[..])?; + read_into(&mut cursor, &mut self.hram[..])?; + read_into(&mut cursor, &mut self.oam[..])?; + } + + self.vram_bank = read_u8(&mut cursor)?; + self.vram_offset = read_u16(&mut cursor)?; + + if format == StateFormat::Full { + for index in 0..TILE_COUNT { + let mut tile = [0u8; 64]; + read_into(&mut cursor, &mut tile)?; + self.tiles[index] = (&tile[..]).into(); + } + for index in 0..OBJ_COUNT { + let mut obj_data = [0u8; 12]; + read_into(&mut cursor, &mut obj_data)?; + self.obj_data[index] = (&obj_data[..]).into(); + } + read_into(&mut cursor, &mut self.palettes)?; + for index in 0..2 { + let mut palette_color = [0u8; 64]; + read_into(&mut cursor, &mut palette_color)?; + self.palettes_color[index] = palette_color; + } + } + + self.obj_priority = read_u8(&mut cursor)? != 0; + self.scy = read_u8(&mut cursor)?; + self.scx = read_u8(&mut cursor)?; + self.wy = read_u8(&mut cursor)?; + self.wx = read_u8(&mut cursor)?; + self.ly = read_u8(&mut cursor)?; + self.lyc = read_u8(&mut cursor)?; + self.mode = read_u8(&mut cursor)?.into(); + self.mode_clock = read_u16(&mut cursor)?; + self.switch_bg = read_u8(&mut cursor)? != 0; + self.switch_obj = read_u8(&mut cursor)? != 0; + self.obj_size = read_u8(&mut cursor)? != 0; + self.bg_map = read_u8(&mut cursor)? != 0; + self.bg_tile = read_u8(&mut cursor)? != 0; + self.switch_window = read_u8(&mut cursor)? != 0; + self.window_map = read_u8(&mut cursor)? != 0; + self.switch_lcd = read_u8(&mut cursor)? != 0; + self.window_counter = read_u8(&mut cursor)?; + self.auto_increment_bg = read_u8(&mut cursor)? != 0; + self.palette_address_bg = read_u8(&mut cursor)?; + self.auto_increment_obj = read_u8(&mut cursor)? != 0; + self.palette_address_obj = read_u8(&mut cursor)?; + self.first_frame = read_u8(&mut cursor)? != 0; + self.frame_index = read_u16(&mut cursor)?; + self.frame_buffer_index = read_u16(&mut cursor)?; + self.stat_hblank = read_u8(&mut cursor)? != 0; + self.stat_vblank = read_u8(&mut cursor)? != 0; + self.stat_oam = read_u8(&mut cursor)? != 0; + self.stat_lyc = read_u8(&mut cursor)? != 0; + self.int_vblank = read_u8(&mut cursor)? != 0; + self.int_stat = read_u8(&mut cursor)? != 0; + self.dmg_compat = read_u8(&mut cursor)? != 0; + self.gb_mode = read_u8(&mut cursor)?.into(); + + Ok(()) + } +} + impl Default for Ppu { fn default() -> Self { Self::new( @@ -2086,7 +2393,15 @@ impl Default for Ppu { #[cfg(test)] mod tests { - use super::Ppu; + use crate::{ + gb::GameBoyMode, + state::{StateComponent, StateFormat}, + }; + + use super::{ + ObjectData, Ppu, PpuMode, Tile, COLOR_BUFFER_SIZE, FRAME_BUFFER_SIZE, HRAM_SIZE, OAM_SIZE, + OBJ_COUNT, SHADE_BUFFER_SIZE, TILE_COUNT, VRAM_SIZE, + }; #[test] fn test_update_tile_simple() { @@ -2115,4 +2430,196 @@ mod tests { let result = ppu.tiles()[256].get(0, 0); assert_eq!(result, 3); } + + #[test] + fn test_state_and_set_state() { + let ppu = Ppu { + color_buffer: Box::new([0x01; COLOR_BUFFER_SIZE]), + shade_buffer: Box::new([0x02; SHADE_BUFFER_SIZE]), + frame_buffer: Box::new([0x03; FRAME_BUFFER_SIZE]), + priority_buffer: Box::new([true; COLOR_BUFFER_SIZE]), + vram: [0x04; VRAM_SIZE], + hram: [0x05; HRAM_SIZE], + oam: [0x06; OAM_SIZE], + vram_bank: 0x07, + vram_offset: 0x08, + tiles: [Tile::new(); TILE_COUNT], + obj_data: [ObjectData::new(); OBJ_COUNT], + palettes: [0x09; 3], + palettes_color: [[0x0a; 64]; 2], + obj_priority: true, + scy: 0x0b, + scx: 0x0c, + wy: 0x0d, + wx: 0x0e, + ly: 0x0f, + lyc: 0x10, + mode: PpuMode::OamRead, + mode_clock: 0x12, + switch_bg: true, + switch_obj: true, + obj_size: true, + bg_map: true, + bg_tile: true, + switch_window: true, + window_map: true, + switch_lcd: true, + window_counter: 0x13, + auto_increment_bg: true, + palette_address_bg: 0x14, + auto_increment_obj: true, + palette_address_obj: 0x15, + first_frame: true, + frame_index: 0x16, + frame_buffer_index: 0x17, + stat_hblank: true, + stat_vblank: true, + stat_oam: true, + stat_lyc: true, + int_vblank: true, + int_stat: true, + dmg_compat: true, + gb_mode: GameBoyMode::Dmg, + ..Default::default() + }; + + let state = ppu.state(Some(StateFormat::Full)).unwrap(); + assert_eq!(state.len(), 204714); + + let mut new_ppu = Ppu::default(); + new_ppu.set_state(&state, Some(StateFormat::Full)).unwrap(); + + assert_eq!(new_ppu.color_buffer, Box::new([0x01; COLOR_BUFFER_SIZE])); + assert_eq!(new_ppu.shade_buffer, Box::new([0x02; SHADE_BUFFER_SIZE])); + assert_eq!(new_ppu.frame_buffer, Box::new([0x03; FRAME_BUFFER_SIZE])); + assert_eq!(new_ppu.priority_buffer, Box::new([true; COLOR_BUFFER_SIZE])); + assert_eq!(new_ppu.vram, [0x04; VRAM_SIZE]); + assert_eq!(new_ppu.hram, [0x05; HRAM_SIZE]); + assert_eq!(new_ppu.oam, [0x06; OAM_SIZE]); + assert_eq!(new_ppu.vram_bank, 0x07); + assert_eq!(new_ppu.vram_offset, 0x08); + assert_eq!(new_ppu.tiles, [Tile::new(); TILE_COUNT]); + assert_eq!(new_ppu.obj_data, [ObjectData::new(); OBJ_COUNT]); + assert_eq!(new_ppu.palettes, [0x09; 3]); + assert_eq!(new_ppu.palettes_color, [[0x0a; 64]; 2]); + assert!(new_ppu.obj_priority); + assert_eq!(new_ppu.scy, 0x0b); + assert_eq!(new_ppu.scx, 0x0c); + assert_eq!(new_ppu.wy, 0x0d); + assert_eq!(new_ppu.wx, 0x0e); + assert_eq!(new_ppu.ly, 0x0f); + assert_eq!(new_ppu.lyc, 0x10); + assert_eq!(new_ppu.mode, PpuMode::OamRead); + assert_eq!(new_ppu.mode_clock, 0x12); + assert!(new_ppu.switch_bg); + assert!(new_ppu.switch_obj); + assert!(new_ppu.obj_size); + assert!(new_ppu.bg_map); + assert!(new_ppu.bg_tile); + assert!(new_ppu.switch_window); + assert!(new_ppu.window_map); + assert!(new_ppu.switch_lcd); + assert_eq!(new_ppu.window_counter, 0x13); + assert!(new_ppu.auto_increment_bg); + assert_eq!(new_ppu.palette_address_bg, 0x14); + assert!(new_ppu.auto_increment_obj); + assert_eq!(new_ppu.palette_address_obj, 0x15); + assert!(new_ppu.first_frame); + assert_eq!(new_ppu.frame_index, 0x16); + assert_eq!(new_ppu.frame_buffer_index, 0x17); + assert!(new_ppu.stat_hblank); + assert!(new_ppu.stat_vblank); + assert!(new_ppu.stat_oam); + assert!(new_ppu.stat_lyc); + assert!(new_ppu.int_vblank); + assert!(new_ppu.int_stat); + assert!(new_ppu.dmg_compat); + assert_eq!(new_ppu.gb_mode, GameBoyMode::Dmg); + } + + #[test] + fn test_state_and_set_state_minimal() { + let ppu = Ppu { + vram_bank: 0x07, + vram_offset: 0x08, + obj_priority: true, + scy: 0x0b, + scx: 0x0c, + wy: 0x0d, + wx: 0x0e, + ly: 0x0f, + lyc: 0x10, + mode: PpuMode::OamRead, + mode_clock: 0x12, + switch_bg: true, + switch_obj: true, + obj_size: true, + bg_map: true, + bg_tile: true, + switch_window: true, + window_map: true, + switch_lcd: true, + window_counter: 0x13, + auto_increment_bg: true, + palette_address_bg: 0x14, + auto_increment_obj: true, + palette_address_obj: 0x15, + first_frame: true, + frame_index: 0x16, + frame_buffer_index: 0x17, + stat_hblank: true, + stat_vblank: true, + stat_oam: true, + stat_lyc: true, + int_vblank: true, + int_stat: true, + dmg_compat: true, + gb_mode: GameBoyMode::Dmg, + ..Default::default() + }; + + let state = ppu.state(Some(StateFormat::Minimal)).unwrap(); + assert_eq!(state.len(), 39); + + let mut new_ppu = Ppu::default(); + new_ppu + .set_state(&state, Some(StateFormat::Minimal)) + .unwrap(); + + assert_eq!(new_ppu.vram_bank, 0x07); + assert_eq!(new_ppu.vram_offset, 0x08); + assert!(new_ppu.obj_priority); + assert_eq!(new_ppu.scy, 0x0b); + assert_eq!(new_ppu.scx, 0x0c); + assert_eq!(new_ppu.wy, 0x0d); + assert_eq!(new_ppu.wx, 0x0e); + assert_eq!(new_ppu.ly, 0x0f); + assert_eq!(new_ppu.lyc, 0x10); + assert_eq!(new_ppu.mode, PpuMode::OamRead); + assert_eq!(new_ppu.mode_clock, 0x12); + assert!(new_ppu.switch_bg); + assert!(new_ppu.switch_obj); + assert!(new_ppu.obj_size); + assert!(new_ppu.bg_map); + assert!(new_ppu.bg_tile); + assert!(new_ppu.switch_window); + assert!(new_ppu.window_map); + assert!(new_ppu.switch_lcd); + assert_eq!(new_ppu.window_counter, 0x13); + assert!(new_ppu.auto_increment_bg); + assert_eq!(new_ppu.palette_address_bg, 0x14); + assert!(new_ppu.auto_increment_obj); + assert_eq!(new_ppu.palette_address_obj, 0x15); + assert!(new_ppu.first_frame); + assert_eq!(new_ppu.frame_index, 0x16); + assert_eq!(new_ppu.frame_buffer_index, 0x17); + assert!(new_ppu.stat_hblank); + assert!(new_ppu.stat_vblank); + assert!(new_ppu.stat_oam); + assert!(new_ppu.stat_lyc); + assert!(new_ppu.int_vblank); + assert!(new_ppu.int_stat); + assert!(new_ppu.dmg_compat); + assert_eq!(new_ppu.gb_mode, GameBoyMode::Dmg); + } } diff --git a/src/state.rs b/src/state.rs index 75be3fa2..1f052083 100644 --- a/src/state.rs +++ b/src/state.rs @@ -56,6 +56,41 @@ pub const BOS_VERSION: u8 = 1; /// Magic number for the BESS file format. pub const BESS_MAGIC: u32 = 0x53534542; +/// Represents the different formats for the state storage +/// and retrieval. +/// +/// Different formats will have different levels of detail +/// and will require different amounts of data to be +/// stored and retrieved. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "wasm", wasm_bindgen)] +pub enum StateFormat { + /// Minimal state format, meaning that only the most basic + /// elements of the component will be stored and retrieved. + Minimal = 1, + + /// Partial state format, meaning that only the essential + /// elements of the component will be stored and retrieved. + /// All the remaining data, should inferred or computed. + Partial = 2, + + /// Full state format, meaning that every single element + /// of the component will be stored and retrieved. This + /// should included redundant and calculated data. + Full = 3, +} + +impl From for StateFormat { + fn from(value: u8) -> Self { + match value { + 1 => Self::Minimal, + 2 => Self::Partial, + 3 => Self::Full, + _ => Self::Partial, + } + } +} + /// Represents a component that is able to store and retrieve /// the state of its internal structure. /// @@ -66,22 +101,23 @@ pub const BESS_MAGIC: u32 = 0x53534542; /// this trait to allow the state to be saved and restored /// in a consistent way. pub trait StateComponent { - fn state(&self) -> Result, Error>; - fn set_state(&mut self, data: &[u8]) -> Result<(), Error>; + fn state(&self, format: Option) -> Result, Error>; + fn set_state(&mut self, data: &[u8], format: Option) -> Result<(), Error>; } +#[derive(Clone, Copy, PartialEq, Eq, Debug)] #[cfg_attr(feature = "wasm", wasm_bindgen)] pub enum SaveStateFormat { /// Boytacean Save Compressed format (BOSC). /// This format uses the Zippy compression algorithm /// to compress the underlying BOS contents. - Bosc, + Bosc = 1, /// Boytacean Save format (uncompressed) (BOS). - Bos, + Bos = 2, /// Best Effort Save State format (BESS). - Bess, + Bess = 3, } impl SaveStateFormat { @@ -132,6 +168,21 @@ impl BosBlockKind { _ => Self::Unknown, } } + + pub fn description(&self) -> String { + match self { + Self::Info => String::from("Info"), + Self::ImageBuffer => String::from("ImageBuffer"), + Self::DeviceState => String::from("DeviceState"), + Self::Unknown => String::from("Unknown"), + } + } +} + +impl Display for BosBlockKind { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.description()) + } } impl From for BosBlockKind { @@ -143,15 +194,22 @@ impl From for BosBlockKind { #[cfg_attr(feature = "wasm", wasm_bindgen)] pub struct FromGbOptions { thumbnail: bool, + state_format: Option, agent: Option, agent_version: Option, } #[cfg_attr(feature = "wasm", wasm_bindgen)] impl FromGbOptions { - pub fn new(thumbnail: bool, agent: Option, agent_version: Option) -> Self { + pub fn new( + thumbnail: bool, + state_format: Option, + agent: Option, + agent_version: Option, + ) -> Self { Self { thumbnail, + state_format, agent, agent_version, } @@ -162,6 +220,7 @@ impl Default for FromGbOptions { fn default() -> Self { Self { thumbnail: true, + state_format: None, agent: None, agent_version: None, } @@ -253,7 +312,13 @@ impl BoscState { pub fn verify(&self) -> Result<(), Error> { if self.magic != BOSC_MAGIC_UINT { - return Err(Error::CustomError(String::from("Invalid magic"))); + return Err(Error::DataError(String::from("Invalid magic"))); + } + if self.version != BOSC_VERSION { + return Err(Error::DataError(format!( + "Invalid version, expected {BOS_VERSION}, got {}", + self.version + ))); } self.bos.verify()?; Ok(()) @@ -337,6 +402,12 @@ impl BosState { if self.magic != BOS_MAGIC_UINT { return Err(Error::CustomError(String::from("Invalid magic"))); } + if self.version != BOS_VERSION { + return Err(Error::CustomError(format!( + "Invalid version, expected {BOS_VERSION}, got {}", + self.version + ))); + } self.bess.verify()?; Ok(()) } @@ -463,7 +534,7 @@ impl Serialize for BosState { for _ in 0..self.block_count { let block = BosBlock::from_data(reader)?; - let offset = -((size_of::() + size_of::()) as i64); + let offset = -((size_of::() + size_of::() + size_of::()) as i64); reader.seek(SeekFrom::Current(offset))?; match block.kind { @@ -504,9 +575,12 @@ impl StateBox for BosState { None }, device_states: vec![ - BosDeviceState::from_gb(gb, GameBoyDevice::Apu)?, - BosDeviceState::from_gb(gb, GameBoyDevice::Dma)?, - BosDeviceState::from_gb(gb, GameBoyDevice::Timer)?, + BosDeviceState::from_gb(gb, GameBoyDevice::Cpu, options)?, + BosDeviceState::from_gb(gb, GameBoyDevice::Ppu, options)?, + BosDeviceState::from_gb(gb, GameBoyDevice::Apu, options)?, + BosDeviceState::from_gb(gb, GameBoyDevice::Dma, options)?, + BosDeviceState::from_gb(gb, GameBoyDevice::Pad, options)?, + BosDeviceState::from_gb(gb, GameBoyDevice::Timer, options)?, ], bess: *BessState::from_gb(gb, options)?, })) @@ -516,7 +590,7 @@ impl StateBox for BosState { self.verify()?; self.bess.to_gb(gb, options)?; for device_state in &self.device_states { - device_state.to_gb(gb)?; + device_state.to_gb(gb, options)?; } Ok(()) } @@ -530,12 +604,17 @@ impl StateConfig for BosState { pub struct BosBlock { kind: BosBlockKind, + version: u16, size: u32, } impl BosBlock { - pub fn new(kind: BosBlockKind, size: u32) -> Self { - Self { kind, size } + pub fn new(kind: BosBlockKind, version: u16, size: u32) -> Self { + Self { + kind, + version, + size, + } } pub fn from_data(reader: &mut R) -> Result { @@ -543,25 +622,47 @@ impl BosBlock { instance.read(reader)?; Ok(instance) } + + pub fn description(&self) -> String { + format!("{} version={} size={}", self.kind, self.version, self.size) + } } impl Serialize for BosBlock { fn write(&mut self, writer: &mut W) -> Result<(), Error> { write_u8(writer, self.kind as u8)?; + write_u16(writer, self.version)?; write_u32(writer, self.size)?; Ok(()) } fn read(&mut self, reader: &mut R) -> Result<(), Error> { + let check = self.version != 0; + let expected_version = self.version; + self.kind = read_u8(reader)?.into(); + self.version = read_u16(reader)?; self.size = read_u32(reader)?; + + if check && self.version != expected_version { + return Err(Error::DataError(format!( + "Invalid version, expected {expected_version}, got {} for block ({})", + self.version, self + ))); + } Ok(()) } } impl Default for BosBlock { fn default() -> Self { - Self::new(BosBlockKind::Info, 0) + Self::new(BosBlockKind::Info, 0, 0) + } +} + +impl Display for BosBlock { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.description()) } } @@ -578,6 +679,7 @@ impl BosInfo { Self { header: BosBlock::new( BosBlockKind::Info, + 1, (size_of::() + size_of::() * agent.len() + size_of::() * agent_version.len() @@ -687,6 +789,7 @@ impl BosImageBuffer { Self { header: BosBlock::new( BosBlockKind::ImageBuffer, + 1, (size_of::() * FRAME_BUFFER_SIZE) as u32, ), image, @@ -743,17 +846,20 @@ impl Default for BosImageBuffer { pub struct BosDeviceState { header: BosBlock, device: GameBoyDevice, + format: StateFormat, state: Vec, } impl BosDeviceState { - pub fn new(device: GameBoyDevice, state: Vec) -> Self { + pub fn new(device: GameBoyDevice, format: StateFormat, state: Vec) -> Self { Self { header: BosBlock::new( BosBlockKind::DeviceState, - (size_of::() + state.len()) as u32, + 1, + (size_of::() + size_of::() + state.len()) as u32, ), device, + format, state, } } @@ -764,20 +870,33 @@ impl BosDeviceState { Ok(instance) } - fn from_gb(gb: &mut GameBoy, device: GameBoyDevice) -> Result { + fn from_gb( + gb: &mut GameBoy, + device: GameBoyDevice, + options: &FromGbOptions, + ) -> Result { + let format: StateFormat = options.state_format.unwrap_or(StateFormat::Partial); match device { - GameBoyDevice::Apu => Ok(Self::new(device, gb.apu_i().state()?)), - GameBoyDevice::Dma => Ok(Self::new(device, gb.dma_i().state()?)), - GameBoyDevice::Timer => Ok(Self::new(device, gb.timer_i().state()?)), + GameBoyDevice::Cpu => Ok(Self::new(device, format, gb.cpu_i().state(Some(format))?)), + GameBoyDevice::Ppu => Ok(Self::new(device, format, gb.ppu_i().state(Some(format))?)), + GameBoyDevice::Apu => Ok(Self::new(device, format, gb.apu_i().state(Some(format))?)), + GameBoyDevice::Dma => Ok(Self::new(device, format, gb.dma_i().state(Some(format))?)), + GameBoyDevice::Pad => Ok(Self::new(device, format, gb.pad_i().state(Some(format))?)), + GameBoyDevice::Timer => { + Ok(Self::new(device, format, gb.timer_i().state(Some(format))?)) + } _ => Err(Error::NotImplemented), } } - fn to_gb(&self, gb: &mut GameBoy) -> Result<(), Error> { + fn to_gb(&self, gb: &mut GameBoy, _options: &ToGbOptions) -> Result<(), Error> { match self.device { - GameBoyDevice::Apu => gb.apu().set_state(&self.state)?, - GameBoyDevice::Dma => gb.dma().set_state(&self.state)?, - GameBoyDevice::Timer => gb.timer().set_state(&self.state)?, + GameBoyDevice::Cpu => gb.cpu().set_state(&self.state, Some(self.format))?, + GameBoyDevice::Ppu => gb.ppu().set_state(&self.state, Some(self.format))?, + GameBoyDevice::Apu => gb.apu().set_state(&self.state, Some(self.format))?, + GameBoyDevice::Dma => gb.dma().set_state(&self.state, Some(self.format))?, + GameBoyDevice::Pad => gb.pad().set_state(&self.state, Some(self.format))?, + GameBoyDevice::Timer => gb.timer().set_state(&self.state, Some(self.format))?, _ => return Err(Error::NotImplemented), } Ok(()) @@ -788,6 +907,7 @@ impl Serialize for BosDeviceState { fn write(&mut self, writer: &mut W) -> Result<(), Error> { self.header.write(writer)?; write_u8(writer, self.device as u8)?; + write_u8(writer, self.format as u8)?; write_bytes(writer, &self.state)?; Ok(()) } @@ -795,7 +915,8 @@ impl Serialize for BosDeviceState { fn read(&mut self, reader: &mut R) -> Result<(), Error> { self.header.read(reader)?; self.device = read_u8(reader)?.into(); - let state_len = self.header.size as usize - size_of::(); + self.format = read_u8(reader)?.into(); + let state_len = self.header.size as usize - size_of::() - size_of::(); self.state.append(&mut read_bytes(reader, state_len)?); Ok(()) } @@ -803,7 +924,7 @@ impl Serialize for BosDeviceState { impl Default for BosDeviceState { fn default() -> Self { - Self::new(GameBoyDevice::Unknown, vec![]) + Self::new(GameBoyDevice::Unknown, StateFormat::Partial, vec![]) } } @@ -1193,7 +1314,7 @@ impl BessFooter { pub fn verify(&self) -> Result<(), Error> { if self.magic != BESS_MAGIC { - return Err(Error::CustomError(String::from("Invalid magic"))); + return Err(Error::DataError(String::from("Invalid magic"))); } Ok(()) } @@ -1373,7 +1494,7 @@ impl State for BessInfo { fn to_gb(&self, gb: &mut GameBoy) -> Result<(), Error> { if self.title() != gb.rom_i().title() { - return Err(Error::CustomError(format!( + return Err(Error::DataError(format!( "Invalid ROM loaded, expected '{}' (len {}) got '{}' (len {})", self.title(), self.title().len(), @@ -1480,25 +1601,31 @@ impl BessCore { pub fn verify(&self) -> Result<(), Error> { if self.header.magic != "CORE" { - return Err(Error::CustomError(String::from("Invalid magic"))); + return Err(Error::DataError(String::from("Invalid magic"))); + } + if self.major != 1 { + return Err(Error::DataError(String::from("Invalid major version"))); + } + if self.minor != 1 { + return Err(Error::DataError(String::from("Invalid minor version"))); } if self.oam.size != 0xa0 { - return Err(Error::CustomError(String::from("Invalid OAM size"))); + return Err(Error::DataError(String::from("Invalid OAM size"))); } if self.hram.size != 0x7f { - return Err(Error::CustomError(String::from("Invalid HRAM size"))); + return Err(Error::DataError(String::from("Invalid HRAM size"))); } if (self.is_cgb() && self.background_palettes.size != 0x40) || (self.is_dmg() && self.background_palettes.size != 0x00) { - return Err(Error::CustomError(String::from( + return Err(Error::DataError(String::from( "Invalid background palettes size", ))); } if (self.is_cgb() && self.object_palettes.size != 0x40) || (self.is_dmg() && self.object_palettes.size != 0x00) { - return Err(Error::CustomError(String::from( + return Err(Error::DataError(String::from( "Invalid object palettes size", ))); } @@ -1715,9 +1842,9 @@ impl State for BessCore { gb.mmu().set_ram(self.ram.buffer.to_vec()); gb.ppu().set_vram(&self.vram.buffer); + gb.ppu().set_oam(&self.oam.buffer); + gb.ppu().set_hram(&self.hram.buffer); gb.rom().set_ram_data(&self.mbc_ram.buffer); - gb.mmu().write_many(0xfe00, &self.oam.buffer); - gb.mmu().write_many(0xff80, &self.hram.buffer); // disables a series of operations that would otherwise be // triggered by the writing of associated registers @@ -1910,12 +2037,12 @@ impl StateManager { options: Option, ) -> Result<(), Error> { let mut file = File::create(file_path) - .map_err(|_| Error::CustomError(format!("Failed to create file: {file_path}")))?; + .map_err(|_| Error::IoError(format!("Failed to create file: {file_path}")))?; let data = Self::save(gb, format, options)?; file.write_all(&data) - .map_err(|_| Error::CustomError(format!("Failed to write to file: {file_path}")))?; + .map_err(|_| Error::IoError(format!("Failed to write to file: {file_path}")))?; file.flush() - .map_err(|_| Error::CustomError(format!("Failed to flush file: {file_path}")))?; + .map_err(|_| Error::IoError(format!("Failed to flush file: {file_path}")))?; Ok(()) } @@ -1926,10 +2053,10 @@ impl StateManager { options: Option, ) -> Result<(), Error> { let mut file = File::open(file_path) - .map_err(|_| Error::CustomError(format!("Failed to open file: {file_path}")))?; + .map_err(|_| Error::IoError(format!("Failed to open file: {file_path}")))?; let mut data = vec![]; file.read_to_end(&mut data) - .map_err(|_| Error::CustomError(format!("Failed to read from file: {file_path}")))?; + .map_err(|_| Error::IoError(format!("Failed to read from file: {file_path}")))?; Self::load(&data, gb, format, options)?; Ok(()) } @@ -1978,7 +2105,7 @@ impl StateManager { } else if BessState::is_bess(data)? { SaveStateFormat::Bess } else { - return Err(Error::CustomError(String::from( + return Err(Error::InvalidParameter(String::from( "Unknown save state file format", ))); } @@ -2015,7 +2142,7 @@ impl StateManager { state.read(data)?; Ok(state) } - SaveStateFormat::Bess => Err(Error::CustomError(String::from( + SaveStateFormat::Bess => Err(Error::InvalidParameter(String::from( "Incompatible save state file format (BESS)", ))), } @@ -2106,7 +2233,7 @@ impl StateManager { state.read(data)?; Ok(state.image_buffer.ok_or(Error::InvalidData)?.image.to_vec()) } - SaveStateFormat::Bess => Err(Error::CustomError(String::from( + SaveStateFormat::Bess => Err(Error::InvalidParameter(String::from( "Format foes not support thumbnail", ))), } diff --git a/src/timer.rs b/src/timer.rs index 1d7abc21..1059fdfc 100644 --- a/src/timer.rs +++ b/src/timer.rs @@ -11,7 +11,7 @@ use crate::{ consts::{DIV_ADDR, TAC_ADDR, TIMA_ADDR, TMA_ADDR}, mmu::BusComponent, panic_gb, - state::StateComponent, + state::{StateComponent, StateFormat}, warnln, }; @@ -171,7 +171,7 @@ impl BusComponent for Timer { } impl StateComponent for Timer { - fn state(&self) -> Result, Error> { + fn state(&self, _format: Option) -> Result, Error> { let mut cursor = Cursor::new(vec![]); write_u8(&mut cursor, self.div)?; write_u8(&mut cursor, self.tima)?; @@ -185,7 +185,7 @@ impl StateComponent for Timer { Ok(cursor.into_inner()) } - fn set_state(&mut self, data: &[u8]) -> Result<(), Error> { + fn set_state(&mut self, data: &[u8], _format: Option) -> Result<(), Error> { let mut cursor = Cursor::new(data); self.div = read_u8(&mut cursor)?; self.tima = read_u8(&mut cursor)?; @@ -193,9 +193,9 @@ impl StateComponent for Timer { self.tac = read_u8(&mut cursor)?; self.div_clock = read_u16(&mut cursor)?; self.tima_clock = read_u16(&mut cursor)?; - self.tima_enabled = read_u8(&mut cursor)? == 1; + self.tima_enabled = read_u8(&mut cursor)? != 0; self.tima_ratio = read_u16(&mut cursor)?; - self.int_tima = read_u8(&mut cursor)? == 1; + self.int_tima = read_u8(&mut cursor)? != 0; Ok(()) } } @@ -214,31 +214,32 @@ mod tests { #[test] fn test_state_and_set_state() { - let mut timer = Timer::new(); - timer.div = 0x12; - timer.tima = 0x34; - timer.tma = 0x56; - timer.tac = 0x78; - timer.div_clock = 0x9abc; - timer.tima_clock = 0xdef0; - timer.tima_enabled = true; - timer.tima_ratio = 0x1234; - timer.int_tima = true; - - let state = timer.state().unwrap(); + let timer = Timer { + div: 0x12, + tima: 0x34, + tma: 0x56, + tac: 0x78, + div_clock: 0x9abc, + tima_clock: 0xdef0, + tima_enabled: true, + tima_ratio: 0x1234, + int_tima: true, + }; + + let state = timer.state(None).unwrap(); assert_eq!(state.len(), 12); let mut new_timer = Timer::new(); - new_timer.set_state(&state).unwrap(); - - assert_eq!(timer.div, new_timer.div); - assert_eq!(timer.tima, new_timer.tima); - assert_eq!(timer.tma, new_timer.tma); - assert_eq!(timer.tac, new_timer.tac); - assert_eq!(timer.div_clock, new_timer.div_clock); - assert_eq!(timer.tima_clock, new_timer.tima_clock); - assert_eq!(timer.tima_enabled, new_timer.tima_enabled); - assert_eq!(timer.tima_ratio, new_timer.tima_ratio); - assert_eq!(timer.int_tima, new_timer.int_tima); + new_timer.set_state(&state, None).unwrap(); + + assert_eq!(new_timer.div, 0x12); + assert_eq!(new_timer.tima, 0x34); + assert_eq!(new_timer.tma, 0x56); + assert_eq!(new_timer.tac, 0x78); + assert_eq!(new_timer.div_clock, 0x9abc); + assert_eq!(new_timer.tima_clock, 0xdef0); + assert!(new_timer.tima_enabled); + assert_eq!(new_timer.tima_ratio, 0x1234); + assert!(new_timer.int_tima); } }