1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
#![feature(decl_macro)]
#![feature(associated_type_defaults)]
#![feature(associated_type_bounds)]

use alloc::string::{FromUtf16Error, FromUtf8Error};
use core::mem::MaybeUninit;
use std::{mem, slice};

pub use dataview::Pod;

#[cfg(feature = "kernel")]
pub mod kernel;

#[cfg(feature = "kernel")]
pub use kernel::*;

#[cfg(feature = "render")]
pub mod render;

#[cfg(feature = "render")]
pub use render::DrawExt;

#[cfg(feature = "test")]
#[macro_use]
pub mod tests;

mod memory_protection;
mod pid_util;
mod slice_impl;

pub use pid_util::*;
pub use slice_impl::*;

pub use memory_protection::MemoryProtection;

extern crate alloc;

pub type MemoryRange = core::ops::Range<u64>;

const MAX_STRING_SIZE: usize = 0x10000;

/// Represents any type with a buffer that can be read from
#[auto_impl::auto_impl(&, & mut, Box)]
pub trait MemoryRead {
    /// Reads bytes from the process at the specified address into a buffer.
    /// Returns None if the address is not valid
    fn try_read_bytes_into(&self, address: u64, buffer: &mut [u8]) -> Option<()>;

    /// Reads bytes from the process at the specified address and returns the bytes as a Vector.
    /// Returns none if the address is not valid
    fn try_read_bytes(&self, address: u64, len: usize) -> Option<Vec<u8>> {
        let mut buf = vec![0u8; len];
        self.try_read_bytes_into(address, &mut buf).map(|_| buf)
    }

    /// Dumps a memory range into a Vector. If any part of the memory range is not
    /// valid, it will return None
    fn dump_memory(&self, range: MemoryRange) -> Option<Vec<u8>> {
        self.try_read_bytes(range.start, (range.end - range.start) as usize)
    }

    /// Returns true if the specified address is valid. By default reads one byte at that location
    /// and returns the success value
    fn valid_address(&self, address: u64) -> bool {
        self.try_read_bytes(address, 1).is_some()
    }

    /// Reads a string at the specified location with char length of 1.
    /// If the address is valid or there is no null terminator
    /// in MAX_STRING_SIZE characters, it will return None
    fn try_read_string(&self, address: u64) -> Option<Result<String, FromUtf8Error>> {
        const CHUNK_SIZE: usize = 0x100;

        let mut bytes = Vec::new();
        let mut buf: [u8; CHUNK_SIZE] = unsafe { core::mem::zeroed() };
        for i in (0..MAX_STRING_SIZE).into_iter().step_by(CHUNK_SIZE) {
            self.try_read_bytes_into(address + i as u64, &mut buf)?;
            if let Some(n) = buf.iter().position(|n| *n == 0u8) {
                bytes.extend_from_slice(&buf[0..n]);
                break;
            } else {
                bytes.extend_from_slice(&buf);
            }
        }

        Some(String::from_utf8(bytes))
    }

    /// Reads a wide string at the specified location with char length of 1.
    /// If the address is valid or there is no null terminator
    /// in MAX_STRING_SIZE characters, it will return None
    fn try_read_string_wide(&self, address: u64) -> Option<Result<String, FromUtf16Error>> {
        const CHUNK_SIZE: usize = 0x100;

        let mut bytes = Vec::new();
        for i in (0..MAX_STRING_SIZE).into_iter().step_by(CHUNK_SIZE * 2) {
            let buf = self.try_read::<[u16; CHUNK_SIZE]>(address + i as u64)?;
            if let Some(n) = buf.iter().position(|n| *n == 0) {
                bytes.extend_from_slice(&buf[0..n]);
                break;
            } else {
                bytes.extend_from_slice(&buf);
            }
        }

        Some(String::from_utf16(&bytes))
    }
}

/// Extension trait for supplying generic util methods for MemoryRead
pub trait MemoryReadExt: MemoryRead {
    /// Reads bytes from the process at the specified address into a value of type T.
    /// Returns None if the address is not valid
    fn try_read<T: Pod>(&self, address: u64) -> Option<T> {
        let mut buffer: MaybeUninit<T> = mem::MaybeUninit::zeroed();

        unsafe {
            self.try_read_bytes_into(address, buffer.assume_init_mut().as_bytes_mut())?;
            Some(buffer.assume_init())
        }
    }

    /// Reads any type T from the process without the restriction of Pod
    #[allow(clippy::missing_safety_doc)]
    unsafe fn try_read_unchecked<T>(&self, address: u64) -> Option<T> {
        let mut buffer: MaybeUninit<T> = mem::MaybeUninit::zeroed();

        self.try_read_bytes_into(
            address,
            slice::from_raw_parts_mut(buffer.as_mut_ptr() as _, mem::size_of::<T>()),
        )?;
        Some(buffer.assume_init())
    }

    /// Reads bytes from the process at the specified address into a value of type T.
    /// Panics if the address is not valid
    fn read<T: Pod>(&self, address: u64) -> T {
        self.try_read(address).unwrap()
    }

    /// Reads a const number of bytes from the process returning a stack allocated array.
    fn try_read_bytes_const<const LEN: usize>(&self, address: u64) -> Option<[u8; LEN]> {
        let mut buffer: [u8; LEN] = [0u8; LEN];
        self.try_read_bytes_into(address, &mut buffer)?;
        Some(buffer)
    }

