Compare commits

...

21 Commits
master ... s3k

Author SHA1 Message Date
Vivian Lim f887af7e73 s3k: sync position and some animations 2020-03-26 02:43:36 -07:00
Vivian Lim e6814926d1 Merge commit 'd130170651383d77690df167d82e63e11796df84' into s3k 2020-03-26 02:03:41 -07:00
Vivian Lim d130170651 Change synced memory addresses 2020-03-26 02:03:11 -07:00
Vivian Lim e1603b38af Add stub sonic 3 & knuckles support 2020-03-25 19:28:04 -07:00
Vivian Lim 646c94194d add more tests, fix lives count to a # 2020-03-23 10:20:38 -07:00
Vivian Lim e33275e65d more cleanup, send entire sonic data each frame instead of diffs 2020-03-16 01:01:29 -07:00
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
Vivian Lim 1de59414da reading emulator memory as u16s (crudely) 2020-03-11 01:35:07 -07:00
Vivian Lim f91c9c91f5 copy sonic's position over tails' 2020-03-10 02:02:43 -07:00
Vivian Lim c3c8c91a76 Add sonic 2 and watch sonic's position 2020-03-10 01:51:18 -07:00
Vivian Lim 1a601f9685 Add initial memory map implementation which just includes the entire system memory that can be obtained from retro.get_memory 2020-03-10 01:40:39 -07:00
15 changed files with 1372 additions and 116 deletions

8
Cargo.lock generated
View File

@ -69,6 +69,11 @@ name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "byte-slice-cast"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "byteorder"
version = "1.3.2"
@ -210,10 +215,12 @@ name = "ferretro-synced"
version = "0.1.0"
dependencies = [
"ascii 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"byte-slice-cast 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)",
"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)",
@ -1367,6 +1374,7 @@ dependencies = [
"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
"checksum base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643"
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
"checksum byte-slice-cast 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b0a5e3906bcbf133e33c1d4d95afc664ad37fbdb9f6568d8043e7ea8c27d93d3"
"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
"checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c"
"checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb"

View File

@ -22,5 +22,7 @@ serde_bytes = "0.11"
serde_json = "1.0.44"
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

@ -5,7 +5,7 @@ use ferretro::retro::wrapped_types::{ControllerDescription2, InputDescriptor2, I
use ferretro::retro::wrapper::LibretroWrapper;
use crate::sync::game::{SyncableGame, KnownGames, read_game_name, create_game};
use crate::emulator::libretro_memory_map::LibRetroMemoryMap;
use crate::emulator::libretro_memory_map;
use crate::emulator::metadata_reader::{GameInfo, get_game_info};
use std::ffi::CStr;
@ -50,7 +50,7 @@ pub struct MyEmulator {
gamepads: Vec<GameController>,
pressed_keys: Vec<Keycode>,
pub memory_map: Option<LibRetroMemoryMap>,
pub memory_map: libretro_memory_map::LibRetroMemoryMap,
synced_game: Option<Box<dyn SyncableGame>>,
}
@ -118,6 +118,8 @@ impl MyEmulator {
let pressed_keys = Vec::new();
let memory_map = libretro_memory_map::LibRetroMemoryMap::create_from_retro(&retro);
/*
let memory_map = std::default::Default::default();
let which_game = read_game_name("whatever");
@ -142,7 +144,7 @@ impl MyEmulator {
gamepad_subsys,
gamepads,
pressed_keys,
memory_map: None,
memory_map,
synced_game: None,
};
let mut pin_emu = Box::pin(emu);
@ -179,7 +181,14 @@ impl MyEmulator {
match g.handle_inbound_messages() {
Ok(_) => (),
Err(e) => {
println!("Error while handling inbound messages");
println!("Error while handling inbound messages {}", e);
}
}
match g.update_state() {
Ok(_) => (),
Err(e) => {
println!("Error while updating pre-frame state {}", e);
}
}
},
@ -198,8 +207,10 @@ impl MyEmulator {
match &mut self.synced_game {
Some(g) => {
g.update_state();
g.send_state_messages();
match g.send_state_messages() {
Ok(_) => (),
Err(e) => println!("Error while sending messages post-frame {}", e)
}
},
None => ()
}
@ -450,7 +461,7 @@ impl retro::wrapper::Handler for MyEmulator {
}
fn set_memory_maps(&mut self, ffi_memory_map: MemoryMap) -> bool {
self.memory_map = Some(LibRetroMemoryMap::create(&ffi_memory_map));
self.memory_map = libretro_memory_map::LibRetroMemoryMap::create_from_map(&ffi_memory_map);
true
}
}

View File

@ -1,15 +1,18 @@
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:{:X}, length:{:X}, bank_switch:{:X}", offset, length, bank_switch))
}
}
@ -21,7 +24,7 @@ impl LibRetroMemoryMap {
for item in self.regions.iter() {
if (item.start <= offset) && (offset < item.end) {
if offset + length > item.end {
println!("({:x}, {:x}) overruns ({:x}, {:x}) memory bank.", offset, length, item.start, item.end);
println!("({:X}, {:X}) overruns ({:X}, {:X}) memory bank.", offset, length, item.start, item.end);
return None;
}
@ -30,11 +33,11 @@ impl LibRetroMemoryMap {
return unsafe { Some(item.pointer.offset(relative_offset)) }
}
}
println!("({:x}, {:x}) address range not found in any memory map region.", offset, length);
println!("({:X}, {:X}) address range not found in any memory map region.", offset, length);
None
}
pub fn create(memory_map: &MemoryMap) -> Self {
pub fn create_from_map(memory_map: &MemoryMap) -> Self {
let mut regions: Vec<LibRetroMemoryRegion> = Vec::new();
let num_descriptors = isize::try_from(memory_map.num_descriptors).unwrap();
for i in 0..num_descriptors {
@ -57,10 +60,89 @@ impl LibRetroMemoryMap {
regions: regions
}
}
pub fn create_from_retro(retro: &LibretroApi) -> Self{
let mut regions: Vec<LibRetroMemoryRegion> = Vec::new();
let memory = retro.get_memory(2 /*MEMORY_SYSTEM_RAM*/);
regions.push(LibRetroMemoryRegion {
start: 0,
end: memory.len(),
pointer: unsafe {memory.as_ptr() as *const std::ffi::c_void}
});
LibRetroMemoryMap {
regions: regions
}
}
pub fn create_from_pointer(pointer: *mut u8, length: usize) -> Self {
let mut regions: Vec<LibRetroMemoryRegion> = Vec::new();
regions.push(LibRetroMemoryRegion {
start: 0,
end: length,
pointer: pointer as *const std::ffi::c_void
});
LibRetroMemoryMap {
regions: regions
}
}
}
struct LibRetroMemoryRegion {
start: usize,
end: usize,
pointer: *const std::ffi::c_void,
}
#[cfg(test)]
mod tests {
use super::*;
use std::ops::Range;
use crate::emulator::libretro_memory_map::LibRetroMemoryMap;
// implementation largely copied from tracked_memory_slice. probably put it somewhere to be shared...
fn create_memory() -> [u8; 0x10000] {
let mut mem = [0; 0x10000];
for i in 0..mem.len() {
mem[i] = expected_value_at_index(i);
}
mem
}
fn expected_value_at_index(index: usize) -> u8 {
(index % 256) as u8
}
fn print_bytes(data: &[u8], range: Range<usize>) {
for i in range {
print!("{:X} ", data[i]);
}
println!("");
}
#[test]
fn correct_memory_address_returned_from_simple_map() -> Result<(), Error> {
let mut memory = create_memory();
let mut map = LibRetroMemoryMap::create_from_pointer(unsafe { memory.as_mut_ptr() }, memory.len());
let mut slice = map.get_slice_from_region(0xb000, 1, 0)?;
assert_eq!(expected_value_at_index(0xb000), slice[0]);
slice = map.get_slice_from_region(0xafff, 3, 0)?;
print_bytes(slice, 0..3);
assert_eq!(0xFF, slice[0]);
assert_eq!(0x0, slice[1]);
assert_eq!(0x1, slice[2]);
slice = map.get_slice_from_region(0xb040, 0x40, 0)?;
print_bytes(slice, 0..0x40);
for i in 0..0x40 {
assert_eq!(expected_value_at_index(0xb040 + i), slice[i]);
}
Ok(())
}
}

