Skip to main content

rosu_mem/
process.rs

1use std::path::PathBuf;
2
3use super::{error::ProcessError, signature::Signature};
4use paste::paste;
5
6#[cfg(target_os = "windows")]
7use windows::Win32::Foundation::HANDLE;
8
9#[derive(Debug)]
10pub struct MemoryRegion {
11    pub from: usize,
12    pub size: usize,
13}
14
15macro_rules! read_generic {
16    ($t: ty, $self: expr, $addr: expr) => {{
17        paste! {
18            let mut bytes = vec![0u8; std::mem::size_of::<T>()];
19
20            $self.read($addr, std::mem::size_of::<T>(), &mut bytes)?;
21
22            unsafe { std::ptr::read(bytes.as_ptr() as *const T) }
23        }
24    }};
25}
26
27macro_rules! prim_read_impl {
28    ($t: ident) => {
29        paste! {
30            fn [<read_ $t>]<T: TryInto<usize>>(
31                &self,
32                addr: T
33            ) -> Result<$t, ProcessError> {
34                let mut bytes = [0u8; std::mem::size_of::<$t>()];
35                self.read(addr, std::mem::size_of::<$t>(), &mut bytes)?;
36
37                Ok($t::from_le_bytes(bytes))
38            }
39        }
40    };
41}
42
43macro_rules! prim_read_array_impl {
44    ($t: ident) => {
45        paste! {
46            fn [<read_ $t _array>]<T: TryInto<usize>>(
47                &self,
48                addr: T,
49                buff: &mut Vec<$t>
50            ) -> Result<(), ProcessError> {
51                let addr: usize = addr.try_into()
52                    .map_err(|_| ProcessError::AddressConvertError)?;
53
54                let items_ptr = self.read_i32(addr + 4)?;
55                let size = self.read_i32(addr + 12)? as usize;
56
57                buff.resize(size, 0 as $t);
58
59                let byte_buff = unsafe { std::slice::from_raw_parts_mut(
60                    buff.as_mut_ptr() as *mut u8,
61                    buff.len() * std::mem::size_of::<$t>()
62                ) };
63
64
65                self.read(
66                    items_ptr + 8,
67                    size * std::mem::size_of::<$t>(),
68                    byte_buff
69                )?;
70
71                Ok(())
72            }
73        }
74    };
75}
76
77pub struct Process {
78    #[cfg(target_os = "linux")]
79    pub pid: i32,
80
81    #[cfg(target_os = "windows")]
82    pub pid: u32,
83
84    #[cfg(target_os = "windows")]
85    pub handle: HANDLE,
86
87    pub maps: Vec<MemoryRegion>,
88
89    pub executable_dir: Option<PathBuf>,
90}
91
92pub trait ProcessTraits
93where
94    Self: Sized,
95{
96    /// Initialize a `Process` struct
97    ///
98    /// * `proc_name` - Name of the process or key words
99    /// * `exclude` - Key words to avoid when searching for process name
100    ///
101    /// Notes:
102    /// For more details of searching the process name see [`find_process`]
103    /// method
104    fn initialize(
105        proc_name: &str,
106        exclude: &[&str],
107    ) -> Result<Self, ProcessError>;
108
109    /// Attemp to find a process
110    ///
111    /// * `proc_name` - Name of the process or key words
112    /// * `exclude` - Keywords to avoid when searching for process name
113    ///
114    /// # Notes
115    /// It's going try to search process name by using [`str::contains`] function
116    /// with `proc_name` argument on process name. Same applies to `exclude`
117    fn find_process(
118        proc_name: &str,
119        exclude: &[&str],
120    ) -> Result<Self, ProcessError>;
121
122    /// Collect memory regions offsets into itself.
123    ///
124    /// Notes:
125    /// * Function isn't whole memory just their offsets.
126    ///   Check out [`MemoryRegion`] for more info
127    fn read_regions(self) -> Result<Self, ProcessError>;
128
129    fn read_signature<T: TryFrom<usize>>(
130        &self,
131        sign: &Signature,
132    ) -> Result<T, ProcessError>;
133
134    fn read<T: TryInto<usize>>(
135        &self,
136        addr: T,
137        len: usize,
138        buff: &mut [u8],
139    ) -> Result<(), ProcessError>;
140
141    fn read_uleb128<T: TryInto<usize>>(
142        &self,
143        addr: T,
144    ) -> Result<u64, ProcessError> {
145        let mut addr: usize = addr
146            .try_into()
147            .map_err(|_| ProcessError::AddressConvertError)?;
148
149        let mut value: u64 = 0;
150        let mut bytes_read = 0;
151
152        loop {
153            let byte = self.read_u8(addr)?;
154            addr += 1;
155
156            let byte_value = (byte & 0b0111_1111) as u64;
157            value |= byte_value << (7 * bytes_read);
158
159            bytes_read += 1;
160
161            if (byte & !0b0111_1111) == 0 {
162                break;
163            }
164        }
165
166        Ok(value)
167    }
168
169    /// Same behaviour as [`ProcessTraits::read_string_from_ptr()`]
170    ///
171    /// The only diffrence is that function will throw
172    /// a [`ProcessError::StringTooLarge`] error if readed string length
173    /// is over a provided limit
174    ///
175    /// Notes:
176    /// * `*_from_ptr()` functions usually will result in additional
177    ///   heap allocation, due to generic behaviour. If you need to avoid
178    ///   heap allocations at all costs, read pointer manually and then pass
179    ///   address to the [`ProcessTraits::read_string()`] function
180    fn read_string_with_limit_from_ptr<T: TryInto<usize>>(
181        &self,
182        addr: T,
183        limit: usize,
184    ) -> Result<String, ProcessError> {
185        let addr = read_generic!(T, self, addr);
186
187        self.read_string_with_limit(addr, limit)
188    }
189
190    /// Reads a C# string. For more info checkout [`ProcessTraits::read_string()`]
191    ///
192    /// The only diffrence is that function will throw
193    /// a [`ProcessError::StringTooLarge`] error if readed string length
194    /// is over a provided limit
195    fn read_string_with_limit<T: TryInto<usize>>(
196        &self,
197        addr: T,
198        limit: usize,
199    ) -> Result<String, ProcessError> {
200        let mut addr: usize = addr
201            .try_into()
202            .map_err(|_| ProcessError::AddressConvertError)?;
203
204        addr += std::mem::size_of::<T>();
205
206        let len = self.read_u32(addr)? as usize; // Reading 4B str len
207
208        if len > limit {
209            return Err(ProcessError::StringTooLarge);
210        }
211
212        addr += 0x4; // Since we read length skipping it too
213
214        let mut buff = vec![0u16; len];
215
216        let byte_buff = unsafe {
217            std::slice::from_raw_parts_mut(
218                buff.as_mut_ptr() as *mut u8,
219                buff.len() * 2,
220            )
221        };
222
223        self.read(addr, byte_buff.len(), byte_buff)?;
224
225        Ok(String::from_utf16_lossy(&buff))
226    }
227
228    /// Reads a C# string based on C# string structure
229    /// Assumes passed `addr` is a pointer, so it's gonna make
230    /// additional pointer read.
231    ///
232    /// Notes:
233    /// * `*_from_ptr()` functions usually will result in additional
234    ///   heap allocation, due to generic behaviour. If you need to avoid
235    ///   heap allocations at all costs, read pointer manually and then pass
236    ///   address to the [`ProcessTraits::read_string()`] function
237    fn read_string_from_ptr<T: TryInto<usize>>(
238        &self,
239        addr: T,
240    ) -> Result<String, ProcessError> {
241        let addr = read_generic!(T, self, addr);
242
243        self.read_string(addr)
244    }
245
246    /// Reads a C# string based on C# string structure
247    /// Assumes passed `addr` is not a pointer, so no additional
248    /// pointer reads is gonna be made.
249    ///
250    /// If you have a pointer to string either read that pointer youself
251    /// or use [`ProcessTraits::read_string_from_ptr()`]
252    fn read_string<T: TryInto<usize>>(
253        &self,
254        addr: T,
255    ) -> Result<String, ProcessError> {
256        let mut addr: usize = addr
257            .try_into()
258            .map_err(|_| ProcessError::AddressConvertError)?;
259
260        // C# string structure: 4B/8B obj header, 4B str len, str itself
261        addr += std::mem::size_of::<T>(); // Skipping 4B/8B obj header depending on endiness
262
263        let len = self.read_u32(addr)? as usize; // Reading 4B str len
264        addr += 0x4; // Since we read length skipping it too
265
266        let mut buff = vec![0u16; len];
267
268        let byte_buff = unsafe {
269            std::slice::from_raw_parts_mut(
270                buff.as_mut_ptr() as *mut u8,
271                buff.len() * 2,
272            )
273        };
274
275        self.read(addr, byte_buff.len(), byte_buff)?;
276
277        Ok(String::from_utf16_lossy(&buff))
278    }
279
280    prim_read_impl!(i8);
281    prim_read_impl!(i16);
282    prim_read_impl!(i32);
283    prim_read_impl!(i64);
284    prim_read_impl!(i128);
285
286    prim_read_impl!(u8);
287    prim_read_impl!(u16);
288    prim_read_impl!(u32);
289    prim_read_impl!(u64);
290    prim_read_impl!(u128);
291
292    prim_read_impl!(f32);
293    prim_read_impl!(f64);
294
295    prim_read_array_impl!(i8);
296    prim_read_array_impl!(i16);
297    prim_read_array_impl!(i32);
298    prim_read_array_impl!(i64);
299    prim_read_array_impl!(i128);
300
301    prim_read_array_impl!(u8);
302    prim_read_array_impl!(u16);
303    prim_read_array_impl!(u32);
304    prim_read_array_impl!(u64);
305    prim_read_array_impl!(u128);
306
307    prim_read_array_impl!(f32);
308    prim_read_array_impl!(f64);
309}