-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #8 from rkr35/hook_process_event
Hook process event
- Loading branch information
Showing
11 changed files
with
349 additions
and
82 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
/target | ||
Cargo.lock | ||
.vs | ||
src/sdk.rs | ||
src/hook/sdk.rs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
Today I want to add two features to Cargo.toml, one for generating the SDK, and | ||
one for hooking the game. | ||
|
||
I'll have to refactor a good chunk of the code because the information that the | ||
SDK generator needs is mostly different from the information the hook needs, | ||
although both share some common code, such as a few game structures and | ||
functions. | ||
|
||
I'll say that generating the SDK is called "dumping". I already have a `dump` | ||
module in place that contains most of the generator-specific code. I imagine | ||
I'll have to move things in and out of that `dump` module when I create the | ||
`hook` module. | ||
|
||
To be more specific, here are functionalities that `hook` will need: | ||
1. Access to the generated SDK. | ||
2. Access to the helper methods that the SDK relies on. | ||
3. Access to the handwritten game structures that the hook needs. | ||
4. Access to the helper methods that the handwritten stuctures rely on. | ||
|
||
So let me create that hook module and start moving things in there. | ||
|
||
I know that module will need at least a top-level Error enum to communicate | ||
hooking errors to lib.rs. I also know that a RAII structure will be useful to | ||
set up the hook without forgetting to clean it up when we unload the .DLL. | ||
|
||
Okay, I got the hook module up, and the printing of unique events through my | ||
detoured ProcessEvent is still working, so the refactor went well. | ||
|
||
Per (1) above, the hook module only needs access to the generated SDK. So let me | ||
make two changes: one, change the (hardcoded) path that the dumper places the | ||
generated SDK so that the sdk.rs is under the hook module; and two, include the | ||
sdk module in the hook module. | ||
|
||
Done. I also had to make changes to remove the old `include_sdk` feature that I | ||
was using to incrementally test whether the generated SDK compiles; to ignore | ||
the sdk.rs under the new path; and to introduce the two new features `dump` and | ||
`hook`. | ||
|
||
While looking at the generated sdk.rs, I noticed that a lot of constants are | ||
duplicated. For example, there are 14 "// WPS_MusicVolume = 107". I'm not going | ||
to dedup those constants, but instead, I'm going to prepend the module and | ||
submodule for each constant. The module and submodule names will provide context | ||
as to where the constant can be used. Let me make an issue on GitHub so I don't | ||
forget. | ||
|
||
There are two functions that the generated SDK uses for querying and modifying | ||
bitfields: is_bit_set() and set_bit(). I placed those functions in game.rs, but | ||
since they're only used in the hook, it makes better sense to place them in a | ||
module under the hook module. | ||
|
||
Okay, now I'm going to try to selectively compile the dump and hook code based | ||
on their respective Cargo.toml features. I also need to add a safeguard to | ||
prevent both features from being enabled at the same time. | ||
|
||
Done. I'm glad I was able to conditionally compile error variants as well. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
pub fn is_bit_set(bitfield: u32, bit: u8) -> bool { | ||
let mask = 1 << bit; | ||
bitfield & mask == mask | ||
} | ||
|
||
pub fn set_bit(bitfield: &mut u32, bit: u8, value: bool) { | ||
let mask = 1 << bit; | ||
|
||
if value { | ||
*bitfield |= mask; | ||
} else { | ||
*bitfield &= !mask; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
use crate::game; | ||
use crate::PROCESS_EVENT; | ||
|
||
use std::ffi::c_void; | ||
use std::mem; | ||
|
||
use detours_sys::{ | ||
DetourAttach, DetourDetach, DetourTransactionBegin, DetourTransactionCommit, | ||
DetourUpdateThread, LONG as DetourErrorCode, | ||
}; | ||
use log::{error, info, warn}; | ||
use thiserror::Error; | ||
use winapi::um::processthreadsapi::GetCurrentThread; | ||
|
||
mod bitfield; | ||
mod sdk; | ||
|
||
#[derive(Error, Debug)] | ||
pub enum Error { | ||
#[error("detour error: {0} returned {1}")] | ||
Detour(&'static str, DetourErrorCode), | ||
} | ||
|
||
/// A helper macro to call Detour functions and wrap any error codes into a | ||
/// variant of the top-level `Error` enum. | ||
macro_rules! det { | ||
($call:expr) => {{ | ||
const NO_ERROR: DetourErrorCode = 0; | ||
|
||
let error_code = $call; | ||
|
||
if error_code == NO_ERROR { | ||
Ok(()) | ||
} else { | ||
Err(Error::Detour(stringify!($call), error_code)) | ||
} | ||
}}; | ||
} | ||
|
||
pub struct Hook; | ||
|
||
impl Hook { | ||
pub unsafe fn new() -> Result<Hook, Error> { | ||
hook_process_event()?; | ||
Ok(Hook) | ||
} | ||
} | ||
|
||
impl Drop for Hook { | ||
fn drop(&mut self) { | ||
unsafe { | ||
if let Err(e) = unhook_process_event() { | ||
error!("{}", e); | ||
} | ||
} | ||
} | ||
} | ||
|
||
unsafe fn hook_process_event() -> Result<(), Error> { | ||
det!(DetourTransactionBegin())?; | ||
det!(DetourUpdateThread(GetCurrentThread()))?; | ||
det!(DetourAttach(&mut PROCESS_EVENT, my_process_event as *mut _))?; | ||
det!(DetourTransactionCommit())?; | ||
Ok(()) | ||
} | ||
|
||
unsafe fn unhook_process_event() -> Result<(), Error> { | ||
det!(DetourTransactionBegin())?; | ||
det!(DetourUpdateThread(GetCurrentThread()))?; | ||
det!(DetourDetach(&mut PROCESS_EVENT, my_process_event as *mut _))?; | ||
det!(DetourTransactionCommit())?; | ||
Ok(()) | ||
} | ||
|
||
unsafe extern "fastcall" fn my_process_event( | ||
this: &game::Object, | ||
edx: usize, | ||
function: &game::Function, | ||
parameters: *mut c_void, | ||
return_value: *mut c_void, | ||
) { | ||
type ProcessEvent = unsafe extern "fastcall" fn( | ||
this: &game::Object, | ||
_edx: usize, | ||
function: &game::Function, | ||
parameters: *mut c_void, | ||
return_value: *mut c_void, | ||
); | ||
|
||
if let Some(full_name) = function.full_name() { | ||
use std::collections::HashSet; | ||
static mut UNIQUE_EVENTS: Option<HashSet<String>> = None; | ||
|
||
if let Some(set) = UNIQUE_EVENTS.as_mut() { | ||
if set.insert(full_name.clone()) { | ||
info!("{}", full_name); | ||
} | ||
} else { | ||
UNIQUE_EVENTS = Some(HashSet::new()); | ||
} | ||
} else { | ||
warn!("couldn't get full name"); | ||
} | ||
|
||
let original = mem::transmute::<*mut c_void, ProcessEvent>(PROCESS_EVENT); | ||
original(this, edx, function, parameters, return_value); | ||
} |
Oops, something went wrong.