use std::cell::UnsafeCell;
pub struct VTableHook<T> {
object: T,
original_vtbl: &'static [usize],
new_vtbl: UnsafeCell<Vec<usize>>,
}
impl<T> Drop for VTableHook<T> {
fn drop(&mut self) {
unsafe {
*std::mem::transmute_copy::<_, *mut *const usize>(&self.object) = self.original_vtbl.as_ptr();
}
}
}
impl<T> VTableHook<T> {
pub unsafe fn new(object: T) -> Self {
Self::init(object, |vtable| Self::detect_vtable_methods_count(vtable))
}
pub unsafe fn with_count(object: T, count: usize) -> Self {
Self::init(object, |_| count)
}
unsafe fn init<F>(object: T, count_fn: F) -> Self
where
F: FnOnce(*const usize) -> usize
{
let object_ptr = std::mem::transmute_copy::<T, *mut *const usize>(&object);
let original_vtbl = *object_ptr;
let count = count_fn(original_vtbl);
let original_vtbl = std::slice::from_raw_parts(original_vtbl, count);
let new_vtbl = original_vtbl.to_vec();
*object_ptr = new_vtbl.as_ptr();
Self {
object,
original_vtbl,
new_vtbl: UnsafeCell::new(new_vtbl),
}
}
unsafe fn detect_vtable_methods_count(vtable: *const usize) -> usize {
let mut vmt = vtable;
while std::ptr::read(vmt) != 0 {
vmt = vmt.add(1);
}
(vmt as usize - vtable as usize) / std::mem::size_of::<usize>()
}
fn vtbl(&self) -> &mut Vec<usize> {
unsafe { &mut *self.new_vtbl.get() }
}
pub fn get_original_method(&self, id: usize) -> usize {
self.original_vtbl[id]
}
pub fn get_replaced_method(&self, id: usize) -> usize {
self.vtbl()[id]
}
pub unsafe fn replace_method(&self, id: usize, func: usize) {
self.vtbl()[id] = func;
}
pub unsafe fn restore_method(&self, id: usize) {
self.vtbl()[id] = self.get_original_method(id);
}
pub unsafe fn restore_all_methods(&self) {
self.vtbl().copy_from_slice(self.original_vtbl);
}
pub fn object(&self) -> &T {
&self.object
}
}