View File

@ -5,6 +5,7 @@ extern crate sdl2;
extern crate serde;
extern crate serde_bytes;
extern crate ascii;
extern crate byte_slice_cast;
use structopt::StructOpt;
use std::path::{Path, PathBuf};
@ -44,13 +45,10 @@ pub fn main() -> failure::Fallible<()> {
fn attempt_to_start_sync(emu: &mut MyEmulator, comms_settings: CommunicationSettings) -> Result<(), failure::Error> {
match &emu.game_info {
Some(game_info) => match read_game_name(&game_info.game_name) {
Some(which_game) => match &emu.memory_map {
Some(memory_map) => {
let synced_game = create_game(which_game, comms_settings, memory_map);
emu.begin_sync(synced_game);
Ok(())
},
None => Err(format_err!("Memory map has not been set"))
Some(which_game) => {
let synced_game = create_game(which_game, comms_settings, &emu.memory_map)?;
emu.begin_sync(synced_game);
Ok(())
},
None => Err(format_err!("Unrecognized game {}", game_info.game_name))
},

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

@ -1,10 +1,13 @@
use crate::sync::pokemon_rb::SyncedPokemonRedBlue;
use crate::sync::sonic2::SyncedSonic2;
use crate::sync::sonic3andknuckles::SyncedSonic3AndKnuckles;
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
fn update_state(&mut self);
fn update_state(&mut self) -> Result<(), failure::Error>;
// Check for messages and handle them
fn handle_inbound_messages(&mut self) -> Result<(), failure::Error>;
@ -15,19 +18,26 @@ pub trait SyncableGame {
pub enum KnownGames {
PokemonRedBlue,
Sonic2,
Sonic3AndKnuckles,
}
pub fn read_game_name(name: &str) -> Option<KnownGames> {
match name {
"POKEMON BLUE" => Some(KnownGames::PokemonRedBlue),
"SONIC THE HEDGEHOG 2" => Some(KnownGames::Sonic2),
"SONIC & KNUCKLES" => Some(KnownGames::Sonic3AndKnuckles),
_ => None
}
}
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))
}
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)?),
KnownGames::Sonic3AndKnuckles => Box::from(SyncedSonic3AndKnuckles::create(comms_settings, memory_map)?),
})
}

View File

