use super::foreign::FOREIGN_KHOOK;
use super::khook::MockKHook;
use super::smm::MockSmm;
use super::sourcehook::MockSourceHook;
use crate::api::{MetamodApi, MetamodVersion};
use crate::hook::Signature;
use crate::sys::khook::IKHook;
use crate::sys::plugin::PluginStatus;
use crate::sys::sourcehook::ISourceHook;
use std::cell::{Cell, RefCell};
use std::ffi::c_void;
use std::ptr::NonNull;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Mutex, MutexGuard, PoisonError};
static GENERATIONS: AtomicU64 = AtomicU64::new(1);
static SERIAL: Mutex<()> = Mutex::new(());
thread_local! {
static FAILURES: RefCell<Vec<&'static str>> = const { RefCell::new(Vec::new()) };
static PLUGIN_STATUS: Cell<Option<PluginStatus>> = const { Cell::new(None) };
}
pub(crate) struct Harness {
pub(crate) version: MetamodVersion,
smm: Box<MockSmm>,
pub(crate) sourcehook: Box<MockSourceHook>,
pub(crate) khook: Box<MockKHook>,
pub(crate) generation: u64,
_serial: MutexGuard<'static, ()>,
}
impl Harness {
pub(crate) fn new(version: MetamodVersion) -> Self {
let serial = SERIAL.lock().unwrap_or_else(PoisonError::into_inner);
let generation = GENERATIONS.fetch_add(1, Ordering::Relaxed);
FAILURES.take();
let mut harness = Self {
version,
smm: MockSmm::new(version, &[]),
sourcehook: MockSourceHook::new(),
khook: MockKHook::new(),
generation,
_serial: serial,
};
harness.smm.sourcehook = harness.sourcehook_ptr().cast();
harness.smm.khook = harness.khook_ptr().cast();
FOREIGN_KHOOK.set(harness.khook_ptr());
harness.set_status(true, false, generation);
harness
}
pub(crate) fn api(&self) -> MetamodApi<'_> {
self.smm.api()
}
pub(crate) fn call<S: Signature>(
&self,
object: *mut S::This,
index: usize,
args: S::Args,
) -> S::Output {
let vtable = unsafe { object.cast::<*mut *mut c_void>().read() };
match self.version {
MetamodVersion::Stable1226 => {
let function = unsafe { vtable.add(index).read() };
unsafe {
S::invoke(
S::from_address(NonNull::new(function).unwrap()),
object,
args,
)
}
}
MetamodVersion::Dev1469 => self.khook.call::<S>(vtable, index, object, args),
}
}
pub(crate) fn khook_ptr(&self) -> *mut IKHook {
self.khook.ptr()
}
pub(crate) fn set_status(&self, loaded: bool, paused: bool, generation: u64) {
PLUGIN_STATUS.set(Some(PluginStatus {
generation,
id: 7,
loaded,
paused,
}));
}
pub(crate) fn sourcehook_ptr(&self) -> *mut ISourceHook {
self.sourcehook.ptr()
}
}
impl Drop for Harness {
fn drop(&mut self) {
PLUGIN_STATUS.set(None);
FOREIGN_KHOOK.set(std::ptr::null_mut());
}
}
pub(crate) fn expect(condition: bool, failure: &'static str) {
if !condition {
FAILURES.with_borrow_mut(|failures| failures.push(failure));
}
}
pub(crate) fn on_both(test: impl Fn(&Harness)) {
for version in [MetamodVersion::Stable1226, MetamodVersion::Dev1469] {
let harness = Harness::new(version);
test(&harness);
assert_eq!(FAILURES.take(), Vec::<&str>::new(), "on {version:?}");
}
}
pub(crate) fn plugin_status() -> Option<PluginStatus> {
PLUGIN_STATUS.get()
}