From e9ae8bed1b4ccb76ead65537e90975803e4f1486 Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Sat, 7 Dec 2024 00:00:40 -0500 Subject: [PATCH] Implement SRAM (only saves on pause) --- src/emulator.rs | 89 +++++++++++++++++++++++++++------ src/emulator/shrooms_vb_core.rs | 67 ++++++++++++++++--------- 2 files changed, 118 insertions(+), 38 deletions(-) diff --git a/src/emulator.rs b/src/emulator.rs index fd04520..00cb318 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -1,6 +1,7 @@ use std::{ collections::HashMap, - fs, + fs::{self, File}, + io::{Read, Seek, SeekFrom, Write}, path::{Path, PathBuf}, sync::{ atomic::{AtomicBool, AtomicUsize, Ordering}, @@ -44,6 +45,43 @@ impl SimId { } } +struct Cart { + rom_path: PathBuf, + rom: Vec, + sram_file: File, + sram: Vec, +} + +impl Cart { + fn load(rom_path: &Path, sim_id: SimId) -> Result { + let rom = fs::read(rom_path)?; + + let mut sram_file = File::options() + .read(true) + .write(true) + .create(true) + .truncate(false) + .open(sram_path(rom_path, sim_id))?; + sram_file.set_len(8 * 1024)?; + + let mut sram = vec![]; + sram_file.read_to_end(&mut sram)?; + Ok(Cart { + rom_path: rom_path.to_path_buf(), + rom, + sram_file, + sram, + }) + } +} + +fn sram_path(rom_path: &Path, sim_id: SimId) -> PathBuf { + match sim_id { + SimId::Player1 => rom_path.with_extension(".p1.sram"), + SimId::Player2 => rom_path.with_extension(".p2.sram"), + } +} + impl EmulatorBuilder { pub fn new() -> (Self, EmulatorClient) { let (queue, commands) = mpsc::channel(); @@ -84,7 +122,7 @@ impl EmulatorBuilder { self.linked, )?; if let Some(path) = self.rom { - emulator.load_rom(SimId::Player1, &path)?; + emulator.load_cart(SimId::Player1, &path)?; } Ok(emulator) } @@ -92,6 +130,7 @@ impl EmulatorBuilder { pub struct Emulator { sims: Vec, + carts: [Option; 2], audio: Audio, commands: mpsc::Receiver, sim_count: Arc, @@ -113,6 +152,7 @@ impl Emulator { ) -> Result { Ok(Self { sims: vec![], + carts: [None, None], audio: Audio::init()?, commands, sim_count, @@ -124,24 +164,28 @@ impl Emulator { }) } - pub fn load_rom(&mut self, sim_id: SimId, path: &Path) -> Result<()> { - let bytes = fs::read(path)?; - self.reset_sim(sim_id, Some(bytes))?; + pub fn load_cart(&mut self, sim_id: SimId, path: &Path) -> Result<()> { + let cart = Cart::load(path, sim_id)?; + self.reset_sim(sim_id, Some(cart))?; Ok(()) } pub fn start_second_sim(&mut self, rom: Option) -> Result<()> { - let bytes = if let Some(path) = rom { - Some(fs::read(path)?) + let rom_path = if let Some(path) = rom { + Some(path) } else { - self.sims.first().and_then(|s| s.clone_rom()) + self.carts[0].as_ref().map(|c| c.rom_path.clone()) }; - self.reset_sim(SimId::Player2, bytes)?; + let cart = match rom_path { + Some(rom_path) => Some(Cart::load(&rom_path, SimId::Player2)?), + None => None, + }; + self.reset_sim(SimId::Player2, cart)?; self.link_sims(); Ok(()) } - fn reset_sim(&mut self, sim_id: SimId, new_rom: Option>) -> Result<()> { + fn reset_sim(&mut self, sim_id: SimId, new_cart: Option) -> Result<()> { let index = sim_id.to_index(); while self.sims.len() <= index { self.sims.push(Sim::new()); @@ -149,8 +193,9 @@ impl Emulator { self.sim_count.store(self.sims.len(), Ordering::Relaxed); let sim = &mut self.sims[index]; sim.reset(); - if let Some(bytes) = new_rom { - sim.load_rom(bytes)?; + if let Some(cart) = new_cart { + sim.load_cart(cart.rom.clone(), cart.sram.clone())?; + self.carts[index] = Some(cart); self.has_game[index].store(true, Ordering::Release); } if self.has_game[index].load(Ordering::Acquire) { @@ -179,6 +224,18 @@ impl Emulator { self.linked.store(false, Ordering::Release); } + pub fn pause_sim(&mut self, sim_id: SimId) -> Result<()> { + self.running[sim_id.to_index()].store(false, Ordering::Release); + let sim = self.sims.get_mut(sim_id.to_index()); + let cart = self.carts[sim_id.to_index()].as_mut(); + if let (Some(sim), Some(cart)) = (sim, cart) { + sim.read_sram(&mut cart.sram); + cart.sram_file.seek(SeekFrom::Start(0))?; + cart.sram_file.write_all(&cart.sram)?; + } + Ok(()) + } + pub fn stop_second_sim(&mut self) { self.renderers.remove(&SimId::Player2); self.sims.truncate(1); @@ -269,7 +326,7 @@ impl Emulator { self.renderers.insert(sim_id, renderer); } EmulatorCommand::LoadGame(sim_id, path) => { - if let Err(error) = self.load_rom(sim_id, &path) { + if let Err(error) = self.load_cart(sim_id, &path) { eprintln!("error loading rom: {}", error); } } @@ -282,8 +339,10 @@ impl Emulator { self.stop_second_sim(); } EmulatorCommand::Pause => { - for sim in SimId::values() { - self.running[sim.to_index()].store(false, Ordering::Release); + for sim_id in SimId::values() { + if let Err(error) = self.pause_sim(sim_id) { + eprintln!("error pausing: {}", error); + } } } EmulatorCommand::Resume => { diff --git a/src/emulator/shrooms_vb_core.rs b/src/emulator/shrooms_vb_core.rs index fe2fa27..6a3394b 100644 --- a/src/emulator/shrooms_vb_core.rs +++ b/src/emulator/shrooms_vb_core.rs @@ -62,6 +62,8 @@ extern "C" { fn vb_emulate(sim: *mut VB, cycles: *mut u32) -> c_int; #[link_name = "vbEmulateEx"] fn vb_emulate_ex(sims: *mut *mut VB, count: c_uint, cycles: *mut u32) -> c_int; + #[link_name = "vbGetCartRAM"] + fn vb_get_cart_ram(sim: *mut VB, size: *mut u32) -> *mut c_void; #[link_name = "vbGetCartROM"] fn vb_get_cart_rom(sim: *mut VB, size: *mut u32) -> *mut c_void; #[link_name = "vbGetPixels"] @@ -87,6 +89,8 @@ extern "C" { fn vb_init(sim: *mut VB) -> *mut VB; #[link_name = "vbReset"] fn vb_reset(sim: *mut VB); + #[link_name = "vbSetCartRAM"] + fn vb_set_cart_ram(sim: *mut VB, sram: *mut c_void, size: u32) -> c_int; #[link_name = "vbSetCartROM"] fn vb_set_cart_rom(sim: *mut VB, rom: *mut c_void, size: u32) -> c_int; #[link_name = "vbSetFrameCallback"] @@ -162,41 +166,58 @@ impl Sim { unsafe { vb_reset(self.sim) }; } - pub fn load_rom(&mut self, rom: Vec) -> Result<()> { - self.unload_rom(); + pub fn load_cart(&mut self, mut rom: Vec, mut sram: Vec) -> Result<()> { + self.unload_cart(); + + rom.shrink_to_fit(); + sram.shrink_to_fit(); let size = rom.len() as u32; let rom = Box::into_raw(rom.into_boxed_slice()).cast(); let status = unsafe { vb_set_cart_rom(self.sim, rom, size) }; - if status == 0 { - Ok(()) - } else { + if status != 0 { let _: Vec = unsafe { Vec::from_raw_parts(rom.cast(), size as usize, size as usize) }; - Err(anyhow!("Invalid ROM size of {} bytes", size)) + return Err(anyhow!("Invalid ROM size of {} bytes", size)); } + + let size = sram.len() as u32; + let sram = Box::into_raw(sram.into_boxed_slice()).cast(); + let status = unsafe { vb_set_cart_ram(self.sim, sram, size) }; + if status != 0 { + let _: Vec = + unsafe { Vec::from_raw_parts(sram.cast(), size as usize, size as usize) }; + return Err(anyhow!("Invalid SRAM size of {} bytes", size)); + } + + Ok(()) } - pub fn clone_rom(&self) -> Option> { + fn unload_cart(&mut self) { let mut size = 0; let rom = unsafe { vb_get_cart_rom(self.sim, &mut size) }; - if rom.is_null() { - return None; - } - // SAFETY: rom definitely points to a valid array of `size` bytes - let slice: &[u8] = unsafe { slice::from_raw_parts(rom.cast(), size as usize) }; - Some(slice.to_vec()) - } - - fn unload_rom(&mut self) -> Option> { - let mut size = 0; - let rom = unsafe { vb_get_cart_rom(self.sim, &mut size) }; - if rom.is_null() { - return None; - } unsafe { vb_set_cart_rom(self.sim, ptr::null_mut(), 0) }; - let vec = unsafe { Vec::from_raw_parts(rom.cast(), size as usize, size as usize) }; - Some(vec) + if !rom.is_null() { + let _: Vec = + unsafe { Vec::from_raw_parts(rom.cast(), size as usize, size as usize) }; + } + + let sram = unsafe { vb_get_cart_ram(self.sim, &mut size) }; + unsafe { vb_set_cart_ram(self.sim, ptr::null_mut(), 0) }; + if !sram.is_null() { + let _: Vec = + unsafe { Vec::from_raw_parts(sram.cast(), size as usize, size as usize) }; + } + } + + pub fn read_sram(&mut self, buffer: &mut [u8]) { + let mut size = 0; + let sram = unsafe { vb_get_cart_ram(self.sim, &mut size) }; + if sram.is_null() { + return; + } + let bytes = unsafe { slice::from_raw_parts(sram.cast(), size as usize) }; + buffer.copy_from_slice(bytes); } pub fn link(&mut self, peer: &mut Sim) {