Skip to content

Commit

Permalink
Applied LiveSplit#89
Browse files Browse the repository at this point in the history
  • Loading branch information
Jujstme committed Jun 12, 2024
1 parent 5eb4619 commit 899e1ee
Show file tree
Hide file tree
Showing 7 changed files with 380 additions and 215 deletions.
106 changes: 60 additions & 46 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 @@ -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<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 @@ 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 Expand Up @@ -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())),
];
];
84 changes: 63 additions & 21 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 All @@ -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())),
];
];
Loading

0 comments on commit 899e1ee

Please sign in to comment.