diff --git a/gameboy_core/src/lib.rs b/gameboy_core/src/lib.rs index 8ec96fd..2c679ff 100644 --- a/gameboy_core/src/lib.rs +++ b/gameboy_core/src/lib.rs @@ -41,11 +41,28 @@ impl Gameboy { pub fn get_audio_buffer(&self) -> &[f32] { self.emulator.get_audio_buffer() } - pub fn get_cartridge(&self) -> &Cartridge { - self.emulator.get_cartridge() + pub fn set_cartridge_ram(&mut self, ram: Vec) { + self.emulator.get_cartridge_mut().set_ram(ram) } - pub fn get_cartridge_mut(&mut self) -> &mut Cartridge { - self.emulator.get_cartridge_mut() + pub fn has_battery(&self) -> bool { + self.emulator.get_cartridge().has_battery() + } + pub fn has_rtc(&self) -> bool { + self.emulator.get_cartridge().has_rtc() + } + pub fn get_cartridge_ram(&self) -> &[u8] { + self.emulator.get_cartridge().get_ram() + } + pub fn get_cartridge_name(&self) -> &str { + self.emulator.get_cartridge().get_name() + } + pub fn get_last_timestamp(&self) -> (rtc::Rtc, u64) { + self.emulator.get_cartridge().get_last_timestamp() + } + pub fn set_last_timestamp(&mut self, rtc: Rtc, timestamp: u64) { + self.emulator + .get_cartridge_mut() + .set_last_timestamp(rtc, timestamp) } pub fn set_ram_change_callback(&mut self, f: Box) { self.emulator.set_ram_change_callback(f) diff --git a/gameboy_opengl/src/lib.rs b/gameboy_opengl/src/lib.rs index aa31037..8333f4e 100644 --- a/gameboy_opengl/src/lib.rs +++ b/gameboy_opengl/src/lib.rs @@ -4,7 +4,7 @@ mod screen; use crate::native_rtc::NativeRTC; use crate::screen::Screen; use directories::BaseDirs; -use gameboy_core::{Button, Cartridge, Gameboy, Rtc, StepResult}; +use gameboy_core::{Button, Gameboy, Rtc, StepResult}; use sdl2::audio::AudioSpecDesired; use sdl2::event::Event; use sdl2::keyboard::Keycode; @@ -54,11 +54,11 @@ pub fn start(rom: Vec) -> Result<(), String> { let rtc = Box::new(NativeRTC::new()); let mut emulator = Gameboy::from_rom(rom, rtc)?; - load_ram_save_data(emulator.get_cartridge_mut()).map_err(|e| format!("{:?}", e))?; - load_timestamp_data(emulator.get_cartridge_mut()).map_err(|e| format!("{:?}", e))?; + load_ram_save_data(&mut emulator).map_err(|e| format!("{:?}", e))?; + load_timestamp_data(&mut emulator).map_err(|e| format!("{:?}", e))?; - let mut ram_save_file = get_ram_save_file(emulator.get_cartridge()); - let mut timestamp_save_file = get_timestamp_save_file(emulator.get_cartridge()); + let mut ram_save_file = get_ram_save_file(&emulator); + let mut timestamp_save_file = get_timestamp_save_file(&emulator); let ram_changed = Rc::new(RefCell::new(true)); { @@ -95,17 +95,16 @@ pub fn start(rom: Vec) -> Result<(), String> { } } - if *ram_changed.borrow() && emulator.get_cartridge().has_battery() { + if *ram_changed.borrow() && emulator.has_battery() { if let Some(ref mut ram_save_file) = ram_save_file { - save_ram_data(emulator.get_cartridge(), ram_save_file) - .map_err(|e| format!("{:?}", e))?; + save_ram_data(&emulator, ram_save_file).map_err(|e| format!("{:?}", e))?; } *ram_changed.borrow_mut() = false; } - if emulator.get_cartridge().has_rtc() { + if emulator.has_rtc() { if let Some(ref mut timestamp_save_file) = timestamp_save_file { - save_timestamp_data(emulator.get_cartridge(), timestamp_save_file) + save_timestamp_data(&emulator, timestamp_save_file) .map_err(|e| format!("{:?}", e))?; } } @@ -160,17 +159,19 @@ fn get_ram_saves_path() -> Option { Some(path_buf) } -fn load_ram_save_data(cartridge: &mut Cartridge) -> std::io::Result<()> { - if cartridge.has_battery() { +fn load_ram_save_data(gameboy: &mut Gameboy) -> std::io::Result<()> { + if gameboy.has_battery() { if let Some(ram_saves_dir) = get_ram_saves_path() { - let ram_save_file = ram_saves_dir.join(format!("{}.bin", cartridge.get_name())); + let ram_save_file = ram_saves_dir.join(format!("{}.bin", gameboy.get_cartridge_name())); if ram_save_file.exists() { let mut ram_save_file = OpenOptions::new().read(true).open(ram_save_file)?; if let Ok(metadata) = ram_save_file.metadata() { // sometimes two different roms have the same name, // so we make sure that the ram length is the same before reading - if metadata.len() == cartridge.get_ram().len() as u64 { - ram_save_file.read_exact(cartridge.get_ram_mut())?; + if metadata.len() == gameboy.get_cartridge_ram().len() as u64 { + let mut buffer = vec![0; metadata.len() as usize]; + ram_save_file.read_exact(&mut buffer)?; + gameboy.set_cartridge_ram(buffer); } } } @@ -179,11 +180,11 @@ fn load_ram_save_data(cartridge: &mut Cartridge) -> std::io::Result<()> { Ok(()) } -fn load_timestamp_data(cartridge: &mut Cartridge) -> std::io::Result<()> { - if cartridge.has_rtc() { +fn load_timestamp_data(gameboy: &mut Gameboy) -> std::io::Result<()> { + if gameboy.has_rtc() { if let Some(ram_saves_dir) = get_ram_saves_path() { let timestamp_save_file = - ram_saves_dir.join(format!("{}-timestamp.bin", cartridge.get_name())); + ram_saves_dir.join(format!("{}-timestamp.bin", gameboy.get_cartridge_name())); if timestamp_save_file.exists() { let mut timestamp_save_file = OpenOptions::new().read(true).open(timestamp_save_file)?; @@ -193,20 +194,21 @@ fn load_timestamp_data(cartridge: &mut Cartridge) -> std::io::Result<()> { timestamp_save_file.read_exact(&mut timestamp_data)?; let last_rtc = Rtc::from_bytes(&rtc_data); let last_timestamp = u64::from_ne_bytes(timestamp_data); - cartridge.set_last_timestamp(last_rtc, last_timestamp); + gameboy.set_last_timestamp(last_rtc, last_timestamp); } } } Ok(()) } -fn get_ram_save_file(cartridge: &Cartridge) -> Option { - if cartridge.has_battery() { +fn get_ram_save_file(gameboy: &Gameboy) -> Option { + if gameboy.has_battery() { let ram_saves_path = get_ram_saves_path()?; if !ram_saves_path.exists() { fs::create_dir_all(&ram_saves_path).ok()?; } - let ram_save_file_path = ram_saves_path.join(format!("{}.bin", cartridge.get_name())); + let ram_save_file_path = + ram_saves_path.join(format!("{}.bin", gameboy.get_cartridge_name())); Some( OpenOptions::new() .write(true) @@ -219,14 +221,14 @@ fn get_ram_save_file(cartridge: &Cartridge) -> Option { } } -fn get_timestamp_save_file(cartridge: &Cartridge) -> Option { - if cartridge.has_rtc() { +fn get_timestamp_save_file(gameboy: &Gameboy) -> Option { + if gameboy.has_rtc() { let ram_saves_path = get_ram_saves_path()?; if !ram_saves_path.exists() { fs::create_dir_all(&ram_saves_path).ok()?; } let timestamp_save_file_path = - ram_saves_path.join(format!("{}-timestamp.bin", cartridge.get_name())); + ram_saves_path.join(format!("{}-timestamp.bin", gameboy.get_cartridge_name())); Some( OpenOptions::new() .write(true) @@ -239,12 +241,9 @@ fn get_timestamp_save_file(cartridge: &Cartridge) -> Option { } } -fn save_ram_data( - cartridge: &Cartridge, - ram_save_file: &mut T, -) -> std::io::Result<()> { - if cartridge.has_battery() { - let ram = cartridge.get_ram(); +fn save_ram_data(gameboy: &Gameboy, ram_save_file: &mut T) -> std::io::Result<()> { + if gameboy.has_battery() { + let ram = gameboy.get_cartridge_ram(); ram_save_file.seek(SeekFrom::Start(0))?; ram_save_file.write_all(ram)?; } @@ -252,10 +251,10 @@ fn save_ram_data( } fn save_timestamp_data( - cartridge: &Cartridge, + gameboy: &Gameboy, timestamp_save_file: &mut T, ) -> std::io::Result<()> { - let (rtc_data, rtc_last_time) = cartridge.get_last_timestamp(); + let (rtc_data, rtc_last_time) = gameboy.get_last_timestamp(); let mut rtc_data = rtc_data.to_bytes().to_vec(); let mut rtc_last_time_data = rtc_last_time.to_ne_bytes().to_vec(); rtc_data.append(&mut rtc_last_time_data); diff --git a/gameboy_opengl_web/src/lib.rs b/gameboy_opengl_web/src/lib.rs index 6fb509a..ba28c39 100644 --- a/gameboy_opengl_web/src/lib.rs +++ b/gameboy_opengl_web/src/lib.rs @@ -11,7 +11,7 @@ mod web_rtc; use crate::screen::Screen; use crate::web_rtc::WebRTC; -use gameboy_core::{Button, Cartridge, ControllerEvent, Gameboy, Rtc, StepResult}; +use gameboy_core::{Button, ControllerEvent, Gameboy, Rtc, StepResult}; use std::cell::RefCell; use std::rc::Rc; use std::sync::mpsc; @@ -33,8 +33,44 @@ struct EmulatorState { js_ctx: Value, busy: bool, audio_underrun: Option, + save: Box, +} +//Generic object for saving emulator state +trait SaveFile { + fn load(&mut self) -> Option>; + fn save(&mut self, ram: Vec); +} +struct LocalStorageSaveFile { + save_name: String, +} +impl LocalStorageSaveFile { + pub fn new(name: String) -> LocalStorageSaveFile { + return LocalStorageSaveFile { save_name: name }; + } +} +impl SaveFile for LocalStorageSaveFile { + fn load(&mut self) -> Option> { + if let Some(ram_str) = window().local_storage().get(&self.save_name) { + let chars: Vec = ram_str.chars().collect(); + let bytes: Vec = chars + .chunks(2) + .map(|chunk| { + let byte: String = chunk.iter().collect(); + u8::from_str_radix(&byte, 16).unwrap() + }) + .collect(); + return Some(bytes); + } + None + } + fn save(&mut self, ram: Vec) { + let save_str: String = ram.iter().map(|x| format!("{:02X}", x)).collect(); + window() + .local_storage() + .insert(&self.save_name, &save_str) + .unwrap(); + } } - impl EmulatorState { pub fn emulate_until_vblank_or_audio(&mut self) -> StepResult { let step_result = loop { @@ -122,20 +158,15 @@ impl EmulatorState { } pub fn save_ram_data(&mut self) { - if *self.should_save_to_local.borrow() && self.gameboy.get_cartridge().has_battery() { - let name = self.gameboy.get_cartridge().get_name(); - window() - .local_storage() - .insert(&name, &self.ram_str.borrow()) - .unwrap(); - *self.should_save_to_local.borrow_mut() = false; + if *self.should_save_to_local.borrow() && self.gameboy.has_battery() { + self.save.save(self.gameboy.get_cartridge_ram().to_vec()); } } pub fn save_timestamp_data(&mut self) { - if self.gameboy.get_cartridge().has_rtc() { - let name = format!("{}-timestamp", self.gameboy.get_cartridge().get_name()); - let (rtc_data, last_timestamp) = self.gameboy.get_cartridge().get_last_timestamp(); + if self.gameboy.has_rtc() { + let name = format!("{}-timestamp", self.gameboy.get_cartridge_name()); + let (rtc_data, last_timestamp) = self.gameboy.get_last_timestamp(); let mut rtc_bytes = rtc_data.to_bytes().to_vec(); let mut last_timestamp_bytes = u64::to_ne_bytes(last_timestamp).to_vec(); rtc_bytes.append(&mut last_timestamp_bytes); @@ -153,7 +184,7 @@ impl EmulatorState { pub fn set_ram_change_listener(&mut self) { let ram_str = self.ram_str.clone(); let should_save_to_local = self.should_save_to_local.clone(); - let has_battery = self.gameboy.get_cartridge().has_battery(); + let has_battery = self.gameboy.has_battery(); self.gameboy .set_ram_change_callback(Box::new(move |address, value| { if has_battery { @@ -269,16 +300,18 @@ pub fn start(rom: Vec, dom_ids: DOMInfo) -> Result<(), String> { }; let rtc = Box::new(WebRTC::new()); let mut gameboy = Gameboy::from_rom(rom, rtc)?; - load_ram_save_data(gameboy.get_cartridge_mut()); - load_timestamp_data(gameboy.get_cartridge_mut()); - let ram = gameboy.get_cartridge().get_ram().to_vec(); + let mut save = LocalStorageSaveFile::new(gameboy.get_cartridge_name().to_string()); + if let Some(ram) = save.load() { + gameboy.set_cartridge_ram(ram) + } + load_timestamp_data(&mut gameboy); + let ram = gameboy.get_cartridge_ram().to_vec(); let ram_str = Rc::new(RefCell::new( ram.iter().map(|byte| format!("{:02x}", byte)).collect(), )); let screen = Screen::new(); let mut emulator_state = EmulatorState { - //from opengl_web gameboy, screen, controller_receiver: receiver, @@ -287,6 +320,7 @@ pub fn start(rom: Vec, dom_ids: DOMInfo) -> Result<(), String> { js_ctx, busy: false, audio_underrun: None, + save: Box::new(save), }; emulator_state.set_ram_change_listener(); @@ -377,23 +411,8 @@ fn add_multi_controller_event_listener( sender.send(second_controller_event).unwrap(); }); } - -fn load_ram_save_data(cartridge: &mut Cartridge) { - if let Some(ram_str) = window().local_storage().get(cartridge.get_name()) { - let chars: Vec = ram_str.chars().collect(); - let bytes: Vec = chars - .chunks(2) - .map(|chunk| { - let byte: String = chunk.iter().collect(); - u8::from_str_radix(&byte, 16).unwrap() - }) - .collect(); - cartridge.set_ram(bytes); - } -} - -fn load_timestamp_data(cartridge: &mut Cartridge) { - let key = format!("{}-timestamp", cartridge.get_name()); +fn load_timestamp_data(cartridge: &mut Gameboy) { + let key = format!("{}-timestamp", cartridge.get_cartridge_name()); if let Some(timestamp_str) = window().local_storage().get(&key) { let chars: Vec = timestamp_str.chars().collect(); let bytes: Vec = chars