Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for future for emulators #84

Merged
merged 3 commits into from
Jan 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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