diff --git a/Cargo.toml b/Cargo.toml index 36c5270..b9ec073 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ wasi-no-std = ["libm"] unity = ["signature", "asr-derive?/unity"] # Emulators -gba = [] +gba = ["flags", "signature"] gcn = ["flags"] genesis = ["flags", "signature"] ps1 = ["flags", "signature"] diff --git a/src/emulator/gba.rs b/src/emulator/gba.rs deleted file mode 100644 index d59b089..0000000 --- a/src/emulator/gba.rs +++ /dev/null @@ -1,80 +0,0 @@ -//! Support for attaching to Game Boy Advance emulators. - -use bytemuck::CheckedBitPattern; - -use crate::{runtime, Address, Address32, Address64, Process}; - -/// A Game Boy Advance emulator that the auto splitter is attached to. -pub struct Emulator { - process: Process, - ewram: Address, - iwram: Address, -} - -impl Emulator { - /// Attaches to a Game Boy Advance emulator. Currently only certain versions - /// of mGBA and Visual Boy Advance on Windows are supported. - pub fn attach() -> Option { - look_for_mgba().or_else(look_for_vba) - } - - /// 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() - } - - /// Reads a value from the emulator's memory. - pub fn read( - &self, - addr: impl Into
, - ) -> Result { - let address: Address = addr.into(); - let address = address.value(); - let memory_section = address >> 24; - let ram_addr = match memory_section { - 2 => self.ewram, - 3 => self.iwram, - _ => return Err(runtime::Error {}), - }; - let addr = ram_addr + (address & 0xFF_FF_FF); - self.process.read(addr) - } -} - -fn look_for_mgba() -> Option { - let process = Process::attach("mGBA.exe")?; - - let [ewram, iwram]: [Address64; 2] = process - .read_pointer_path64( - process.get_module_address("mGBA.exe").ok()?, - &[0x01D01868, 0x38, 0x10, 0x8, 0x28], - ) - .ok()?; - - if ewram.is_null() || iwram.is_null() { - return None; - } - - Some(Emulator { - process, - ewram: ewram.into(), - iwram: iwram.into(), - }) -} - -fn look_for_vba() -> Option { - let process = Process::attach("VisualBoyAdvance.exe")?; - - let [ewram, iwram]: [Address32; 2] = process.read(0x00400000u32 + 0x001A8F50u32).ok()?; - - if ewram.is_null() || iwram.is_null() { - return None; - } - - Some(Emulator { - process, - ewram: ewram.into(), - iwram: iwram.into(), - }) -} diff --git a/src/emulator/gba/emuhawk.rs b/src/emulator/gba/emuhawk.rs new file mode 100644 index 0000000..a16c2c5 --- /dev/null +++ b/src/emulator/gba/emuhawk.rs @@ -0,0 +1,36 @@ +use crate::{Address, MemoryRangeFlags, Process}; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct State { + base_addr: Address, +} + +impl State { + pub fn find_ram(&mut self, game: &Process) -> Option<[Address; 2]> { + self.base_addr = game.get_module_address("mgba.dll").ok()?; + + let addr = game + .memory_ranges() + .find(|range| { + range.size().is_ok_and(|size| size == 0x48000) + && range.flags().is_ok_and(|flag| { + flag.contains(MemoryRangeFlags::WRITE | MemoryRangeFlags::READ) + }) + })? + .address() + .ok()?; + + Some([addr, addr + 0x40000]) + } + + pub fn keep_alive(&self, game: &Process, ram_base: &Option<[Address; 2]>) -> bool { + ram_base.is_some_and(|[ewram, _]| game.read::(ewram).is_ok()) + && game.read::(self.base_addr).is_ok() + } + + pub const fn new() -> Self { + Self { + base_addr: Address::NULL, + } + } +} diff --git a/src/emulator/gba/mednafen.rs b/src/emulator/gba/mednafen.rs new file mode 100644 index 0000000..d9e0707 --- /dev/null +++ b/src/emulator/gba/mednafen.rs @@ -0,0 +1,106 @@ +use crate::{file_format::pe, signature::Signature, Address, Address32, Address64, Error, Process}; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct State { + cached_ewram_pointer: Address, + cached_iwram_pointer: Address, + is_64_bit: bool, +} + +impl State { + pub fn find_ram(&mut self, game: &Process) -> Option<[Address; 2]> { + let main_module_range = super::PROCESS_NAMES + .iter() + .filter(|(_, state)| matches!(state, super::State::Mednafen(_))) + .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); + + if self.is_64_bit { + self.cached_ewram_pointer = { + const SIG: Signature<13> = Signature::new("48 8B 05 ?? ?? ?? ?? 81 E1 FF FF 03 00"); + let ptr: Address = SIG.scan_process_range(game, main_module_range)? + 3; + let mut addr: Address = ptr + 0x4 + game.read::(ptr).ok()?; + + if game.read::(ptr + 10).ok()? == 0x48 { + addr = game.read::(addr).ok()?.into(); + if addr.is_null() { + return None; + } + } + + addr + }; + + + self.cached_iwram_pointer = { + const SIG2: Signature<13> = Signature::new("48 8B 05 ?? ?? ?? ?? 81 E1 FF 7F 00 00"); + let ptr: Address = SIG2.scan_process_range(game, main_module_range)? + 3; + let mut addr: Address = ptr + 0x4 + game.read::(ptr).ok()?; + + if game.read::(ptr + 10).ok()? == 0x48 { + addr = game.read::(addr).ok()?.into(); + if addr.is_null() { + return None; + } + } + + addr + }; + + let ewram = game.read::(self.cached_ewram_pointer).ok()?; + let iwram = game.read::(self.cached_iwram_pointer).ok()?; + + Some([ewram.into(), iwram.into()]) + } else { + self.cached_ewram_pointer = { + const SIG: Signature<11> = Signature::new("A1 ?? ?? ?? ?? 81 ?? FF FF 03 00"); + let ptr = SIG.scan_process_range(game, main_module_range)?; + game.read::(ptr + 1).ok()?.into() + }; + + + self.cached_iwram_pointer = { + const SIG2: Signature<11> = Signature::new("A1 ?? ?? ?? ?? 81 ?? FF 7F 00 00"); + let ptr = SIG2.scan_process_range(game, main_module_range)?; + game.read::(ptr + 1).ok()?.into() + }; + + let ewram = game.read::(self.cached_ewram_pointer).ok()?; + let iwram = game.read::(self.cached_iwram_pointer).ok()?; + + Some([ewram.into(), iwram.into()]) + } + } + + fn read_pointer(&self, game: &Process, address: Address) -> Result { + Ok(match self.is_64_bit { + true => game.read::(address)?.into(), + false => game.read::(address)?.into(), + }) + } + + pub fn keep_alive(&self, game: &Process, ram: &mut Option<[Address; 2]>) -> bool { + let ewram = match self.read_pointer(game, self.cached_ewram_pointer) { + Ok(x) => x, + _ => return false, + }; + + let iwram = match self.read_pointer(game, self.cached_iwram_pointer) { + Ok(x) => x, + _ => return false, + }; + + *ram = Some([ewram, iwram]); + true + } + + pub const fn new() -> Self { + Self { + cached_ewram_pointer: Address::NULL, + cached_iwram_pointer: Address::NULL, + is_64_bit: false, + } + } +} diff --git a/src/emulator/gba/mgba.rs b/src/emulator/gba/mgba.rs new file mode 100644 index 0000000..89d67a0 --- /dev/null +++ b/src/emulator/gba/mgba.rs @@ -0,0 +1,25 @@ +use crate::{Address, MemoryRangeFlags, Process}; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct State; + +impl State { + pub fn find_ram(&self, game: &Process) -> Option<[Address; 2]> { + // Latest version tested: 0.10.2 (September 2023) + let addr = game + .memory_ranges() + .find(|range| { + range.size().is_ok_and(|size| size == 0x48000) + && range.flags().is_ok_and(|flag| { + flag.contains(MemoryRangeFlags::WRITE | MemoryRangeFlags::READ) + }) + })? + .address() + .ok()?; + Some([addr, addr + 0x40000]) + } + + pub fn keep_alive(&self, game: &Process, ram_base: &Option<[Address; 2]>) -> bool { + ram_base.is_some_and(|[ewram, _]| game.read::(ewram).is_ok()) + } +} diff --git a/src/emulator/gba/mod.rs b/src/emulator/gba/mod.rs new file mode 100644 index 0000000..8e97338 --- /dev/null +++ b/src/emulator/gba/mod.rs @@ -0,0 +1,181 @@ +//! Support for attaching to Nintendo Gameboy Advance emulators. + +use crate::{Address, Error, Process}; +use bytemuck::CheckedBitPattern; + +mod mgba; +mod nocashgba; +mod retroarch; +mod vba; +mod emuhawk; +mod mednafen; + +/// A Nintendo Gameboy Advance 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; 2]>, // [ewram, iwram] +} + +impl Emulator { + /// Attaches to the emulator process + /// + /// Returns `Option` if successful, `None` otherwise. + /// + /// Supported emulators are: + /// - VisualBoyAdvance + /// - VisualBoyAdvance-M + /// - mGBA + /// - 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` + 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, + }) + } + + /// 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::VisualBoyAdvance(x) => x.find_ram(&self.process), + State::Mgba(x) => x.find_ram(&self.process), + State::NoCashGba(x) => x.find_ram(&self.process), + State::Retroarch(x) => x.find_ram(&self.process), + State::EmuHawk(x) => x.find_ram(&self.process), + State::Mednafen(x) => x.find_ram(&self.process), + } { + None => return false, + something => something, + }; + } + + let success = match &self.state { + State::VisualBoyAdvance(x) => x.keep_alive(&self.process, &mut self.ram_base), + State::Mgba(x) => x.keep_alive(&self.process, &self.ram_base), + State::NoCashGba(x) => x.keep_alive(&self.process, &mut self.ram_base), + State::Retroarch(x) => x.keep_alive(&self.process), + State::EmuHawk(x) => x.keep_alive(&self.process, &self.ram_base), + State::Mednafen(x) => x.keep_alive(&self.process, &mut self.ram_base), + }; + + match success { + true => true, + false => { + self.ram_base = None; + false + }, + } + } + + /// Reads any value from the emulated RAM. + /// + /// 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(&self, offset: u32) -> Result { + match offset >> 24 { + 2 => self.read_from_ewram(offset), + 3 => self.read_from_iwram(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. + /// + /// 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(&self, offset: u32) -> Result { + if (offset > 0x3FFFF && offset < 0x02000000) || offset > 0x0203FFFF { + return Err(Error {}); + } + + let Some([ewram, _]) = self.ram_base else { + return Err(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. + /// + /// Valid addresses range from `0x03000000` to `0x03007FFF`. + /// For example, providing an offset value of `0x3000` or `0x03003000` + /// 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_iwram(&self, offset: u32) -> Result { + if (offset > 0x7FFF && offset < 0x03000000) || offset > 0x03007FFF { + return Err(Error {}); + } + + let Some([_, iwram]) = self.ram_base else { + return Err(Error {}); + }; + let end_offset = offset.checked_sub(0x03000000).unwrap_or(offset); + + self.process.read(iwram + end_offset) + } +} + +#[doc(hidden)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum State { + VisualBoyAdvance(vba::State), + Mgba(mgba::State), + NoCashGba(nocashgba::State), + Retroarch(retroarch::State), + EmuHawk(emuhawk::State), + Mednafen(mednafen::State), +} + +static PROCESS_NAMES: [(&str, State); 7] = [ + ( + "visualboyadvance-m.exe", + State::VisualBoyAdvance(vba::State::new()), + ), + ( + "VisualBoyAdvance.exe", + State::VisualBoyAdvance(vba::State::new()), + ), + ("mGBA.exe", State::Mgba(mgba::State)), + ("NO$GBA.EXE", State::NoCashGba(nocashgba::State::new())), + ("retroarch.exe", State::Retroarch(retroarch::State::new())), + ("EmuHawk.exe", State::EmuHawk(emuhawk::State::new())), + ("mednafen.exe", State::Mednafen(mednafen::State::new())), +]; diff --git a/src/emulator/gba/nocashgba.rs b/src/emulator/gba/nocashgba.rs new file mode 100644 index 0000000..a7d2a4c --- /dev/null +++ b/src/emulator/gba/nocashgba.rs @@ -0,0 +1,57 @@ +use crate::{signature::Signature, Address, Address32, Process}; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct State { + base_addr: Address, +} + +impl State { + pub fn find_ram(&mut self, game: &Process) -> Option<[Address; 2]> { + // Tested and working on NO$GBA 3.2 and 3.05 + const SIG: Signature<7> = Signature::new("FF 35 ?? ?? ?? ?? 55"); + + let main_module_range = super::PROCESS_NAMES + .iter() + .filter(|(_, state)| matches!(state, super::State::NoCashGba(_))) + .find_map(|(name, _)| game.get_module_range(name).ok())?; + + self.base_addr = game + .read::(SIG.scan_process_range(game, main_module_range)? + 0x2) + .ok()? + .into(); + + let addr: Address = game.read::(self.base_addr).ok()?.into(); + + let ewram_pointer = addr.add(0x938C).add(0x8); + let iwram_pointer = addr.add(0x95D4); + + Some([ + game.read::(ewram_pointer).ok()?.into(), + game.read::(iwram_pointer).ok()?.into(), + ]) + } + + pub fn keep_alive(&self, game: &Process, ram: &mut Option<[Address; 2]>) -> bool { + let Ok(addr) = game.read::(self.base_addr) else { + return false; + }; + let ewram_pointer = addr.add(0x938C).add(0x8); + let iwram_pointer = addr.add(0x95D4); + + let Ok(ewram) = game.read::(ewram_pointer) else { + return false; + }; + let Ok(iwram) = game.read::(iwram_pointer) else { + return false; + }; + + *ram = Some([ewram.into(), iwram.into()]); + true + } + + pub const fn new() -> Self { + Self { + base_addr: Address::NULL, + } + } +} diff --git a/src/emulator/gba/retroarch.rs b/src/emulator/gba/retroarch.rs new file mode 100644 index 0000000..a4a9156 --- /dev/null +++ b/src/emulator/gba/retroarch.rs @@ -0,0 +1,150 @@ +use crate::{ + file_format::pe, signature::Signature, Address, Address32, 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; 2]> { + const SUPPORTED_CORES: &[&str] = &[ + "vbam_libretro.dll", + "mednafen_gba_libretro.dll", + "vba_next_libretro.dll", + "mgba_libretro.dll", + "gpsp_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); + + let (core_name, core_address) = SUPPORTED_CORES + .iter() + .find_map(|&m| Some((m, game.get_module_address(m).ok()?)))?; + + self.core_base = core_address; + + match core_name { + "vbam_libretro.dll" | "vba_next_libretro.dll" | "mednafen_gba_libretro.dll" => self.vba(game, is_64_bit, core_name), + "mgba_libretro.dll" => super::mgba::State::find_ram(&mut super::mgba::State, game), + "gpsp_libretro.dll" => self.gpsp(game, is_64_bit, core_name), + _ => None, + } + } + + fn vba(&self, game: &Process, is_64_bit: bool, core_name: &str) -> Option<[Address; 2]> { + let module_range = (self.core_base, game.get_module_size(core_name).ok()?); + + if is_64_bit { + const SIG: Signature<13> = Signature::new("48 8B 05 ?? ?? ?? ?? 81 E1 FF FF 03 00"); + const SIG2: Signature<13> = Signature::new("48 8B 05 ?? ?? ?? ?? 81 E1 FF 7F 00 00"); + + let ewram_pointer = { + let ptr: Address = SIG.scan_process_range(game, module_range)? + 3; + let mut addr: Address = ptr + 0x4 + game.read::(ptr).ok()?; + + if game.read::(ptr + 10).ok()? == 0x48 { + addr = game.read::(addr).ok()?.into(); + if addr.is_null() { + return None; + } + } + + addr + }; + + let iwram_pointer = { + let ptr: Address = SIG2.scan_process_range(game, module_range)? + 3; + let mut addr: Address = ptr + 0x4 + game.read::(ptr).ok()?; + + if game.read::(ptr + 10).ok()? == 0x48 { + addr = game.read::(addr).ok()?.into(); + if addr.is_null() { + return None; + } + } + + addr + }; + + let ewram = game.read::(ewram_pointer).ok()?; + let iwram = game.read::(iwram_pointer).ok()?; + + if ewram.is_null() || iwram.is_null() { + None + } else { + Some([ewram.into(), iwram.into()]) + } + } else { + let ewram_pointer: Address = { + const SIG: Signature<11> = Signature::new("A1 ?? ?? ?? ?? 81 ?? FF FF 03 00"); + let ptr = SIG.scan_process_range(game, module_range)?; + game.read::(ptr + 1).ok()?.into() + }; + let iwram_pointer: Address = { + const SIG2: Signature<11> = Signature::new("A1 ?? ?? ?? ?? 81 ?? FF 7F 00 00"); + let ptr = SIG2.scan_process_range(game, module_range)?; + game.read::(ptr + 1).ok()?.into() + }; + + let ewram = game.read::(ewram_pointer).ok()?; + let iwram = game.read::(iwram_pointer).ok()?; + + if ewram.is_null() || iwram.is_null() { + None + } else { + Some([ewram.into(), iwram.into()]) + } + } + } + + fn gpsp(&self, game: &Process, is_64_bit: bool, core_name: &str) -> Option<[Address; 2]> { + const SIG_EWRAM: Signature<8> = Signature::new("25 FF FF 03 00 88 94 03"); + const SIG_IWRAM: Signature<9> = Signature::new("25 FE 7F 00 00 66 89 94 03"); + + let module_size = game.get_module_size(core_name).ok()?; + + let base_addr: Address = match is_64_bit { + true => { + const SIG: Signature<10> = Signature::new("48 8B 15 ?? ?? ?? ?? 8B 42 40"); + let ptr = SIG.scan_process_range(game, (self.core_base, module_size))? + 3; + let ptr: Address = ptr + 0x4 + game.read::(ptr).ok()?; + game.read::(ptr).ok()?.into() + } + false => { + const SIG: Signature<11> = Signature::new("A3 ?? ?? ?? ?? F7 C5 02 00 00 00"); + let ptr = SIG.scan_process_range(game, (self.core_base, module_size))? + 1; + game.read::(ptr).ok()?.into() + } + }; + + let ewram = { + let offset = SIG_EWRAM.scan_process_range(game, (self.core_base, module_size))? + 8; + base_addr + game.read::(offset).ok()? + }; + + let iwram = { + let offset = SIG_IWRAM.scan_process_range(game, (self.core_base, module_size))? + 9; + base_addr + game.read::(offset).ok()? + }; + + Some([ewram, iwram]) + } + + pub fn keep_alive(&self, game: &Process) -> bool { + game.read::(self.core_base).is_ok() + } + + pub const fn new() -> Self { + Self { + core_base: Address::NULL, + } + } +} diff --git a/src/emulator/gba/vba.rs b/src/emulator/gba/vba.rs new file mode 100644 index 0000000..6db3633 --- /dev/null +++ b/src/emulator/gba/vba.rs @@ -0,0 +1,156 @@ +use crate::{file_format::pe, signature::Signature, Address, Address32, Address64, Error, Process}; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct State { + cached_ewram_pointer: Address, + cached_iwram_pointer: Address, + is_emulating: Address, + is_64_bit: bool, +} + +impl State { + pub fn find_ram(&mut self, game: &Process) -> Option<[Address; 2]> { + // Latest version tested: 2.1.7 + + let main_module_range = super::PROCESS_NAMES + .iter() + .filter(|(_, state)| matches!(state, super::State::VisualBoyAdvance(_))) + .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); + + if self.is_64_bit { + const SIG: Signature<13> = Signature::new("48 8B 05 ?? ?? ?? ?? 81 E3 FF FF 03 00"); + const SIG2: Signature<13> = Signature::new("48 8B 05 ?? ?? ?? ?? 81 E3 FF 7F 00 00"); + + self.cached_ewram_pointer = { + let ptr: Address = SIG.scan_process_range(game, main_module_range)? + 3; + let mut addr: Address = ptr + 0x4 + game.read::(ptr).ok()?; + + if game.read::(ptr + 10).ok()? == 0x48 { + addr = game.read::(addr).ok()?.into(); + if addr.is_null() { + return None; + } + } + + addr + }; + + self.cached_iwram_pointer = { + let ptr: Address = SIG2.scan_process_range(game, main_module_range)? + 3; + let mut addr: Address = ptr + 0x4 + game.read::(ptr).ok()?; + + if game.read::(ptr + 10).ok()? == 0x48 { + addr = game.read::(addr).ok()?.into(); + if addr.is_null() { + return None; + } + } + + addr + }; + + + self.is_emulating = { + const SIG_RUNNING: Signature<19> = Signature::new("83 3D ?? ?? ?? ?? 00 74 ?? 80 3D ?? ?? ?? ?? 00 75 ?? 66"); + const SIG_RUNNING2: Signature<16> = Signature::new("48 8B 15 ?? ?? ?? ?? 31 C0 8B 12 85 D2 74 ?? 48"); + + if let Some(ptr) = SIG_RUNNING.scan_process_range(game, main_module_range) { + let ptr = ptr + 2; + ptr + 0x4 + game.read::(ptr).ok()? + 0x1 + } else { + let ptr = SIG_RUNNING2.scan_process_range(game, main_module_range)? + 3; + let ptr = ptr + 0x4 + game.read::(ptr).ok()?; + game.read::(ptr).ok()?.into() + } + + }; + + let ewram = game.read::(self.cached_ewram_pointer).ok()?; + let iwram = game.read::(self.cached_iwram_pointer).ok()?; + + Some([ewram.into(), iwram.into()]) + } else { + const SIG: Signature<11> = Signature::new("A1 ?? ?? ?? ?? 81 ?? FF FF 03 00"); + const SIG_OLD: Signature<12> = Signature::new("81 E6 FF FF 03 00 8B 15 ?? ?? ?? ??"); + + if let Some(ptr) = SIG.scan_process_range(game, main_module_range) { + self.cached_ewram_pointer = game.read::(ptr + 1).ok()?.into(); + self.cached_iwram_pointer = { + const SIG2: Signature<11> = Signature::new("A1 ?? ?? ?? ?? 81 ?? FF 7F 00 00"); + let ptr = SIG2.scan_process_range(game, main_module_range)?; + game.read::(ptr + 1).ok()?.into() + }; + + self.is_emulating = { + const SIG: Signature<19> = Signature::new("83 3D ?? ?? ?? ?? 00 74 ?? 80 3D ?? ?? ?? ?? 00 75 ?? 66"); + const SIG_OLD: Signature<13> = Signature::new("8B 15 ?? ?? ?? ?? 31 C0 85 D2 74 ?? 0F"); + + let ptr = SIG.scan_process_range(game, main_module_range) + .or_else(|| SIG_OLD.scan_process_range(game, main_module_range))?; + + game.read::(ptr + 2).ok()?.into() + }; + + let ewram = game.read::(self.cached_ewram_pointer).ok()?; + let iwram = game.read::(self.cached_iwram_pointer).ok()?; + + Some([ewram.into(), iwram.into()]) + } else if let Some(ptr) = SIG_OLD.scan_process_range(game, main_module_range) { + // This code is for very old versions of VisualBoyAdvance (1.8.0-beta 3) + self.cached_ewram_pointer = game.read::(ptr + 8).ok()?.into(); + self.cached_iwram_pointer = self.cached_ewram_pointer.add_signed(0x4); + + self.is_emulating = { + const SIG_RUNNING: Signature<11> = Signature::new("8B 0D ?? ?? ?? ?? 85 C9 74 ?? 8A"); + let ptr = SIG_RUNNING.scan_process_range(game, main_module_range)? + 2; + game.read::(ptr).ok()?.into() + }; + + let ewram = game.read::(self.cached_ewram_pointer).ok()?; + let iwram = game.read::(self.cached_iwram_pointer).ok()?; + + Some([ewram.into(), iwram.into()]) + } else { + None + } + } + } + + fn read_pointer(&self, game: &Process, address: Address) -> Result { + Ok(match self.is_64_bit { + true => game.read::(address)?.into(), + false => game.read::(address)?.into(), + }) + } + + pub fn keep_alive(&self, game: &Process, ram: &mut Option<[Address; 2]>) -> bool { + match game.read::(self.is_emulating) { + Ok(false) => { + *ram = Some([Address::NULL; 2]); + } + Ok(true) => { + let Ok(ewram) = self.read_pointer(game, self.cached_ewram_pointer) else { + return false; + }; + let Ok(iwram) = self.read_pointer(game, self.cached_iwram_pointer) else { + return false; + }; + *ram = Some([ewram, iwram]); + } + _ => return false, + }; + true + } + + pub const fn new() -> Self { + Self { + cached_ewram_pointer: Address::NULL, + cached_iwram_pointer: Address::NULL, + is_emulating: Address::NULL, + is_64_bit: false, + } + } +}