use crate::interfaces::Cvar;
#[cfg(target_os = "windows")]
use sdk_raw::tf2::host_timescale::Gate;
use sdk_raw::tf2::host_timescale::GateError;
use sdk_raw::util::patch::{self, PatchOperation};
use std::marker::PhantomData;
use std::rc::Rc;
#[doc(alias("Host_AccumulateTime"))]
#[must_use = "keep the patch guard until the plugin restores normal time scaling"]
pub struct HostTimescalePatch {
#[cfg(target_os = "windows")]
gate: Gate,
_not_thread_safe: PhantomData<Rc<()>>,
}
impl HostTimescalePatch {
pub unsafe fn locate(cvar: Cvar<'_>) -> Result<Self, PatchError> {
#[cfg(target_os = "windows")]
let gate = {
let timescale = cvar
.find_var(c"host_timescale")
.ok_or(PatchError::MissingVariable("host_timescale"))?;
let cheats = cvar
.find_var(c"sv_cheats")
.ok_or(PatchError::MissingVariable("sv_cheats"))?;
unsafe { Gate::locate(timescale.as_ptr(), cheats.as_ptr()) }?
};
#[cfg(not(target_os = "windows"))]
let _ = cvar;
Ok(Self {
#[cfg(target_os = "windows")]
gate,
_not_thread_safe: PhantomData,
})
}
pub unsafe fn enable(&mut self) -> Result<(), PatchError> {
#[cfg(target_os = "windows")]
{
Ok(unsafe { self.gate.enable() }?)
}
#[cfg(not(target_os = "windows"))]
Ok(())
}
pub fn is_active(&self) -> bool {
#[cfg(target_os = "windows")]
return self.gate.is_active();
#[cfg(not(target_os = "windows"))]
false
}
pub unsafe fn restore(&mut self) -> Result<(), PatchError> {
#[cfg(target_os = "windows")]
{
Ok(unsafe { self.gate.restore() }?)
}
#[cfg(not(target_os = "windows"))]
Ok(())
}
}
#[derive(Debug, thiserror::Error)]
pub enum PatchError {
#[error("the engine does not register `{0}`")]
MissingVariable(&'static str),
#[error("engine.dll has an unsupported or inaccessible PE image")]
InvalidImage,
#[error("engine.dll does not contain the verified TF2 time-scale gate")]
UnsupportedEngine,
#[error("engine.dll contains more than one verified TF2 time-scale gate")]
AmbiguousGate,
#[error("another component changed the time-scale gate's instruction")]
InstructionChanged,
#[error("{operation} failed: {source}")]
Windows {
operation: &'static str,
#[source]
source: std::io::Error,
},
}
impl From<GateError> for PatchError {
fn from(error: GateError) -> Self {
match error {
GateError::AmbiguousGate => Self::AmbiguousGate,
GateError::InvalidImage => Self::InvalidImage,
GateError::ModuleNotLoaded(source) => Self::Windows {
operation: "GetModuleHandleExW(engine.dll)",
source,
},
GateError::UnsupportedEngine => Self::UnsupportedEngine,
}
}
}
impl From<patch::PatchError> for PatchError {
fn from(error: patch::PatchError) -> Self {
match error {
patch::PatchError::InstructionChanged => Self::InstructionChanged,
patch::PatchError::Os { operation, source } => Self::Windows {
operation: match operation {
PatchOperation::FlushInstructionCache => {
"FlushInstructionCache(time-scale gate)"
}
PatchOperation::Reprotect => {
"VirtualProtect(time-scale gate, original protection)"
}
PatchOperation::Unprotect => "VirtualProtect(time-scale gate, writable)",
},
source,
},
}
}
}