From 899e1eec5999b36497d7a99dc17ce6fff54a8c90 Mon Sep 17 00:00:00 2001 From: Jujstme Date: Wed, 12 Jun 2024 15:28:58 +0200 Subject: [PATCH] Applied https://github.com/LiveSplit/asr/pull/89 --- src/emulator/gba/mod.rs | 106 ++++++++++++++++++-------------- src/emulator/gcn/mod.rs | 84 +++++++++++++++++++------- src/emulator/genesis/mod.rs | 100 +++++++++++++++++++++--------- src/emulator/ps1/mod.rs | 73 +++++++++++++++++----- src/emulator/ps2/mod.rs | 69 ++++++++++++++------- src/emulator/sms/mod.rs | 46 ++++++++++---- src/emulator/wii/mod.rs | 117 ++++++++++++++---------------------- 7 files changed, 380 insertions(+), 215 deletions(-) diff --git a/src/emulator/gba/mod.rs b/src/emulator/gba/mod.rs index df97ad6..988213e 100644 --- a/src/emulator/gba/mod.rs +++ b/src/emulator/gba/mod.rs @@ -3,6 +3,8 @@ use core::{ cell::Cell, future::Future, + mem::size_of, + ops::Sub, pin::Pin, task::{Context, Poll}, }; @@ -118,64 +120,72 @@ impl Emulator { success } - /// Reads any value from the emulated RAM. + /// Converts a GBA memory address to a real memory address in the emulator process' virtual memory space /// - /// The offset provided is meant to be the same memory address as usually mapped on the original hardware. /// Valid addresses range: /// - from `0x02000000` to `0x0203FFFF` for EWRAM /// - from `0x03000000` to `0x03007FFF` for IWRAM - /// - /// Values outside these ranges will be considered invalid, and will make this method immediately return `Err()`. - pub fn read(&self, offset: u32) -> Result { - match offset >> 24 { - 2 => self.read_from_ewram(offset), - 3 => self.read_from_iwram(offset), + pub fn get_address(&self, offset: u32) -> Result { + match offset { + (0x02000000..=0x0203FFFF) => { + let r_offset = offset.sub(0x02000000); + let [ewram, _] = self.ram_base.get().ok_or(Error {})?; + Ok(ewram + r_offset) + } + (0x03000000..=0x03007FFF) => { + let r_offset = offset.sub(0x03000000); + let [_, iwram] = self.ram_base.get().ok_or(Error {})?; + Ok(iwram + r_offset) + } _ => Err(Error {}), } } - /// Reads any value from the EWRAM section of the emulated RAM. - /// - /// The offset provided can either be the relative offset from the - /// start of EWRAM, or a memory address as mapped on the original hardware. + /// Checks if a memory reading operation would exceed the memory bounds of the emulated system. /// - /// Valid addresses range from `0x02000000` to `0x0203FFFF`. - /// For example, providing an offset value of `0x3000` or `0x02003000` - /// will return the exact same value. - /// - /// Invalid offset values, or values outside the allowed ranges will - /// make this method immediately return `Err()`. - pub fn read_from_ewram(&self, offset: u32) -> Result { - if (offset > 0x3FFFF && offset < 0x02000000) || offset > 0x0203FFFF { - return Err(Error {}); + /// Returns `true` if the read operation can be performed safely, `false` otherwise. + const fn check_bounds(&self, offset: u32) -> bool { + match offset { + (0x02000000..=0x0203FFFF) => offset + size_of::() as u32 <= 0x02040000, + (0x03000000..=0x03007FFF) => offset + size_of::() as u32 <= 0x03008000, + _ => false, } - - let [ewram, _] = self.ram_base.get().ok_or(Error {})?; - let end_offset = offset.checked_sub(0x02000000).unwrap_or(offset); - - self.process.read(ewram + end_offset) } - /// Reads any value from the IWRAM section of the emulated RAM. - /// - /// The offset provided can either be the relative offset from the - /// start of IWRAM, or a memory address as mapped on the original hardware. + /// Reads any value from the emulated RAM. /// - /// Valid addresses range from `0x03000000` to `0x03007FFF`. - /// For example, providing an offset value of `0x3000` or `0x03003000` - /// will return the exact same value. + /// The offset provided is meant to be the same memory address as usually mapped on the original hardware. + /// Valid addresses range: + /// - from `0x02000000` to `0x0203FFFF` for EWRAM + /// - from `0x03000000` to `0x03007FFF` for IWRAM /// - /// Invalid offset values, or values outside the allowed ranges will - /// make this method immediately return `Err()`. - pub fn read_from_iwram(&self, offset: u32) -> Result { - if (offset > 0x7FFF && offset < 0x03000000) || offset > 0x03007FFF { - return Err(Error {}); + /// Values outside these ranges are invalid, and will make this method immediately return `Err()`. + pub fn read(&self, offset: u32) -> Result { + match self.check_bounds::(offset) { + true => self.process.read(self.get_address(offset)?), + false => Err(Error {}), } + } - let [_, iwram] = self.ram_base.get().ok_or(Error {})?; - let end_offset = offset.checked_sub(0x03000000).unwrap_or(offset); + /// Follows a path of pointers from the address given and reads a value of the type specified from + /// the process at the end of the pointer path. + pub fn read_pointer_path( + &self, + base_address: u32, + path: &[u32], + ) -> Result { + self.read(self.deref_offsets(base_address, path)?) + } - self.process.read(iwram + end_offset) + /// Follows a path of pointers from the address given and returns the address at the end + /// of the pointer path + fn deref_offsets(&self, base_address: u32, path: &[u32]) -> Result { + let mut address = base_address; + let (&last, path) = path.split_last().ok_or(Error {})?; + for &offset in path { + address = self.read::(address + offset)?; + } + Ok(address + last) } } @@ -185,16 +195,20 @@ pub struct UntilEmulatorCloses<'a, F> { future: F, } -impl> Future for UntilEmulatorCloses<'_, F> { - type Output = (); +impl> Future for UntilEmulatorCloses<'_, F> { + type Output = Option; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if !self.emulator.is_open() { - return Poll::Ready(()); + return Poll::Ready(None); } self.emulator.update(); // SAFETY: We are simply projecting the Pin. - unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().future).poll(cx) } + unsafe { + Pin::new_unchecked(&mut self.get_unchecked_mut().future) + .poll(cx) + .map(Some) + } } } @@ -223,4 +237,4 @@ static PROCESS_NAMES: [(&str, State); 7] = [ ("retroarch.exe", State::Retroarch(retroarch::State::new())), ("EmuHawk.exe", State::EmuHawk(emuhawk::State::new())), ("mednafen.exe", State::Mednafen(mednafen::State::new())), -]; +]; \ No newline at end of file diff --git a/src/emulator/gcn/mod.rs b/src/emulator/gcn/mod.rs index c6f13cf..c4c7354 100644 --- a/src/emulator/gcn/mod.rs +++ b/src/emulator/gcn/mod.rs @@ -3,6 +3,8 @@ use core::{ cell::Cell, future::Future, + mem::size_of, + ops::Sub, pin::Pin, task::{Context, Poll}, }; @@ -106,47 +108,83 @@ impl Emulator { } } + /// Converts a GameCube memory address to a real memory address in the emulator process' virtual memory space + /// + /// Valid addresses range from `0x80000000` to `0x817FFFFF`. + pub fn get_address(&self, offset: u32) -> Result { + match offset { + (0x80000000..=0x817FFFFF) => { + Ok(self.mem1_base.get().ok_or(Error {})? + offset.sub(0x80000000)) + } + _ => Err(Error {}), + } + } + + /// Checks if a memory reading operation would exceed the memory bounds of the emulated system. + /// + /// Returns `true` if the read operation can be performed safely, `false` otherwise. + const fn check_bounds(&self, offset: u32) -> bool { + match offset { + (0x80000000..=0x817FFFFF) => offset + size_of::() as u32 <= 0x81800000, + _ => false, + } + } + /// Reads raw data from the emulated RAM ignoring all endianness settings. /// The same call, performed on two different emulators, might return different /// results due to the endianness used by the emulator. /// - /// The offset provided is meant to be the same used on the original, + /// The offset provided is meant to be the same address as mapped on the original, /// big-endian system. /// - /// You can alternatively provide the memory address as usually mapped on the original hardware. /// Valid addresses for the Nintendo Gamecube range from `0x80000000` to `0x817FFFFF`. /// - /// Values below and up to `0x017FFFFF` are automatically assumed to be offsets from the memory's base address. /// Any other invalid value will make this method immediately return `Err()`. /// /// This call is meant to be used by experienced users. pub fn read_ignoring_endianness(&self, offset: u32) -> Result { - if (0x01800000..0x80000000).contains(&offset) || offset >= 0x81800000 { - return Err(Error {}); + match self.check_bounds::(offset) { + true => self.process.read(self.get_address(offset)?), + false => Err(Error {}), } - - let mem1 = self.mem1_base.get().ok_or(Error {})?; - let end_offset = offset.checked_sub(0x80000000).unwrap_or(offset); - - self.process.read(mem1 + end_offset) } - /// Reads any value from the emulated RAM. + /// Reads raw data from the emulated RAM ignoring all endianness settings. + /// The same call, performed on two different emulators, might return different + /// results due to the endianness used by the emulator. /// - /// The offset provided is meant to be the same used on the original, - /// big-endian system. The call will automatically convert the offset and - /// the output value to little endian. + /// The offset provided is meant to be the same address as mapped on the original, + /// big-endian system. /// - /// You can alternatively provide the memory address as usually mapped on the original hardware. /// Valid addresses for the Nintendo Gamecube range from `0x80000000` to `0x817FFFFF`. /// - /// Values below and up to `0x017FFFFF` are automatically assumed to be offsets from the memory's base address. /// Any other invalid value will make this method immediately return `Err()`. pub fn read(&self, offset: u32) -> Result { Ok(self .read_ignoring_endianness::(offset)? .from_endian(self.endian.get())) } + + /// Follows a path of pointers from the address given and reads a value of the type specified from + /// the process at the end of the pointer path. + pub fn read_pointer_path( + &self, + base_address: u32, + path: &[u32], + ) -> Result { + self.read(self.deref_offsets(base_address, path)?) + } + + /// Follows a path of pointers from the address given and returns the address at the end + /// of the pointer path + fn deref_offsets(&self, base_address: u32, path: &[u32]) -> Result { + let mut address = base_address; + let (&last, path) = path.split_last().ok_or(Error {})?; + for &offset in path { + address = self.read::(address + offset)?; + } + Ok(address + last) + } } /// A future that executes a future until the emulator closes. @@ -155,16 +193,20 @@ pub struct UntilEmulatorCloses<'a, F> { future: F, } -impl> Future for UntilEmulatorCloses<'_, F> { - type Output = (); +impl> Future for UntilEmulatorCloses<'_, F> { + type Output = Option; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if !self.emulator.is_open() { - return Poll::Ready(()); + return Poll::Ready(None); } self.emulator.update(); // SAFETY: We are simply projecting the Pin. - unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().future).poll(cx) } + unsafe { + Pin::new_unchecked(&mut self.get_unchecked_mut().future) + .poll(cx) + .map(Some) + } } } @@ -178,4 +220,4 @@ pub enum State { static PROCESS_NAMES: [(&str, State); 2] = [ ("Dolphin.exe", State::Dolphin(dolphin::State)), ("retroarch.exe", State::Retroarch(retroarch::State::new())), -]; +]; \ No newline at end of file diff --git a/src/emulator/genesis/mod.rs b/src/emulator/genesis/mod.rs index 84d5c25..be59882 100644 --- a/src/emulator/genesis/mod.rs +++ b/src/emulator/genesis/mod.rs @@ -3,8 +3,9 @@ use core::{ cell::Cell, future::Future, - mem, + mem::{size_of, MaybeUninit}, pin::Pin, + slice, task::{Context, Poll}, }; @@ -122,21 +123,24 @@ impl Emulator { } } - /// Reads raw data from the emulated RAM ignoring all endianness settings - /// The same call, performed on two different emulators, can be different - /// due to the endianness used by the emulator. + /// Converts a SEGA Genesis memory address to a real memory address in the emulator process' virtual memory space /// - /// The offset provided must not be higher than `0xFFFF`, otherwise this - /// method will immediately return `Err()`. - /// - /// This call is meant to be used by experienced users. - pub fn read_ignoring_endianness(&self, offset: u32) -> Result { - if offset > 0xFFFF { - return Err(Error {}); + /// The offset provided must not be higher than `0xFFFF` + pub fn get_address(&self, offset: u32) -> Result { + match offset { + (0..=0xFFFF) => Ok(self.wram_base.get().ok_or(Error {})? + offset), + _ => Err(Error {}), } + } - let wram = self.wram_base.get().ok_or(Error {})?; - self.process.read(wram + offset) + /// Checks if a memory reading operation would exceed the memory bounds of the emulated system. + /// + /// Returns `true` if the read operation can be performed safely, `false` otherwise. + const fn check_bounds(&self, offset: u32) -> bool { + match offset { + (0..=0xFFFF) => offset + size_of::() as u32 <= 0x10000, + _ => false, + } } /// Reads any value from the emulated RAM. @@ -148,20 +152,58 @@ impl Emulator { /// The offset provided must not be higher than `0xFFFF`, otherwise this /// method will immediately return `Err()`. pub fn read(&self, offset: u32) -> Result { - if (offset > 0xFFFF && offset < 0xFF0000) || offset > 0xFFFFFF { + if !self.check_bounds::(offset) { return Err(Error {}); } - let wram = self.wram_base.get().ok_or(Error {})?; - - let mut end_offset = offset.checked_sub(0xFF0000).unwrap_or(offset); + let aligned_offset = offset & !1; + let Ok(address) = self.get_address(aligned_offset) else { + return Err(Error {}); + }; let endian = self.endian.get(); - let toggle = endian == Endian::Little && mem::size_of::() == 1; - end_offset ^= toggle as u32; + #[derive(Copy, Clone)] + #[repr(packed)] + struct MaybePadded { + _before: MaybeUninit, + value: MaybeUninit, + _after: MaybeUninit, + } + + let misalignment = offset as usize & 1; + let mut padded_value = MaybeUninit::>::uninit(); + + // We always want to read a multiple of 2 bytes, so at the end we need + // to find the next multiple of 2 bytes for T. However because we maybe + // are misaligned, we need to also take that misalignment in the + // opposite direction into account before finding the next multiple of + // two as otherwise we may not read all of T. This would otherwise go + // wrong when e.g. reading a u16 at a misaligned offset. We would start + // at the padding byte before the u16, but if we only read 2 bytes, we + // then would miss the half of the u16. So adding the misalignment of 1 + // on top and then rounding up to the next multiple of 2 bytes leaves us + // with 4 bytes to read, which we can then nicely swap. + let buf = unsafe { + slice::from_raw_parts_mut( + padded_value.as_mut_ptr().byte_add(misalignment ^ 1) as *mut MaybeUninit, + (size_of::() + misalignment).next_multiple_of(2), + ) + }; + + let buf = self.process.read_into_uninit_buf(address, buf)?; - let value = self.process.read::(wram + end_offset)?; - Ok(value.from_endian(endian)) + if endian.eq(&Endian::Little) { + buf.chunks_exact_mut(2).for_each(|chunk| chunk.swap(0, 1)); + } + + unsafe { + let value = padded_value.assume_init_ref().value; + if !T::is_valid_bit_pattern(&*value.as_ptr().cast::()) { + return Err(Error {}); + } + + Ok(value.assume_init().from_be()) + } } } @@ -171,16 +213,20 @@ pub struct UntilEmulatorCloses<'a, F> { future: F, } -impl> Future for UntilEmulatorCloses<'_, F> { - type Output = (); +impl> Future for UntilEmulatorCloses<'_, F> { + type Output = Option; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - if !self.emulator.process.is_open() { - return Poll::Ready(()); + if !self.emulator.is_open() { + return Poll::Ready(None); } self.emulator.update(); // SAFETY: We are simply projecting the Pin. - unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().future).poll(cx) } + unsafe { + Pin::new_unchecked(&mut self.get_unchecked_mut().future) + .poll(cx) + .map(Some) + } } } @@ -207,4 +253,4 @@ static PROCESS_NAMES: [(&str, State); 6] = [ ("Fusion.exe", State::Fusion(fusion::State::new())), ("gens.exe", State::Gens(gens::State)), ("blastem.exe", State::BlastEm(blastem::State)), -]; +]; \ No newline at end of file diff --git a/src/emulator/ps1/mod.rs b/src/emulator/ps1/mod.rs index 8a1250a..9b0ddb8 100644 --- a/src/emulator/ps1/mod.rs +++ b/src/emulator/ps1/mod.rs @@ -3,6 +3,8 @@ use core::{ cell::Cell, future::Future, + mem::size_of, + ops::Sub, pin::Pin, task::{Context, Poll}, }; @@ -126,27 +128,66 @@ impl Emulator { } } + /// Converts a PS1 memory address to a real memory address in the emulator process' virtual memory space + /// + /// Valid addresses for the PS1 range from `0x80000000` to `0x817FFFFF`. + pub fn get_address(&self, offset: u32) -> Result { + match offset { + (0x80000000..=0x817FFFFF) => { + Ok(self.ram_base.get().ok_or(Error {})? + offset.sub(0x80000000)) + } + _ => Err(Error {}), + } + } + + /// Checks if a memory reading operation would exceed the memory bounds of the emulated system. + /// + /// Returns `true` if the read operation can be performed safely, `false` otherwise. + const fn check_bounds(&self, offset: u32) -> bool { + match offset { + (0x80000000..=0x817FFFFF) => offset + size_of::() as u32 <= 0x81800000, + _ => false, + } + } + /// Reads any value from the emulated RAM. /// /// In PS1, memory addresses are usually mapped at fixed locations starting /// from `0x80000000`, and is the way many emulators, as well as the /// GameShark on original hardware, access memory. /// - /// For this reason, this method will automatically convert offsets provided - /// in such format. For example, providing an offset of `0x1234` or - /// `0x80001234` will return the same value. + /// The offset provided is meant to be the same memory address as usually + /// mapped on the original hardware. Valid addresses range from `0x80000000` + /// to `0x817FFFFF`. /// /// Providing any offset outside the range of the PS1's RAM will return /// `Err()`. pub fn read(&self, offset: u32) -> Result { - if (offset > 0x1FFFFF && offset < 0x80000000) || offset > 0x801FFFFF { - return Err(Error {}); - }; + match self.check_bounds::(offset) { + true => self.process.read(self.get_address(offset)?), + false => Err(Error {}), + } + } - let ram_base = self.ram_base.get().ok_or(Error {})?; - let end_offset = offset.checked_sub(0x80000000).unwrap_or(offset); + /// Follows a path of pointers from the address given and reads a value of the type specified from + /// the process at the end of the pointer path. + pub fn read_pointer_path( + &self, + base_address: u32, + path: &[u32], + ) -> Result { + self.read(self.deref_offsets(base_address, path)?) + } - self.process.read(ram_base + end_offset) + /// Follows a path of pointers from the address given and returns the address at the end + /// of the pointer path + fn deref_offsets(&self, base_address: u32, path: &[u32]) -> Result { + let mut address = base_address; + let (&last, path) = path.split_last().ok_or(Error {})?; + for &offset in path { + address = self.read::(address + offset)?; + } + Ok(address + last) } } @@ -156,16 +197,20 @@ pub struct UntilEmulatorCloses<'a, F> { future: F, } -impl> Future for UntilEmulatorCloses<'_, F> { - type Output = (); +impl> Future for UntilEmulatorCloses<'_, F> { + type Output = Option; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if !self.emulator.is_open() { - return Poll::Ready(()); + return Poll::Ready(None); } self.emulator.update(); // SAFETY: We are simply projecting the Pin. - unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().future).poll(cx) } + unsafe { + Pin::new_unchecked(&mut self.get_unchecked_mut().future) + .poll(cx) + .map(Some) + } } } @@ -199,4 +244,4 @@ const PROCESS_NAMES: [(&str, State); 8] = [ ), ("XEBRA.EXE", State::Xebra(xebra::State)), ("mednafen.exe", State::Mednafen(mednafen::State)), -]; +]; \ No newline at end of file diff --git a/src/emulator/ps2/mod.rs b/src/emulator/ps2/mod.rs index b734dd5..5291dec 100644 --- a/src/emulator/ps2/mod.rs +++ b/src/emulator/ps2/mod.rs @@ -3,6 +3,8 @@ use core::{ cell::Cell, future::Future, + mem::size_of, + ops::Sub, pin::Pin, task::{Context, Poll}, }; @@ -101,27 +103,29 @@ impl Emulator { } } - /// Reads any value from the emulated RAM. - /// - /// In PS2, memory addresses are mapped at fixed locations starting - /// from `0x00100000` (addresses below this threashold are - /// reserved for the kernel). + /// Converts a PS2 memory address to a real memory address in the emulator process' virtual memory space /// - /// Valid addresses for the PS2's memory range from `0x00100000` to `0x01FFFFFF` - /// - /// Providing any offset outside the range of the PS2's RAM will return - /// `Err()`. - pub fn read(&self, address: u32) -> Result { - if !(0x00100000..0x02000000).contains(&address) { - return Err(Error {}); + /// Valid addresses for the PS2 range from `0x00100000` to `0x01FFFFFF`. + pub fn get_address(&self, offset: u32) -> Result { + match offset { + (0x00100000..=0x01FFFFFF) => { + Ok(self.ram_base.get().ok_or(Error {})? + offset.sub(0x00100000)) + } + _ => Err(Error {}), } + } - let ram_base = self.ram_base.get().ok_or(Error {})?; - self.process.read(ram_base + address) + /// Checks if a memory reading operation would exceed the memory bounds of the emulated system. + /// + /// Returns `true` if the read operation can be performed safely, `false` otherwise. + const fn check_bounds(&self, offset: u32) -> bool { + match offset { + (0x00100000..=0x01FFFFFF) => offset + size_of::() as u32 <= 0x02000000, + _ => false, + } } - /// Follows a path of pointers from the base address given and reads a value of the - /// type specified at the end of the pointer path. + /// Reads any value from the emulated RAM. /// /// In PS2, memory addresses are mapped at fixed locations starting /// from `0x00100000` (addresses below this threashold are @@ -131,17 +135,32 @@ impl Emulator { /// /// Providing any offset outside the range of the PS2's RAM will return /// `Err()`. + pub fn read(&self, offset: u32) -> Result { + match self.check_bounds::(offset) { + true => self.process.read(self.get_address(offset)?), + false => Err(Error {}), + } + } + + /// Follows a path of pointers from the address given and reads a value of the type specified from + /// the process at the end of the pointer path. pub fn read_pointer_path( &self, base_address: u32, path: &[u32], ) -> Result { + self.read(self.deref_offsets(base_address, path)?) + } + + /// Follows a path of pointers from the address given and returns the address at the end + /// of the pointer path + fn deref_offsets(&self, base_address: u32, path: &[u32]) -> Result { let mut address = base_address; let (&last, path) = path.split_last().ok_or(Error {})?; for &offset in path { - address = self.read(address + offset)?; + address = self.read::(address + offset)?; } - self.read(address + last) + Ok(address + last) } } @@ -151,16 +170,20 @@ pub struct UntilEmulatorCloses<'a, F> { future: F, } -impl> Future for UntilEmulatorCloses<'_, F> { - type Output = (); +impl> Future for UntilEmulatorCloses<'_, F> { + type Output = Option; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if !self.emulator.is_open() { - return Poll::Ready(()); + return Poll::Ready(None); } self.emulator.update(); // SAFETY: We are simply projecting the Pin. - unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().future).poll(cx) } + unsafe { + Pin::new_unchecked(&mut self.get_unchecked_mut().future) + .poll(cx) + .map(Some) + } } } @@ -178,4 +201,4 @@ const PROCESS_NAMES: [(&str, State); 6] = [ ("pcsx2-avx2.exe", State::Pcsx2(pcsx2::State::new())), ("pcsx2.exe", State::Pcsx2(pcsx2::State::new())), ("retroarch.exe", State::Retroarch(retroarch::State::new())), -]; +]; \ No newline at end of file diff --git a/src/emulator/sms/mod.rs b/src/emulator/sms/mod.rs index 8b81e24..0daca58 100644 --- a/src/emulator/sms/mod.rs +++ b/src/emulator/sms/mod.rs @@ -3,6 +3,8 @@ use core::{ cell::Cell, future::Future, + mem::size_of, + ops::Sub, pin::Pin, task::{Context, Poll}, }; @@ -109,6 +111,26 @@ impl Emulator { } } + /// Converts a SEGA Master System memory address to a real memory address in the emulator process' virtual memory space + /// + /// Valid addresses for the SMS range from `0xC000` to `0xDFFF`. + pub fn get_address(&self, offset: u32) -> Result { + match offset { + (0xC000..=0xDFFF) => Ok(self.ram_base.get().ok_or(Error {})? + offset.sub(0xC000)), + _ => Err(Error {}), + } + } + + /// Checks if a memory reading operation would exceed the memory bounds of the emulated system. + /// + /// Returns `true` if the read operation can be performed safely, `false` otherwise. + const fn check_bounds(&self, offset: u32) -> bool { + match offset { + (0xC000..=0xDFFF) => offset + size_of::() as u32 <= 0xE000, + _ => false, + } + } + /// Reads any value from the emulated RAM. /// /// The offset provided is meant to be the same used on the original hardware. @@ -118,14 +140,10 @@ impl Emulator { /// /// Providing any offset outside this range will return `Err()`. pub fn read(&self, offset: u32) -> Result { - if (offset > 0x1FFF && offset < 0xC000) || offset > 0xDFFF { - return Err(Error {}); + match self.check_bounds::(offset) { + true => self.process.read(self.get_address(offset)?), + false => Err(Error {}), } - - let wram = self.ram_base.get().ok_or(Error {})?; - let end_offset = offset.checked_sub(0xC000).unwrap_or(offset); - - self.process.read(wram + end_offset) } } @@ -135,16 +153,20 @@ pub struct UntilEmulatorCloses<'a, F> { future: F, } -impl> Future for UntilEmulatorCloses<'_, F> { - type Output = (); +impl> Future for UntilEmulatorCloses<'_, F> { + type Output = Option; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if !self.emulator.is_open() { - return Poll::Ready(()); + return Poll::Ready(None); } self.emulator.update(); // SAFETY: We are simply projecting the Pin. - unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().future).poll(cx) } + unsafe { + Pin::new_unchecked(&mut self.get_unchecked_mut().future) + .poll(cx) + .map(Some) + } } } @@ -162,4 +184,4 @@ static PROCESS_NAMES: &[(&str, State)] = &[ ("Fusion.exe", State::Fusion(fusion::State::new())), ("blastem.exe", State::BlastEm(blastem::State)), ("mednafen.exe", State::Mednafen(mednafen::State)), -]; +]; \ No newline at end of file diff --git a/src/emulator/wii/mod.rs b/src/emulator/wii/mod.rs index bf57084..25278f2 100644 --- a/src/emulator/wii/mod.rs +++ b/src/emulator/wii/mod.rs @@ -3,6 +3,8 @@ use core::{ cell::Cell, future::Future, + mem::size_of, + ops::Sub, pin::Pin, task::{Context, Poll}, }; @@ -107,6 +109,35 @@ impl Emulator { } } + /// Converts a Wii memory address to a real memory address in the emulator process' virtual memory space + /// + /// - Valid addresses for `MEM1` range from `0x80000000` to `0x817FFFFF` + /// - Valid addresses for `MEM2` range from `0x90000000` to `0x93FFFFFF` + pub fn get_address(&self, offset: u32) -> Result { + match offset { + (0x80000000..=0x817FFFFF) => { + let [mem1, _] = self.ram_base.get().ok_or(Error {})?; + Ok(mem1 + offset.sub(0x80000000)) + } + (0x90000000..=0x93FFFFFF) => { + let [_, mem2] = self.ram_base.get().ok_or(Error {})?; + Ok(mem2 + offset.sub(0x90000000)) + } + _ => Err(Error {}), + } + } + + /// Checks if a memory reading operation would exceed the memory bounds of the emulated system. + /// + /// Returns `true` if the read operation can be performed safely, `false` otherwise. + const fn check_bounds(&self, offset: u32) -> bool { + match offset { + (0x80000000..=0x817FFFFF) => offset + size_of::() as u32 <= 0x81800000, + (0x90000000..=0x93FFFFFF) => offset + size_of::() as u32 <= 0x94000000, + _ => false, + } + } + /// Reads raw data from the emulated RAM ignoring all endianness settings. /// The same call, performed on two different emulators, might return different /// results due to the endianness used by the emulator. @@ -123,12 +154,9 @@ impl Emulator { /// /// This call is meant to be used by experienced users. pub fn read_ignoring_endianness(&self, address: u32) -> Result { - if (0x80000000..0x81800000).contains(&address) { - self.read_ignoring_endianness_from_mem_1(address) - } else if (0x90000000..0x94000000).contains(&address) { - self.read_ignoring_endianness_from_mem_2(address) - } else { - Err(Error {}) + match self.check_bounds::(address) { + true => self.process.read(self.get_address(address)?), + false => Err(Error {}), } } @@ -151,8 +179,6 @@ impl Emulator { /// Follows a path of pointers from the address given and reads a value of the type specified from /// the process at the end of the pointer path. - /// - /// The end value is automatically converted to little endian if needed. pub fn read_pointer_path( &self, base_address: u32, @@ -161,16 +187,8 @@ impl Emulator { self.read(self.deref_offsets(base_address, path)?) } - /// Follows a path of pointers from the address given and reads a value of the type specified from - /// the process at the end of the pointer path. - pub fn read_pointer_path_ignoring_endianness( - &self, - base_address: u32, - path: &[u32], - ) -> Result { - self.read_ignoring_endianness(self.deref_offsets(base_address, path)?) - } - + /// Follows a path of pointers from the address given and returns the address at the end + /// of the pointer path fn deref_offsets(&self, base_address: u32, path: &[u32]) -> Result { let mut address = base_address; let (&last, path) = path.split_last().ok_or(Error {})?; @@ -179,55 +197,6 @@ impl Emulator { } Ok(address + last) } - - /// Reads raw data from the emulated RAM ignoring all endianness settings. - /// The same call, performed on two different emulators, might return different - /// results due to the endianness used by the emulator. - /// - /// The address provided is meant to be the mapped address used on the original, big-endian system. - /// The call will automatically convert the address provided to its corresponding offset from - /// `MEM1` or and read the value. - /// - /// The provided memory address has to match a mapped memory address on the original Wii. - /// Valid addresses for `MEM1` range from `0x80000000` to `0x817FFFFF` - /// - /// Any other invalid value will make this method immediately return `Err()`. - pub fn read_ignoring_endianness_from_mem_1( - &self, - address: u32, - ) -> Result { - if !(0x80000000..0x81800000).contains(&address) { - return Err(Error {}); - } - - let [mem1, _] = self.ram_base.get().ok_or(Error {})?; - let end_offset = address.checked_sub(0x80000000).unwrap_or(address); - self.process.read(mem1 + end_offset) - } - - /// Reads raw data from the emulated RAM ignoring all endianness settings. - /// The same call, performed on two different emulators, might return different - /// results due to the endianness used by the emulator. - /// - /// The address provided is meant to be the mapped address used on the original, big-endian system. - /// The call will automatically convert the address provided to its corresponding offset from - /// `MEM2` or and read the value. - /// - /// The provided memory address has to match a mapped memory address on the original Wii. - /// Valid addresses for `MEM2` range from `0x90000000` to `0x93FFFFFF` - /// - /// Any other invalid value will make this method immediately return `Err()`. - pub fn read_ignoring_endianness_from_mem_2( - &self, - address: u32, - ) -> Result { - if !(0x90000000..0x94000000).contains(&address) { - return Err(Error {}); - } - let [_, mem2] = self.ram_base.get().ok_or(Error {})?; - let end_offset = address.checked_sub(0x90000000).unwrap_or(address); - self.process.read(mem2 + end_offset) - } } /// A future that executes a future until the emulator closes. @@ -236,16 +205,20 @@ pub struct UntilEmulatorCloses<'a, F> { future: F, } -impl> Future for UntilEmulatorCloses<'_, F> { - type Output = (); +impl> Future for UntilEmulatorCloses<'_, F> { + type Output = Option; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if !self.emulator.is_open() { - return Poll::Ready(()); + return Poll::Ready(None); } self.emulator.update(); // SAFETY: We are simply projecting the Pin. - unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().future).poll(cx) } + unsafe { + Pin::new_unchecked(&mut self.get_unchecked_mut().future) + .poll(cx) + .map(Some) + } } } @@ -259,4 +232,4 @@ pub enum State { static PROCESS_NAMES: [(&str, State); 2] = [ ("Dolphin.exe", State::Dolphin(dolphin::State)), ("retroarch.exe", State::Retroarch(retroarch::State::new())), -]; +]; \ No newline at end of file