diff --git a/.vscode/launch.json b/.vscode/launch.json index 4cb3dfd..b6a81d5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,7 +14,7 @@ "cwd": "${workspaceFolder}", "environment": [{ "name": "RUST_BACKTRACE", - "value": "1" + "value": "full" }], "externalConsole": false, }, diff --git a/src/emulator.rs b/src/emulator/emulator.rs similarity index 93% rename from src/emulator.rs rename to src/emulator/emulator.rs index d84d43d..465c958 100644 --- a/src/emulator.rs +++ b/src/emulator/emulator.rs @@ -4,8 +4,7 @@ use ferretro::retro::constants::{InputIndex, JoypadButton, AnalogAxis, DeviceTyp use ferretro::retro::wrapped_types::{ControllerDescription2, InputDescriptor2, InputDeviceId, SubsystemInfo2, Variable2}; use ferretro::retro::wrapper::LibretroWrapper; -use ferretro_synced::sync::memory_rw::ReadWriteMemoryMap; -use ferretro_synced::sync::pokemon_rb::SyncedPokemonRedBlue; +use crate::sync::game::{SyncableGame, KnownGames, read_game_name, create_game}; use std::ffi::CStr; use std::io::Read; @@ -48,8 +47,8 @@ pub struct MyEmulator { gamepads: Vec, pressed_keys: Vec, - // memory - synced_pokemonrb: SyncedPokemonRedBlue, + pub memory_map: Option, + synced_game: Option>, } impl MyEmulator { @@ -116,8 +115,11 @@ impl MyEmulator { let pressed_keys = Vec::new(); + /* let memory_map = std::default::Default::default(); + let which_game = read_game_name("whatever"); let synced_pokemonrb = SyncedPokemonRedBlue::create(memory_map); + */ let emu = MyEmulator { retro, @@ -136,7 +138,8 @@ impl MyEmulator { gamepad_subsys, gamepads, pressed_keys, - synced_pokemonrb, + memory_map: None, + synced_game: None, }; let mut pin_emu = Box::pin(emu); retro::wrapper::set_handler(pin_emu.as_mut()); @@ -144,9 +147,12 @@ impl MyEmulator { pin_emu } + pub fn begin_sync(&mut self, synced_game: Box){ + self.synced_game = Some(synced_game); + } + pub fn run(&mut self) { self.audio_device.resume(); - self.synced_pokemonrb.start_comms(); let mut event_pump = self.sdl_context.event_pump().unwrap(); 'running: loop { let frame_begin = Instant::now(); @@ -164,6 +170,18 @@ impl MyEmulator { self.update_key_state(&event_pump.keyboard_state()); + match &mut self.synced_game { + Some(g) => { + match g.handle_inbound_messages() { + Ok(_) => (), + Err(e) => { + println!("Error while handling inbound messages: {:?}", e); + } + } + }, + None => () + } + // The rest of the game loop goes here... self.retro.run(); self.canvas.present(); @@ -174,9 +192,13 @@ impl MyEmulator { spf = 1.0 / 60.0; } - self.synced_pokemonrb.handle_inbound_msgs(); - - self.synced_pokemonrb.update_from_mem(); + match &mut self.synced_game { + Some(g) => { + g.update_state(); + g.send_state_messages(); + }, + None => () + } //println!("{}", self.synced_pokemonrb); Duration::from_secs_f64(spf) @@ -410,7 +432,7 @@ impl retro::wrapper::Handler for MyEmulator { } fn set_memory_maps(&mut self, memory_map: MemoryMap) -> bool { - self.synced_pokemonrb.libretro_set_memory_maps(memory_map); + self.memory_map = Some(memory_map); true } } diff --git a/src/sync/memory_rw.rs b/src/emulator/libretro_memory_map.rs similarity index 85% rename from src/sync/memory_rw.rs rename to src/emulator/libretro_memory_map.rs index b88a40a..6383740 100644 --- a/src/sync/memory_rw.rs +++ b/src/emulator/libretro_memory_map.rs @@ -1,13 +1,11 @@ use std::convert::TryFrom; -use std::default::Default; use ferretro::retro::ffi::{MemoryMap}; -#[derive(Default)] -pub struct ReadWriteMemoryMap { - regions: Vec, +pub struct LibRetroMemoryMap { + regions: Vec, } -impl ReadWriteMemoryMap { +impl LibRetroMemoryMap { pub fn get_slice_from_region(&self, offset: usize, length: usize, bank_switch: usize) -> Option<&'static mut [u8]> { match self.find_bank(offset, length, bank_switch) { Some(ptr) => Some(unsafe { std::slice::from_raw_parts_mut(ptr as *mut u8, length) }), @@ -36,8 +34,8 @@ impl ReadWriteMemoryMap { None } - pub fn libretro_set_memory_maps(&mut self, memory_map: MemoryMap) { - let mut regions: Vec = Vec::new(); + pub fn create(memory_map: &MemoryMap) -> Self { + let mut regions: Vec = Vec::new(); let num_descriptors = isize::try_from(memory_map.num_descriptors).unwrap(); for i in 0..num_descriptors { let region = unsafe { &*memory_map.descriptors.offset(i) }; @@ -48,18 +46,20 @@ impl ReadWriteMemoryMap { println!("truncating memory region {:x} from size {:x} to {:x} banks of size {:x}", region.start, region.len, region.len/length, length) } - regions.push(MemoryRegion { + regions.push(LibRetroMemoryRegion { start: region.start, end: region.start + length, pointer: region.ptr, }) } - self.regions = regions; + LibRetroMemoryMap { + regions: regions + } } } -struct MemoryRegion { +struct LibRetroMemoryRegion { start: usize, end: usize, pointer: *const std::ffi::c_void, diff --git a/src/emulator/mod.rs b/src/emulator/mod.rs new file mode 100644 index 0000000..552b575 --- /dev/null +++ b/src/emulator/mod.rs @@ -0,0 +1,2 @@ +pub mod emulator; +pub mod libretro_memory_map; \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 9301560..9e71475 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,4 +4,5 @@ extern crate num; #[macro_use] extern crate num_derive; -pub mod sync; \ No newline at end of file +pub mod sync; +pub mod emulator; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 0dc49d1..07ada1b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,18 +7,42 @@ extern crate serde_bytes; use structopt::StructOpt; use std::path::{Path, PathBuf}; -mod emulator; -use emulator::MyEmulator; +use ferretro_synced::emulator::emulator::MyEmulator; +use ferretro_synced::emulator::libretro_memory_map::LibRetroMemoryMap; +use ferretro_synced::sync::game::{SyncableGame, KnownGames, read_game_name, create_game}; +use ferretro_synced::sync::comms::CommunicationSettings; pub fn main() -> failure::Fallible<()> { let opt: Opt = Opt::from_args(); let mut emu = MyEmulator::new(&opt.core, &opt.system); emu.load_game(&opt.rom); + + // memory map should be ready after init & loading game. + let ffi_memory_map = emu.memory_map.as_ref().expect("Memory map was not set by emulator core, cannot continue."); + let memory_map = LibRetroMemoryMap::create(ffi_memory_map); + match (&opt.state) { Some(s) => emu.load_state(s), None => () } + let which_game = read_game_name("assume you can get it from emu".to_string()); + + let comms_settings = CommunicationSettings { + connection: "ws://127.0.0.1:8765".to_string() + }; + + match which_game { + Some(which_game) => { + let synced_game = create_game(which_game, comms_settings, &memory_map); + + emu.begin_sync(synced_game); + }, + None => { + println!("Game is unknown, not syncing.") + } + + } //let mut comms = Communication::new::(); emu.run(); diff --git a/src/sync/comms.rs b/src/sync/comms.rs index fdd2bd0..f87a39c 100644 --- a/src/sync/comms.rs +++ b/src/sync/comms.rs @@ -20,15 +20,19 @@ pub struct Communication { outbound_thread: JoinHandle>, } +pub struct CommunicationSettings { + pub connection: String +} + impl Communication where T: std::marker::Send, T: Serialize, T: DeserializeOwned, T: 'static { - pub fn new() -> Self { + pub fn new(settings: CommunicationSettings) -> Self { let (tx, from_main): (Sender, Receiver) = mpsc::channel(); let (to_main, rx): (Sender, Receiver) = mpsc::channel(); // transmitter for ping and close messages let tx_1 = tx.clone(); - let ws_client = ClientBuilder::new(CONNECTION) + let ws_client = ClientBuilder::new(settings.connection.as_str()) .unwrap() .add_protocol("rust-websocket") .connect_insecure() @@ -124,9 +128,4 @@ impl Communication where T: std::marker::Send, T: Serialize, T: Deserializ pub fn try_recv(&mut self) -> Result { self.rx.try_recv() } -} - -struct WebsocketClient { - thread_tx: Sender, - thread_rx: Receiver, } \ No newline at end of file diff --git a/src/sync/game.rs b/src/sync/game.rs new file mode 100644 index 0000000..279dfe0 --- /dev/null +++ b/src/sync/game.rs @@ -0,0 +1,30 @@ +use crate::sync::pokemon_rb::SyncedPokemonRedBlue; +use crate::sync::comms::CommunicationSettings; +use crate::emulator::libretro_memory_map::LibRetroMemoryMap; + +pub trait SyncableGame { + // Update internal state based on tracked memory and the emulator + fn update_state(&mut self); + + // Check for messages and handle them + fn handle_inbound_messages(&mut self) -> Result<(), failure::Error>; + + // Check for messages and handle them + fn send_state_messages(&mut self) -> Result<(), failure::Error>; +} + +pub enum KnownGames { + PokemonRedBlue, +} + +pub fn read_game_name(name: String) -> Option { + Some(KnownGames::PokemonRedBlue) // todo: actually read the name of the game +} + + +pub fn create_game(which_game: KnownGames, comms_settings: CommunicationSettings, memory_map: &LibRetroMemoryMap) -> Box { + match which_game { + KnownGames::PokemonRedBlue => Box::from(SyncedPokemonRedBlue::create(comms_settings, memory_map)) + } + +} \ No newline at end of file diff --git a/src/sync/memory_slice_handle.rs b/src/sync/memory_slice_handle.rs new file mode 100644 index 0000000..e9c5262 --- /dev/null +++ b/src/sync/memory_slice_handle.rs @@ -0,0 +1,55 @@ +use crate::emulator::libretro_memory_map::LibRetroMemoryMap; + +pub struct MemorySliceHandle<'a> { + pub offset: usize, + pub length: usize, + pub bank_switch: usize, + pub slice: Option<&'a mut [u8]>, + pub last_read_value: Option>, +} + +impl MemorySliceHandle<'_> { + pub fn create(offset: usize, length: usize, bank_switch: usize, memory_map: &LibRetroMemoryMap) -> Self { + let slice = memory_map.get_slice_from_region(offset, length, bank_switch); + + MemorySliceHandle { + offset: offset, + length: length, + bank_switch: bank_switch, + slice, + last_read_value: None + } + } + + pub fn get_copy_if_changed_since_last_read(&mut self) -> Option> { + match &mut self.slice { + None => None, + Some(data) => match &mut self.last_read_value { + None => { + // last read value isn't allocated yet + let last = data.to_vec(); + self.last_read_value = Some(last); + self.last_read_value.clone() + }, + Some(last) => { + let mut index: usize = 0; + let mut any_changes = false; + + for value in data.into_iter() { + if last[index] != *value { + last[index] = *value; + any_changes = true; + } + index += 1; + } + + if any_changes { + return Some(last.clone()); + } + None + } + } + } + + } +} \ No newline at end of file diff --git a/src/sync/mod.rs b/src/sync/mod.rs index 267b727..0a57e5f 100644 --- a/src/sync/mod.rs +++ b/src/sync/mod.rs @@ -1,3 +1,4 @@ pub mod pokemon_rb; -pub mod memory_rw; -pub mod comms; \ No newline at end of file +pub mod comms; +pub mod memory_slice_handle; +pub mod game; \ No newline at end of file diff --git a/src/sync/pokemon_rb.rs b/src/sync/pokemon_rb.rs index f6f8009..0296216 100644 --- a/src/sync/pokemon_rb.rs +++ b/src/sync/pokemon_rb.rs @@ -1,181 +1,77 @@ use std::default::Default; use std::fmt::Display; -use crate::sync::memory_rw::ReadWriteMemoryMap; +use crate::emulator::libretro_memory_map::LibRetroMemoryMap; use serde::{Serialize, Deserialize}; use ferretro::retro::wrapper::LibretroWrapper; // want LibretroWrapper.api : LibretroApi.get_memory() use crate::num::ToPrimitive; use crate::sync::comms::Communication; +use crate::sync::comms::CommunicationSettings; +use crate::sync::memory_slice_handle::MemorySliceHandle; +use crate::sync::game::SyncableGame; pub struct SyncedPokemonRedBlue { - raw: Raw<'static>, + memory_handles: PokemonRedBlueMemoryHandles<'static>, battle_context: BattleContext, - comms: Option> + comms: Communication } - #[derive(Serialize, Deserialize)] -pub struct Message { +pub struct PokemonRedBlueMessage { active_pkmn: Option> } -impl From<&mut Raw<'_>> for Message { - fn from(src: &mut Raw) -> Self { - Message { - active_pkmn: src.active_pokemon_raw.get_copy_if_changed_since_last_read() +impl From<&mut PokemonRedBlueMemoryHandles<'_>> for PokemonRedBlueMessage { + fn from(src: &mut PokemonRedBlueMemoryHandles) -> Self { + PokemonRedBlueMessage { + active_pkmn: src.active_pokemon.get_copy_if_changed_since_last_read() } - } } -struct Raw<'a> { - active_pokemon_raw: EmulatorMemory<'a>, - player_and_party: EmulatorMemory<'a>, - pokemon_out: EmulatorMemory<'a>, - pub memory_map: ReadWriteMemoryMap, -} - -struct EmulatorMemory<'a> { - offset: usize, - length: usize, - bank_switch: usize, - pub slice: Option<&'a mut [u8]>, - last_read_value: Option>, -} - -impl EmulatorMemory<'_> { - pub fn update_slice(&mut self, map: &ReadWriteMemoryMap) { - self.slice = map.get_slice_from_region(self.offset, self.length, self.bank_switch); - } - - pub fn get_copy_if_changed_since_last_read(&mut self) -> Option> { - match &mut self.slice { - None => None, - Some(data) => match &mut self.last_read_value { - None => { - // last read value isn't allocated yet - let mut last = vec![0; data.len()]; - last.copy_from_slice(data); - self.last_read_value = Some(last); - self.last_read_value.clone() - }, - Some(last) => { - let mut index: usize = 0; - let mut any_changes = false; - - for value in data.into_iter() { - if last[index] != *value { - last[index] = *value; - any_changes = true; - } - index += 1; - } - - if any_changes { - return Some(last.clone()); - } - None - } - } - } - - } +struct PokemonRedBlueMemoryHandles<'a> { + active_pokemon: MemorySliceHandle<'a>, + player_and_party: MemorySliceHandle<'a>, + pokemon_out: MemorySliceHandle<'a>, } struct BattleContext { active_pokemon: Pokemon, } - -impl SyncedPokemonRedBlue { - pub fn update_from_mem(&mut self) { +impl SyncableGame for SyncedPokemonRedBlue { + fn update_state(&mut self) { let battle_context = BattleContext { - active_pokemon: match &self.raw.active_pokemon_raw.slice { + active_pokemon: match &self.memory_handles.active_pokemon.slice { Some(d) => num::FromPrimitive::from_u8(d[0xB]).unwrap_or(Pokemon::Unknown), None => Pokemon::Unknown } }; - // test to see that we can indeed write back to the slice - // match self.raw.active_pokemon_raw.get_slice(mem){ - // Some(s) => { - // s[0xD] = 69 - // }, - // _ => () - // } - self.battle_context = battle_context; + } - let message: Message = (&mut self.raw).into(); - - match &mut self.comms { - Some(comms) => { - comms.send(message); - }, - None => () + fn send_state_messages(&mut self) -> Result<(), failure::Error>{ + let message: PokemonRedBlueMessage = (&mut self.memory_handles).into(); + if !message.active_pkmn.is_none() { + self.comms.send(message) + } + else { + Ok(()) } } - pub fn handle_inbound_msgs(&mut self) -> Result<(), failure::Error>{ - let in_msg = self.comms.as_mut().ok_or(failure::err_msg("comms doesn't exist, can't handle inbound msgs"))?.try_recv()?; + fn handle_inbound_messages(&mut self) -> Result<(), failure::Error>{ + let in_msg = self.comms.try_recv()?; self.handle_message(in_msg) } - - fn handle_message(&mut self, msg: Message) -> Result<(), failure::Error> { - match msg.active_pkmn { - Some(src) => { - println!("message contains a new active pokemon. data: {:?}", &src); - let target_slice = self.raw.active_pokemon_raw.slice.as_mut().ok_or(failure::err_msg("can't write message back to None memory slice"))?; - if src.len() != target_slice.len() { - return Err(failure::err_msg("message size and slice size differ.")); - } - - let mut index: usize = 0; - for value in src.into_iter(){ - target_slice[index] = value; - index += 1; - } - - Ok(()) - - } - None => Ok(()) - } - } - - - // pub fn get_message<'a>(&self) -> Message { - // Message { - // active_pkmn: Box::from(&self.raw.active_pokemon_raw.clone()) - // } - - // } } -impl Raw<'_> { - pub fn create(map: ReadWriteMemoryMap) -> Self { - Raw { - active_pokemon_raw: EmulatorMemory { - offset: 0xd009, - length: 0x27, - bank_switch: 0, - slice: None, - last_read_value: None, - }, - player_and_party: EmulatorMemory { - offset: 0xd158, - length: 0x19e, - bank_switch: 0, - slice: None, - last_read_value: None, - }, - pokemon_out: EmulatorMemory { - offset: 0xcc2f, - length: 0x1, - bank_switch: 0, - slice: None, - last_read_value: None, - }, - memory_map: map +impl PokemonRedBlueMemoryHandles<'_> { + pub fn create(memory_map: &LibRetroMemoryMap) -> Self { + PokemonRedBlueMemoryHandles { + active_pokemon: MemorySliceHandle::create(0xd009, 0x27, 0, memory_map), + player_and_party: MemorySliceHandle::create(0xd158, 0x19e, 0, memory_map), + pokemon_out: MemorySliceHandle::create(0xcc2f, 0x1, 0, memory_map), } } } @@ -195,31 +91,34 @@ impl Display for SyncedPokemonRedBlue { } impl SyncedPokemonRedBlue { - pub fn create(map: ReadWriteMemoryMap) -> Self { + pub fn create(comms_settings: CommunicationSettings, memory_map: &LibRetroMemoryMap) -> Self { SyncedPokemonRedBlue { - raw: Raw::create(map), + memory_handles: PokemonRedBlueMemoryHandles::create(memory_map), battle_context: BattleContext::default(), - comms: None, + comms: Communication::new(comms_settings), } } - pub fn libretro_set_memory_maps(&mut self, libretro_memory_map: ferretro::retro::ffi::MemoryMap) { - self.raw.libretro_set_memory_maps(libretro_memory_map) - } + fn handle_message(&mut self, msg: PokemonRedBlueMessage) -> Result<(), failure::Error> { + match msg.active_pkmn { + Some(src) => { + println!("message contains a new active pokemon. data: {:?}", &src); + let target_slice = self.memory_handles.active_pokemon.slice.as_mut().ok_or(failure::err_msg("can't write message back to None memory slice"))?; + if src.len() != target_slice.len() { + return Err(failure::err_msg("message size and slice size differ.")); + } - pub fn start_comms(&mut self) { - self.comms = Some(Communication::new()); - } -} + let mut index: usize = 0; + for value in src.into_iter(){ + target_slice[index] = value; + index += 1; + } -impl Raw<'_> { - pub fn libretro_set_memory_maps(&mut self, libretro_memory_map: ferretro::retro::ffi::MemoryMap) { - self.memory_map.libretro_set_memory_maps(libretro_memory_map); + Ok(()) - // update our slices - self.active_pokemon_raw.update_slice(&self.memory_map); - self.player_and_party.update_slice(&self.memory_map); - self.pokemon_out.update_slice(&self.memory_map); + } + None => Ok(()) + } } } @@ -384,8 +283,8 @@ mod tests { use super::*; use serde_json; - fn build_message(em: &EmulatorMemory) -> Message { - Message { + fn build_message(em: &MemorySliceHandle) -> PokemonRedBlueMessage { + PokemonRedBlueMessage { active_pkmn: match &em.slice { Some(m) => Some(m.to_vec()), None => None @@ -397,11 +296,12 @@ mod tests { fn serde_mem() -> Result<(), String> { let mut data: [u8; 5] = [1, 2, 3, 4, 5]; let b: &mut [u8] = &mut data; - let slice = EmulatorMemory { + let slice = MemorySliceHandle { offset: 0, length: 0, bank_switch: 0, slice: Some(b), + last_read_value: None }; let message = build_message(&slice); @@ -410,7 +310,7 @@ mod tests { println!("serialized data: {}", serialized); - let deserialized: Message = serde_json::from_str(&serialized).unwrap(); + let deserialized: PokemonRedBlueMessage = serde_json::from_str(&serialized).unwrap(); assert_eq!(deserialized.active_pkmn.unwrap()[0], data[0]); Ok(())