diff --git a/src/emulator/gba/mod.rs b/src/emulator/gba/mod.rs index 7a594b1..71847b3 100644 --- a/src/emulator/gba/mod.rs +++ b/src/emulator/gba/mod.rs @@ -1,6 +1,13 @@ //! Support for attaching to Nintendo Gameboy Advance emulators. -use crate::{Address, Error, Process}; +use core::{ + cell::Cell, + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +use crate::{future::retry, Address, Error, Process}; use bytemuck::CheckedBitPattern; mod emuhawk; @@ -15,9 +22,9 @@ pub struct Emulator { /// The attached emulator process process: Process, /// An enum stating which emulator is currently attached - state: State, + state: Cell, /// The memory address of the emulated RAM - ram_base: Option<[Address; 2]>, // [ewram, iwram] + ram_base: Cell>, // [ewram, iwram] } impl Emulator { @@ -40,24 +47,50 @@ impl Emulator { Some(Self { process, - state, - ram_base: None, + state: Cell::new(state), + ram_base: Cell::new(None), }) } + /// Asynchronously awaits attaching to a target emulator, + /// yielding back to the runtime between each try. + /// + /// Supported emulators are: + /// - VisualBoyAdvance + /// - VisualBoyAdvance-M + /// - mGBA + /// - NO$GBA + /// - BizHawk + /// - Retroarch, with one of the following cores: `vbam_libretro.dll`, `vba_next_libretro.dll`, + /// `mednafen_gba_libretro.dll`, `mgba_libretro.dll`, `gpsp_libretro.dll` + pub async fn wait_attach() -> Self { + retry(Self::attach).await + } + /// Checks whether the emulator is still open. If it is not open anymore, /// you should drop the emulator. pub fn is_open(&self) -> bool { self.process.is_open() } + /// Executes a future until the emulator process closes. + pub const fn until_closes(&self, future: F) -> UntilEmulatorCloses<'_, F> { + UntilEmulatorCloses { + emulator: self, + future, + } + } + /// Calls the internal routines needed in order to find (and update, if /// needed) the address of the emulated RAM. /// /// Returns true if successful, false otherwise. - pub fn update(&mut self) -> bool { - if self.ram_base.is_none() { - self.ram_base = match match &mut self.state { + pub fn update(&self) -> bool { + let mut ram_base = self.ram_base.get(); + let mut state = self.state.get(); + + if ram_base.is_none() { + ram_base = match match &mut state { State::VisualBoyAdvance(x) => x.find_ram(&self.process), State::Mgba(x) => x.find_ram(&self.process), State::NoCashGba(x) => x.find_ram(&self.process), @@ -70,21 +103,23 @@ impl Emulator { }; } - let success = match &self.state { - State::VisualBoyAdvance(x) => x.keep_alive(&self.process, &mut self.ram_base), - State::Mgba(x) => x.keep_alive(&self.process, &self.ram_base), - State::NoCashGba(x) => x.keep_alive(&self.process, &mut self.ram_base), + let success = match &state { + State::VisualBoyAdvance(x) => x.keep_alive(&self.process, &mut ram_base), + State::Mgba(x) => x.keep_alive(&self.process, &ram_base), + State::NoCashGba(x) => x.keep_alive(&self.process, &mut ram_base), State::Retroarch(x) => x.keep_alive(&self.process), - State::EmuHawk(x) => x.keep_alive(&self.process, &self.ram_base), - State::Mednafen(x) => x.keep_alive(&self.process, &mut self.ram_base), + State::EmuHawk(x) => x.keep_alive(&self.process, &ram_base), + State::Mednafen(x) => x.keep_alive(&self.process, &mut ram_base), }; - match success { - true => true, - false => { - self.ram_base = None; - false - } + self.state.set(state); + + if success { + self.ram_base.set(ram_base); + true + } else { + self.ram_base.set(None); + false } } @@ -120,9 +155,7 @@ impl Emulator { return Err(Error {}); } - let Some([ewram, _]) = self.ram_base else { - return Err(Error {}); - }; + 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) @@ -144,15 +177,32 @@ impl Emulator { return Err(Error {}); } - let Some([_, iwram]) = self.ram_base else { - return Err(Error {}); - }; + let [_, iwram] = self.ram_base.get().ok_or(Error {})?; let end_offset = offset.checked_sub(0x03000000).unwrap_or(offset); self.process.read(iwram + end_offset) } } +/// A future that executes a future until the emulator closes. +pub struct UntilEmulatorCloses<'a, F> { + emulator: &'a Emulator, + future: F, +} + +impl> Future for UntilEmulatorCloses<'_, F> { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if !self.emulator.is_open() { + return Poll::Ready(()); + } + self.emulator.update(); + // SAFETY: We are simply projecting the Pin. + unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().future).poll(cx) } + } +} + #[doc(hidden)] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum State { diff --git a/src/emulator/gcn/mod.rs b/src/emulator/gcn/mod.rs index 9920d89..c6f13cf 100644 --- a/src/emulator/gcn/mod.rs +++ b/src/emulator/gcn/mod.rs @@ -1,6 +1,13 @@ //! Support for attaching to Nintendo Gamecube emulators. -use crate::{Address, Endian, Error, FromEndian, Process}; +use core::{ + cell::Cell, + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +use crate::{future::retry, Address, Endian, Error, FromEndian, Process}; use bytemuck::CheckedBitPattern; mod dolphin; @@ -11,11 +18,11 @@ pub struct Emulator { /// The attached emulator process process: Process, /// An enum stating which emulator is currently attached - state: State, + state: Cell, /// The memory address of the emulated RAM - mem1_base: Option
, + mem1_base: Cell>, /// The endianness used by the emulator process - endian: Endian, + endian: Cell, } impl Emulator { @@ -33,44 +40,70 @@ impl Emulator { Some(Self { process, - state, - mem1_base: None, - endian: Endian::Big, // Endianness is usually Big across all GCN emulators + state: Cell::new(state), + mem1_base: Cell::new(None), + endian: Cell::new(Endian::Big), // Endianness is usually Big across all GCN emulators }) } + /// Asynchronously awaits attaching to a target emulator, + /// yielding back to the runtime between each try. + /// + /// Supported emulators are: + /// - Dolphin + /// - Retroarch (using the `dolphin_libretro.dll` core) + pub async fn wait_attach() -> Self { + retry(Self::attach).await + } + /// Checks whether the emulator is still open. If it is not open anymore, /// you should drop the emulator. pub fn is_open(&self) -> bool { self.process.is_open() } + /// Executes a future until the emulator process closes. + pub const fn until_closes(&self, future: F) -> UntilEmulatorCloses<'_, F> { + UntilEmulatorCloses { + emulator: self, + future, + } + } + /// Calls the internal routines needed in order to find (and update, if /// needed) the address of the emulated RAM. /// /// Returns true if successful, false otherwise. - pub fn update(&mut self) -> bool { - if self.mem1_base.is_none() { - let mem1_base = match &mut self.state { - State::Dolphin(x) => x.find_ram(&self.process, &mut self.endian), - State::Retroarch(x) => x.find_ram(&self.process, &mut self.endian), + pub fn update(&self) -> bool { + let mut mem1_base = self.mem1_base.get(); + let mut state = self.state.get(); + let mut endian = self.endian.get(); + + if mem1_base.is_none() { + mem1_base = match match &mut state { + State::Dolphin(x) => x.find_ram(&self.process, &mut endian), + State::Retroarch(x) => x.find_ram(&self.process, &mut endian), + } { + None => return false, + something => something, }; - if mem1_base.is_none() { - return false; - } - self.mem1_base = mem1_base; } - let success = match &self.state { - State::Dolphin(x) => x.keep_alive(&self.process, &self.mem1_base), - State::Retroarch(x) => x.keep_alive(&self.process, &self.mem1_base), + let success = match &state { + State::Dolphin(x) => x.keep_alive(&self.process, &mem1_base), + State::Retroarch(x) => x.keep_alive(&self.process, &mem1_base), }; - if !success { - self.mem1_base = None; - } + self.state.set(state); + self.endian.set(endian); - success + if success { + self.mem1_base.set(mem1_base); + true + } else { + self.mem1_base.set(None); + false + } } /// Reads raw data from the emulated RAM ignoring all endianness settings. @@ -92,7 +125,7 @@ impl Emulator { return Err(Error {}); } - let mem1 = self.mem1_base.ok_or(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) @@ -112,7 +145,26 @@ impl Emulator { pub fn read(&self, offset: u32) -> Result { Ok(self .read_ignoring_endianness::(offset)? - .from_endian(self.endian)) + .from_endian(self.endian.get())) + } +} + +/// A future that executes a future until the emulator closes. +pub struct UntilEmulatorCloses<'a, F> { + emulator: &'a Emulator, + future: F, +} + +impl> Future for UntilEmulatorCloses<'_, F> { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if !self.emulator.is_open() { + return Poll::Ready(()); + } + self.emulator.update(); + // SAFETY: We are simply projecting the Pin. + unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().future).poll(cx) } } } diff --git a/src/emulator/genesis/mod.rs b/src/emulator/genesis/mod.rs index a9cc85f..84d5c25 100644 --- a/src/emulator/genesis/mod.rs +++ b/src/emulator/genesis/mod.rs @@ -1,8 +1,14 @@ //! Support for attaching to SEGA Genesis emulators. -use core::mem; - -use crate::{Address, Endian, Error, FromEndian, Process}; +use core::{ + cell::Cell, + future::Future, + mem, + pin::Pin, + task::{Context, Poll}, +}; + +use crate::{future::retry, Address, Endian, Error, FromEndian, Process}; use bytemuck::CheckedBitPattern; mod blastem; @@ -16,11 +22,11 @@ pub struct Emulator { /// The attached emulator process process: Process, /// An enum stating which emulator is currently attached - state: State, + state: Cell, /// The memory address of the emulated RAM - wram_base: Option
, + wram_base: Cell>, /// The endianness used by the emulator process - endian: Endian, + endian: Cell, } impl Emulator { @@ -41,48 +47,77 @@ impl Emulator { Some(Self { process, - state, - wram_base: None, - endian: Endian::Little, // Endianness is supposed to be Little, until stated otherwise in the code + state: Cell::new(state), + wram_base: Cell::new(None), + endian: Cell::new(Endian::Little), // Endianness is supposed to be Little, until stated otherwise in the code }) } + /// Asynchronously awaits attaching to a target emulator, + /// yielding back to the runtime between each try. + /// + /// Supported emulators are: + /// - Retroarch + /// - SEGA Classics / SEGA Game Room + /// - Fusion + /// - Gens + /// - BlastEm + pub async fn wait_attach() -> Self { + retry(Self::attach).await + } + /// Checks whether the emulator is still open. If it is not open anymore, /// you should drop the emulator. pub fn is_open(&self) -> bool { self.process.is_open() } + /// Executes a future until the emulator process closes. + pub const fn until_closes(&self, future: F) -> UntilEmulatorCloses<'_, F> { + UntilEmulatorCloses { + emulator: self, + future, + } + } + /// Calls the internal routines needed in order to find (and update, if /// needed) the address of the emulated RAM. /// /// Returns true if successful, false otherwise. - pub fn update(&mut self) -> bool { - if self.wram_base.is_none() { - self.wram_base = match match &mut self.state { - State::Retroarch(x) => x.find_wram(&self.process, &mut self.endian), - State::SegaClassics(x) => x.find_wram(&self.process, &mut self.endian), - State::Fusion(x) => x.find_wram(&self.process, &mut self.endian), - State::Gens(x) => x.find_wram(&self.process, &mut self.endian), - State::BlastEm(x) => x.find_wram(&self.process, &mut self.endian), + pub fn update(&self) -> bool { + let mut wram_base = self.wram_base.get(); + let mut endian = self.endian.get(); + let mut state = self.state.get(); + + if wram_base.is_none() { + wram_base = match match &mut state { + State::Retroarch(x) => x.find_wram(&self.process, &mut endian), + State::SegaClassics(x) => x.find_wram(&self.process, &mut endian), + State::Fusion(x) => x.find_wram(&self.process, &mut endian), + State::Gens(x) => x.find_wram(&self.process, &mut endian), + State::BlastEm(x) => x.find_wram(&self.process, &mut endian), } { None => return false, something => something, }; } - let success = match &self.state { + let success = match &state { State::Retroarch(x) => x.keep_alive(&self.process), - State::SegaClassics(x) => x.keep_alive(&self.process, &mut self.wram_base), - State::Fusion(x) => x.keep_alive(&self.process, &mut self.wram_base), + State::SegaClassics(x) => x.keep_alive(&self.process, &mut wram_base), + State::Fusion(x) => x.keep_alive(&self.process, &mut wram_base), State::Gens(x) => x.keep_alive(), State::BlastEm(x) => x.keep_alive(), }; + self.endian.set(endian); + self.state.set(state); + if success { + self.wram_base.set(wram_base); true } else { - self.wram_base = None; + self.wram_base.set(None); false } } @@ -100,8 +135,7 @@ impl Emulator { return Err(Error {}); } - let wram = self.wram_base.ok_or(Error {})?; - + let wram = self.wram_base.get().ok_or(Error {})?; self.process.read(wram + offset) } @@ -118,15 +152,35 @@ impl Emulator { return Err(Error {}); } - let wram = self.wram_base.ok_or(Error {})?; + let wram = self.wram_base.get().ok_or(Error {})?; let mut end_offset = offset.checked_sub(0xFF0000).unwrap_or(offset); + let endian = self.endian.get(); - let toggle = self.endian == Endian::Little && mem::size_of::() == 1; + let toggle = endian == Endian::Little && mem::size_of::() == 1; end_offset ^= toggle as u32; let value = self.process.read::(wram + end_offset)?; - Ok(value.from_endian(self.endian)) + Ok(value.from_endian(endian)) + } +} + +/// A future that executes a future until the emulator closes. +pub struct UntilEmulatorCloses<'a, F> { + emulator: &'a Emulator, + future: F, +} + +impl> Future for UntilEmulatorCloses<'_, F> { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if !self.emulator.process.is_open() { + return Poll::Ready(()); + } + self.emulator.update(); + // SAFETY: We are simply projecting the Pin. + unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().future).poll(cx) } } } diff --git a/src/emulator/ps1/mod.rs b/src/emulator/ps1/mod.rs index b63251d..8a1250a 100644 --- a/src/emulator/ps1/mod.rs +++ b/src/emulator/ps1/mod.rs @@ -1,6 +1,13 @@ //! Support for attaching to Playstation 1 emulators. -use crate::{Address, Error, Process}; +use core::{ + cell::Cell, + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +use crate::{future::retry, Address, Error, Process}; use bytemuck::CheckedBitPattern; mod duckstation; @@ -16,9 +23,9 @@ pub struct Emulator { /// The attached emulator process process: Process, /// An enum stating which emulator is currently attached - state: State, + state: Cell, /// The memory address of the emulated RAM - ram_base: Option
, + ram_base: Cell>, } impl Emulator { @@ -30,6 +37,7 @@ impl Emulator { /// - ePSXe /// - pSX /// - Duckstation + /// - Mednafen /// - Retroarch (supported cores: Beetle-PSX, Swanstation, PCSX ReARMed) /// - PCSX-redux /// - XEBRA @@ -40,24 +48,50 @@ impl Emulator { Some(Self { process, - state, - ram_base: None, + state: Cell::new(state), + ram_base: Cell::new(None), }) } + /// Asynchronously awaits attaching to a target emulator, + /// yielding back to the runtime between each try. + /// + /// Supported emulators are: + /// - ePSXe + /// - pSX + /// - Duckstation + /// - Mednafen + /// - Retroarch (supported cores: Beetle-PSX, Swanstation, PCSX ReARMed) + /// - PCSX-redux + /// - XEBRA + pub async fn wait_attach() -> Self { + retry(Self::attach).await + } + /// Checks whether the emulator is still open. If it is not open anymore, /// you should drop the emulator. pub fn is_open(&self) -> bool { self.process.is_open() } + /// Executes a future until the emulator process closes. + pub const fn until_closes(&self, future: F) -> UntilEmulatorCloses<'_, F> { + UntilEmulatorCloses { + emulator: self, + future, + } + } + /// Calls the internal routines needed in order to find (and update, if /// needed) the address of the emulated RAM. /// /// Returns true if successful, false otherwise. - pub fn update(&mut self) -> bool { - if self.ram_base.is_none() { - self.ram_base = match match &mut self.state { + pub fn update(&self) -> bool { + let mut ram_base = self.ram_base.get(); + let mut state = self.state.get(); + + if ram_base.is_none() { + ram_base = match match &mut state { State::Epsxe(x) => x.find_ram(&self.process), State::PsxFin(x) => x.find_ram(&self.process), State::Duckstation(x) => x.find_ram(&self.process), @@ -71,20 +105,23 @@ impl Emulator { }; } - let success = match &self.state { + let success = match &state { State::Epsxe(x) => x.keep_alive(), State::PsxFin(x) => x.keep_alive(), - State::Duckstation(x) => x.keep_alive(&self.process, &mut self.ram_base), + State::Duckstation(x) => x.keep_alive(&self.process, &mut ram_base), State::Retroarch(x) => x.keep_alive(&self.process), State::PcsxRedux(x) => x.keep_alive(&self.process), State::Xebra(x) => x.keep_alive(), State::Mednafen(x) => x.keep_alive(), }; + self.state.set(state); + if success { + self.ram_base.set(ram_base); true } else { - self.ram_base = None; + self.ram_base.set(None); false } } @@ -106,16 +143,32 @@ impl Emulator { return Err(Error {}); }; - let Some(ram_base) = self.ram_base else { - return Err(Error {}); - }; - + let ram_base = self.ram_base.get().ok_or(Error {})?; let end_offset = offset.checked_sub(0x80000000).unwrap_or(offset); self.process.read(ram_base + end_offset) } } +/// A future that executes a future until the emulator closes. +pub struct UntilEmulatorCloses<'a, F> { + emulator: &'a Emulator, + future: F, +} + +impl> Future for UntilEmulatorCloses<'_, F> { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if !self.emulator.is_open() { + return Poll::Ready(()); + } + self.emulator.update(); + // SAFETY: We are simply projecting the Pin. + unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().future).poll(cx) } + } +} + #[doc(hidden)] #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum State { diff --git a/src/emulator/ps2/mod.rs b/src/emulator/ps2/mod.rs index 77c4b6d..b734dd5 100644 --- a/src/emulator/ps2/mod.rs +++ b/src/emulator/ps2/mod.rs @@ -1,6 +1,13 @@ //! Support for attaching to Playstation 2 emulators. -use crate::{Address, Error, Process}; +use core::{ + cell::Cell, + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +use crate::{future::retry, Address, Error, Process}; use bytemuck::CheckedBitPattern; mod pcsx2; @@ -11,9 +18,9 @@ pub struct Emulator { /// The attached emulator process process: Process, /// An enum stating which emulator is currently attached - state: State, + state: Cell, /// The memory address of the emulated RAM - ram_base: Option
, + ram_base: Cell>, } impl Emulator { @@ -31,43 +38,67 @@ impl Emulator { Some(Self { process, - state, - ram_base: None, + state: Cell::new(state), + ram_base: Cell::new(None), }) } + /// Asynchronously awaits attaching to a target emulator, + /// yielding back to the runtime between each try. + /// + /// Supported emulators are: + /// - PCSX2 + /// - Retroarch (64-bit version, using the `pcsx2_libretro.dll` core) + pub async fn wait_attach() -> Self { + retry(Self::attach).await + } + /// Checks whether the emulator is still open. If it is not open anymore, /// you should drop the emulator. pub fn is_open(&self) -> bool { self.process.is_open() } + /// Executes a future until the emulator process closes. + pub const fn until_closes(&self, future: F) -> UntilEmulatorCloses<'_, F> { + UntilEmulatorCloses { + emulator: self, + future, + } + } + /// Calls the internal routines needed in order to find (and update, if /// needed) the address of the emulated RAM. /// /// Returns true if successful, false otherwise. - pub fn update(&mut self) -> bool { - if self.ram_base.is_none() { - let ram_base = match &mut self.state { + pub fn update(&self) -> bool { + let mut ram_base = self.ram_base.get(); + let mut state = self.state.get(); + + if ram_base.is_none() { + ram_base = match match &mut state { State::Pcsx2(x) => x.find_ram(&self.process), State::Retroarch(x) => x.find_ram(&self.process), + } { + None => return false, + something => something, }; - if ram_base.is_none() { - return false; - } - self.ram_base = ram_base; } - let success = match &self.state { - State::Pcsx2(x) => x.keep_alive(&self.process, &mut self.ram_base), + let success = match &state { + State::Pcsx2(x) => x.keep_alive(&self.process, &mut ram_base), State::Retroarch(x) => x.keep_alive(&self.process), }; - if !success { - self.ram_base = None; - } + self.state.set(state); - success + if success { + self.ram_base.set(ram_base); + true + } else { + self.ram_base.set(None); + false + } } /// Reads any value from the emulated RAM. @@ -81,14 +112,11 @@ impl Emulator { /// Providing any offset outside the range of the PS2's RAM will return /// `Err()`. pub fn read(&self, address: u32) -> Result { - if !(0x00100000..=0x01FFFFFF).contains(&address) { + if !(0x00100000..0x02000000).contains(&address) { return Err(Error {}); } - let Some(ram_base) = self.ram_base else { - return Err(Error {}); - }; - + let ram_base = self.ram_base.get().ok_or(Error {})?; self.process.read(ram_base + address) } @@ -117,6 +145,25 @@ impl Emulator { } } +/// A future that executes a future until the emulator closes. +pub struct UntilEmulatorCloses<'a, F> { + emulator: &'a Emulator, + future: F, +} + +impl> Future for UntilEmulatorCloses<'_, F> { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if !self.emulator.is_open() { + return Poll::Ready(()); + } + self.emulator.update(); + // SAFETY: We are simply projecting the Pin. + unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().future).poll(cx) } + } +} + #[doc(hidden)] #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum State { diff --git a/src/emulator/sms/mod.rs b/src/emulator/sms/mod.rs index 99206ee..8b81e24 100644 --- a/src/emulator/sms/mod.rs +++ b/src/emulator/sms/mod.rs @@ -1,6 +1,13 @@ //! Support for attaching to SEGA Master System / SEGA GameGear emulators. -use crate::{Address, Error, Process}; +use core::{ + cell::Cell, + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +use crate::{future::retry, Address, Error, Process}; use bytemuck::CheckedBitPattern; mod blastem; @@ -13,9 +20,9 @@ pub struct Emulator { /// The attached emulator process process: Process, /// An enum stating which emulator is currently attached - state: State, + state: Cell, /// The memory address of the emulated RAM - ram_base: Option
, + ram_base: Cell>, } impl Emulator { @@ -35,24 +42,47 @@ impl Emulator { Some(Self { process, - state, - ram_base: None, + state: Cell::new(state), + ram_base: Cell::new(None), }) } + /// Asynchronously awaits attaching to a target emulator, + /// yielding back to the runtime between each try. + /// + /// Supported emulators are: + /// - Retroarch, with one of the following cores: `genesis_plus_gx_libretro.dll`, + /// `genesis_plus_gx_wide_libretro.dll`, `picodrive_libretro.dll`, `smsplus_libretro.dll`, `gearsystem_libretro.dll` + /// - Fusion + /// - BlastEm + pub async fn wait_attach() -> Self { + retry(Self::attach).await + } + /// Checks whether the emulator is still open. If it is not open anymore, /// you should drop the emulator. pub fn is_open(&self) -> bool { self.process.is_open() } + /// Executes a future until the emulator process closes. + pub const fn until_closes(&self, future: F) -> UntilEmulatorCloses<'_, F> { + UntilEmulatorCloses { + emulator: self, + future, + } + } + /// Calls the internal routines needed in order to find (and update, if /// needed) the address of the emulated RAM. /// /// Returns true if successful, false otherwise. - pub fn update(&mut self) -> bool { - if self.ram_base.is_none() { - self.ram_base = match match &mut self.state { + pub fn update(&self) -> bool { + let mut ram_base = self.ram_base.get(); + let mut state = self.state.get(); + + if ram_base.is_none() { + ram_base = match match &mut state { State::Retroarch(x) => x.find_ram(&self.process), State::Fusion(x) => x.find_ram(&self.process), State::BlastEm(x) => x.find_ram(&self.process), @@ -63,17 +93,18 @@ impl Emulator { }; } - let success = match &self.state { + let success = match &state { State::Retroarch(x) => x.keep_alive(&self.process), - State::Fusion(x) => x.keep_alive(&self.process, &mut self.ram_base), + State::Fusion(x) => x.keep_alive(&self.process, &mut ram_base), State::BlastEm(x) => x.keep_alive(), State::Mednafen(x) => x.keep_alive(), }; if success { + self.ram_base.set(ram_base); true } else { - self.ram_base = None; + self.ram_base.set(None); false } } @@ -91,13 +122,32 @@ impl Emulator { return Err(Error {}); } - let wram = self.ram_base.ok_or(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) } } +/// A future that executes a future until the emulator closes. +pub struct UntilEmulatorCloses<'a, F> { + emulator: &'a Emulator, + future: F, +} + +impl> Future for UntilEmulatorCloses<'_, F> { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if !self.emulator.is_open() { + return Poll::Ready(()); + } + self.emulator.update(); + // SAFETY: We are simply projecting the Pin. + unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().future).poll(cx) } + } +} + #[doc(hidden)] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum State { diff --git a/src/emulator/wii/mod.rs b/src/emulator/wii/mod.rs index 6c11cfe..bf57084 100644 --- a/src/emulator/wii/mod.rs +++ b/src/emulator/wii/mod.rs @@ -1,6 +1,13 @@ //! Support for attaching to Nintendo Wii emulators. -use crate::{Address, Endian, Error, FromEndian, Process}; +use core::{ + cell::Cell, + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +use crate::{future::retry, Address, Endian, Error, FromEndian, Process}; use bytemuck::CheckedBitPattern; mod dolphin; @@ -12,11 +19,11 @@ pub struct Emulator { /// The attached emulator process process: Process, /// An enum stating which emulator is currently attached - state: State, + state: Cell, /// The memory address of the emulated RAM - ram_base: Option<[Address; 2]>, // [MEM1, MEM2] + ram_base: Cell>, // [MEM1, MEM2] /// The endianness used by the emulator process - endian: Endian, + endian: Cell, } impl Emulator { @@ -34,42 +41,68 @@ impl Emulator { Some(Self { process, - state, - ram_base: None, // [MEM1, MEM2] - endian: Endian::Big, // Endianness is usually Big in Wii emulators + state: Cell::new(state), + ram_base: Cell::new(None), // [MEM1, MEM2] + endian: Cell::new(Endian::Big), // Endianness is usually Big in Wii emulators }) } + /// Asynchronously awaits attaching to a target emulator, + /// yielding back to the runtime between each try. + /// + /// Supported emulators are: + /// - Dolphin + /// - Retroarch (using the `dolphin_libretro.dll` core) + pub async fn wait_attach() -> Self { + retry(Self::attach).await + } + /// Checks whether the emulator is still open. If it is not open anymore, /// you should drop the emulator. pub fn is_open(&self) -> bool { self.process.is_open() } + /// Executes a future until the emulator process closes. + pub const fn until_closes(&self, future: F) -> UntilEmulatorCloses<'_, F> { + UntilEmulatorCloses { + emulator: self, + future, + } + } + /// Calls the internal routines needed in order to find (and update, if /// needed) the address of the emulated RAM. /// /// Returns true if successful, false otherwise. - pub fn update(&mut self) -> bool { - if self.ram_base.is_none() { - self.ram_base = match match &mut self.state { - State::Dolphin(x) => x.find_ram(&self.process, &mut self.endian), - State::Retroarch(x) => x.find_ram(&self.process, &mut self.endian), + pub fn update(&self) -> bool { + let mut ram_base = self.ram_base.get(); + let mut state = self.state.get(); + let mut endian = self.endian.get(); + + if ram_base.is_none() { + ram_base = match match &mut state { + State::Dolphin(x) => x.find_ram(&self.process, &mut endian), + State::Retroarch(x) => x.find_ram(&self.process, &mut endian), } { None => return false, something => something, }; } - let success = match &self.state { - State::Dolphin(x) => x.keep_alive(&self.process, &self.ram_base), - State::Retroarch(x) => x.keep_alive(&self.process, &self.ram_base), + let success = match &state { + State::Dolphin(x) => x.keep_alive(&self.process, &ram_base), + State::Retroarch(x) => x.keep_alive(&self.process, &ram_base), }; + self.state.set(state); + self.endian.set(endian); + if success { + self.ram_base.set(ram_base); true } else { - self.ram_base = None; + self.ram_base.set(None); false } } @@ -90,9 +123,9 @@ impl Emulator { /// /// This call is meant to be used by experienced users. pub fn read_ignoring_endianness(&self, address: u32) -> Result { - if address >= 0x80000000 && address <= 0x817FFFFF { + if (0x80000000..0x81800000).contains(&address) { self.read_ignoring_endianness_from_mem_1(address) - } else if address >= 0x90000000 && address <= 0x93FFFFFF { + } else if (0x90000000..0x94000000).contains(&address) { self.read_ignoring_endianness_from_mem_2(address) } else { Err(Error {}) @@ -113,7 +146,7 @@ impl Emulator { pub fn read(&self, address: u32) -> Result { Ok(self .read_ignoring_endianness::(address)? - .from_endian(self.endian)) + .from_endian(self.endian.get())) } /// Follows a path of pointers from the address given and reads a value of the type specified from @@ -163,12 +196,11 @@ impl Emulator { &self, address: u32, ) -> Result { - if address < 0x80000000 || address > 0x817FFFFF { + if !(0x80000000..0x81800000).contains(&address) { return Err(Error {}); } - let Some([mem1, _]) = self.ram_base else { - 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) } @@ -189,17 +221,34 @@ impl Emulator { &self, address: u32, ) -> Result { - if address < 0x90000000 || address > 0x93FFFFFF { + if !(0x90000000..0x94000000).contains(&address) { return Err(Error {}); } - let Some([_, mem2]) = self.ram_base else { - 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. +pub struct UntilEmulatorCloses<'a, F> { + emulator: &'a Emulator, + future: F, +} + +impl> Future for UntilEmulatorCloses<'_, F> { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if !self.emulator.is_open() { + return Poll::Ready(()); + } + self.emulator.update(); + // SAFETY: We are simply projecting the Pin. + unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().future).poll(cx) } + } +} + #[doc(hidden)] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum State {