Skip to content

Commit

Permalink
Added support for PS2 emulators (#54)
Browse files Browse the repository at this point in the history
This adds support for coding auto splitters for PS2 emulators.

Code and the general inner workings of the ps2::Emulator struct uses the same implementation seen previously in #26 and #33

So, in order to use it:

1. Enable the `ps2` feature in your `Cargo.toml`
2. Attach to the supported PS2 emulators by running `ps2::Emulator::attach()`
3. Run the internal `update() -> bool` to locate and update the address of the emulated game's memory
4. Read from the game's memory via its `read` function.

Supported Windows emulators:
- PCSX2
- Retroarch (using the `pcsx2_libretro.dll` core)
  • Loading branch information
Jujstme committed Sep 24, 2023
1 parent d15719d commit b7276cb
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ gba = []
gcn = ["flags"]
genesis = ["flags", "signature"]
ps1 = ["flags", "signature"]
ps2 = ["flags", "signature"]
2 changes: 2 additions & 0 deletions src/emulator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ pub mod gcn;
pub mod genesis;
#[cfg(feature = "ps1")]
pub mod ps1;
#[cfg(feature = "ps2")]
pub mod ps2;
134 changes: 134 additions & 0 deletions src/emulator/ps2/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
//! Support for attaching to Playstation 2 emulators.

use crate::{Address, Error, Process};
use bytemuck::CheckedBitPattern;

mod pcsx2;
mod retroarch;

/// A Playstation 2 emulator that the auto splitter is attached to.
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>,
}

impl Emulator {
/// Attaches to the emulator process
///
/// Returns `Option<T>` if successful, `None` otherwise.
///
/// Supported emulators are:
/// - PCSX2
/// - Retroarch (64-bit version, using the `pcsx2_libretro.dll` core)
pub fn attach() -> Option<Self> {
let (&state, process) = PROCESS_NAMES
.iter()
.find_map(|(name, state)| Some((state, Process::attach(name)?)))?;

Some(Self {
process,
state,
ram_base: None,
})
}

/// 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() {
let ram_base = match &mut self.state {
State::Pcsx2(x) => x.find_ram(&self.process),
State::Retroarch(x) => x.find_ram(&self.process),
};
if ram_base.is_none() {
return false;
}
self.ram_base = ram_base;
}

let success = match &self.state {
State::Pcsx2(x) => x.keep_alive(&self.process, &mut self.ram_base),
State::Retroarch(x) => x.keep_alive(&self.process),
};

if !success {
self.ram_base = None;
}

success
}

/// Reads any value from the emulated RAM.
///
/// In PS2, memory addresses are mapped at fixed locations starting
/// from `0x00100000` (addresses below this threashold are
/// reserved for the kernel).
///
/// Valid addresses for the PS2's memory range from `0x00100000` to `0x01FFFFFF`
///
/// Providing any offset outside the range of the PS2's RAM will return
/// `Err()`.
pub fn read<T: CheckedBitPattern>(&self, address: u32) -> Result<T, Error> {
if !(0x00100000..=0x01FFFFFF).contains(&address) {
return Err(Error {});
}

let Some(ram_base) = self.ram_base else {
return Err(Error {});
};

self.process.read(ram_base + address)
}

/// Follows a path of pointers from the base address given and reads a value of the
/// type specified at the end of the pointer path.
///
/// In PS2, memory addresses are mapped at fixed locations starting
/// from `0x00100000` (addresses below this threashold are
/// reserved for the kernel).
///
/// Valid addresses for the PS2's memory range from `0x00100000` to `0x01FFFFFF`
///
/// Providing any offset outside the range of the PS2's RAM will return
/// `Err()`.
pub fn read_pointer_path<T: CheckedBitPattern>(
&self,
base_address: u32,
path: &[u32],
) -> Result<T, Error> {
let mut address = base_address;
let (&last, path) = path.split_last().ok_or(Error {})?;
for &offset in path {
address = self.read(address + offset)?;
}
self.read(address + last)
}
}

#[doc(hidden)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum State {
Pcsx2(pcsx2::State),
Retroarch(retroarch::State),
}

const PROCESS_NAMES: [(&str, State); 6] = [
("pcsx2x64.exe", State::Pcsx2(pcsx2::State::new())),
("pcsx2-qt.exe", State::Pcsx2(pcsx2::State::new())),
("pcsx2x64-avx2.exe", State::Pcsx2(pcsx2::State::new())),
("pcsx2-avx2.exe", State::Pcsx2(pcsx2::State::new())),
("pcsx2.exe", State::Pcsx2(pcsx2::State::new())),
("retroarch.exe", State::Retroarch(retroarch::State::new())),
];
58 changes: 58 additions & 0 deletions src/emulator/ps2/pcsx2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use crate::{file_format::pe, signature::Signature, Address, Address32, Address64, Error, Process};

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct State {
is_64_bit: bool,
addr_base: Address,
}

impl State {
pub fn find_ram(&mut self, game: &Process) -> Option<Address> {
let main_module_range = super::PROCESS_NAMES
.iter()
.filter(|(_, state)| matches!(state, super::State::Pcsx2(_)))
.find_map(|(name, _)| game.get_module_range(name).ok())?;

self.is_64_bit =
pe::MachineType::read(game, main_module_range.0) == Some(pe::MachineType::X86_64);

self.addr_base = if self.is_64_bit {
const SIG: Signature<12> = Signature::new("48 8B ?? ?? ?? ?? ?? 25 F0 3F 00 00");
let ptr = SIG.scan_process_range(game, main_module_range)? + 3;
ptr + 0x4 + game.read::<i32>(ptr).ok()?
} else {
const SIG: Signature<11> = Signature::new("8B ?? ?? ?? ?? ?? 25 F0 3F 00 00");
const SIG_ALT: Signature<12> = Signature::new("8B ?? ?? ?? ?? ?? 81 ?? F0 3F 00 00");
let ptr = if let Some(addr) = SIG.scan_process_range(game, main_module_range) {
addr + 2
} else {
SIG_ALT.scan_process_range(game, main_module_range)? + 2
};
self.read_pointer(game, ptr).ok()?
};

self.read_pointer(game, self.addr_base).ok()
}

pub fn keep_alive(&self, game: &Process, ram_base: &mut Option<Address>) -> bool {
*ram_base = Some(match self.read_pointer(game, self.addr_base) {
Ok(x) => x,
Err(_) => return false,
});
true
}

fn read_pointer(&self, game: &Process, address: Address) -> Result<Address, Error> {
Ok(match self.is_64_bit {
true => game.read::<Address64>(address)?.into(),
false => game.read::<Address32>(address)?.into(),
})
}

pub const fn new() -> Self {
Self {
is_64_bit: true,
addr_base: Address::NULL,
}
}
}
56 changes: 56 additions & 0 deletions src/emulator/ps2/retroarch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use crate::{file_format::pe, signature::Signature, Address, Address64, Process};

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct State {
core_base: Address,
}

impl State {
pub fn find_ram(&mut self, game: &Process) -> Option<Address> {
const SUPPORTED_CORES: [&str; 1] = ["pcsx2_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 LRPS2 core, the only one available for retroarch at
// the time of writing (Sep 14th, 2023), only supports 64-bit
return None;
}

let (core_name, core_address) = SUPPORTED_CORES
.iter()
.find_map(|&m| Some((m, game.get_module_address(m).ok()?)))?;

self.core_base = core_address;

let base_addr = {
const SIG: Signature<13> = Signature::new("48 8B ?? ?? ?? ?? ?? 81 ?? F0 3F 00 00");
let ptr = SIG
.scan_process_range(game, (core_address, game.get_module_size(core_name).ok()?))?
+ 3;
ptr + 0x4 + game.read::<i32>(ptr).ok()?
};

match game.read::<Address64>(base_addr) {
Ok(Address64::NULL) => None,
Ok(x) => Some(x.into()),
_ => None,
}
}

pub fn keep_alive(&self, game: &Process) -> bool {
game.read::<u8>(self.core_base).is_ok()
}

pub const fn new() -> Self {
Self {
core_base: Address::NULL,
}
}
}

0 comments on commit b7276cb

Please sign in to comment.