@ -1,91 +1,339 @@
use std::convert::{TryFrom, TryInto};
use crate::emulator::libretro_memory_map::LibRetroMemoryMap;
use failure::{format_err, Error};
use byte_slice_cast::*;
use std::result::Result;
use std::ops::{Index, IndexMut, Range};
pub struct MemorySliceHandle<'a> {
/*
pub struct MemoryBackedNumbers<'a, T: 'a + FromByteSlice> {
handle: MemorySliceHandle,
value_type: std::marker::PhantomData<T>,
}
impl<'a, T: 'a + FromByteSlice> MemoryBackedNumbers<'a, T> {
pub fn create(offset: usize, length: usize, bank_switch: usize, memory_map: &LibRetroMemoryMap) -> Self {
MemoryBackedNumbers::<'a, T> {
handle: MemorySliceHandle::create(offset, length, bank_switch, memory_map),
value_type: std::marker::PhantomData,
}
}
pub fn get(&mut self) -> Result<&mut [T], failure::Error> {
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]>()?;
if new_bytes.len() != self.handle.slice.len() {
Err(format_err!("Size of bytes from converted value ({}) differs from handle size {}", new_bytes.len(), self.handle.slice.len()))
}
else {
Ok(())
}
}
*/
//}
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 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 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."));
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 [{:X}, {:X}), they are outside of memory handle at offset {:X} with length {:X}.", offsets.start, offsets.end, self.offset, self.length));
}
// 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;
}
}
}
let start_index = offsets.start - self.offset;
let end_index = offsets.end - self.offset;
Ok(())
Ok(MemorySpan::ByIndex(start_index..end_index))
},
None => {
println!("slice doesn't exist to write to.");
Ok(())
}
MemorySpan::ByIndex(_) => Ok(range) // no-op
}
}
pub fn get_copy_if_changed_since_last_read(&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;
pub fn write_to_slice(&mut self, data: Vec<u8>) -> Result<(), Error> {
self.write_to_slice_with_filter(data, |_| true)
}
for value in data.into_iter() {
if last[index] != *value {
last[index] = *value;
any_changes = true;
}
index += 1;
}
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 any_changes {
return Some(last.clone());
// 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;
}
None
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.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 self.slice.into_iter() {
if last[index] != *value {
last[index] = *value;
any_changes = true;
}
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 {:X} is not one byte", slice.len());
assert_eq!(expected_value, slice[0], "looking up address {:X} in slice, expecting {:X}, actual {:X}, length {:X}", 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 {:X} is not one byte", mut_slice.len());
assert_eq!(expected_value, mut_slice[0], "looking up address {:X} in mut_slice, expecting {:X}, actual {:X}, length {:X}", 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 {:X} is not one byte", slice.len());
assert_eq!(expected_value, slice[0], "looking up address {:X} in slice, expecting {:X}, actual {:X}, length {:X}", 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 {:X} is not one byte", mut_slice.len());
assert_eq!(expected_value, mut_slice[0], "looking up address {:X} in mut_slice, expecting {:X}, actual {:X}, length {:X}", 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(())
}
}

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

@ -0,0 +1,58 @@
use failure::{Error, format_err};
pub trait CopyFromMemory<T> {
fn copy_from_memory(&self) -> Result<T, Error>;
fn write_back(&mut self, source: T) -> Result<(), Error>;
}
impl CopyFromMemory<[u8; 2]> for &'static mut [u8] {
fn copy_from_memory(&self) -> Result<[u8; 2], Error> {
let mut result = [0, 0];
if self.len() != result.len() {
return Err(format_err!("Source ({:X}) and destination ({:X}) lengths differ", self.len(), result.len()));
}
for i in 0..result.len() {
result[i] = self[i];
}
Ok(result)
}
fn write_back(&mut self, source: [u8; 2]) -> Result<(), Error> {
if source.len() != self.len() {
return Err(format_err!("Source ({:X}) and destination ({:X}) lengths differ", source.len(), self.len()));
}
for i in 0..source.len() {
self[i] = source[i];
}
Ok(())
}
}
impl CopyFromMemory<[u8; 4]> for &'static mut [u8] {
fn copy_from_memory(&self) -> Result<[u8; 4], Error> {
let mut result = [0, 0, 0, 0];
if self.len() != result.len() {
return Err(format_err!("Source ({:X}) and destination ({:X}) lengths differ", self.len(), result.len()));
}
for i in 0..result.len() {
result[i] = self[i];
}
Ok(result)
}
fn write_back(&mut self, source: [u8; 4]) -> Result<(), Error> {
if source.len() != self.len() {
return Err(format_err!("Source ({:X}) and destination ({:X}) lengths differ", source.len(), self.len()));
}
for i in 0..source.len() {
self[i] = source[i];
}
Ok(())
}
}

View File

@ -1,4 +1,9 @@
pub mod pokemon_rb;
pub mod sonic2;
pub mod sonic3andknuckles;
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,18 +21,18 @@ 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_since_last_read()
active_pkmn: src.active_pokemon.get_copy_if_changed()
}
}
}
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 {
@ -39,15 +40,13 @@ struct BattleContext {
}
impl SyncableGame for SyncedPokemonRedBlue {
fn update_state(&mut self) {
fn update_state(&mut self) -> Result<(), failure::Error> {
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;
Ok(())
}
fn send_state_messages(&mut self) -> Result<(), failure::Error>{
@ -68,13 +67,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 +92,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 +274,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 +287,7 @@ mod tests {
offset: 0,
length: 0,
bank_switch: 0,
slice: Some(b),
slice: b,
last_read_value: None
};
@ -305,5 +302,6 @@ mod tests {
assert_eq!(deserialized.active_pkmn.unwrap()[0], data[0]);
Ok(())
}
*/
}

256
src/sync/sonic2.rs Normal file
View File

@ -0,0 +1,256 @@
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};
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, 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,
comms: Communication<Sonic2Message>,
}
#[derive(Serialize, Deserialize)]
pub struct Sonic2Message {
sonic_pos: SparseVector<u8>,
ctrl1_held_press: [u8; 2],
ctrl1_held_press_logical: [u8; 2],
}
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_tracked_data_if_changed()?,
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 {
sonic_data: TrackedMemorySlice,
tails_data: MemorySliceHandle,
sonic_obj: PlayerCharacterHandle,
global_game_state: GlobalGameHandle,
}
struct GlobalGameHandle {
handle: MemorySliceHandle,
}
#[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,
}
#[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],
pub lives_counter: &'static mut [u8],
pub hud_redraw: &'static mut [u8],
}
impl<'a> GlobalGameHandle {
pub fn create(memory_map: &LibRetroMemoryMap) -> Result<Self, Error> {
let mut handle = MemorySliceHandle::create(0xf600, 0x900, 0, memory_map)?;
Ok(GlobalGameHandle {
handle,
})
}
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))?,
lives_counter: self.handle.subslice_mut(MemorySpan::ByAddress(0xfe12..0xfe14))?,
hud_redraw: self.handle.subslice_mut(MemorySpan::ByAddress(0xfe1c..0xfe20))?,
})
}
}
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) -> Result<(), Error> {
//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()?;
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 (global_game_state.lives_counter[1] < 0x45) {
global_game_state.lives_counter.write_back([0x00, 0x45]); // fix lives count
global_game_state.hud_redraw.write_back([0x01, 0x01, 0x01, 0x01]); // force hud to redraw
}
/*
if let Some(d) = &self.memory_handles.sonic_pos.slice {
writeln!("sonic pos: {:?}", d)
}*/
Ok(())
}
fn send_state_messages(&mut self) -> Result<(), failure::Error>{
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
}
}
fn handle_inbound_messages(&mut self) -> Result<(), failure::Error>{
match self.comms.receive_if_any() {
Some(msg) => self.handle_message(msg),
None => Ok(())
}
}
}
impl Sonic2MemoryHandles {
pub fn create(memory_map: &LibRetroMemoryMap) -> Result<Self, Error> {
Ok(Sonic2MemoryHandles {
sonic_data: TrackedMemorySlice::create(
MemorySliceHandle::create(0xb000, 0x40, 0, memory_map)?,
vec![
MemorySpan::ByAddress(0xb000..0xb001), // Render flags
MemorySpan::ByAddress(0xb008..0xb016), // position and velocity
MemorySpan::ByAddress(0xb01d..0xb01e), // which animation. don't copy b01d so that the game will start playing the new animation if it differs
MemorySpan::ByAddress(0xb023..0xb024), // status bitfield
MemorySpan::ByAddress(0xb02a..0xb02b), // status bitfield 2
MemorySpan::ByAddress(0xb039..0xb03c), // charging a spindash
MemorySpan::ByAddress(0xb03d..0xb03e), // jumping
])?,
//MemorySpan::ByAddress(0xb028..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) -> 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> {
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(())
}
_ => Ok(()) // drop inbound messages if we aren't in a level.
}
}
}

View File

@ -0,0 +1,224 @@
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};
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, 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 SyncedSonic3AndKnuckles {
memory_handles: Sonic3AndKnucklesMemoryHandles,
comms: Communication<Sonic3AndKnucklesMessage>,
}
#[derive(Serialize, Deserialize)]
pub struct Sonic3AndKnucklesMessage {
sonic_pos: SparseVector<u8>,
//ctrl1_held_press: [u8; 2],
//ctrl1_held_press_logical: [u8; 2],
}
impl TryFrom<&mut Sonic3AndKnucklesMemoryHandles> for Sonic3AndKnucklesMessage {
type Error = failure::Error;
fn try_from(src: &mut Sonic3AndKnucklesMemoryHandles) -> Result<Self, Error> {
let global_game_state = src.global_game_state.get()?;
Ok(Sonic3AndKnucklesMessage {
sonic_pos: src.sonic_data.get_tracked_data_if_changed()?,
//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 Sonic3AndKnucklesMemoryHandles {
sonic_data: TrackedMemorySlice,
tails_data: MemorySliceHandle,
global_game_state: GlobalGameHandle,
arbitrary_ram: MemorySliceHandle,
}
struct GlobalGameHandle {
handle: MemorySliceHandle,
}
#[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,
}
#[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],
pub lives_counter: &'static mut [u8],
pub hud_redraw: &'static mut [u8],
}
impl<'a> GlobalGameHandle {
pub fn create(memory_map: &LibRetroMemoryMap) -> Result<Self, Error> {
let mut handle = MemorySliceHandle::create(0xf600, 0x900, 0, memory_map)?;
Ok(GlobalGameHandle {
handle,
})
}
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(0xf600..0xf601)][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))?,
lives_counter: self.handle.subslice_mut(MemorySpan::ByAddress(0xfe12..0xfe14))?,
hud_redraw: self.handle.subslice_mut(MemorySpan::ByAddress(0xfe1c..0xfe20))?,
})
}
}
impl SyncableGame for SyncedSonic3AndKnuckles {
fn update_state(&mut self) -> Result<(), Error> {
//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()?;
//println!("flag {:X?}", self.memory_handles.arbitrary_ram.subslice(MemorySpan::ByAddress(0xf600..0xf609))?);
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 (global_game_state.lives_counter[1] < 0x45) {
global_game_state.lives_counter.write_back([0x00, 0x45]); // fix lives count
global_game_state.hud_redraw.write_back([0x01, 0x01, 0x01, 0x01]); // force hud to redraw
}
/*
if let Some(d) = &self.memory_handles.sonic_pos.slice {
writeln!("sonic pos: {:?}", d)
}*/
Ok(())
}
fn send_state_messages(&mut self) -> Result<(), failure::Error>{
let state = self.memory_handles.global_game_state.get()?;
match state.game_mode {
//GameMode::Level => {
GameMode::SegaScreen => {
let message: Sonic3AndKnucklesMessage = (&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
}
}
fn handle_inbound_messages(&mut self) -> Result<(), failure::Error>{
match self.comms.receive_if_any() {
Some(msg) => self.handle_message(msg),
None => Ok(())
}
}
}
impl Sonic3AndKnucklesMemoryHandles {
pub fn create(memory_map: &LibRetroMemoryMap) -> Result<Self, Error> {
Ok(Sonic3AndKnucklesMemoryHandles {
sonic_data: TrackedMemorySlice::create(
MemorySliceHandle::create(0xb000, 0x4A, 0, memory_map)?,
vec![
MemorySpan::ByAddress(0xb004..0xb006), // Render flags + routine
MemorySpan::ByAddress(0xb010..0xb01e), // position and velocity
MemorySpan::ByAddress(0xb020..0xb022), // which animation. don't copy b021 so that the game will start playing the new animation if it differs
MemorySpan::ByAddress(0xb026..0xb028), // angles
/*MemorySpan::ByAddress(0xb025..0xb026), // character ability tracker*/
MemorySpan::ByAddress(0xb02a..0xb02c), // status bitfield 2
MemorySpan::ByAddress(0xb02d..0xb02e), // air
MemorySpan::ByAddress(0xb02f..0xb030), // double jump flag
//MemorySpan::ByAddress(0xb038..0xb039), // character id
MemorySpan::ByAddress(0xb03d..0xb03e), // charging spindash
MemorySpan::ByAddress(0xb040..0xb041), // jumping
])?,
//MemorySpan::ByAddress(0xb028..0xb03d)])?,
tails_data: MemorySliceHandle::create(0xb04A, 0x4A, 0, memory_map)?,
global_game_state: GlobalGameHandle::create(memory_map)?,
arbitrary_ram: MemorySliceHandle::create(0xf600, 0x900, 0, memory_map)?,
})
}
}
impl SyncedSonic3AndKnuckles {
pub fn create(comms_settings: CommunicationSettings, memory_map: &LibRetroMemoryMap) -> Result<Self, Error> {
Ok(SyncedSonic3AndKnuckles {
memory_handles: Sonic3AndKnucklesMemoryHandles::create(memory_map)?,
comms: Communication::new(comms_settings),
})
}
fn handle_message(&mut self, msg: Sonic3AndKnucklesMessage) -> Result<(), failure::Error> {
let mut global_game_state =self.memory_handles.global_game_state.get()?;
match global_game_state.game_mode {
GameMode::SegaScreen => {
//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(())
}
_ => Ok(()) // drop inbound messages if we aren't in a level.
}
}
}

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

@ -0,0 +1,179 @@
use serde::{Serialize, Deserialize};
use std::ops::Range;
#[derive(Debug, Serialize, Deserialize, Default)]
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 [{:X}, {:X}) is outside the bounds of source, which has length {:X}", range.start, range.end, 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 {:X}) which is smaller than the source (length {:X}).", target.len(), self.length);
}
for (range, values) in &self.contents {
if range.end > target.len() {
panic!("range [{:X}, {:X}) is outside the bounds of target, which has length {:X}", range.start, range.end, 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 [{:X}, {:X}) extends beyond data (lengths: {:X}, {:X})", index_range.start, index_range.end, 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]);
let mut destination = vec![1, 0, 0, 0, 5, 0, 0];
sparse.write_into(destination.as_mut());
assert_eq!(a, destination);
}
#[test]
fn partial_diff() {
let a = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
let b = vec![0, 1, 0, 0, 0, 5, 0, 0, 8, 9, 0, 0, 0, 13, 0];
let sparse = SparseVector::create_from_diff_in_spans(&a, &b, vec![(1..4), (6..9), (10..12), (14..15)]).expect("error during diffing");
println!("{:?}", sparse);
assert_eq!((2..4, vec![2, 3]), sparse.contents[0]);
assert_eq!((6..8, vec![6, 7]), sparse.contents[1]);
assert_eq!((10..12, vec![10, 11]), sparse.contents[2]);
assert_eq!((14..15, vec![14]), sparse.contents[3]);
let expected = vec![0, 0, 2, 3, 0, 0, 6, 7, 0, 0, 10, 11, 0, 0, 14];
let mut destination = vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
sparse.write_into(destination.as_mut());
assert_eq!(expected, destination);
}
#[test]
fn write_into_different_span() {
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);
}
}

