From 3a4ba115206b439da3d11762132f8a611d4f4cd1 Mon Sep 17 00:00:00 2001 From: sup39 Date: Wed, 26 Jul 2023 00:44:24 +0900 Subject: [PATCH] [v0.1.0-beta.4] Added validity check on MEM1 of DolphinProcessMemory; fixed write memory API --- CHANGELOG.md | 4 +++ Cargo.lock | 2 +- Cargo.toml | 3 +- src/dolphin/mod.rs | 56 +++++++++++++++++++++-------------- src/dolphin/process_memory.rs | 46 +++++++++++++++++++++++----- src/dolphin/shared_memory.rs | 22 +++++++++++--- src/sms/mod.rs | 22 ++++++++++---- www/LICENSE.html | 2 +- www/index.html | 2 +- 9 files changed, 116 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3304029..c05654c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ # Changelog +## v0.1.0-beta.4 (2023/07/26) +- Added validity check on MEM1 of DolphinProcessMemory +- Fixed write memory API + ## v0.1.0-beta.3 (2023/07/25) - Added support for older Dolphin (e.g. Dolphin 5.0-114) - Added support for MEM2 diff --git a/Cargo.lock b/Cargo.lock index 18fd1c0..e9075c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -854,7 +854,7 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "sup-smsac" -version = "0.1.0-beta.3" +version = "0.1.0-beta.4" dependencies = [ "clap", "encoding_rs", diff --git a/Cargo.toml b/Cargo.toml index 786a55c..1398142 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sup-smsac" -version = "0.1.0-beta.3" +version = "0.1.0-beta.4" edition = "2021" license = "MIT OR Apache-2.0" authors = ["sup39 "] @@ -16,6 +16,7 @@ windows = {version = "0.48.0", features = [ "Win32_System_Diagnostics_Debug", "Win32_System_Memory", "Win32_System_Threading", + "Win32_System_ProcessStatus", ]} futures-util = "0.3.28" hyper = { version = "0.14.27", features = ["full"] } diff --git a/src/dolphin/mod.rs b/src/dolphin/mod.rs index a5cefd2..025c183 100644 --- a/src/dolphin/mod.rs +++ b/src/dolphin/mod.rs @@ -15,38 +15,51 @@ use process_memory::DolphinProcessMemory; pub trait Dolphin { /// # Safety /// `maddr + size` must be in bound - unsafe fn operate_memory_unchecked( + unsafe fn read_memory_unchecked( &self, maddr: DolphinMemAddr, size: usize, operator: F, ) -> Option - where F: FnOnce(*mut u8) -> T; + where F: FnOnce(*const u8) -> T; + + /// # Safety + /// `maddr + payload.len()` must be in bound + unsafe fn write_memory_unchecked(&self, maddr: DolphinMemAddr, payload: &[u8]) -> Option<()>; #[inline] - fn operate_memory(&self, addr: Addr, size: usize, operator: F) -> Option - where F: FnOnce(*mut u8) -> T + fn read_memory(&self, addr: Addr, size: usize, operator: F) -> Option + where F: FnOnce(*const u8) -> T { DolphinMemAddr::try_from(addr).ok().and_then(|maddr| { if (maddr.space() as usize) < size {return None} - unsafe {self.operate_memory_unchecked(maddr, size, operator)} + unsafe {self.read_memory_unchecked(maddr, size, operator)} }) } #[inline] - fn operate_memory_truncated(&self, addr: Addr, max_size: usize, operator: F) -> Option - where F: FnOnce(*mut u8, usize) -> T + fn read_memory_truncated(&self, addr: Addr, max_size: usize, operator: F) -> Option + where F: FnOnce(*const u8, usize) -> T { DolphinMemAddr::try_from(addr).ok().and_then(|maddr| { let size = std::cmp::min(maddr.space() as usize, max_size); - unsafe {self.operate_memory_unchecked(maddr, size, |ptr| operator(ptr, size))} + unsafe {self.read_memory_unchecked(maddr, size, |ptr| operator(ptr, size))} + }) + } + + #[inline] + fn write_bytes(&self, addr: Addr, payload: &[u8]) -> Option<()> { + let size = payload.len(); + DolphinMemAddr::try_from(addr).ok().and_then(|maddr| { + if (maddr.space() as usize) < size {return None} + unsafe {self.write_memory_unchecked(maddr, payload)} }) } fn read(&self, addr: Addr) -> Option { let size = std::mem::size_of::(); - self.operate_memory(addr, size, |ptr| unsafe {T::decode_be(ptr)}) + self.read_memory(addr, size, |ptr| unsafe {T::decode_be(ptr)}) } fn read_str(&self, addr: Addr) -> Option { let maxlen = 256; // TODO - self.operate_memory_truncated(addr, maxlen, |ptr, maxlen| { + self.read_memory_truncated(addr, maxlen, |ptr, maxlen| { let mut len = 0usize; while len < maxlen && unsafe{*ptr.add(len)} != 0 { len += 1; @@ -57,19 +70,12 @@ pub trait Dolphin { }).unwrap_or(None) } fn dump_hex(&self, addr: Addr, size: usize) -> Option { - self.operate_memory(addr, size, |ptr| { + self.read_memory(addr, size, |ptr| { (0..size) .map(|i| format!("{:02X}", unsafe {*ptr.add(i)})) .collect() }) } - - fn write_bytes(&self, addr: Addr, payload: &[u8]) -> Option<()> { - let size = payload.len(); - self.operate_memory(addr, size, |ptr| unsafe { - std::ptr::copy(payload.as_ptr(), ptr, size); - }) - } } pub enum DolphinMemory { @@ -77,12 +83,18 @@ pub enum DolphinMemory { ProcessMemory(DolphinProcessMemory), } impl Dolphin for DolphinMemory { - unsafe fn operate_memory_unchecked(&self, maddr: DolphinMemAddr, size: usize, operator: F) -> Option - where F: FnOnce(*mut u8) -> T + unsafe fn read_memory_unchecked(&self, maddr: DolphinMemAddr, size: usize, operator: F) -> Option + where F: FnOnce(*const u8) -> T { match self { - DolphinMemory::SharedMemory(m) => m.operate_memory_unchecked(maddr, size, operator), - DolphinMemory::ProcessMemory(m) => m.operate_memory_unchecked(maddr, size, operator), + DolphinMemory::SharedMemory(m) => m.read_memory_unchecked(maddr, size, operator), + DolphinMemory::ProcessMemory(m) => m.read_memory_unchecked(maddr, size, operator), + } + } + unsafe fn write_memory_unchecked(&self, maddr: DolphinMemAddr, payload: &[u8]) -> Option<()> { + match self { + DolphinMemory::SharedMemory(m) => m.write_memory_unchecked(maddr, payload), + DolphinMemory::ProcessMemory(m) => m.write_memory_unchecked(maddr, payload), } } } diff --git a/src/dolphin/process_memory.rs b/src/dolphin/process_memory.rs index 6f82e64..503bc55 100644 --- a/src/dolphin/process_memory.rs +++ b/src/dolphin/process_memory.rs @@ -14,9 +14,10 @@ use crate::sys::process_memory::ProcessMemoryIterator; use core::ffi::c_void; use windows::Win32::Foundation::{HANDLE, CloseHandle}; use windows::Win32::System::{ - Threading::{PROCESS_QUERY_INFORMATION, PROCESS_VM_READ, PROCESS_VM_WRITE, OpenProcess}, + Threading::{PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION, PROCESS_VM_READ, PROCESS_VM_WRITE, OpenProcess}, Memory::MEM_MAPPED, - Diagnostics::Debug::ReadProcessMemory, + Diagnostics::Debug::{ReadProcessMemory, WriteProcessMemory}, + ProcessStatus::{PSAPI_WORKING_SET_EX_INFORMATION, QueryWorkingSetEx}, }; pub struct DolphinProcessMemory { @@ -32,8 +33,8 @@ impl Drop for DolphinProcessMemory { } } impl Dolphin for DolphinProcessMemory { - unsafe fn operate_memory_unchecked(&self, maddr: DolphinMemAddr, size: usize, operator: F) -> Option - where F: FnOnce(*mut u8) -> T + unsafe fn read_memory_unchecked(&self, maddr: DolphinMemAddr, size: usize, operator: F) -> Option + where F: FnOnce(*const u8) -> T { match maddr { DolphinMemAddr::MEM1(offset) => Some(self.base_addr_mem1 + (offset as usize)), @@ -50,6 +51,23 @@ impl Dolphin for DolphinProcessMemory { } }) } + + unsafe fn write_memory_unchecked(&self, maddr: DolphinMemAddr, payload: &[u8]) -> Option<()> { + match maddr { + DolphinMemAddr::MEM1(offset) => Some(self.base_addr_mem1 + (offset as usize)), + DolphinMemAddr::MEM2(offset) => self.base_addr_mem2.map(|base_addr| base_addr + (offset as usize)), + }.and_then(|base_addr| { + let size = payload.len(); + let ptr = payload.as_ptr(); + match unsafe {WriteProcessMemory( + self.h_proc, base_addr as *const c_void, + ptr as *mut c_void, size, None, + ).as_bool()} { + true => Some(()), + false => None, + } + }) + } } pub enum DolphinProcessMemoryFindError { @@ -64,13 +82,26 @@ impl From for DolphinProcessMemoryFindError { impl DolphinProcessMemory { pub fn open_pid(pid: PidType) -> Result { unsafe { - let h_proc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_VM_WRITE, false, pid)?; + let h_proc = OpenProcess( + PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, + false, pid, + )?; let mut itr = ProcessMemoryIterator::with_handle(h_proc); // find MEM1 let Some(base_addr_mem1) = - itr.find(|meminfo| meminfo.RegionSize == 0x2000000 && meminfo.Type == MEM_MAPPED) - .map(|meminfo| meminfo.BaseAddress) + itr.find(|meminfo| meminfo.RegionSize == 0x2000000 && meminfo.Type == MEM_MAPPED && { + let mut wsinfo = PSAPI_WORKING_SET_EX_INFORMATION { + VirtualAddress: meminfo.BaseAddress, + ..PSAPI_WORKING_SET_EX_INFORMATION::default() + }; + QueryWorkingSetEx( + h_proc, + &mut wsinfo as *mut PSAPI_WORKING_SET_EX_INFORMATION as *mut c_void, + std::mem::size_of::() as u32 + ).as_bool() + && wsinfo.VirtualAttributes.Flags & 1 != 0 + }).map(|meminfo| meminfo.BaseAddress) else { CloseHandle(h_proc); return Err(DolphinProcessMemoryFindError::MemoryNotFound); @@ -83,6 +114,7 @@ impl DolphinProcessMemory { meminfo.BaseAddress == base_addr_mem2_check && meminfo.RegionSize == 0x4000000 && meminfo.Type == MEM_MAPPED + // TODO check valid }).map(|_| base_addr_mem2_check as usize); Ok(DolphinProcessMemory { diff --git a/src/dolphin/shared_memory.rs b/src/dolphin/shared_memory.rs index 188de9b..c80142d 100644 --- a/src/dolphin/shared_memory.rs +++ b/src/dolphin/shared_memory.rs @@ -12,10 +12,8 @@ pub struct DolphinSharedMemory { pub const MEM2_OFFSET: u32 = 0x4040000; impl Dolphin for DolphinSharedMemory { - /// # Safety - /// `maddr + size` must be in bound - unsafe fn operate_memory_unchecked(&self, maddr: DolphinMemAddr, _size: usize, operator: F) -> Option - where F: FnOnce(*mut u8) -> T + unsafe fn read_memory_unchecked(&self, maddr: DolphinMemAddr, _size: usize, operator: F) -> Option + where F: FnOnce(*const u8) -> T { match maddr { DolphinMemAddr::MEM1(offset) => Some(offset), @@ -27,6 +25,22 @@ impl Dolphin for DolphinSharedMemory { operator(self.shared_memory.get_ptr().add(offset as usize)) }) } + + unsafe fn write_memory_unchecked(&self, maddr: DolphinMemAddr, payload: &[u8]) -> Option<()> { + match maddr { + DolphinMemAddr::MEM1(offset) => Some(offset), + DolphinMemAddr::MEM2(offset) => match self.has_mem2 { + true => Some(MEM2_OFFSET + offset), + false => None, + }, + }.map(|offset| { + std::ptr::copy( + payload.as_ptr(), + self.shared_memory.get_ptr().add(offset as usize), + payload.len(), + ); + }) + } } impl DolphinSharedMemory { diff --git a/src/sms/mod.rs b/src/sms/mod.rs index 1be3805..0a46465 100644 --- a/src/sms/mod.rs +++ b/src/sms/mod.rs @@ -20,10 +20,13 @@ pub struct SMSDolphin { ver: SMSVersion, } impl Dolphin for SMSDolphin { - unsafe fn operate_memory_unchecked(&self, maddr: DolphinMemAddr, size: usize, operator: F) -> Option - where F: FnOnce(*mut u8) -> T + unsafe fn read_memory_unchecked(&self, maddr: DolphinMemAddr, size: usize, operator: F) -> Option + where F: FnOnce(*const u8) -> T { - self.d.operate_memory_unchecked(maddr, size, operator) + self.d.read_memory_unchecked(maddr, size, operator) + } + unsafe fn write_memory_unchecked(&self, maddr: DolphinMemAddr, payload: &[u8]) -> Option<()> { + self.d.write_memory_unchecked(maddr, payload) } } @@ -81,10 +84,17 @@ impl SMSDolphin { for (pid, d) in DolphinMemory::list() { match d { Some(d) => { - if let Ok(o) = SMSDolphin::from_dolphin_memory(d, pid) { - return Ok(o) + match SMSDolphin::from_dolphin_memory(d, pid) { + Ok(o) => return Ok(o), + Err(e) => { + game_running = true; + match e { + Some(e) => eprintln!("Unknown game (pid: {pid}): {}", + e.map(|c| format!("{c:02X}")).join("")), + None => eprintln!("Unknown game (pid: {pid}): fail to get version"), + } + } } - game_running = true; }, None => dolphin_running = true, } diff --git a/www/LICENSE.html b/www/LICENSE.html index 2913560..d3389df 100644 --- a/www/LICENSE.html +++ b/www/LICENSE.html @@ -4687,7 +4687,7 @@ limitations under the License.

Used by: