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

More uniform methods for emulators #89

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
104 changes: 59 additions & 45 deletions src/emulator/gba/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
use core::{
cell::Cell,
future::Future,
mem::size_of,
ops::Sub,
pin::Pin,
task::{Context, Poll},
};
Expand Down Expand Up @@ -39,7 +41,7 @@
/// - 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`

Check warning on line 44 in src/emulator/gba/mod.rs

View workflow job for this annotation

GitHub Actions / Check clippy lints

doc list item without indentation
pub fn attach() -> Option<Self> {
let (&state, process) = PROCESS_NAMES
.iter()
Expand All @@ -62,7 +64,7 @@
/// - 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`

Check warning on line 67 in src/emulator/gba/mod.rs

View workflow job for this annotation

GitHub Actions / Check clippy lints

doc list item without indentation
pub async fn wait_attach() -> Self {
retry(Self::attach).await
}
Expand Down Expand Up @@ -118,64 +120,72 @@
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<T: CheckedBitPattern>(&self, offset: u32) -> Result<T, Error> {
match offset >> 24 {
2 => self.read_from_ewram(offset),
3 => self.read_from_iwram(offset),
pub fn get_address(&self, offset: u32) -> Result<Address, Error> {
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<T: CheckedBitPattern>(&self, offset: u32) -> Result<T, Error> {
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<T>(&self, offset: u32) -> bool {
match offset {
(0x02000000..=0x0203FFFF) => offset + size_of::<T>() as u32 <= 0x02040000,
(0x03000000..=0x03007FFF) => offset + size_of::<T>() 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<T: CheckedBitPattern>(&self, offset: u32) -> Result<T, Error> {
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<T: CheckedBitPattern>(&self, offset: u32) -> Result<T, Error> {
match self.check_bounds::<T>(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<T: CheckedBitPattern>(
&self,
base_address: u32,
path: &[u32],
) -> Result<T, Error> {
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<u32, Error> {
let mut address = base_address;
let (&last, path) = path.split_last().ok_or(Error {})?;
for &offset in path {
address = self.read::<u32>(address + offset)?;
}
Ok(address + last)
}
}

Expand All @@ -185,16 +195,20 @@
future: F,
}

impl<F: Future<Output = ()>> Future for UntilEmulatorCloses<'_, F> {
type Output = ();
impl<T, F: Future<Output = T>> Future for UntilEmulatorCloses<'_, F> {
type Output = Option<T>;

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

Expand Down
82 changes: 62 additions & 20 deletions src/emulator/gcn/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
use core::{
cell::Cell,
future::Future,
mem::size_of,
ops::Sub,
pin::Pin,
task::{Context, Poll},
};
Expand Down Expand Up @@ -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<Address, Error> {
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<T>(&self, offset: u32) -> bool {
match offset {
(0x80000000..=0x817FFFFF) => offset + size_of::<T>() 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<T: CheckedBitPattern>(&self, offset: u32) -> Result<T, Error> {
if (0x01800000..0x80000000).contains(&offset) || offset >= 0x81800000 {
return Err(Error {});
match self.check_bounds::<T>(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<T: CheckedBitPattern + FromEndian>(&self, offset: u32) -> Result<T, Error> {
Ok(self
.read_ignoring_endianness::<T>(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<T: CheckedBitPattern + FromEndian>(
&self,
base_address: u32,
path: &[u32],
) -> Result<T, Error> {
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<u32, Error> {
let mut address = base_address;
let (&last, path) = path.split_last().ok_or(Error {})?;
for &offset in path {
address = self.read::<u32>(address + offset)?;
}
Ok(address + last)
}
}

/// A future that executes a future until the emulator closes.
Expand All @@ -155,16 +193,20 @@ pub struct UntilEmulatorCloses<'a, F> {
future: F,
}

impl<F: Future<Output = ()>> Future for UntilEmulatorCloses<'_, F> {
type Output = ();
impl<T, F: Future<Output = T>> Future for UntilEmulatorCloses<'_, F> {
type Output = Option<T>;

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

Expand Down
Loading
Loading