From 45f8cb8d898eff507697b3be5db9d82ab96b60e7 Mon Sep 17 00:00:00 2001 From: PARTY MAN X <4970541+PARTYMANX@users.noreply.github.com> Date: Fri, 8 Sep 2023 00:07:07 -0500 Subject: [PATCH 1/6] livesplit-auto-splitting: add process listing and attaching by pid to API This allows for users to list all process pids matching a name and then attach to processes using those pids for when custom picking logic is needed for processes. --- crates/livesplit-auto-splitting/src/lib.rs | 4 ++ .../livesplit-auto-splitting/src/process.rs | 36 ++++++++++++++ .../livesplit-auto-splitting/src/runtime.rs | 48 ++++++++++++++++++- 3 files changed, 87 insertions(+), 1 deletion(-) diff --git a/crates/livesplit-auto-splitting/src/lib.rs b/crates/livesplit-auto-splitting/src/lib.rs index 92a88ba3..937ad687 100644 --- a/crates/livesplit-auto-splitting/src/lib.rs +++ b/crates/livesplit-auto-splitting/src/lib.rs @@ -99,8 +99,12 @@ //! //! /// Attaches to a process based on its name. //! pub fn process_attach(name_ptr: *const u8, name_len: usize) -> Option; +//! /// Attaches to a process based on its pid. +//! pub fn process_attach_pid(pid: u32); //! /// Detaches from a process. //! pub fn process_detach(process: ProcessId); +//! /// Lists processes (as pids) based on their name. +//! pub fn process_list(name_ptr: *const u8, name_len: usize, list_ptr: *mut u8, list_len_ptr: *mut usize) -> bool //! /// Checks whether is a process is still open. You should detach from a //! /// process and stop using it if this returns `false`. //! pub fn process_is_open(process: ProcessId) -> bool; diff --git a/crates/livesplit-auto-splitting/src/process.rs b/crates/livesplit-auto-splitting/src/process.rs index 8245df9a..da966fd1 100644 --- a/crates/livesplit-auto-splitting/src/process.rs +++ b/crates/livesplit-auto-splitting/src/process.rs @@ -79,6 +79,42 @@ impl Process { }) } + pub(super) fn with_pid(pid: u32, process_list: &mut ProcessList) -> Result { + process_list.refresh(); + let process = process_list.get(sysinfo::Pid::from_u32(pid)).context(ProcessDoesntExist)?; + + let path = build_path(process.exe()); + + let handle = pid.try_into().context(InvalidHandle)?; + + let now = Instant::now(); + Ok(Process { + handle, + pid, + memory_ranges: Vec::new(), + next_memory_range_check: now, + next_open_check: now + Duration::from_secs(1), + path, + }) + } + + pub(super) fn list_pids_by_name(name: &str, process_list: &mut ProcessList) -> Result, OpenError> { + let mut result = Vec::new(); + + process_list.refresh(); + let processes = process_list.processes_by_name(name); + + for process in processes { + result.push(process.pid().as_u32()); + } + + if result.is_empty() { + Err(OpenError::ProcessDoesntExist) + } else { + Ok(result) + } + } + pub(super) fn is_open(&mut self, process_list: &mut ProcessList) -> bool { let now = Instant::now(); let pid = sysinfo::Pid::from_u32(self.pid as u32); diff --git a/crates/livesplit-auto-splitting/src/runtime.rs b/crates/livesplit-auto-splitting/src/runtime.rs index 1ffe08c2..a1471cf4 100644 --- a/crates/livesplit-auto-splitting/src/runtime.rs +++ b/crates/livesplit-auto-splitting/src/runtime.rs @@ -14,7 +14,7 @@ use std::{ env::consts::{ARCH, OS}, path::{Path, PathBuf}, str, - time::{Duration, Instant}, + time::{Duration, Instant}, mem, }; use sysinfo::{ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt}; use wasi_common::{ @@ -668,6 +668,25 @@ fn bind_interface(linker: &mut Linker>) -> Result<(), Creat source, name: "process_attach", })? + .func_wrap("env", "process_attach_pid", { + |mut caller: Caller<'_, Context>, pid: u32| { + let (_, context) = memory_and_context(&mut caller); + Ok( + if let Ok(p) = Process::with_pid(pid, &mut context.process_list) { + context + .timer + .log(format_args!("Attached to a new process with pid {pid}")); + context.processes.insert(p).data().as_ffi() + } else { + 0 + }, + ) + } + }) + .map_err(|source| CreationError::LinkFunction { + source, + name: "process_attach_pid", + })? .func_wrap("env", "process_detach", { |mut caller: Caller<'_, Context>, process: u64| { caller @@ -686,6 +705,33 @@ fn bind_interface(linker: &mut Linker>) -> Result<(), Creat source, name: "process_detach", })? + .func_wrap("env", "process_list", { + |mut caller: Caller<'_, Context>, name_ptr: u32, name_len: u32, list_ptr: u32, list_len_ptr: u32| { + let (memory, context) = memory_and_context(&mut caller); + let process_name = get_str(memory, name_ptr, name_len)?; + if let Ok(list) = Process::list_pids_by_name(process_name, &mut context.process_list) { + let list_len_bytes = get_arr_mut(memory, list_len_ptr)?; + let list_len = u32::from_le_bytes(*list_len_bytes) as usize; + *list_len_bytes = (list.len() as u32).to_le_bytes(); + if list_len < list.len() { + return Ok(0u32); + } + let mut list_bytes = Vec::new(); + for i in &list { + list_bytes.extend_from_slice(&i.to_le_bytes()); + } + let buf = get_slice_mut(memory, list_ptr, (list.len() * mem::size_of_val(&list_len)) as _)?; + buf.copy_from_slice(list_bytes.as_slice()); + Ok(1u32) + } else { + Ok(0u32) + } + } + }) + .map_err(|source| CreationError::LinkFunction { + source, + name: "process_list", + })? .func_wrap("env", "process_is_open", { |mut caller: Caller<'_, Context>, process: u64| { let ctx = caller.data_mut(); From 62e68a15afd695729de661f65b85b773e3687131 Mon Sep 17 00:00:00 2001 From: PARTY MAN X <4970541+PARTYMANX@users.noreply.github.com> Date: Fri, 8 Sep 2023 02:12:42 -0500 Subject: [PATCH 2/6] livesplit-auto-splitting: fix process list buffer length --- crates/livesplit-auto-splitting/src/runtime.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/livesplit-auto-splitting/src/runtime.rs b/crates/livesplit-auto-splitting/src/runtime.rs index a1471cf4..f4e8b46b 100644 --- a/crates/livesplit-auto-splitting/src/runtime.rs +++ b/crates/livesplit-auto-splitting/src/runtime.rs @@ -720,7 +720,7 @@ fn bind_interface(linker: &mut Linker>) -> Result<(), Creat for i in &list { list_bytes.extend_from_slice(&i.to_le_bytes()); } - let buf = get_slice_mut(memory, list_ptr, (list.len() * mem::size_of_val(&list_len)) as _)?; + let buf = get_slice_mut(memory, list_ptr, (list.len() * mem::size_of::()) as _)?; buf.copy_from_slice(list_bytes.as_slice()); Ok(1u32) } else { From c0ac688671293a4935e315f7f3b3c77665a85c32 Mon Sep 17 00:00:00 2001 From: PARTY MAN X <4970541+PARTYMANX@users.noreply.github.com> Date: Sat, 9 Sep 2023 01:32:35 -0500 Subject: [PATCH 3/6] livesplit-auto-splitting: cleanup comments for list processes and attach pid --- crates/livesplit-auto-splitting/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/livesplit-auto-splitting/src/lib.rs b/crates/livesplit-auto-splitting/src/lib.rs index 937ad687..6c206034 100644 --- a/crates/livesplit-auto-splitting/src/lib.rs +++ b/crates/livesplit-auto-splitting/src/lib.rs @@ -103,7 +103,10 @@ //! pub fn process_attach_pid(pid: u32); //! /// Detaches from a process. //! pub fn process_detach(process: ProcessId); -//! /// Lists processes (as pids) based on their name. +//! /// Lists processes (as pids) based on their name. Returns `false` +//! /// if the buffer is too small. After this call, no matter whether +//! /// it was successful or not, the `buf_len_ptr` will be set to the +//! /// required buffer size. //! pub fn process_list(name_ptr: *const u8, name_len: usize, list_ptr: *mut u8, list_len_ptr: *mut usize) -> bool //! /// Checks whether is a process is still open. You should detach from a //! /// process and stop using it if this returns `false`. From ae57ab13bd260987237c153d79eb5ef9dc80c004 Mon Sep 17 00:00:00 2001 From: PARTY MAN X <4970541+PARTYMANX@users.noreply.github.com> Date: Sat, 9 Sep 2023 01:42:05 -0500 Subject: [PATCH 4/6] livesplit-auto-splitting: cleanup std use block in runtime --- crates/livesplit-auto-splitting/src/runtime.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/livesplit-auto-splitting/src/runtime.rs b/crates/livesplit-auto-splitting/src/runtime.rs index f4e8b46b..62128338 100644 --- a/crates/livesplit-auto-splitting/src/runtime.rs +++ b/crates/livesplit-auto-splitting/src/runtime.rs @@ -14,7 +14,8 @@ use std::{ env::consts::{ARCH, OS}, path::{Path, PathBuf}, str, - time::{Duration, Instant}, mem, + time::{Duration, Instant}, + mem, }; use sysinfo::{ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt}; use wasi_common::{ From fb3189bb71c1f04783e6fd7471616b39e701c0d1 Mon Sep 17 00:00:00 2001 From: PARTY MAN X <4970541+PARTYMANX@users.noreply.github.com> Date: Sat, 9 Sep 2023 17:05:35 -0500 Subject: [PATCH 5/6] livesplit-auto-splitting: fix failing compiles on certain platforms by converting u32 pid to Pid type --- crates/livesplit-auto-splitting/src/process.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/livesplit-auto-splitting/src/process.rs b/crates/livesplit-auto-splitting/src/process.rs index da966fd1..79b4397c 100644 --- a/crates/livesplit-auto-splitting/src/process.rs +++ b/crates/livesplit-auto-splitting/src/process.rs @@ -85,12 +85,14 @@ impl Process { let path = build_path(process.exe()); - let handle = pid.try_into().context(InvalidHandle)?; + let pid_out = pid as Pid; + + let handle = pid_out.try_into().context(InvalidHandle)?; let now = Instant::now(); Ok(Process { handle, - pid, + pid: pid_out, memory_ranges: Vec::new(), next_memory_range_check: now, next_open_check: now + Duration::from_secs(1), From 84472e0246e0fc0c6b36b15ad743a4fc0f3c719c Mon Sep 17 00:00:00 2001 From: Christopher Serr Date: Sun, 1 Oct 2023 18:21:03 +0200 Subject: [PATCH 6/6] Some cleanup --- crates/livesplit-auto-splitting/Cargo.toml | 1 + crates/livesplit-auto-splitting/README.md | 56 ++++--- crates/livesplit-auto-splitting/src/lib.rs | 61 +++++--- .../livesplit-auto-splitting/src/process.rs | 34 +++-- .../livesplit-auto-splitting/src/runtime.rs | 139 +++++++++++++----- src/auto_splitting/mod.rs | 68 ++++++++- 6 files changed, 262 insertions(+), 97 deletions(-) diff --git a/crates/livesplit-auto-splitting/Cargo.toml b/crates/livesplit-auto-splitting/Cargo.toml index e0e0b3c9..c72fe971 100644 --- a/crates/livesplit-auto-splitting/Cargo.toml +++ b/crates/livesplit-auto-splitting/Cargo.toml @@ -12,6 +12,7 @@ edition = "2021" [dependencies] anyhow = { version = "1.0.45", default-features = false } async-trait = "0.1.73" +bytemuck = { version = "1.14.0", features = ["min_const_generics"] } proc-maps = { version = "0.3.0", default-features = false } read-process-memory = { version = "0.1.4", default-features = false } slotmap = { version = "1.0.2", default-features = false } diff --git a/crates/livesplit-auto-splitting/README.md b/crates/livesplit-auto-splitting/README.md index c3b2dd7c..92695d13 100644 --- a/crates/livesplit-auto-splitting/README.md +++ b/crates/livesplit-auto-splitting/README.md @@ -34,7 +34,10 @@ pub struct Address(pub u64); pub struct NonZeroAddress(pub NonZeroU64); #[repr(transparent)] -pub struct ProcessId(NonZeroU64); +pub struct Process(NonZeroU64); + +#[repr(transparent)] +pub struct ProcessId(u64); #[repr(transparent)] pub struct TimerState(u32); @@ -56,13 +59,13 @@ pub struct MemoryRangeFlags(NonZeroU64); impl MemoryRangeFlags { /// The memory range is readable. - pub const READ: Self = Self(NonZeroU64::new(1 << 1).unwrap()); + pub const READ: Self = Self(match NonZeroU64::new(1 << 1) { Some(v) => v, None => panic!() }); /// The memory range is writable. - pub const WRITE: Self = Self(NonZeroU64::new(1 << 2).unwrap()); + pub const WRITE: Self = Self(match NonZeroU64::new(1 << 2) { Some(v) => v, None => panic!() }); /// The memory range is executable. - pub const EXECUTE: Self = Self(NonZeroU64::new(1 << 3).unwrap()); + pub const EXECUTE: Self = Self(match NonZeroU64::new(1 << 3) { Some(v) => v, None => panic!() }); /// The memory range has a file path. - pub const PATH: Self = Self(NonZeroU64::new(1 << 4).unwrap()); + pub const PATH: Self = Self(match NonZeroU64::new(1 << 4) { Some(v) => v, None => panic!() }); } extern "C" { @@ -98,16 +101,35 @@ extern "C" { pub fn timer_resume_game_time(); /// Attaches to a process based on its name. - pub fn process_attach(name_ptr: *const u8, name_len: usize) -> Option; + pub fn process_attach(name_ptr: *const u8, name_len: usize) -> Option; + /// Attaches to a process based on its process id. + pub fn process_attach_by_pid(pid: ProcessId) -> Option; /// Detaches from a process. - pub fn process_detach(process: ProcessId); + pub fn process_detach(process: Process); + /// Lists processes based on their name. Returns `false` if listing the + /// processes failed. If it was successful, the buffer is now filled + /// with the process ids. They are in no specific order. The + /// `list_len_ptr` will be updated to the amount of process ids that + /// were found. If this is larger than the original value provided, the + /// buffer provided was too small and not all process ids could be + /// stored. This is still considered successful and can optionally be + /// treated as an error condition by the caller by checking if the + /// length increased and potentially reallocating a larger buffer. If + /// the length decreased after the call, the buffer was larger than + /// needed and the remaining entries are untouched. + pub fn process_list_by_name( + name_ptr: *const u8, + name_len: usize, + list_ptr: *mut ProcessId, + list_len_ptr: *mut usize, + ) -> bool; /// Checks whether is a process is still open. You should detach from a /// process and stop using it if this returns `false`. - pub fn process_is_open(process: ProcessId) -> bool; + pub fn process_is_open(process: Process) -> bool; /// Reads memory from a process at the address given. This will write /// the memory to the buffer given. Returns `false` if this fails. pub fn process_read( - process: ProcessId, + process: Process, address: Address, buf_ptr: *mut u8, buf_len: usize, @@ -115,41 +137,41 @@ extern "C" { /// Gets the address of a module in a process. pub fn process_get_module_address( - process: ProcessId, + process: Process, name_ptr: *const u8, name_len: usize, ) -> Option; /// Gets the size of a module in a process. pub fn process_get_module_size( - process: ProcessId, + process: Process, name_ptr: *const u8, name_len: usize, ) -> Option; /// Gets the number of memory ranges in a given process. - pub fn process_get_memory_range_count(process: ProcessId) -> Option; + pub fn process_get_memory_range_count(process: Process) -> Option; /// Gets the start address of a memory range by its index. pub fn process_get_memory_range_address( - process: ProcessId, + process: Process, idx: u64, ) -> Option; /// Gets the size of a memory range by its index. - pub fn process_get_memory_range_size(process: ProcessId, idx: u64) -> Option; + pub fn process_get_memory_range_size(process: Process, idx: u64) -> Option; /// Gets the flags of a memory range by its index. pub fn process_get_memory_range_flags( - process: ProcessId, + process: Process, idx: u64, ) -> Option; /// Stores the file system path of the executable in the buffer given. The - /// path is a pa thth that is accessiblerough the WASI file system, so a + /// path is a path that is accessible through the WASI file system, so a /// Windows path of `C:\foo\bar.exe` would be returned as /// `/mnt/c/foo/bar.exe`. Returns `false` if the buffer is too small. After /// this call, no matter whether it was successful or not, the /// `buf_len_ptr` will be set to the required buffer size. The path is /// guaranteed to be valid UTF-8 and is not nul-terminated. pub fn process_get_path( - process: ProcessId, + process: Process, buf_ptr: *mut u8, buf_len_ptr: *mut usize, ) -> bool; diff --git a/crates/livesplit-auto-splitting/src/lib.rs b/crates/livesplit-auto-splitting/src/lib.rs index 6c206034..08aef106 100644 --- a/crates/livesplit-auto-splitting/src/lib.rs +++ b/crates/livesplit-auto-splitting/src/lib.rs @@ -34,7 +34,10 @@ //! pub struct NonZeroAddress(pub NonZeroU64); //! //! #[repr(transparent)] -//! pub struct ProcessId(NonZeroU64); +//! pub struct Process(NonZeroU64); +//! +//! #[repr(transparent)] +//! pub struct ProcessId(u64); //! //! #[repr(transparent)] //! pub struct TimerState(u32); @@ -56,13 +59,13 @@ //! //! impl MemoryRangeFlags { //! /// The memory range is readable. -//! pub const READ: Self = Self(NonZeroU64::new(1 << 1).unwrap()); +//! pub const READ: Self = Self(match NonZeroU64::new(1 << 1) { Some(v) => v, None => panic!() }); //! /// The memory range is writable. -//! pub const WRITE: Self = Self(NonZeroU64::new(1 << 2).unwrap()); +//! pub const WRITE: Self = Self(match NonZeroU64::new(1 << 2) { Some(v) => v, None => panic!() }); //! /// The memory range is executable. -//! pub const EXECUTE: Self = Self(NonZeroU64::new(1 << 3).unwrap()); +//! pub const EXECUTE: Self = Self(match NonZeroU64::new(1 << 3) { Some(v) => v, None => panic!() }); //! /// The memory range has a file path. -//! pub const PATH: Self = Self(NonZeroU64::new(1 << 4).unwrap()); +//! pub const PATH: Self = Self(match NonZeroU64::new(1 << 4) { Some(v) => v, None => panic!() }); //! } //! //! extern "C" { @@ -98,23 +101,35 @@ //! pub fn timer_resume_game_time(); //! //! /// Attaches to a process based on its name. -//! pub fn process_attach(name_ptr: *const u8, name_len: usize) -> Option; -//! /// Attaches to a process based on its pid. -//! pub fn process_attach_pid(pid: u32); +//! pub fn process_attach(name_ptr: *const u8, name_len: usize) -> Option; +//! /// Attaches to a process based on its process id. +//! pub fn process_attach_by_pid(pid: ProcessId) -> Option; //! /// Detaches from a process. -//! pub fn process_detach(process: ProcessId); -//! /// Lists processes (as pids) based on their name. Returns `false` -//! /// if the buffer is too small. After this call, no matter whether -//! /// it was successful or not, the `buf_len_ptr` will be set to the -//! /// required buffer size. -//! pub fn process_list(name_ptr: *const u8, name_len: usize, list_ptr: *mut u8, list_len_ptr: *mut usize) -> bool +//! pub fn process_detach(process: Process); +//! /// Lists processes based on their name. Returns `false` if listing the +//! /// processes failed. If it was successful, the buffer is now filled +//! /// with the process ids. They are in no specific order. The +//! /// `list_len_ptr` will be updated to the amount of process ids that +//! /// were found. If this is larger than the original value provided, the +//! /// buffer provided was too small and not all process ids could be +//! /// stored. This is still considered successful and can optionally be +//! /// treated as an error condition by the caller by checking if the +//! /// length increased and potentially reallocating a larger buffer. If +//! /// the length decreased after the call, the buffer was larger than +//! /// needed and the remaining entries are untouched. +//! pub fn process_list_by_name( +//! name_ptr: *const u8, +//! name_len: usize, +//! list_ptr: *mut ProcessId, +//! list_len_ptr: *mut usize, +//! ) -> bool; //! /// Checks whether is a process is still open. You should detach from a //! /// process and stop using it if this returns `false`. -//! pub fn process_is_open(process: ProcessId) -> bool; +//! pub fn process_is_open(process: Process) -> bool; //! /// Reads memory from a process at the address given. This will write //! /// the memory to the buffer given. Returns `false` if this fails. //! pub fn process_read( -//! process: ProcessId, +//! process: Process, //! address: Address, //! buf_ptr: *mut u8, //! buf_len: usize, @@ -122,29 +137,29 @@ //! //! /// Gets the address of a module in a process. //! pub fn process_get_module_address( -//! process: ProcessId, +//! process: Process, //! name_ptr: *const u8, //! name_len: usize, //! ) -> Option; //! /// Gets the size of a module in a process. //! pub fn process_get_module_size( -//! process: ProcessId, +//! process: Process, //! name_ptr: *const u8, //! name_len: usize, //! ) -> Option; //! //! /// Gets the number of memory ranges in a given process. -//! pub fn process_get_memory_range_count(process: ProcessId) -> Option; +//! pub fn process_get_memory_range_count(process: Process) -> Option; //! /// Gets the start address of a memory range by its index. //! pub fn process_get_memory_range_address( -//! process: ProcessId, +//! process: Process, //! idx: u64, //! ) -> Option; //! /// Gets the size of a memory range by its index. -//! pub fn process_get_memory_range_size(process: ProcessId, idx: u64) -> Option; +//! pub fn process_get_memory_range_size(process: Process, idx: u64) -> Option; //! /// Gets the flags of a memory range by its index. //! pub fn process_get_memory_range_flags( -//! process: ProcessId, +//! process: Process, //! idx: u64, //! ) -> Option; //! @@ -156,7 +171,7 @@ //! /// `buf_len_ptr` will be set to the required buffer size. The path is //! /// guaranteed to be valid UTF-8 and is not nul-terminated. //! pub fn process_get_path( -//! process: ProcessId, +//! process: Process, //! buf_ptr: *mut u8, //! buf_len_ptr: *mut usize, //! ) -> bool; diff --git a/crates/livesplit-auto-splitting/src/process.rs b/crates/livesplit-auto-splitting/src/process.rs index 79b4397c..0f932680 100644 --- a/crates/livesplit-auto-splitting/src/process.rs +++ b/crates/livesplit-auto-splitting/src/process.rs @@ -81,7 +81,9 @@ impl Process { pub(super) fn with_pid(pid: u32, process_list: &mut ProcessList) -> Result { process_list.refresh(); - let process = process_list.get(sysinfo::Pid::from_u32(pid)).context(ProcessDoesntExist)?; + let process = process_list + .get(sysinfo::Pid::from_u32(pid)) + .context(ProcessDoesntExist)?; let path = build_path(process.exe()); @@ -100,21 +102,14 @@ impl Process { }) } - pub(super) fn list_pids_by_name(name: &str, process_list: &mut ProcessList) -> Result, OpenError> { - let mut result = Vec::new(); - + pub(super) fn list_pids_by_name<'a>( + name: &'a str, + process_list: &'a mut ProcessList, + ) -> impl Iterator + 'a { process_list.refresh(); - let processes = process_list.processes_by_name(name); - - for process in processes { - result.push(process.pid().as_u32()); - } - - if result.is_empty() { - Err(OpenError::ProcessDoesntExist) - } else { - Ok(result) - } + process_list + .processes_by_name(name) + .map(|p| p.pid().as_u32()) } pub(super) fn is_open(&mut self, process_list: &mut ProcessList) -> bool { @@ -203,6 +198,15 @@ impl Process { self.path.as_deref() } + /// Returns the name of the executable of the process. + pub fn name(&self) -> Option<&str> { + let path = &self.path.as_deref()?; + Some(match path.rsplit_once('/') { + Some((_, name)) => name, + None => path, + }) + } + fn refresh_memory_ranges(&mut self) -> Result<(), ModuleError> { let now = Instant::now(); if now >= self.next_memory_range_check { diff --git a/crates/livesplit-auto-splitting/src/runtime.rs b/crates/livesplit-auto-splitting/src/runtime.rs index 62128338..c86116c7 100644 --- a/crates/livesplit-auto-splitting/src/runtime.rs +++ b/crates/livesplit-auto-splitting/src/runtime.rs @@ -14,8 +14,7 @@ use std::{ env::consts::{ARCH, OS}, path::{Path, PathBuf}, str, - time::{Duration, Instant}, - mem, + time::{Duration, Instant}, }; use sysinfo::{ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt}; use wasi_common::{ @@ -655,9 +654,10 @@ fn bind_interface(linker: &mut Linker>) -> Result<(), Creat let process_name = get_str(memory, ptr, len)?; Ok( if let Ok(p) = Process::with_name(process_name, &mut context.process_list) { - context - .timer - .log(format_args!("Attached to a new process: {process_name}")); + context.timer.log(format_args!( + "Attached to a new process: {}", + p.name().unwrap_or("") + )); context.processes.insert(p).data().as_ffi() } else { 0 @@ -669,14 +669,19 @@ fn bind_interface(linker: &mut Linker>) -> Result<(), Creat source, name: "process_attach", })? - .func_wrap("env", "process_attach_pid", { - |mut caller: Caller<'_, Context>, pid: u32| { + .func_wrap("env", "process_attach_by_pid", { + |mut caller: Caller<'_, Context>, pid: u64| { let (_, context) = memory_and_context(&mut caller); Ok( - if let Ok(p) = Process::with_pid(pid, &mut context.process_list) { - context - .timer - .log(format_args!("Attached to a new process with pid {pid}")); + if let Some(p) = pid + .try_into() + .ok() + .and_then(|pid| Process::with_pid(pid, &mut context.process_list).ok()) + { + context.timer.log(format_args!( + "Attached to a new process: {}", + p.name().unwrap_or("") + )); context.processes.insert(p).data().as_ffi() } else { 0 @@ -686,7 +691,7 @@ fn bind_interface(linker: &mut Linker>) -> Result<(), Creat }) .map_err(|source| CreationError::LinkFunction { source, - name: "process_attach_pid", + name: "process_attach_by_pid", })? .func_wrap("env", "process_detach", { |mut caller: Caller<'_, Context>, process: u64| { @@ -706,32 +711,52 @@ fn bind_interface(linker: &mut Linker>) -> Result<(), Creat source, name: "process_detach", })? - .func_wrap("env", "process_list", { - |mut caller: Caller<'_, Context>, name_ptr: u32, name_len: u32, list_ptr: u32, list_len_ptr: u32| { + .func_wrap("env", "process_list_by_name", { + |mut caller: Caller<'_, Context>, + name_ptr: u32, + name_len: u32, + list_ptr: u32, + list_len_ptr: u32| { let (memory, context) = memory_and_context(&mut caller); - let process_name = get_str(memory, name_ptr, name_len)?; - if let Ok(list) = Process::list_pids_by_name(process_name, &mut context.process_list) { - let list_len_bytes = get_arr_mut(memory, list_len_ptr)?; - let list_len = u32::from_le_bytes(*list_len_bytes) as usize; - *list_len_bytes = (list.len() as u32).to_le_bytes(); - if list_len < list.len() { - return Ok(0u32); - } - let mut list_bytes = Vec::new(); - for i in &list { - list_bytes.extend_from_slice(&i.to_le_bytes()); - } - let buf = get_slice_mut(memory, list_ptr, (list.len() * mem::size_of::()) as _)?; - buf.copy_from_slice(list_bytes.as_slice()); - Ok(1u32) - } else { - Ok(0u32) + + let list_len_bytes = get_arr_mut(memory, list_len_ptr)?; + let list_len = u32::from_le_bytes(*list_len_bytes); + + let [name, list] = get_two_slice_mut( + memory, + name_ptr, + name_len, + list_ptr, + list_len + .checked_mul(8) + .context("The list length overflows the size of the address space.")?, + )?; + + let mut count = 0u32; + + let mut iter = + Process::list_pids_by_name(str::from_utf8(name)?, &mut context.process_list) + .inspect(|_| { + count = count.saturating_add(1); + }); + + for (pid, list_element) in iter.by_ref().zip(bytemuck::cast_slice_mut(list)) { + *list_element = (pid as u64).to_le_bytes(); } + // Consume the rest of the PIDs to ensure we fully count them. + iter.for_each(drop); + + let list_len_bytes = get_arr_mut(memory, list_len_ptr)?; + *list_len_bytes = count.to_le_bytes(); + + // Currently this can't fail, but that's only because `sysinfo` + // doesn't report any errors when listing the processes fails. + Ok(1u32) } }) .map_err(|source| CreationError::LinkFunction { source, - name: "process_list", + name: "process_list_by_name", })? .func_wrap("env", "process_is_open", { |mut caller: Caller<'_, Context>, process: u64| { @@ -972,13 +997,17 @@ fn get_arr_mut(memory: &mut [u8], ptr: u32) -> Result<&mut [u8; fn get_slice(memory: &[u8], ptr: u32, len: u32) -> Result<&[u8]> { memory - .get(ptr as usize..(ptr + len) as usize) + .get(ptr as usize..) + .context("Out of bounds pointer and length pair.")? + .get(..len as usize) .context("Out of bounds pointer and length pair.") } fn get_slice_mut(memory: &mut [u8], ptr: u32, len: u32) -> Result<&mut [u8]> { memory - .get_mut(ptr as usize..(ptr + len) as usize) + .get_mut(ptr as usize..) + .context("Out of bounds pointer and length pair.")? + .get_mut(..len as usize) .context("Out of bounds pointer and length pair.") } @@ -986,3 +1015,45 @@ fn get_str(memory: &[u8], ptr: u32, len: u32) -> Result<&str> { let slice = get_slice(memory, ptr, len)?; str::from_utf8(slice).map_err(Into::into) } + +fn get_two_slice_mut( + memory: &mut [u8], + ptr1: u32, + len1: u32, + ptr2: u32, + len2: u32, +) -> Result<[&mut [u8]; 2]> { + let (ptr1, ptr2) = (ptr1 as usize, ptr2 as usize); + let (len1, len2) = (len1 as usize, len2 as usize); + if ptr1 < ptr2 { + if ptr2 >= memory.len() { + return Err(format_err!("Out of bounds pointer and length pair.")); + } + let (first, second) = memory.split_at_mut(ptr2); + Ok([ + first + .get_mut(ptr1..) + .context("Out of bounds pointer and length pair.")? + .get_mut(..len1) + .context("Overlapping pair of pointer ranges.")?, + second + .get_mut(..len2) + .context("Out of bounds pointer and length pair.")?, + ]) + } else { + if ptr1 >= memory.len() { + return Err(format_err!("Out of bounds pointer and length pair.")); + } + let (first, second) = memory.split_at_mut(ptr1); + Ok([ + second + .get_mut(..len1) + .context("Out of bounds pointer and length pair.")?, + first + .get_mut(ptr2..) + .context("Out of bounds pointer and length pair.")? + .get_mut(..len2) + .context("Overlapping pair of pointer ranges.")?, + ]) + } +} diff --git a/src/auto_splitting/mod.rs b/src/auto_splitting/mod.rs index 2cb8706a..0467df59 100644 --- a/src/auto_splitting/mod.rs +++ b/src/auto_splitting/mod.rs @@ -34,7 +34,10 @@ //! pub struct NonZeroAddress(pub NonZeroU64); //! //! #[repr(transparent)] -//! pub struct ProcessId(NonZeroU64); +//! pub struct Process(NonZeroU64); +//! +//! #[repr(transparent)] +//! pub struct ProcessId(u64); //! //! #[repr(transparent)] //! pub struct TimerState(u32); @@ -51,6 +54,20 @@ //! pub const ENDED: Self = Self(3); //! } //! +//! #[repr(transparent)] +//! pub struct MemoryRangeFlags(NonZeroU64); +//! +//! impl MemoryRangeFlags { +//! /// The memory range is readable. +//! pub const READ: Self = Self(match NonZeroU64::new(1 << 1) { Some(v) => v, None => panic!() }); +//! /// The memory range is writable. +//! pub const WRITE: Self = Self(match NonZeroU64::new(1 << 2) { Some(v) => v, None => panic!() }); +//! /// The memory range is executable. +//! pub const EXECUTE: Self = Self(match NonZeroU64::new(1 << 3) { Some(v) => v, None => panic!() }); +//! /// The memory range has a file path. +//! pub const PATH: Self = Self(match NonZeroU64::new(1 << 4) { Some(v) => v, None => panic!() }); +//! } +//! //! extern "C" { //! /// Gets the state that the timer currently is in. //! pub fn timer_get_state() -> TimerState; @@ -84,33 +101,68 @@ //! pub fn timer_resume_game_time(); //! //! /// Attaches to a process based on its name. -//! pub fn process_attach(name_ptr: *const u8, name_len: usize) -> Option; +//! pub fn process_attach(name_ptr: *const u8, name_len: usize) -> Option; +//! /// Attaches to a process based on its process id. +//! pub fn process_attach_by_pid(pid: ProcessId) -> Option; //! /// Detaches from a process. -//! pub fn process_detach(process: ProcessId); +//! pub fn process_detach(process: Process); +//! /// Lists processes based on their name. Returns `false` if listing the +//! /// processes failed. If it was successful, the buffer is now filled +//! /// with the process ids. They are in no specific order. The +//! /// `list_len_ptr` will be updated to the amount of process ids that +//! /// were found. If this is larger than the original value provided, the +//! /// buffer provided was too small and not all process ids could be +//! /// stored. This is still considered successful and can optionally be +//! /// treated as an error condition by the caller by checking if the +//! /// length increased and potentially reallocating a larger buffer. If +//! /// the length decreased after the call, the buffer was larger than +//! /// needed and the remaining entries are untouched. +//! pub fn process_list_by_name( +//! name_ptr: *const u8, +//! name_len: usize, +//! list_ptr: *mut ProcessId, +//! list_len_ptr: *mut usize, +//! ) -> bool; //! /// Checks whether is a process is still open. You should detach from a //! /// process and stop using it if this returns `false`. -//! pub fn process_is_open(process: ProcessId) -> bool; +//! pub fn process_is_open(process: Process) -> bool; //! /// Reads memory from a process at the address given. This will write //! /// the memory to the buffer given. Returns `false` if this fails. //! pub fn process_read( -//! process: ProcessId, +//! process: Process, //! address: Address, //! buf_ptr: *mut u8, //! buf_len: usize, //! ) -> bool; +//! //! /// Gets the address of a module in a process. //! pub fn process_get_module_address( -//! process: ProcessId, +//! process: Process, //! name_ptr: *const u8, //! name_len: usize, //! ) -> Option; //! /// Gets the size of a module in a process. //! pub fn process_get_module_size( -//! process: ProcessId, +//! process: Process, //! name_ptr: *const u8, //! name_len: usize, //! ) -> Option; //! +//! /// Gets the number of memory ranges in a given process. +//! pub fn process_get_memory_range_count(process: Process) -> Option; +//! /// Gets the start address of a memory range by its index. +//! pub fn process_get_memory_range_address( +//! process: Process, +//! idx: u64, +//! ) -> Option; +//! /// Gets the size of a memory range by its index. +//! pub fn process_get_memory_range_size(process: Process, idx: u64) -> Option; +//! /// Gets the flags of a memory range by its index. +//! pub fn process_get_memory_range_flags( +//! process: Process, +//! idx: u64, +//! ) -> Option; +//! //! /// Stores the file system path of the executable in the buffer given. The //! /// path is a path that is accessible through the WASI file system, so a //! /// Windows path of `C:\foo\bar.exe` would be returned as @@ -119,7 +171,7 @@ //! /// `buf_len_ptr` will be set to the required buffer size. The path is //! /// guaranteed to be valid UTF-8 and is not nul-terminated. //! pub fn process_get_path( -//! process: ProcessId, +//! process: Process, //! buf_ptr: *mut u8, //! buf_len_ptr: *mut usize, //! ) -> bool;