Skip to content

Commit

Permalink
Merge pull request #84 from Jujstme/master
Browse files Browse the repository at this point in the history
Added support for `future` for emulators
  • Loading branch information
CryZe committed Jan 20, 2024
2 parents 8b955eb + 8774923 commit 53d629d
Show file tree
Hide file tree
Showing 7 changed files with 509 additions and 154 deletions.
102 changes: 76 additions & 26 deletions src/emulator/gba/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<State>,
/// The memory address of the emulated RAM
ram_base: Option<[Address; 2]>, // [ewram, iwram]
ram_base: Cell<Option<[Address; 2]>>, // [ewram, iwram]
}

impl Emulator {
Expand All @@ -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<F>(&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),
Expand All @@ -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
}
}

Expand Down Expand Up @@ -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)
Expand All @@ -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<F: Future<Output = ()>> Future for UntilEmulatorCloses<'_, F> {
type Output = ();

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
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 {
Expand Down
102 changes: 77 additions & 25 deletions src/emulator/gcn/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<State>,
/// The memory address of the emulated RAM
mem1_base: Option<Address>,
mem1_base: Cell<Option<Address>>,
/// The endianness used by the emulator process
endian: Endian,
endian: Cell<Endian>,
}

impl Emulator {
Expand All @@ -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<F>(&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.
Expand All @@ -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)
Expand All @@ -112,7 +145,26 @@ impl Emulator {
pub fn read<T: CheckedBitPattern + FromEndian>(&self, offset: u32) -> Result<T, Error> {
Ok(self
.read_ignoring_endianness::<T>(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<F: Future<Output = ()>> Future for UntilEmulatorCloses<'_, F> {
type Output = ();

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
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) }
}
}

Expand Down
Loading

0 comments on commit 53d629d

Please sign in to comment.