Skip to content

Commit

Permalink
Genesis emulators + fix for memory reading function
Browse files Browse the repository at this point in the history
This also fixes #86
  • Loading branch information
Jujstme authored and CryZe committed Jan 22, 2025
1 parent 3c73d6e commit 0c76ec9
Showing 1 changed file with 84 additions and 21 deletions.
105 changes: 84 additions & 21 deletions src/emulator/genesis/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
use core::{
cell::Cell,
future::Future,
mem,
mem::{self, size_of, MaybeUninit},
pin::Pin,
slice,
task::{Context, Poll},
};

Expand Down Expand Up @@ -122,21 +123,24 @@ impl Emulator {
}
}

/// Reads raw data from the emulated RAM ignoring all endianness settings
/// The same call, performed on two different emulators, can be different
/// due to the endianness used by the emulator.
/// Converts a SEGA Genesis memory address to a real memory address in the emulator process' virtual memory space
///
/// The offset provided must not be higher than `0xFFFF`, otherwise this
/// method will 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 offset > 0xFFFF {
return Err(Error {});
/// The offset provided must not be higher than `0xFFFF`
pub fn get_address(&self, offset: u32) -> Result<Address, Error> {
match offset {
(0..=0xFFFF) => Ok(self.wram_base.get().ok_or(Error {})? + offset),
_ => Err(Error {}),
}
}

let wram = self.wram_base.get().ok_or(Error {})?;
self.process.read(wram + offset)
/// 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.
fn check_bounds<T>(&self, offset: u32) -> bool {
match offset {
(0..=0xFFFF) => offset + size_of::<T>() as u32 <= 0x10000,
_ => false,
}
}

/// Reads any value from the emulated RAM.
Expand All @@ -148,20 +152,79 @@ impl Emulator {
/// The offset provided must not be higher than `0xFFFF`, otherwise this
/// method will immediately return `Err()`.
pub fn read<T: CheckedBitPattern + FromEndian>(&self, offset: u32) -> Result<T, Error> {
if (offset > 0xFFFF && offset < 0xFF0000) || offset > 0xFFFFFF {
if !self.check_bounds::<T>(offset) {
return Err(Error {});
}

let wram = self.wram_base.get().ok_or(Error {})?;

let mut end_offset = offset.checked_sub(0xFF0000).unwrap_or(offset);
let aligned_offset = offset & !1;
let Ok(address) = self.get_address(aligned_offset) else {
return Err(Error {});
};
let endian = self.endian.get();

let toggle = endian == Endian::Little && mem::size_of::<T>() == 1;
end_offset ^= toggle as u32;
#[derive(Copy, Clone)]
#[repr(packed)]
struct MaybePadded<T> {
_before: MaybeUninit<u8>,
value: MaybeUninit<T>,
_after: MaybeUninit<u8>,
}

let misalignment = offset as usize & 1;
let mut padded_value = MaybeUninit::<MaybePadded<T>>::uninit();

let value = self.process.read::<T>(wram + end_offset)?;
Ok(value.from_endian(endian))
// We always want to read a multiple of 2 bytes, so at the end we need
// to find the next multiple of 2 bytes for T. However because we maybe
// are misaligned, we need to also take that misalignment in the
// opposite direction into account before finding the next multiple of
// two as otherwise we may not read all of T. This would otherwise go
// wrong when e.g. reading a u16 at a misaligned offset. We would start
// at the padding byte before the u16, but if we only read 2 bytes, we
// then would miss the half of the u16. So adding the misalignment of 1
// on top and then rounding up to the next multiple of 2 bytes leaves us
// with 4 bytes to read, which we can then nicely swap.
let buf = unsafe {
slice::from_raw_parts_mut(
padded_value.as_mut_ptr().byte_add(misalignment ^ 1) as *mut MaybeUninit<u8>,
(size_of::<T>() + misalignment).next_multiple_of(2),
)
};

let buf = self.process.read_into_uninit_buf(address, buf)?;

if endian.eq(&Endian::Little) {
buf.chunks_exact_mut(2).for_each(|chunk| chunk.swap(0, 1));
}

unsafe {
let value = padded_value.assume_init_ref().value;
if !T::is_valid_bit_pattern(&*value.as_ptr().cast::<T::Bits>()) {
return Err(Error {});
}

Ok(value.assume_init().from_be())
}
}

/// 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)
}
}

Expand Down

0 comments on commit 0c76ec9

Please sign in to comment.