/ bindings / rust / emf-rs / src / hook.rs
hook.rs
  1  use std::ffi::c_void;
  2  
  3  /// A function hook that redirects function calls to a replacement function.
  4  pub struct Hook {
  5  	hook_name: String,
  6  	raw: safer_ffi::boxed::Box<crate::sys::HookRaw>,
  7  
  8  	/// Whether the hook should be used when the plugin is enabled
  9  	enabled: bool,
 10  }
 11  
 12  impl Hook {
 13  	/// Create a new function hook.
 14  	///
 15  	/// # Examples
 16  	///
 17  	/// ```
 18  	/// extern "C" fn replacement_fn() {
 19  	///     println!("Hello, world!");
 20  	/// }
 21  	///
 22  	/// unsafe fn enable() {
 23  	///     use std::ptr::addr_of_mut;
 24  	///     let mut target_fn = 0xDEADBEEF as *mut c_void;
 25  	///     let hook = Hook::new("my_hook", addr_of_mut!(target_fn), replacement_fn as _);
 26  	/// }
 27  	/// ```
 28  	pub unsafe fn new(
 29  		hook_name: &str,
 30  		target_fn: *mut *mut c_void,
 31  		replacement_fn: *mut c_void,
 32  	) -> Self {
 33  		let hook_name = hook_name.to_string();
 34  		let raw = crate::sys::hook_new(hook_name.clone().into(), target_fn, replacement_fn);
 35  
 36  		Self {
 37  			hook_name,
 38  			raw,
 39  			enabled: true,
 40  		}
 41  	}
 42  
 43  	/// Create a new function hook from a signature.
 44  	///
 45  	/// # Examples
 46  	///
 47  	/// ```
 48  	/// extern "C" fn replacement_fn() {
 49  	///    println!("Hello, world!");
 50  	/// }
 51  	///
 52  	/// unsafe fn enable() {
 53  	///   let hook = Hook::from_signature("my_hook", "48 8B 05 ? ? ? ? 48 8B 0C C8", replacement_fn as _);
 54  	/// }
 55  	#[allow(unused)]
 56  	pub unsafe fn from_signature(
 57  		hook_name: &str,
 58  		signature: &str,
 59  		replacement_fn: *mut c_void,
 60  	) -> Option<Self> {
 61  		let hook_name = hook_name.to_string();
 62  		let result = crate::sys::hook_from_signature(
 63  			hook_name.clone().into(),
 64  			signature.into(),
 65  			replacement_fn,
 66  		);
 67  
 68  		result.map(|raw| Self {
 69  			hook_name,
 70  			raw,
 71  			enabled: true,
 72  		})
 73  	}
 74  }
 75  
 76  pub trait Hookable {
 77  	/// Check if the hook is currently applied.
 78  	unsafe fn is_applied(&self) -> bool;
 79  
 80  	/// Apply the hook.
 81  	///
 82  	/// # Errors
 83  	///
 84  	/// Returns an error if the hook is already applied or if the hook failed to apply.
 85  	///
 86  	/// # Safety
 87  	///
 88  	/// This function is unsafe because it crosses the FFI boundary and writes directly to memory.
 89  	unsafe fn apply(&mut self) -> anyhow::Result<()>;
 90  
 91  	/// Revert the hook.
 92  	///
 93  	/// # Errors
 94  	///
 95  	/// Returns an error if the hook is not applied or if the hook failed to revert.
 96  	///
 97  	/// # Safety
 98  	///
 99  	/// This function is unsafe because it crosses the FFI boundary and writes directly to memory.
100  	unsafe fn revert(&mut self) -> anyhow::Result<()>;
101  
102  	/// Check if the hook should be enabled.
103  	///
104  	/// If the hook is not enabled, the hook will not be applied when the plugin is enabled.
105  	fn is_enabled(&self) -> bool;
106  
107  	/// Set whether the hook should be enabled.
108  	///
109  	/// If the hook is not enabled, the hook will not be applied when the plugin is enabled.
110  	fn set_enabled(&mut self, enabled: bool);
111  
112  	/// Get the name of the hook.
113  	///
114  	/// This is the name used to identify the hook in the configuration file.
115  	fn get_name(&self) -> &str;
116  
117  	/// Check if the hook should be enabled based on the configuration (id: `hook::hook_name`).
118  	///
119  	/// If the configuration value is not found, the default value is used.
120  	///
121  	/// # Safety
122  	///
123  	/// This function is unsafe because it crosses the FFI boundary.
124  	unsafe fn is_config_enabled(&self, plugin_id: &str) -> bool;
125  }
126  
127  impl Hookable for Hook {
128  	unsafe fn is_applied(&self) -> bool {
129  		crate::sys::hook_is_applied(&self.raw)
130  	}
131  
132  	unsafe fn apply(&mut self) -> anyhow::Result<()> {
133  		if self.is_applied() {
134  			Err(anyhow::anyhow!("Hook is already applied."))
135  		} else if crate::sys::hook_apply(&mut self.raw) {
136  			Ok(())
137  		} else {
138  			Err(anyhow::anyhow!("Failed to apply hook."))
139  		}
140  	}
141  
142  	unsafe fn revert(&mut self) -> anyhow::Result<()> {
143  		if !self.is_applied() {
144  			Err(anyhow::anyhow!("Hook is not applied."))
145  		} else if crate::sys::hook_revert(&mut self.raw) {
146  			Ok(())
147  		} else {
148  			Err(anyhow::anyhow!("Failed to revert hook."))
149  		}
150  	}
151  
152  	fn is_enabled(&self) -> bool {
153  		self.enabled
154  	}
155  
156  	fn set_enabled(&mut self, enabled: bool) {
157  		self.enabled = enabled;
158  	}
159  
160  	fn get_name(&self) -> &str {
161  		&self.hook_name
162  	}
163  
164  	unsafe fn is_config_enabled(&self, plugin_id: &str) -> bool {
165  		let result = crate::sys::get_setting_bool(
166  			plugin_id.into(),
167  			format!("hook::{}", self.get_name()).into(),
168  		);
169  
170  		match result.found {
171  			true => result.value,
172  			false => self.is_enabled(),
173  		}
174  	}
175  }