    /// Reads bytes from the process in chunks with the specified size
    fn try_read_bytes_into_chunked<const CHUNK_SIZE: usize>(&self, address: u64, buf: &mut [u8]) -> Option<()> {
        let mut chunk = [0u8; CHUNK_SIZE];
        for i in (0..buf.len()).into_iter().step_by(CHUNK_SIZE) {
            let read_len = if i + CHUNK_SIZE > buf.len() {
                buf.len() - i
            } else {
                CHUNK_SIZE
            };
            self.try_read_bytes_into(address + i as u64, &mut chunk[0..read_len])?;
            buf[i..i + read_len].copy_from_slice(&chunk[0..read_len]);
        }

        Some(())
    }

    /// Reads bytes from the process in chunks with the specified size. The function will return Some(n)
    /// with the number of bytes read unless every single chunk fails
    fn try_read_bytes_into_chunked_fallible<const CHUNK_SIZE: usize>(&self, address: u64, buf: &mut [u8]) -> Option<usize> {
        let mut chunk = [0u8; CHUNK_SIZE];
        let mut success_count = 0;
        for i in (0..buf.len()).into_iter().step_by(CHUNK_SIZE) {
            let read_len = if i + CHUNK_SIZE > buf.len() {
                buf.len() - i
            } else {
                CHUNK_SIZE
            };
            if self.try_read_bytes_into(address + i as u64, &mut chunk[0..read_len]).is_some() {
                success_count += read_len;
                buf[i..i + read_len].copy_from_slice(&chunk[0..read_len]);
            }
        }

        if success_count > 0 {
            Some(success_count)
        } else {
            None
        }
    }
}

impl<T: MemoryRead> MemoryReadExt for T {}

impl MemoryReadExt for dyn MemoryRead {}

/// Represents any type with a buffer that can be written to
#[auto_impl::auto_impl(&, & mut, Box)]
pub trait MemoryWrite {
    /// Writes bytes from the buffer into the process at the specified address.
    /// Returns None if the address is not valid
    fn try_write_bytes(&self, address: u64, buffer: &[u8]) -> Option<()>;
}

/// Extension trait for supplying generic util methods for MemoryWrite
pub trait MemoryWriteExt: MemoryWrite {
    /// Returns None if the address is not valid
    fn try_write<T: Pod>(&self, address: u64, buffer: &T) -> Option<()> {
        self.try_write_bytes(address, buffer.as_bytes())
    }

    /// Writes any type T to the process without the restriction of Pod
    #[allow(clippy::missing_safety_doc)]
    unsafe fn try_write_unchecked<T>(&self, address: u64, buffer: &T) -> Option<()> {
        self.try_write_bytes(
            address,
            slice::from_raw_parts(buffer as *const T as _, mem::size_of::<T>()),
        )
    }

    /// Writes bytes to the process at the specified address with the value of type T.
    /// Panics if the address is not valid
    fn write<T: Pod>(&self, address: u64, buffer: &T) {
        self.try_write(address, buffer).unwrap()
    }
}

impl<T: MemoryWrite> MemoryWriteExt for T {}

impl MemoryWriteExt for dyn MemoryWrite {}

/// Represents a single process module with a name, base, and size
#[derive(Debug, Clone)]
#[repr(C)]
pub struct Module {
    pub name: String,
    pub base: u64,
    pub size: u64,
}

impl Module {
    /// Returns the memory range of the entire module
    pub fn memory_range(&self) -> MemoryRange {
        self.base..(self.base + self.size)
    }
}

/// Represents a type that has access to a process's modules
#[auto_impl::auto_impl(&, & mut, Box)]
pub trait ModuleList {
    /// Returns a list of all modules. If the implementor can only
    /// provide a single module based on the name, this function should panic
    fn get_module_list(&self) -> Vec<Module>;

    /// Returns a single module by name.
    /// If the module name does not exist, returns None
    fn get_module(&self, name: &str) -> Option<Module> {
        self.get_module_list()
            .into_iter()
            .find(|m| m.name.to_lowercase() == name.to_lowercase())
    }

    /// Gets the main module from the process.
    fn get_main_module(&self) -> Module;
}

#[non_exhaustive]
#[derive(Debug)]
pub enum MemoryProtectError {
    InvalidMemoryRange(MemoryRange),
    NtStatus(u32),
    Message(String),
}

pub trait MemoryProtect {
    /// Sets the protection of the memory range to the specified protection.
    /// Returns the old memory protection or an error
    fn set_protection(&self, range: MemoryRange, protection: MemoryProtection) -> Result<MemoryProtection, MemoryProtectError>;
}

#[non_exhaustive]
#[derive(Debug)]
pub enum MemoryAllocateError {
    NtStatus(u32),
    Message(String),
}

pub trait MemoryAllocate {
    /// Allocates size bytes of memory in the process with the specified protection.
    /// Returns the allocated memory or an error.
    fn allocate(&self, size: u64, protection: MemoryProtection) -> Result<u64, MemoryAllocateError>;

    /// Frees allocated memory at the specified address and size.
    fn free(&self, base: u64, size: u64) -> Result<(), MemoryAllocateError>;
}

/// Represents a type that can retrieve the corresponding process's name and peb base address
#[auto_impl::auto_impl(&, & mut, Box)]
pub trait ProcessInfo {
    fn process_name(&self) -> String;
    fn peb_base_address(&self) -> u64;
    fn pid(&self) -> u32;
}

/// Represents a type that allows for sending mouse inputs
#[auto_impl::auto_impl(&, & mut, Box)]
pub trait MouseMove {
    fn mouse_move(&self, dx: i32, dy: i32);
}