View File

@ -0,0 +1,177 @@
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
}
pub fn has_changes(&mut self) -> bool {
for span in &self.tracked_spans {
for i in span.start..span.end {
if self.handle.slice[i] != self.remembered_state[i] {
self.remembered_state = self.handle.slice.into();
return true;
}
}
}
return false;
}
pub fn get_tracked_data(&mut self) -> Result<SparseVector<u8>, failure::Error>{
Ok(SparseVector::create_from(self.handle.slice, self.tracked_spans.clone()))
}
pub fn get_tracked_data_if_changed(&mut self) -> Result<SparseVector<u8>, failure::Error> {
if !self.has_changes() {
Ok(SparseVector::default())
}
else {
Ok(self.get_tracked_data()?)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use lazy_static::lazy_static;
use std::sync::Mutex;
use std::cell::RefCell;
use crate::emulator::libretro_memory_map::LibRetroMemoryMap;
// implementation differs from memory_slice_handler test. this is simpler and might be worth reusing
fn create_memory() -> [u8; 0x10000] {
let mut mem = [0; 0x10000];
for i in 0..mem.len() {
mem[i] = expected_value_at_index(i);
}
mem
}
fn expected_value_at_index(index: usize) -> u8 {
(index % 256) as u8
}
fn print_bytes(data: &[u8], range: Range<usize>) {
for i in range {
print!("{:X} ", data[i]);
}
println!("");
}
#[test]
fn write_into_different_handle_with_sparse_vector() -> Result<(), Error> {
let mut memory = create_memory();
let mut map = LibRetroMemoryMap::create_from_pointer(unsafe { memory.as_mut_ptr() }, memory.len());
// create two handles
let mut handle_entire_memory = MemorySliceHandle::create(0x0, 0x10000, 0, &map)?;
let mut handle_source = MemorySliceHandle::create(0xb000, 0x40, 0, &map)?;
let tracked_spans = vec![MemorySpan::ByAddress(0xb008..0xb018), MemorySpan::ByAddress(0xb028..0xb040)];
let mut source = TrackedMemorySlice::create(handle_source, tracked_spans)?;
let mut handle_destination = MemorySliceHandle::create(0xb040, 0x40, 0, &map)?;
print_bytes(handle_entire_memory.slice, 0xb000..0xb080);
// copy a message from the source
let message = source.get_tracked_data()?;
// zero out entire source range so we can make sure it doesn't get written into
for i in 0xb000..0xb040 {
// make sure the existing value here is not 0 if it shouldn't be... just in case there's a bug in test code
let expected = expected_value_at_index(i);
if handle_entire_memory.slice[i] != expected {
return Err(format_err!("Memory at {:X} wasn't initialized to expected value {:X}, it was {:X}", i, expected, handle_entire_memory.slice[i]));
}
handle_entire_memory.slice[i] = 0;
}
print_bytes(handle_entire_memory.slice, 0xb000..0xb080);
print_bytes(source.handle.slice, 0..0x10);
print_bytes(handle_destination.slice, 0..0x10);
// just double check: the source handle view of that data should be all zeroes
for i in 0xb000..0xb040 {
let value = source.handle.subslice_mut(MemorySpan::ByAddress(i..i+1))?[0];
if value != 0 {
return Err(format_err!("data at {:X} should have been 0, but was {:X}.", i, value));
}
}
// and the destination handle view should be inaccessible. make sure it really is.
for i in 0xb000..0xb040 {
match handle_destination.subslice_mut(MemorySpan::ByAddress(i..i+1)) {
Ok(d) => return Err(format_err!("data at {:X} should have been inaccessible to destination handle, but was read as {:X}", i, d[0])),
Err(_) => ()
}
}
message.write_into(handle_destination.slice);
// now make sure that we didn't write into the source memory range.
for i in 0xb000..0xb040 {
let value_from_raw = memory[i];
let value_from_handle = source.handle.subslice_mut(MemorySpan::ByAddress(i..i+1))?[0];
if value_from_handle != 0 {
return Err(format_err!("after writing into destination, data at {:X} should have been 0, but was {:X}.", i, value_from_handle));
}
assert_eq!(value_from_raw, value_from_handle);
}
// and make sure we wrote to the right parts of the destination.
for i in 0xb040..0xb080 {
let value_from_raw = memory[i];
let value_from_handle = handle_destination.subslice_mut(MemorySpan::ByAddress(i..i+1))?[0];
if (i >= 0xb048 && i < 0xb058) || (i >= 0xb068 && i < 0xb080) {
// these bytes should have been written.
assert_eq!(expected_value_at_index(i - 0x40), value_from_handle);
assert_eq!(value_from_raw, value_from_handle);
}
else {
// these bytes should be untouched
assert_eq!(expected_value_at_index(i), value_from_handle);
assert_eq!(value_from_raw, value_from_handle);
}
}
print_bytes(handle_entire_memory.slice, 0xb000..0xb080);
Ok(())
}
}