Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Process Listing and Attach by PID to Autosplitting API #721

Merged
merged 6 commits into from
Oct 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/livesplit-auto-splitting/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
56 changes: 39 additions & 17 deletions crates/livesplit-auto-splitting/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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" {
Expand Down Expand Up @@ -98,58 +101,77 @@ 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<ProcessId>;
pub fn process_attach(name_ptr: *const u8, name_len: usize) -> Option<Process>;
/// Attaches to a process based on its process id.
pub fn process_attach_by_pid(pid: ProcessId) -> Option<Process>;
/// 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<NonZeroAddress>;
/// 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<NonZeroU64>;

/// Gets the number of memory ranges in a given process.
pub fn process_get_memory_range_count(process: ProcessId) -> Option<NonZeroU64>;
pub fn process_get_memory_range_count(process: Process) -> Option<NonZeroU64>;
/// 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<NonZeroAddress>;
/// Gets the size of a memory range by its index.
pub fn process_get_memory_range_size(process: ProcessId, idx: u64) -> Option<NonZeroU64>;
pub fn process_get_memory_range_size(process: Process, idx: u64) -> Option<NonZeroU64>;
/// Gets the flags of a memory range by its index.
pub fn process_get_memory_range_flags(
process: ProcessId,
process: Process,
idx: u64,
) -> Option<MemoryRangeFlags>;

/// 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;
Expand Down
54 changes: 38 additions & 16 deletions crates/livesplit-auto-splitting/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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" {
Expand Down Expand Up @@ -98,46 +101,65 @@
//! 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<ProcessId>;
//! pub fn process_attach(name_ptr: *const u8, name_len: usize) -> Option<Process>;
//! /// Attaches to a process based on its process id.
//! pub fn process_attach_by_pid(pid: ProcessId) -> Option<Process>;
//! /// 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<NonZeroAddress>;
//! /// 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<NonZeroU64>;
//!
//! /// Gets the number of memory ranges in a given process.
//! pub fn process_get_memory_range_count(process: ProcessId) -> Option<NonZeroU64>;
//! pub fn process_get_memory_range_count(process: Process) -> Option<NonZeroU64>;
//! /// 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<NonZeroAddress>;
//! /// Gets the size of a memory range by its index.
//! pub fn process_get_memory_range_size(process: ProcessId, idx: u64) -> Option<NonZeroU64>;
//! pub fn process_get_memory_range_size(process: Process, idx: u64) -> Option<NonZeroU64>;
//! /// Gets the flags of a memory range by its index.
//! pub fn process_get_memory_range_flags(
//! process: ProcessId,
//! process: Process,
//! idx: u64,
//! ) -> Option<MemoryRangeFlags>;
//!
Expand All @@ -149,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;
Expand Down
42 changes: 42 additions & 0 deletions crates/livesplit-auto-splitting/src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,39 @@ impl Process {
})
}

pub(super) fn with_pid(pid: u32, process_list: &mut ProcessList) -> Result<Self, OpenError> {
process_list.refresh();
let process = process_list
.get(sysinfo::Pid::from_u32(pid))
.context(ProcessDoesntExist)?;

let path = build_path(process.exe());

let pid_out = pid as Pid;

let handle = pid_out.try_into().context(InvalidHandle)?;

let now = Instant::now();
Ok(Process {
handle,
pid: pid_out,
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<'a>(
name: &'a str,
process_list: &'a mut ProcessList,
) -> impl Iterator<Item = u32> + 'a {
process_list.refresh();
process_list
.processes_by_name(name)
.map(|p| p.pid().as_u32())
}

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);
Expand Down Expand Up @@ -165,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 {
Expand Down
Loading
Loading