From 0ad1d1ff80ffe31926026748d94e13e7c4750bcc Mon Sep 17 00:00:00 2001 From: Jujstme Date: Fri, 1 Dec 2023 15:51:45 +0100 Subject: [PATCH] Added support for Wii emulators (#59) --- Cargo.toml | 1 + src/emulator/mod.rs | 2 + src/emulator/wii/dolphin.rs | 46 ++++++++ src/emulator/wii/mod.rs | 213 ++++++++++++++++++++++++++++++++++ src/emulator/wii/retroarch.rs | 45 +++++++ 5 files changed, 307 insertions(+) create mode 100644 src/emulator/wii/dolphin.rs create mode 100644 src/emulator/wii/mod.rs create mode 100644 src/emulator/wii/retroarch.rs diff --git a/Cargo.toml b/Cargo.toml index b9ec073..f26dade 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,3 +38,4 @@ gcn = ["flags"] genesis = ["flags", "signature"] ps1 = ["flags", "signature"] ps2 = ["flags", "signature"] +wii = ["flags"] diff --git a/src/emulator/mod.rs b/src/emulator/mod.rs index 4ae865f..adb03e8 100644 --- a/src/emulator/mod.rs +++ b/src/emulator/mod.rs @@ -10,3 +10,5 @@ pub mod genesis; pub mod ps1; #[cfg(feature = "ps2")] pub mod ps2; +#[cfg(feature = "wii")] +pub mod wii; diff --git a/src/emulator/wii/dolphin.rs b/src/emulator/wii/dolphin.rs new file mode 100644 index 0000000..7c54a9c --- /dev/null +++ b/src/emulator/wii/dolphin.rs @@ -0,0 +1,46 @@ +use crate::{Address, Endian, FromEndian, MemoryRangeFlags, Process}; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct State; + +impl State { + pub fn find_ram(&self, game: &Process, endian: &mut Endian) -> Option<[Address; 2]> { + let mem_1 = game + .memory_ranges() + .find(|range| { + range + .flags() + .is_ok_and(|r| r.contains(MemoryRangeFlags::WRITE | MemoryRangeFlags::READ)) + && range.size().is_ok_and(|size| size == 0x2000000) + && range.address().is_ok_and(|addr| { + game.read::<[u32; 2]>(addr + 0x3118) + .is_ok_and(|val| val.from_endian(Endian::Big) == [0x4000000; 2]) + }) + })? + .address() + .ok()?; + + let mem_2 = game + .memory_ranges() + .find(|range| { + range + .flags() + .is_ok_and(|r| r.contains(MemoryRangeFlags::WRITE | MemoryRangeFlags::READ)) + && range.size().is_ok_and(|size| size == 0x4000000) + && range + .address() + .is_ok_and(|addr| addr > mem_1 && addr < mem_1 + 0x10000000) + })? + .address() + .ok()?; + + *endian = Endian::Big; + Some([mem_1, mem_2]) + } + + pub fn keep_alive(&self, game: &Process, ram_base: &Option<[Address; 2]>) -> bool { + ram_base.is_some_and(|[mem1, mem2]| { + game.read::(mem1).is_ok() && game.read::(mem2).is_ok() + }) + } +} diff --git a/src/emulator/wii/mod.rs b/src/emulator/wii/mod.rs new file mode 100644 index 0000000..6c11cfe --- /dev/null +++ b/src/emulator/wii/mod.rs @@ -0,0 +1,213 @@ +//! Support for attaching to Nintendo Wii emulators. + +use crate::{Address, Endian, Error, FromEndian, Process}; +use bytemuck::CheckedBitPattern; + +mod dolphin; +mod retroarch; + +/// A Nintendo Wii emulator that the auto splitter is attached to, +/// for supporting Wii and WiiWare games. +pub struct Emulator { + /// The attached emulator process + process: Process, + /// An enum stating which emulator is currently attached + state: State, + /// The memory address of the emulated RAM + ram_base: Option<[Address; 2]>, // [MEM1, MEM2] + /// The endianness used by the emulator process + endian: Endian, +} + +impl Emulator { + /// Attaches to the emulator process + /// + /// Returns `Option` if successful, `None` otherwise. + /// + /// Supported emulators are: + /// - Dolphin + /// - Retroarch (using the `dolphin_libretro.dll` core) + pub fn attach() -> Option { + let (&state, process) = PROCESS_NAMES + .iter() + .find_map(|(name, state)| Some((state, Process::attach(name)?)))?; + + Some(Self { + process, + state, + ram_base: None, // [MEM1, MEM2] + endian: Endian::Big, // Endianness is usually Big in Wii emulators + }) + } + + /// 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() + } + + /// 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 { + State::Dolphin(x) => x.find_ram(&self.process, &mut self.endian), + State::Retroarch(x) => x.find_ram(&self.process, &mut self.endian), + } { + None => return false, + something => something, + }; + } + + let success = match &self.state { + State::Dolphin(x) => x.keep_alive(&self.process, &self.ram_base), + State::Retroarch(x) => x.keep_alive(&self.process, &self.ram_base), + }; + + if success { + true + } else { + self.ram_base = None; + 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 address provided is meant to be the mapped address used on the original, big-endian system. + /// The call will automatically convert the address provided to its corresponding offset from + /// `MEM1` or `MEM2` and read the value. + /// + /// The provided memory address has to match a mapped memory address on the original Wii: + /// - Valid addresses for `MEM1` range from `0x80000000` to `0x817FFFFF` + /// - Valid addresses for `MEM2` range from `0x90000000` to `0x93FFFFFF` + /// + /// 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(&self, address: u32) -> Result { + if address >= 0x80000000 && address <= 0x817FFFFF { + self.read_ignoring_endianness_from_mem_1(address) + } else if address >= 0x90000000 && address <= 0x93FFFFFF { + self.read_ignoring_endianness_from_mem_2(address) + } else { + Err(Error {}) + } + } + + /// Reads any value from the emulated RAM. + /// + /// The offset provided is meant to be the mapped address used on the original, big-endian system. + /// The call will automatically convert the address provided to its corresponding offset from + /// `MEM1` or `MEM2` and read the value, providing conversion from Big Endian to Little Endian. + /// + /// The provided memory address has to match a mapped memory address on the original Wii: + /// - Valid addresses for `MEM1` range from `0x80000000` to `0x817FFFFF` + /// - Valid addresses for `MEM2` range from `0x90000000` to `0x93FFFFFF` + /// + /// Any other invalid value will make this method immediately return `Err()`. + pub fn read(&self, address: u32) -> Result { + Ok(self + .read_ignoring_endianness::(address)? + .from_endian(self.endian)) + } + + /// 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. + /// + /// The end value is automatically converted to little endian if needed. + pub fn read_pointer_path( + &self, + base_address: u32, + path: &[u32], + ) -> Result { + self.read(self.deref_offsets(base_address, path)?) + } + + /// 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_ignoring_endianness( + &self, + base_address: u32, + path: &[u32], + ) -> Result { + self.read_ignoring_endianness(self.deref_offsets(base_address, path)?) + } + + fn deref_offsets(&self, base_address: u32, path: &[u32]) -> Result { + let mut address = base_address; + let (&last, path) = path.split_last().ok_or(Error {})?; + for &offset in path { + address = self.read::(address + offset)?; + } + Ok(address + last) + } + + /// 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 address provided is meant to be the mapped address used on the original, big-endian system. + /// The call will automatically convert the address provided to its corresponding offset from + /// `MEM1` or and read the value. + /// + /// The provided memory address has to match a mapped memory address on the original Wii. + /// Valid addresses for `MEM1` range from `0x80000000` to `0x817FFFFF` + /// + /// Any other invalid value will make this method immediately return `Err()`. + pub fn read_ignoring_endianness_from_mem_1( + &self, + address: u32, + ) -> Result { + if address < 0x80000000 || address > 0x817FFFFF { + return Err(Error {}); + } + let Some([mem1, _]) = self.ram_base else { + return Err(Error {}); + }; + let end_offset = address.checked_sub(0x80000000).unwrap_or(address); + self.process.read(mem1 + end_offset) + } + + /// 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 address provided is meant to be the mapped address used on the original, big-endian system. + /// The call will automatically convert the address provided to its corresponding offset from + /// `MEM2` or and read the value. + /// + /// The provided memory address has to match a mapped memory address on the original Wii. + /// Valid addresses for `MEM2` range from `0x90000000` to `0x93FFFFFF` + /// + /// Any other invalid value will make this method immediately return `Err()`. + pub fn read_ignoring_endianness_from_mem_2( + &self, + address: u32, + ) -> Result { + if address < 0x90000000 || address > 0x93FFFFFF { + return Err(Error {}); + } + let Some([_, mem2]) = self.ram_base else { + return Err(Error {}); + }; + let end_offset = address.checked_sub(0x90000000).unwrap_or(address); + self.process.read(mem2 + end_offset) + } +} + +#[doc(hidden)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum State { + Dolphin(dolphin::State), + Retroarch(retroarch::State), +} + +static PROCESS_NAMES: [(&str, State); 2] = [ + ("Dolphin.exe", State::Dolphin(dolphin::State)), + ("retroarch.exe", State::Retroarch(retroarch::State::new())), +]; diff --git a/src/emulator/wii/retroarch.rs b/src/emulator/wii/retroarch.rs new file mode 100644 index 0000000..589e630 --- /dev/null +++ b/src/emulator/wii/retroarch.rs @@ -0,0 +1,45 @@ +use crate::{file_format::pe, Address, Endian, Process}; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct State { + core_base: Address, +} + +impl State { + pub fn find_ram(&mut self, game: &Process, endian: &mut Endian) -> Option<[Address; 2]> { + const SUPPORTED_CORES: [&str; 1] = ["dolphin_libretro.dll"]; + + let main_module_address = super::PROCESS_NAMES + .iter() + .filter(|(_, state)| matches!(state, super::State::Retroarch(_))) + .find_map(|(name, _)| game.get_module_address(name).ok())?; + + let is_64_bit = + pe::MachineType::read(game, main_module_address) == Some(pe::MachineType::X86_64); + + if !is_64_bit { + // The Dolphin core, the only one available for retroarch, only supports 64-bit + return None; + } + + self.core_base = SUPPORTED_CORES + .iter() + .find_map(|&m| game.get_module_address(m).ok())?; + + *endian = Endian::Big; + super::dolphin::State::find_ram(&super::dolphin::State, game, endian) + } + + pub fn keep_alive(&self, game: &Process, ram_base: &Option<[Address; 2]>) -> bool { + game.read::(self.core_base).is_ok() + && ram_base.is_some_and(|[mem1, mem2]| { + game.read::(mem1).is_ok() && game.read::(mem2).is_ok() + }) + } + + pub const fn new() -> Self { + Self { + core_base: Address::NULL, + } + } +}