Compare commits

...

11 Commits

Author SHA1 Message Date
Vivian Lim 419e97af23 add missed cargo.toml change 2020-03-15 19:33:18 -07:00
Vivian Lim 9ba078111c change lots of things to return result, write some UT 2020-03-15 19:33:06 -07:00
Vivian Lim 84d87e21ff try to bring over more of the s-o-s sonic2 code 2020-03-15 14:27:43 -07:00
Vivian Lim 54aeb2c0f3 sonic pos syncing appears to be working with tracked memory slice & sparse vector 2020-03-15 02:52:46 -07:00
Vivian Lim 26898d4ec2 add tracked memory slice (but don't use it yet) 2020-03-15 02:38:07 -07:00
Vivian Lim 295956631a misc lifetime cleanup, things working again 2020-03-15 01:39:24 -07:00
Vivian Lim d83bbb606a bunch of work on sonic2, make MemorySliceHandle panic if it fails 2020-03-14 19:56:54 -07:00
Vivian Lim 6d6ad9ffee sparse vector 2020-03-14 19:56:21 -07:00
Vivian Lim b257dc5071 ability to get a struct based off of player data 2020-03-14 17:16:07 -07:00
Vivian Lim cd97095b03 a change i made before that works 2020-03-14 16:14:01 -07:00
Vivian Lim 285eb6052e something a little nicer (but only slightly)
return a struct containing object info...
2020-03-11 01:52:52 -07:00
13 changed files with 710 additions and 170 deletions

1
Cargo.lock generated
View File

@ -220,6 +220,7 @@ dependencies = [
"crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"ferretro 0.1.0",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libloading 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"num 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -24,4 +24,5 @@ websocket = "0.24.0"
ascii = "1.0.0"
byte-slice-cast = "0.3.5"
[dev-dependencies]
[dev-dependencies]
lazy_static = "1.4.0"

View File

@ -2,16 +2,17 @@ use std::convert::TryFrom;
use ferretro::retro::ffi::{MemoryMap};
use ferretro::retro::loading::LibretroApi;
use crate::emulator::emulator::MyEmulator;
use failure::{Error, format_err};
pub struct LibRetroMemoryMap {
regions: Vec<LibRetroMemoryRegion>,
}
impl LibRetroMemoryMap {
pub fn get_slice_from_region(&self, offset: usize, length: usize, bank_switch: usize) -> Option<&'static mut [u8]> {
pub fn get_slice_from_region(&self, offset: usize, length: usize, bank_switch: usize) -> Result<&'static mut [u8], Error> {
match self.find_bank(offset, length, bank_switch) {
Some(ptr) => Some(unsafe { std::slice::from_raw_parts_mut(ptr as *mut u8, length) }),
None => None
Some(ptr) => Ok(unsafe { std::slice::from_raw_parts_mut(ptr as *mut u8, length) }),
None => Err(format_err!("Couldn't find memory bank for offset:{}, length:{}, bank_switch:{}", offset, length, bank_switch))
}
}

View File

@ -46,7 +46,7 @@ fn attempt_to_start_sync(emu: &mut MyEmulator, comms_settings: CommunicationSett
match &emu.game_info {
Some(game_info) => match read_game_name(&game_info.game_name) {
Some(which_game) => {
let synced_game = create_game(which_game, comms_settings, &emu.memory_map);
let synced_game = create_game(which_game, comms_settings, &emu.memory_map)?;
emu.begin_sync(synced_game);
Ok(())
},

View File

@ -45,7 +45,7 @@ impl<T> Communication<T> where T: std::marker::Send, T: Serialize, T: Deserializ
let mut ws_sender = ws_sender;
let mut flush = || -> Result<(), failure::Error> {
let msg = from_main.recv()?;
println!("sending a message {:?}", &msg);
//println!("sending a message {:?}", &msg);
ws_sender.send_message(&msg)?;
Ok(())
};
@ -71,7 +71,7 @@ impl<T> Communication<T> where T: std::marker::Send, T: Serialize, T: Deserializ
match msg {
OwnedMessage::Text(text) => match serde_json::from_str(&text) {
Ok(m) => {
println!("receiving a message {}", &text);
//println!("receiving a message {}", &text);
to_main.send(m); // todo print an error
Ok(())
}

View File

@ -2,6 +2,7 @@ use crate::sync::pokemon_rb::SyncedPokemonRedBlue;
use crate::sync::sonic2::SyncedSonic2;
use crate::sync::comms::CommunicationSettings;
use crate::emulator::libretro_memory_map::LibRetroMemoryMap;
use failure::{Error, format_err};
pub trait SyncableGame {
// Update internal state based on tracked memory and the emulator
@ -29,10 +30,10 @@ pub fn read_game_name(name: &str) -> Option<KnownGames> {
}
pub fn create_game(which_game: KnownGames, comms_settings: CommunicationSettings, memory_map: &LibRetroMemoryMap) -> Box<dyn SyncableGame> {
match which_game {
KnownGames::PokemonRedBlue => Box::from(SyncedPokemonRedBlue::create(comms_settings, memory_map)),
KnownGames::Sonic2 => Box::from(SyncedSonic2::create(comms_settings, memory_map)),
}
pub fn create_game(which_game: KnownGames, comms_settings: CommunicationSettings, memory_map: &LibRetroMemoryMap) -> Result<Box<dyn SyncableGame>, Error> {
Ok(match which_game {
KnownGames::PokemonRedBlue => Box::from(SyncedPokemonRedBlue::create(comms_settings, memory_map)?),
KnownGames::Sonic2 => Box::from(SyncedSonic2::create(comms_settings, memory_map)?),
})
}

View File

@ -1,11 +1,13 @@
use std::convert::{TryFrom, TryInto};
use crate::emulator::libretro_memory_map::LibRetroMemoryMap;
use failure::format_err;
use failure::{format_err, Error};
use byte_slice_cast::*;
use std::result::Result;
use std::ops::{Index, IndexMut, Range};
/*
pub struct MemoryBackedNumbers<'a, T: 'a + FromByteSlice> {
handle: MemorySliceHandle<'a>,
handle: MemorySliceHandle,
value_type: std::marker::PhantomData<T>,
}
@ -18,11 +20,11 @@ impl<'a, T: 'a + FromByteSlice> MemoryBackedNumbers<'a, T> {
}
pub fn get(&mut self) -> Result<&mut [T], failure::Error> {
let byte_slices = self.handle.slice.as_mut().unwrap();
let byte_slices = self.handle.slice.as_mut();
let slices = byte_slices.as_mut_slice_of::<T>()?;
Ok(slices)
}
*/
/*
pub fn set(&self, value: T) -> Result<(), failure::Error> {
let new_bytes = value.try_into::<&[u8]>()?;
@ -34,100 +36,305 @@ impl<'a, T: 'a + FromByteSlice> MemoryBackedNumbers<'a, T> {
}
}
*/
}
//}
pub struct MemorySliceHandle<'a> {
pub struct MemorySliceHandle {
pub offset: usize,
pub length: usize,
pub bank_switch: usize,
pub slice: Option<&'a mut [u8]>,
pub slice: &'static mut [u8],
pub last_read_value: Option<Vec<u8>>,
}
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);
#[derive(Debug)]
pub enum MemorySpan {
ByAddress(Range<usize>),
ByIndex(Range<usize>),
}
MemorySliceHandle {
impl Index<MemorySpan> for MemorySliceHandle {
type Output = [u8];
fn index(&self, range: MemorySpan) -> &'static Self::Output {
self.subslice(range).unwrap()
}
}
impl IndexMut<MemorySpan> for MemorySliceHandle {
fn index_mut(&mut self, range: MemorySpan) -> &'static mut Self::Output {
self.subslice_mut(range).unwrap()
}
}
impl Clone for MemorySpan {
fn clone(&self) -> Self {
match self {
MemorySpan::ByAddress(span) => MemorySpan::ByAddress(span.start..span.end),
MemorySpan::ByIndex(span) => MemorySpan::ByIndex(span.start..span.end),
}
}
}
impl MemorySliceHandle {
pub fn create(offset: usize, length: usize, bank_switch: usize, memory_map: &LibRetroMemoryMap) -> Result<Self, Error> {
let slice = memory_map.get_slice_from_region(offset, length, bank_switch)?;
Ok(MemorySliceHandle {
offset: offset,
length: length,
bank_switch: bank_switch,
slice,
last_read_value: None
}
})
}
pub fn get_at_offset/*<T: AsMutSliceOf + FromByteSlice>*/(&mut self, offset: usize) -> &u16 {
let byte_slices = self.slice.as_mut().unwrap();
let slices = byte_slices.as_mut_slice_of::<u16>().unwrap();
&slices[0]
}
pub fn write_to_slice(&mut self, data: Vec<u8>) -> Result<(), failure::Error> {
match &mut self.slice {
Some(slice) => {
if data.len() != slice.len() {
return Err(failure::err_msg("message size and slice size differ."));
}
// if last_read_value is set, update it as the same time that we write back to the emulator.
match &mut self.last_read_value {
None => {
let mut index: usize = 0;
for value in data.into_iter(){
slice[index] = value;
index += 1;
}
},
Some(last_read_value) => {
let mut index: usize = 0;
for value in data.into_iter(){
slice[index] = value;
last_read_value[index] = value;
index += 1;
}
}
}
Ok(())
},
None => {
println!("slice doesn't exist to write to.");
Ok(())
pub fn subslice(&self, range: MemorySpan) -> Result<&'static [u8], failure::Error> {
match self.map_offsets(range)? {
MemorySpan::ByAddress(_) => Err(format_err!("Mapping offsets failed, cannot subslice without indices.")),
MemorySpan::ByIndex(indices) => {
let len= indices.end - indices.start;
Ok(unsafe { std::slice::from_raw_parts(self.slice.as_ptr().offset(indices.start as isize), len) })
}
}
}
pub fn subslice_mut(&mut self, range: MemorySpan) -> Result<&'static mut [u8], failure::Error> {
match self.map_offsets(range)? {
MemorySpan::ByAddress(_) => Err(format_err!("Mapping offsets failed, cannot subslice without indices.")),
MemorySpan::ByIndex(indices) => {
let len= indices.end - indices.start;
Ok(unsafe { std::slice::from_raw_parts_mut(self.slice.as_mut_ptr().offset(indices.start as isize), len) })
}
}
}
pub fn map_offsets(&self, range: MemorySpan) -> Result<MemorySpan, Error> {
match range {
MemorySpan::ByAddress(offsets) => {
if offsets.start < self.offset // inclusive start bound is before first index
|| offsets.end <= self.offset // exclusive end bound is before second index
|| offsets.start >= self.offset + self.length // inclusive start bound is beyond final index
|| offsets.end > self.offset + self.length { // exclusive end bound is beyond final index + 1
return Err(format_err!("Cannot map offsets [{}, {}), they are outside of memory handle at offset {} with length {}.", offsets.start, offsets.end, self.offset, self.length));
}
let start_index = offsets.start - self.offset;
let end_index = offsets.end - self.offset;
Ok(MemorySpan::ByIndex(start_index..end_index))
},
MemorySpan::ByIndex(_) => Ok(range) // no-op
}
}
pub fn write_to_slice(&mut self, data: Vec<u8>) -> Result<(), Error> {
self.write_to_slice_with_filter(data, |_| true)
}
pub fn write_to_slice_with_filter(&mut self, data: Vec<u8>, index_filter: fn(usize) -> bool) -> Result<(), failure::Error> {
if data.len() != self.slice.len() {
return Err(failure::err_msg("message size and slice size differ."));
}
// if last_read_value is set, update it as the same time that we write back to the emulator.
match &mut self.last_read_value {
None => {
let mut index: usize = 0;
for value in data.into_iter(){
if index_filter(index){
self.slice[index] = value;
}
index += 1;
}
},
Some(last_read_value) => {
let mut index: usize = 0;
for value in data.into_iter(){
if index_filter(index) {
self.slice[index] = value;
last_read_value[index] = value;
}
index += 1;
}
}
}
Ok(())
}
pub fn get_copy_if_changed(&mut self) -> Option<Vec<u8>> {
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;
match &mut self.last_read_value {
None => {
// last read value isn't allocated yet
let last = self.slice.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;
for value in self.slice.into_iter() {
if last[index] != *value {
last[index] = *value;
any_changes = true;
}
if any_changes {
return Some(last.clone());
}
None
index += 1;
}
if any_changes {
return Some(last.clone());
}
None
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use lazy_static::lazy_static;
use std::sync::Mutex;
fn create_memory_handle(start_address: usize, length: usize) -> MemorySliceHandle {
lazy_static! {
static ref MEMORY: Mutex<[u8; 0x10000]> = {
let mut mem = [0; 0x10000];
for i in 0..mem.len() {
mem[i] = expected_value_at_index(i);
}
Mutex::new(mem)
};
}
// use transmute to assert that this memory can be borrowed for static lifetime
let memory = unsafe { std::mem::transmute::<&mut [u8], &'static mut [u8]>(&mut *MEMORY.lock().unwrap()) };
let handle = MemorySliceHandle {
offset: start_address,
length: length,
bank_switch: 0,
slice: memory,
last_read_value: None,
};
handle
}
fn expected_value_at_index(index: usize) -> u8 {
(index % 256) as u8
}
fn expect_failure<T>(result: Result<T, Error>, error_if_succeeded: Error) -> Result<(), Error> {
match result{
Err(e) => {
//println!("Expected failure: {:?}", e);
Ok(())
}
Ok(_) => Err(error_if_succeeded)
}
}
#[test]
fn address_in_entire_memory() -> Result<(), failure::Error>{
let mut handle = create_memory_handle(0x0000, 0x10000);
// look up individual bytes
let byte_addresses_to_look_up = vec![0x0000, 0xffff, 0xb008, 0xb03a, 0xb03b, 0xb03c, 0xf704, 0xf705];
for address in byte_addresses_to_look_up {
let expected_value = expected_value_at_index(address);
let span_by_addr = MemorySpan::ByAddress(address..address + 1);
let slice = handle.subslice(span_by_addr.clone())?;
assert_eq!(1, slice.len(), "slice length {} is not one byte", slice.len());
assert_eq!(expected_value, slice[0], "looking up address {} in slice, expecting {}, actual {}, length {}", address, expected_value, slice[0], slice.len());
let mut_slice = handle.subslice_mut(span_by_addr)?;
assert_eq!(1, mut_slice.len(), "mut_slice length {} is not one byte", mut_slice.len());
assert_eq!(expected_value, mut_slice[0], "looking up address {} in mut_slice, expecting {}, actual {}, length {}", address, expected_value, mut_slice[0], mut_slice.len());
}
// get ranges at the start and end
let range_addresses_to_look_up = vec![
(0x0000, vec![0,1,2,3,4]),
(0xfffc, vec![252, 253, 254, 255])];
for (range_start_address, expected_values) in range_addresses_to_look_up {
let range = MemorySpan::ByAddress(range_start_address..range_start_address + expected_values.len());
let slice = handle.subslice(range.clone())?;
assert_eq!(expected_values, slice);
let mut_slice = handle.subslice_mut(range)?;
assert_eq!(expected_values, mut_slice);
}
Ok(())
}
#[test]
fn address_oob_span_in_entire_memory() -> Result<(), Error> {
let handle = create_memory_handle(0x0000, 0x10000);
expect_failure(handle.subslice(MemorySpan::ByAddress(0xfffd..0x10001)),
format_err!("Trying to get a subslice that extends past the end of memory should fail"))?;
Ok(())
}
#[test]
fn address_in_partial_memory() -> Result<(), Error> {
// create a handle which only covers a small amount of memory
let mut handle = create_memory_handle(0xb000, 0xb040);
// look up individual bytes
let valid_byte_addresses_to_look_up = vec![0xb000, 0xb040, 0xb008, 0xb009];
for address in valid_byte_addresses_to_look_up {
let expected_value = expected_value_at_index(address);
let span_by_addr = MemorySpan::ByAddress(address..address + 1);
let slice = handle.subslice(span_by_addr.clone())?;
assert_eq!(1, slice.len(), "slice length {} is not one byte", slice.len());
assert_eq!(expected_value, slice[0], "looking up address {} in slice, expecting {}, actual {}, length {}", address, expected_value, slice[0], slice.len());
let mut_slice = handle.subslice_mut(span_by_addr)?;
assert_eq!(1, mut_slice.len(), "mut_slice length {} is not one byte", mut_slice.len());
assert_eq!(expected_value, mut_slice[0], "looking up address {} in mut_slice, expecting {}, actual {}, length {}", address, expected_value, mut_slice[0], mut_slice.len());
}
// get ranges at the start and end
let range_addresses_to_look_up = vec![
(0xb000, vec![0,1,2,3,4]),
(0xb024, vec![expected_value_at_index(0xb024), expected_value_at_index(0xb025), expected_value_at_index(0xb026)]),
(0xb03e, vec![expected_value_at_index(0xb03e), expected_value_at_index(0xb03f), expected_value_at_index(0xb040)])];
for (range_start_address, expected_values) in range_addresses_to_look_up {
let range = MemorySpan::ByAddress(range_start_address..range_start_address + expected_values.len());
let slice = handle.subslice(range.clone())?;
assert_eq!(expected_values, slice);
let mut_slice = handle.subslice_mut(range)?;
assert_eq!(expected_values, mut_slice);
}
Ok(())
}
#[test]
fn address_oob_in_partial_memory() -> Result<(), Error> {
// create a handle which only covers a small amount of memory
let mut handle = create_memory_handle(0xb000, 0x40);
expect_failure(handle.subslice(MemorySpan::ByAddress(0xfffd..0x10001)),
format_err!("Trying to get a subslice that extends past the end of memory should fail"))?;
expect_failure(handle.subslice(MemorySpan::ByAddress(0xb03c..0xb041)),
format_err!("Trying to get a subslice that extends past the end of the slice should fail"))?;
expect_failure(handle.subslice(MemorySpan::ByAddress(0xafff..0xb001)),
format_err!("Trying to get a subslice that extends past the beginning of the slice should fail"))?;
expect_failure(handle.subslice(MemorySpan::ByAddress(0xaffb..0xafff)),
format_err!("Trying to get a subslice that is completely before the slice should fail"))?;
expect_failure(handle.subslice(MemorySpan::ByAddress(0xb041..0xb045)),
format_err!("Trying to get a subslice that is completely after the slice should fail"))?;
Ok(())
}
}

20
src/sync/memory_traits.rs Normal file
View File

@ -0,0 +1,20 @@
pub trait CopyFromMemory<T> {
fn copy_from_memory(&self) -> T;
fn write_back(&mut self, source: T);
}
impl CopyFromMemory<[u8; 2]> for &'static mut [u8] {
fn copy_from_memory(&self) -> [u8; 2] {
let mut result = [0, 0];
for i in 0..result.len() {
result[i] = self[i];
}
result
}
fn write_back(&mut self, source: [u8; 2]) {
for i in 0..source.len() {
self[i] = source[i];
}
}
}

View File

@ -2,4 +2,7 @@ pub mod pokemon_rb;
pub mod sonic2;
pub mod comms;
pub mod memory_slice_handle;
pub mod game;
pub mod game;
pub mod sparse_vector;
pub mod tracked_memory_slice;
pub mod memory_traits;

View File

@ -8,9 +8,10 @@ use crate::sync::comms::Communication;
use crate::sync::comms::CommunicationSettings;
use crate::sync::memory_slice_handle::MemorySliceHandle;
use crate::sync::game::SyncableGame;
use failure::{Error, format_err};
pub struct SyncedPokemonRedBlue {
memory_handles: PokemonRedBlueMemoryHandles<'static>,
memory_handles: PokemonRedBlueMemoryHandles,
battle_context: BattleContext,
comms: Communication<PokemonRedBlueMessage>
}
@ -20,7 +21,7 @@ pub struct PokemonRedBlueMessage {
active_pkmn: Option<Vec<u8>>
}
impl From<&mut PokemonRedBlueMemoryHandles<'_>> for PokemonRedBlueMessage {
impl From<&mut PokemonRedBlueMemoryHandles> for PokemonRedBlueMessage {
fn from(src: &mut PokemonRedBlueMemoryHandles) -> Self {
PokemonRedBlueMessage {
active_pkmn: src.active_pokemon.get_copy_if_changed()
@ -28,10 +29,10 @@ impl From<&mut PokemonRedBlueMemoryHandles<'_>> for PokemonRedBlueMessage {
}
}
struct PokemonRedBlueMemoryHandles<'a> {
active_pokemon: MemorySliceHandle<'a>,
player_and_party: MemorySliceHandle<'a>,
pokemon_out: MemorySliceHandle<'a>,
struct PokemonRedBlueMemoryHandles {
active_pokemon: MemorySliceHandle,
player_and_party: MemorySliceHandle,
pokemon_out: MemorySliceHandle,
}
struct BattleContext {
@ -41,10 +42,7 @@ struct BattleContext {
impl SyncableGame for SyncedPokemonRedBlue {
fn update_state(&mut self) {
let battle_context = BattleContext {
active_pokemon: match &self.memory_handles.active_pokemon.slice {
Some(d) => num::FromPrimitive::from_u8(d[0xB]).unwrap_or(Pokemon::Unknown),
None => Pokemon::Unknown
}
active_pokemon: num::FromPrimitive::from_u8(self.memory_handles.active_pokemon.slice[0xB]).unwrap_or(Pokemon::Unknown),
};
self.battle_context = battle_context;
@ -68,13 +66,13 @@ impl SyncableGame for SyncedPokemonRedBlue {
}
}
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),
}
impl PokemonRedBlueMemoryHandles {
pub fn create(memory_map: &LibRetroMemoryMap) -> Result<Self, Error> {
Ok(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)?,
})
}
}
@ -93,15 +91,15 @@ impl Display for SyncedPokemonRedBlue {
}
impl SyncedPokemonRedBlue {
pub fn create(comms_settings: CommunicationSettings, memory_map: &LibRetroMemoryMap) -> Self {
SyncedPokemonRedBlue {
memory_handles: PokemonRedBlueMemoryHandles::create(memory_map),
pub fn create(comms_settings: CommunicationSettings, memory_map: &LibRetroMemoryMap) -> Result<Self, Error> {
Ok(SyncedPokemonRedBlue {
memory_handles: PokemonRedBlueMemoryHandles::create(memory_map)?,
battle_context: BattleContext::default(),
comms: Communication::new(comms_settings),
}
})
}
fn handle_message(&mut self, msg: PokemonRedBlueMessage) -> Result<(), failure::Error> {
fn handle_message(&mut self, msg: PokemonRedBlueMessage) -> Result<(), Error> {
match msg.active_pkmn {
Some(src) => {
println!("message contains a new active pokemon. data: {:?}", &src);
@ -275,13 +273,11 @@ mod tests {
fn build_message(em: &MemorySliceHandle) -> PokemonRedBlueMessage {
PokemonRedBlueMessage {
active_pkmn: match &em.slice {
Some(m) => Some(m.to_vec()),
None => None
}
active_pkmn: Some(em.slice.to_vec())
}
}
/*
#[test]
fn serde_mem() -> Result<(), String> {
let mut data: [u8; 5] = [1, 2, 3, 4, 5];
@ -290,7 +286,7 @@ mod tests {
offset: 0,
length: 0,
bank_switch: 0,
slice: Some(b),
slice: b,
last_read_value: None
};
@ -305,5 +301,6 @@ mod tests {
assert_eq!(deserialized.active_pkmn.unwrap()[0], data[0]);
Ok(())
}
*/
}

View File

@ -1,4 +1,6 @@
use failure::{format_err, Error};
use std::default::Default;
use std::convert::{TryFrom, TryInto};
use std::fmt::Display;
use crate::emulator::libretro_memory_map::LibRetroMemoryMap;
use serde::{Serialize, Deserialize};
@ -6,65 +8,155 @@ use ferretro::retro::wrapper::LibretroWrapper; // want LibretroWrapper.api : Lib
use crate::num::ToPrimitive;
use crate::sync::comms::Communication;
use crate::sync::comms::CommunicationSettings;
use crate::sync::memory_slice_handle::{MemoryBackedNumbers, MemorySliceHandle};
use crate::sync::memory_slice_handle::{MemorySliceHandle, MemorySpan};
use crate::sync::tracked_memory_slice::TrackedMemorySlice;
use crate::sync::game::SyncableGame;
use crate::sync::sparse_vector::SparseVector;
use crate::sync::memory_traits::CopyFromMemory;
use byte_slice_cast::*;
use std::result::Result;
pub struct SyncedSonic2 {
memory_handles: Sonic2MemoryHandles<'static>,
memory_handles: Sonic2MemoryHandles,
comms: Communication<Sonic2Message>
}
#[derive(Serialize, Deserialize)]
pub struct Sonic2Message {
sonic_pos: Option<Vec<u8>>
sonic_pos: SparseVector<u8>,
ctrl1_held_press: [u8; 2],
ctrl1_held_press_logical: [u8; 2],
}
impl From<&mut Sonic2MemoryHandles<'_>> for Sonic2Message {
fn from(src: &mut Sonic2MemoryHandles) -> Self {
Sonic2Message {
sonic_pos: src.sonic_pos.get_copy_if_changed()
}
impl TryFrom<&mut Sonic2MemoryHandles> for Sonic2Message {
type Error = failure::Error;
fn try_from(src: &mut Sonic2MemoryHandles) -> Result<Self, Error> {
let global_game_state = src.global_game_state.get()?;
Ok(Sonic2Message {
sonic_pos: src.sonic_data.get_changes().expect("error getting changes"),
ctrl1_held_press: global_game_state.ctrl1_held_press.copy_from_memory(),
ctrl1_held_press_logical: global_game_state.ctrl1_held_press_logical.copy_from_memory(),
})
}
}
struct Sonic2MemoryHandles<'a> {
sonic_pos: MemorySliceHandle<'a>,
tails_pos: MemorySliceHandle<'a>,
sonic_pos2: MemoryBackedNumbers<'a, u16>,
struct Sonic2MemoryHandles {
sonic_data: TrackedMemorySlice,
tails_data: MemorySliceHandle,
sonic_obj: PlayerCharacterHandle,
global_game_state: GlobalGameHandle,
}
struct Object<'a> {
handle: MemorySliceHandle<'a>,
//x_pos: u16,
x_pos: MemoryBackedNumbers<'a, u16>,
//y_pos: MemoryBackedNumbers<'a, u16>,
struct GlobalGameHandle {
handle: MemorySliceHandle,
}
impl<'a> Object<'_> {
pub fn create(start_offset: usize, memory_map: &LibRetroMemoryMap) -> Self {
/* Object {
x_pos: MemoryBackedNumbers::create(start_offset + 0x08, 2, 0, memory_map),
y_pos: MemoryBackedNumbers::create(start_offset + 0x0a, 2, 0, memory_map),
}
*/
let handle = MemorySliceHandle::create(start_offset, 10, 0, memory_map);
let x_pos = MemoryBackedNumbers::create(start_offset + 0x08, 2, 0, memory_map);
//let x_pos = handle.get_at_offset(0x08);
#[derive(Debug, FromPrimitive, ToPrimitive)]
enum GameMode {
SegaScreen = 0x00,
TitleScreen = 0x04,
Demo = 0x08,
Level = 0x0C,
SpecialStage = 0x10,
ContinueScreen = 0x14,
TwoPlayerResults = 0x18,
TwoPlayerLevelSelect = 0x1C,
EndingSequence = 0x20,
OptionsMenu = 0x24,
LevelSelect = 0x28,
Unknown = 0xFF,
}
Object {
#[derive(Debug)]
struct GlobalGameState {
pub game_mode: GameMode,
pub ctrl1_held_press: &'static mut [u8],
pub ctrl1_held_press_logical: &'static mut [u8],
pub ctrl2_held_press: &'static mut [u8],
pub ctrl2_held_press_logical: &'static mut [u8],
pub tails_control_counter: &'static mut [u8],
pub tails_respawn_counter: &'static mut [u8],
}
impl<'a> GlobalGameHandle {
pub fn create(memory_map: &LibRetroMemoryMap) -> Result<Self, Error> {
let mut handle = MemorySliceHandle::create(0xf600, 0x200, 0, memory_map)?;
Ok(GlobalGameHandle {
handle,
x_pos,
}
})
}
pub fn get(&mut self) -> Result<GlobalGameState, failure::Error> {
// for some reason, my offsets are off by +1 here.
Ok(GlobalGameState {
game_mode: num::FromPrimitive::from_u8(self.handle[MemorySpan::ByAddress(0xf601..0xf602)][0]).unwrap_or(GameMode::Unknown),
ctrl1_held_press: self.handle.subslice_mut(MemorySpan::ByAddress(0xf604..0xf606))?,
ctrl1_held_press_logical: self.handle.subslice_mut(MemorySpan::ByAddress(0xf602..0xf604))?,
ctrl2_held_press: self.handle.subslice_mut(MemorySpan::ByAddress(0xf606..0xf608))?,
ctrl2_held_press_logical: self.handle.subslice_mut(MemorySpan::ByAddress(0xf66a..0xf66c))?,
tails_control_counter: self.handle.subslice_mut(MemorySpan::ByAddress(0xf702..0xf704))?,
tails_respawn_counter: self.handle.subslice_mut(MemorySpan::ByAddress(0xf704..0xf706))?,
})
}
}
struct PlayerCharacterHandle {
pos_data: &'static mut [u16],
}
#[derive(Debug)]
struct PlayerCharacterState {
pub playfield_x: u16,
pub playfield_x_subpixel: u16,
pub playfield_y: u16,
pub playfield_y_subpixel: u16,
pub x_vel: u16,
pub y_vel: u16,
pub inertia: u16,
}
impl PlayerCharacterHandle {
pub fn create(start_offset: usize, memory_map: &LibRetroMemoryMap) -> Result<Self, Error> {
let mut player_data_handle = MemorySliceHandle::create(start_offset, 0x40, 0, memory_map)?;
//let pos_data = player_data_handle.slice[0x08..0x16].as_mut_slice_of::<u16>().unwrap();
let pos_data = player_data_handle.subslice_mut(MemorySpan::ByIndex(0x08..0x16))?.as_mut_slice_of::<u16>()?;
Ok(PlayerCharacterHandle {
pos_data,
})
}
pub fn get(&mut self) -> PlayerCharacterState {
PlayerCharacterState {
playfield_x: self.pos_data[0],
playfield_x_subpixel: self.pos_data[1],
playfield_y: self.pos_data[2],
playfield_y_subpixel: self.pos_data[3],
x_vel: self.pos_data[4],
y_vel: self.pos_data[5],
inertia: self.pos_data[6],
}
}
}
impl SyncableGame for SyncedSonic2 {
fn update_state(&mut self) {
let mut pos = self.memory_handles.sonic_pos2.get().unwrap();
println!("sonic pos: {:?}", pos);
//let game_state = self.memory_handles.global_game_state.get();
//let sonic_state = self.memory_handles.sonic_obj.get();
//let mut pos = self.memory_handles.sonic_pos2.get().unwrap();
//println!("game: {:?}, sonic: {:?}", game_state, sonic_state);
println!("sonic data: {:?}", self.memory_handles.sonic_data.handle.slice);
let mut global_game_state = self.memory_handles.global_game_state.get().unwrap();
global_game_state.tails_control_counter.write_back([0x77, 0x77]); // force CPU tails to never take over
global_game_state.tails_respawn_counter.write_back([0x00, 0x00]); // force tails to never helicopter-respawn
/*
if let Some(d) = &self.memory_handles.sonic_pos.slice {
@ -73,12 +165,17 @@ impl SyncableGame for SyncedSonic2 {
}
fn send_state_messages(&mut self) -> Result<(), failure::Error>{
let message: Sonic2Message = (&mut self.memory_handles).into();
if !message.sonic_pos.is_none() {
self.comms.send(message)
}
else {
Ok(())
match self.memory_handles.global_game_state.get()?.game_mode {
GameMode::Level => {
let message: Sonic2Message = (&mut self.memory_handles).try_into()?;
if message.sonic_pos.num_chunks > 0{
self.comms.send(message)
}
else {
Ok(())
}
},
_ => Ok(()) // Don't send messages if we aren't in a level
}
}
@ -90,33 +187,54 @@ impl SyncableGame for SyncedSonic2 {
}
}
impl Sonic2MemoryHandles<'_> {
pub fn create(memory_map: &LibRetroMemoryMap) -> Self {
let sonic_pos2 = MemoryBackedNumbers::create(0xb000, 0x10, 0, memory_map);
impl Sonic2MemoryHandles {
pub fn create(memory_map: &LibRetroMemoryMap) -> Result<Self, Error> {
Sonic2MemoryHandles {
sonic_pos: MemorySliceHandle::create(0xb008, 0x10, 0, memory_map),
tails_pos: MemorySliceHandle::create(0xb048, 0x10, 0, memory_map),
sonic_pos2,
}
Ok(Sonic2MemoryHandles {
sonic_data: TrackedMemorySlice::create(
MemorySliceHandle::create(0xb000, 0x40, 0, memory_map)?,
vec![MemorySpan::ByAddress(0xb008..0xb01F),
//MemorySpan::ByAddress(0xb022..0xb024),
MemorySpan::ByAddress(0xb026..0xb03A),
MemorySpan::ByAddress(0xb03c..0xb03d)])?,
tails_data: MemorySliceHandle::create(0xb040, 0x40, 0, memory_map)?,
sonic_obj: PlayerCharacterHandle::create(0xb000, memory_map)?,
global_game_state: GlobalGameHandle::create(memory_map)?,
})
}
}
impl SyncedSonic2 {
pub fn create(comms_settings: CommunicationSettings, memory_map: &LibRetroMemoryMap) -> Self {
SyncedSonic2 {
memory_handles: Sonic2MemoryHandles::create(memory_map),
pub fn create(comms_settings: CommunicationSettings, memory_map: &LibRetroMemoryMap) -> Result<Self, Error> {
Ok(SyncedSonic2 {
memory_handles: Sonic2MemoryHandles::create(memory_map)?,
comms: Communication::new(comms_settings),
}
})
}
fn handle_message(&mut self, msg: Sonic2Message) -> Result<(), failure::Error> {
match msg.sonic_pos {
Some(src) => {
println!("message contains a new sonic pos. writing to tails. {:?}", &src);
self.memory_handles.tails_pos.write_to_slice(src)
let mut global_game_state =self.memory_handles.global_game_state.get()?;
match global_game_state.game_mode {
GameMode::Level => {
/*
const SKIP_INDICES: [usize; 5]= [0x01, 0x20, 0x21, 0x3e, 0x3f];
match msg.sonic_pos {
Some(src) => {
println!("message contains a new sonic pos. writing to tails. {:?}", &src);
self.memory_handles.tails_data.write_to_slice_with_filter(src, |x| SKIP_INDICES.contains(&x))
}
None => Ok(())
}
*/
msg.sonic_pos.write_into(self.memory_handles.tails_data.slice);
global_game_state.ctrl2_held_press.write_back(msg.ctrl1_held_press);
global_game_state.ctrl2_held_press_logical.write_back(msg.ctrl1_held_press_logical);
Ok(())
}
None => Ok(())
_ => Ok(()) // drop inbound messages if we aren't in a level.
}
}
}

154
src/sync/sparse_vector.rs Normal file
View File

@ -0,0 +1,154 @@
use serde::{Serialize, Deserialize};
use std::ops::Range;
#[derive(Debug, Serialize, Deserialize)]
pub struct SparseVector<T: Clone> {
contents: Vec<(Range<usize>, Vec<T>)>,
pub length: usize,
pub num_chunks: usize,
}
impl<T: Clone> SparseVector<T> {
pub fn create_from(source: &[T], indices_to_take: Vec<Range<usize>>) -> SparseVector<T> {
let mut contents = vec![];
for range in indices_to_take {
if range.end > source.len() {
panic!("range {:?} is outside the bounds of source, which has length {}", range, source.len());
}
contents.push((range.clone(), source[range].into()));
}
let num_chunks = contents.len();
SparseVector {
contents,
length: source.len(),
num_chunks,
}
}
pub fn write_into(&self, target: &mut [T]) {
if target.len() < self.length {
panic!("writing into a target (length {}) which is smaller than the source (length {}).", target.len(), self.length);
}
for (range, values) in &self.contents {
if range.end > target.len() {
panic!("range {:?} is outside the bounds of target, which has length {}", range, target.len())
}
let mut index = range.start;
for value in values {
target[index] = value.clone();
index += 1;
}
}
}
}
impl<T: Clone + PartialEq> SparseVector<T> {
pub fn create_from_diff(source: &[T], diffed: &[T]) -> Result<SparseVector<T>, failure::Error> {
SparseVector::create_from_diff_in_spans(source, diffed, vec![(0..source.len())])
}
pub fn create_from_diff_in_spans(source: &[T], diffed: &[T], indices_to_inspect: Vec<Range<usize>>) -> Result<SparseVector<T>, failure::Error> {
let mut ranges = Vec::default();
for index_range in indices_to_inspect {
if index_range.start < 0 || index_range.end > source.len() || index_range.end > diffed.len() {
return Err(failure::format_err!("Index range to inspect {:?} extends beyond data (lengths: {}, {})", index_range, source.len(), diffed.len()));
}
let last_index_in_range = index_range.end;
let mut span_first_index = None;
for index in index_range {
if source[index] != diffed[index] { // if they differ, start a span or continue an existing one
match span_first_index {
Some(first) => (), // no action. the current span continues.
None => { // begin a new span
span_first_index = Some(index)
}
}
}
else { // if they're the same, close a span if one is open
match span_first_index {
Some(first) => { // a diff span has ended
ranges.push(first..index);
span_first_index = None;
},
None => () // no action
}
}
}
if let Some(first) = span_first_index {
// a span was still open, so close it
ranges.push(first..last_index_in_range)
}
}
Ok(SparseVector::create_from(&source, ranges))
}
}
impl<T: Clone> From<SparseVector<T>> for Vec<T> {
fn from(sparse_vector: SparseVector<T>) -> Self {
let mut vec = Vec::with_capacity(sparse_vector.length);
sparse_vector.write_into(&mut vec);
vec
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn create_from_vec() {
let source = vec![1, 2, 3, 4, 5, 6, 7];
let sparse = SparseVector::create_from(&source, vec![(0..1), (3..5)]);
assert_eq!((0..1, vec![1]), sparse.contents[0]);
assert_eq!((3..5, vec![4, 5]), sparse.contents[1]);
println!("{:?}", sparse);
}
#[test]
fn write_into() {
let source = vec![1, 2, 3, 4, 5, 6, 7, 8];
let mut destination = vec![0, 0, 0, 0, 0, 0, 0, 0];
let sparse = SparseVector::create_from(&source, vec![(0..1), (3..5), (7..8)]);
sparse.write_into(destination.as_mut());
let expected = vec![1, 0, 0, 4, 5, 0, 0, 8];
assert_eq!(expected, destination);
}
#[test]
fn simple_diff() {
let a = vec![1, 2, 3, 4, 5, 6, 7];
let b = vec![1, 0, 0, 0, 5, 0, 0];
let sparse = SparseVector::create_from_diff(&a, &b).expect("error during diffing");
println!("{:?}", sparse);
assert_eq!((1..4, vec![2, 3, 4]), sparse.contents[0]);
assert_eq!((5..7, vec![6, 7]), sparse.contents[1]);
}
#[test]
fn partial_diff() {
let a = vec![1, 2, 3, 4, 5, 6, 7];
let b = vec![1, 0, 0, 0, 5, 0, 0];
let sparse = SparseVector::create_from_diff_in_spans(&a, &b, vec![(2..4), (6..7)]).expect("error during diffing");
println!("{:?}", sparse);
assert_eq!((2..4, vec![3, 4]), sparse.contents[0]);
assert_eq!((6..7, vec![7]), sparse.contents[1]);
}
}

View File

@ -0,0 +1,37 @@
use std::ops::Range;
use crate::sync::memory_slice_handle::{MemorySliceHandle, MemorySpan};
use crate::sync::sparse_vector::SparseVector;
pub struct TrackedMemorySlice {
pub handle: MemorySliceHandle,
tracked_spans: Vec<Range<usize>>,
remembered_state: Vec<u8>,
}
impl TrackedMemorySlice {
pub fn create(handle: MemorySliceHandle, tracked_spans: Vec<MemorySpan>) -> Result<TrackedMemorySlice, failure::Error> {
let mut index_spans = Vec::new();
for span in tracked_spans {
match handle.map_offsets(span)? {
MemorySpan::ByIndex(range) => index_spans.push(range),
MemorySpan::ByAddress(_) => {
return Err(failure::format_err!("Failed to map offset"));
}
}
}
let remembered_state: Vec<u8> = handle.slice.into();
Ok(TrackedMemorySlice {
handle,
tracked_spans: index_spans,
remembered_state: remembered_state,
})
}
pub fn get_changes(&mut self) -> Result<SparseVector<u8>, failure::Error>{
let changes = SparseVector::create_from_diff_in_spans(self.handle.slice, &self.remembered_state, self.tracked_spans.clone());
self.remembered_state = self.handle.slice.into();
changes
}
}