-
Notifications
You must be signed in to change notification settings - Fork 10
Add new DataMember constructors, extend, shift. #6
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -72,6 +72,108 @@ impl<T: Sized + Copy> DataMember<T> { | |
| _phantom: std::marker::PhantomData, | ||
| } | ||
| } | ||
|
|
||
| /// Create a new `DataMember` from a [`ProcessHandle`] and some number of (potentially negative) offsets. | ||
| /// You must remember to call [`try_into_process_handle`] on a [`Pid`] as sometimes the `Pid` can have | ||
| /// the same backing type as a [`ProcessHandle`], resulting in an error. | ||
| /// | ||
| /// [`try_into_process_handle`]: trait.TryIntoProcessHandle.html#tymethod.try_into_process_handle | ||
| /// [`ProcessHandle`]: type.ProcessHandle.html | ||
| /// [`Pid`]: type.Pid.htm | ||
| #[must_use] | ||
| #[allow(clippy::cast_sign_loss)] | ||
| pub fn new_offset_relative(handle: ProcessHandle, offsets: Vec<isize>) -> Self { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you copy these functions to |
||
| Self { | ||
| // Yes, we are casting to usize. This will not touch any bits, but due to 2s complement, | ||
| // we still get the correct result when adding offsets. | ||
| offsets: offsets.into_iter().map(|x| x as usize).collect(), | ||
| process: handle, | ||
| _phantom: std::marker::PhantomData, | ||
| } | ||
| } | ||
|
|
||
| /// Create a new `DataMember` from a [`ProcessHandle`], pointing to the memory address in the | ||
| /// remote process. Equivalent to `new_offset(handle, vec![addr])`. You must | ||
| /// remember to call [`try_into_process_handle`] on a [`Pid`] as sometimes the `Pid` can have | ||
| /// the same backing type as a [`ProcessHandle`], resulting in an error. | ||
| /// | ||
| /// [`try_into_process_handle`]: trait.TryIntoProcessHandle.html#tymethod.try_into_process_handle | ||
| /// [`ProcessHandle`]: type.ProcessHandle.html | ||
| /// [`Pid`]: type.Pid.html | ||
| #[must_use] | ||
| pub fn new_addr(handle: ProcessHandle, addr: usize) -> Self { | ||
| Self { | ||
| offsets: vec![addr], | ||
| process: handle, | ||
| _phantom: std::marker::PhantomData, | ||
| } | ||
| } | ||
|
|
||
| /// Create a new `DataMember` from a [`ProcessHandle`], pointing to the memory address in the | ||
| /// process, then following a bunch of pointers and offsets, which may be negative. | ||
| /// If you use CheatEngine and get a pointer of form "MyModule.dll + 0x12345678", plus a bunch | ||
| /// of offsets, then you want to put the base address of the module as `addr`, `0x12345678` as | ||
| /// the first offset, then any further offsets etc. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you add a newline after this? I think that when it gets rendered into HTML, single linebreaks are removed. |
||
| /// This function is merely a convenience function, and is equivalent to | ||
| /// `new_offset_relative(handle, vec![addr as isize, offsets[0], offsets[1], ...])`. | ||
| /// You must | ||
| /// remember to call [`try_into_process_handle`] on a [`Pid`] as sometimes the `Pid` can have | ||
| /// the same backing type as a [`ProcessHandle`], resulting in an error. | ||
| /// | ||
| /// [`try_into_process_handle`]: trait.TryIntoProcessHandle.html#tymethod.try_into_process_handle | ||
| /// [`ProcessHandle`]: type.ProcessHandle.html | ||
| /// [`Pid`]: type.Pid.html | ||
| #[must_use] | ||
| #[allow(clippy::doc_markdown)] | ||
| #[allow(clippy::cast_sign_loss)] | ||
| pub fn new_addr_offset(handle: ProcessHandle, addr: usize, offsets: Vec<isize>) -> Self { | ||
| let mut vec = vec![addr]; | ||
| // Yes, we are casting to usize. This will not touch any bits, and due to 2s complement, | ||
| // we still get the correct result when adding offsets. | ||
| vec.extend(offsets.into_iter().map(|x| x as usize)); | ||
| Self { | ||
| offsets: vec, | ||
| process: handle, | ||
| _phantom: std::marker::PhantomData, | ||
| } | ||
| } | ||
|
|
||
| /// Creates a new `DataMember` by appending some more offets. Useful when you have a data | ||
| /// structure, and want to refer to multiple fields in it, or use it as a starting point | ||
| /// for chasing down more pointers. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one as well. |
||
| /// Since the pointed-to data type might have changed, this function is generic. It is your | ||
| /// responsibility to make sure you know what you point to. | ||
| #[allow(clippy::cast_sign_loss)] | ||
| #[must_use] | ||
| pub fn extend<TNew>(&self, more_offsets: Vec<isize>) -> DataMember<TNew> { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can these functions be put on the |
||
| let mut clone = DataMember { | ||
| offsets: self.offsets.clone(), | ||
| process: self.process, | ||
| _phantom: std::marker::PhantomData, | ||
| }; | ||
| // Yes, we are casting to usize. This will not touch any bits, and due to 2s complement, | ||
| // we still get the correct result when adding offsets. | ||
| clone | ||
| .offsets | ||
| .extend(more_offsets.into_iter().map(|x| x as usize)); | ||
| clone | ||
| } | ||
|
|
||
| /// Creates a new `DataMember`, based on self, by shifting the last offset by a number of | ||
| /// bytes. Does not append new offsets. This is useful if you have a pointer to a struct | ||
| /// and want to address different fields, or access elements in an array. | ||
| #[allow(clippy::cast_sign_loss)] | ||
| #[must_use] | ||
| pub fn shift<TNew>(&self, n_bytes: isize) -> DataMember<TNew> { | ||
| let mut clone = DataMember { | ||
| offsets: self.offsets.clone(), | ||
| process: self.process, | ||
| _phantom: std::marker::PhantomData, | ||
| }; | ||
| let new = clone.offsets[self.offsets.len() - 1].wrapping_add(n_bytes as usize); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The number of offsets needs to be checked here, or else you'll panic if someone just called I'm not sure of the best way to handle this. Maybe returning an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another potential option would be turning it into |
||
| clone.offsets[self.offsets.len() - 1] = new; | ||
| clone | ||
| } | ||
| } | ||
|
|
||
| impl<T: Sized + Copy> Memory<T> for DataMember<T> { | ||
|
|
@@ -147,4 +249,57 @@ mod test { | |
| member.write(&0xffff).unwrap(); | ||
| assert_eq!(test, 0xffff); | ||
| } | ||
|
|
||
| #[repr(C)] | ||
| #[derive(Clone, Copy)] | ||
| struct Player { | ||
| x: u32, | ||
| y: u32, | ||
| } | ||
|
|
||
| #[repr(C)] | ||
| struct GameState { | ||
| garbage: u32, | ||
| garbage2: u32, | ||
| players: [Box<Player>; 2], // note that this array is in-place, since it's fixed size. | ||
| } | ||
|
|
||
| #[test] | ||
| fn multilevel_pointers() { | ||
| let game = GameState { | ||
| garbage: 42, | ||
| garbage2: 1337, | ||
| players: [ | ||
| Box::new(Player { x: 1, y: 2 }), | ||
| Box::new(Player { x: 3, y: 4 }), | ||
| ], | ||
| }; | ||
| let handle = (std::process::id() as crate::Pid) | ||
| .try_into_process_handle() | ||
| .unwrap(); | ||
|
|
||
| // point to `game`, then our data is +4 from the base of `game`. | ||
| let garbage2 = DataMember::<u32>::new_addr(handle, &game as *const _ as usize + 4); | ||
| assert_eq!(1337, garbage2.read().unwrap()); | ||
|
|
||
| let garbage1 = garbage2.shift(-4); | ||
| assert_eq!(42u32, garbage1.read().unwrap()); | ||
|
|
||
| // At `game + 2*sizeof(u32) + 1*sizeof(Player*) is where we find | ||
| // a pointer to the second player. | ||
| // So second_player.read() right now would just get you the pointer to the player. | ||
| let second_player = DataMember::<*mut Player>::new_addr( | ||
| handle, | ||
| (&game as *const _ as usize) + 8 + handle.get_pointer_width().pointer_width_bytes(), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you can do |
||
| ); | ||
|
|
||
| // But when we add an offset, in this case an offset of 0, we follow the pointer, | ||
| // and thus second_player_x points to the beginning of the second player, which in this | ||
| // case is also the x coordinate. | ||
| let second_player_x = second_player.extend::<u32>(vec![0]); | ||
| let second_player_y = second_player.extend::<u32>(vec![4]); // sizeof u32 = 4 | ||
|
|
||
| assert_eq!(3, second_player_x.read().unwrap()); | ||
| assert_eq!(4, second_player_y.read().unwrap()); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can already cast
Architecturetousizewitharchitecture as usize. Perhaps what's needed instead is making that availability more clear in documentation?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought casting an enum was pretty unintuitive, and even if documentation exists but someone stumbles upon it in the wild, they will have no clue what's going on. Especially when that person is new to rust too, and have no idea that it returns the
Arch64Bit = 8enum value.I would instead rename
get_pointer_widthtoget_arch, and keep the function I added (or rename it topointer_width).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's the entire point of the enum being
repr(u8), although I guess you'd probably need to read the nomicon to know that.I'd probably prefer not adding another function, do you think it'd be okay to just add a doc comment to the
Architecturestruct with something along the lines of: