use super::*; use crate::native_core::apu::ApuStateTail; use crate::native_core::state_io as sio; const BUS_STATE_CTX: &str = "bus state"; impl NativeBus { pub fn save_state(&self, out: &mut Vec) { out.extend_from_slice(&self.cpu_ram); self.ppu.save_state(out); out.extend_from_slice(self.apu.registers()); out.push(self.cpu_open_bus); out.push(self.joypad_state); out.push(self.joypad_shift); out.push(self.joypad2_state); out.push(self.joypad2_shift); out.push(u8::from(self.joypad_strobe)); out.push(u8::from(self.nmi_pending)); out.extend_from_slice(&self.ppu_dot.to_le_bytes()); out.push(u8::from(self.odd_frame)); out.push(u8::from(self.in_vblank)); out.push(u8::from(self.mmc3_a12_prev_high)); out.extend_from_slice(&self.mmc3_a12_low_dots.to_le_bytes()); out.extend_from_slice(&self.mmc3_last_irq_scanline.to_le_bytes()); self.apu.save_state_tail(out); let mut mapper_state = Vec::new(); self.mapper.save_state(&mut mapper_state); out.extend_from_slice(&(mapper_state.len() as u32).to_le_bytes()); out.extend_from_slice(&mapper_state); } pub fn load_state(&mut self, data: &[u8]) -> Result<(), String> { let mut cursor = 0usize; self.cpu_ram.copy_from_slice(sio::take_exact( data, &mut cursor, CPU_RAM_SIZE, BUS_STATE_CTX, )?); let ppu_consumed = self.ppu.load_state(&data[cursor..])?; cursor = cursor.saturating_add(ppu_consumed); let mut apu_regs = [0u8; 0x20]; apu_regs.copy_from_slice(sio::take_exact(data, &mut cursor, 0x20, BUS_STATE_CTX)?); self.apu.set_registers(apu_regs); self.frame_complete = false; self.cpu_open_bus = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?; self.joypad_state = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?; self.joypad_shift = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?; self.joypad2_state = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?; self.joypad2_shift = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?; self.joypad_strobe = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)? != 0; self.nmi_pending = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)? != 0; self.suppress_vblank_this_frame = false; self.ppu_dot = sio::take_u32(data, &mut cursor, BUS_STATE_CTX)?; self.ppu_dot %= PPU_DOTS_PER_FRAME; self.odd_frame = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)? != 0; self.in_vblank = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)? != 0; self.mmc3_a12_prev_high = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)? != 0; self.mmc3_a12_low_dots = u16::from_le_bytes([ sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?, sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?, ]); self.mmc3_last_irq_scanline = u32::from_le_bytes([ sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?, sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?, sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?, sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?, ]); let frame_cycle = sio::take_u32(data, &mut cursor, BUS_STATE_CTX)?; let frame_mode_5step = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)? != 0; let frame_irq_inhibit = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)? != 0; let frame_irq_pending = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)? != 0; let channel_enable_mask = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?; let mut length_counters = [0u8; 4]; length_counters.copy_from_slice(sio::take_exact(data, &mut cursor, 4, BUS_STATE_CTX)?); let dmc_bytes_remaining = u16::from_le_bytes([ sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?, sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?, ]); let dmc_irq_enabled = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)? != 0; let dmc_irq_pending = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)? != 0; let dmc_cycle_counter = u16::from_le_bytes([ sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?, sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?, ]); let dmc_current_addr = u16::from_le_bytes([ sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?, sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?, ]); let dmc_sample_buffer = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?; let dmc_sample_buffer_valid = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)? != 0; let dmc_shift_reg = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?; let dmc_bits_remaining = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?; let dmc_silence = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)? != 0; let dmc_output_level = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?; let dmc_dma_request = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)? != 0; let mut envelope_divider = [0u8; 3]; envelope_divider.copy_from_slice(sio::take_exact(data, &mut cursor, 3, BUS_STATE_CTX)?); let mut envelope_decay = [0u8; 3]; envelope_decay.copy_from_slice(sio::take_exact(data, &mut cursor, 3, BUS_STATE_CTX)?); let envelope_start_flags = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?; let triangle_linear_counter = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?; let triangle_linear_reload_flag = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)? != 0; let mut sweep_divider = [0u8; 2]; sweep_divider.copy_from_slice(sio::take_exact(data, &mut cursor, 2, BUS_STATE_CTX)?); let sweep_reload_flags = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?; let cpu_cycle_parity = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)? != 0; let frame_reset_pending = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)? != 0; let frame_reset_delay = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?; let pending_frame_mode_5step = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)? != 0; let pending_frame_irq_inhibit = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)? != 0; let pulse_timer_counter = [ u16::from_le_bytes([ sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?, sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?, ]), u16::from_le_bytes([ sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?, sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?, ]), ]; let mut pulse_duty_step = [0u8; 2]; pulse_duty_step.copy_from_slice(sio::take_exact(data, &mut cursor, 2, BUS_STATE_CTX)?); let triangle_timer_counter = u16::from_le_bytes([ sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?, sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?, ]); let triangle_step = sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?; let noise_timer_counter = u16::from_le_bytes([ sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?, sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?, ]); let noise_lfsr = u16::from_le_bytes([ sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?, sio::take_u8(data, &mut cursor, BUS_STATE_CTX)?, ]); self.apu.load_state_tail(ApuStateTail { frame_cycle, frame_mode_5step, frame_irq_inhibit, frame_irq_pending, channel_enable_mask, length_counters, dmc_bytes_remaining, dmc_irq_enabled, dmc_irq_pending, dmc_cycle_counter, dmc_current_addr, dmc_sample_buffer, dmc_sample_buffer_valid, dmc_shift_reg, dmc_bits_remaining, dmc_silence, dmc_output_level, dmc_dma_request, envelope_divider, envelope_decay, envelope_start_flags, triangle_linear_counter, triangle_linear_reload_flag, sweep_divider, sweep_reload_flags, cpu_cycle_parity, frame_reset_pending, frame_reset_delay, pending_frame_mode_5step, pending_frame_irq_inhibit, pulse_timer_counter, pulse_duty_step, triangle_timer_counter, triangle_step, noise_timer_counter, noise_lfsr, }); let mapper_len = sio::take_u32(data, &mut cursor, BUS_STATE_CTX)? as usize; let mapper_state = sio::take_exact(data, &mut cursor, mapper_len, BUS_STATE_CTX)?; self.ppu.set_vblank(self.in_vblank); self.mapper.load_state(mapper_state)?; if cursor != data.len() { return Err("bus state: trailing bytes in payload".to_string()); } Ok(()) } }