Compare commits
21 Commits
Author | SHA1 | Date |
---|---|---|
Vivian Lim | f887af7e73 | |
Vivian Lim | e6814926d1 | |
Vivian Lim | d130170651 | |
Vivian Lim | e1603b38af | |
Vivian Lim | 646c94194d | |
Vivian Lim | e33275e65d | |
Vivian Lim | 419e97af23 | |
Vivian Lim | 9ba078111c | |
Vivian Lim | 84d87e21ff | |
Vivian Lim | 54aeb2c0f3 | |
Vivian Lim | 26898d4ec2 | |
Vivian Lim | 295956631a | |
Vivian Lim | d83bbb606a | |
Vivian Lim | 6d6ad9ffee | |
Vivian Lim | b257dc5071 | |
Vivian Lim | cd97095b03 | |
Vivian Lim | 285eb6052e | |
Vivian Lim | 1de59414da | |
Vivian Lim | f91c9c91f5 | |
Vivian Lim | c3c8c91a76 | |
Vivian Lim | 1a601f9685 |
|
@ -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"
|
||||
|
|
|
@ -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"
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
||||
}
|
12
src/main.rs
12
src/main.rs
|
@ -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))
|
||||
},
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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)?),
|
||||
})
|
||||
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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(())
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
|
@ -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.
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue