use crate::emulation::{
runtime::hook::{Hook, HookContext, HookManager, PreHookResult},
thread::EmulationThread,
EmValue,
};
pub fn register(manager: &mut HookManager) {
manager.register(
Hook::new("System.Math.Abs")
.match_name("System", "Math", "Abs")
.pre(system_math_abs_pre),
);
manager.register(
Hook::new("System.Math.Min")
.match_name("System", "Math", "Min")
.pre(system_math_min_pre),
);
manager.register(
Hook::new("System.Math.Max")
.match_name("System", "Math", "Max")
.pre(system_math_max_pre),
);
manager.register(
Hook::new("System.Math.Sign")
.match_name("System", "Math", "Sign")
.pre(system_math_sign_pre),
);
manager.register(
Hook::new("System.Math.Clamp")
.match_name("System", "Math", "Clamp")
.pre(system_math_clamp_pre),
);
manager.register(
Hook::new("System.Math.DivRem")
.match_name("System", "Math", "DivRem")
.pre(system_math_divrem_pre),
);
manager.register(
Hook::new("System.Math.Floor")
.match_name("System", "Math", "Floor")
.pre(system_math_floor_pre),
);
manager.register(
Hook::new("System.Math.Ceiling")
.match_name("System", "Math", "Ceiling")
.pre(system_math_ceiling_pre),
);
manager.register(
Hook::new("System.Math.Round")
.match_name("System", "Math", "Round")
.pre(system_math_round_pre),
);
manager.register(
Hook::new("System.Math.Truncate")
.match_name("System", "Math", "Truncate")
.pre(system_math_truncate_pre),
);
manager.register(
Hook::new("System.Math.Pow")
.match_name("System", "Math", "Pow")
.pre(system_math_pow_pre),
);
manager.register(
Hook::new("System.Math.Sqrt")
.match_name("System", "Math", "Sqrt")
.pre(system_math_sqrt_pre),
);
manager.register(
Hook::new("System.Math.Log")
.match_name("System", "Math", "Log")
.pre(system_math_log_pre),
);
manager.register(
Hook::new("System.Math.Log10")
.match_name("System", "Math", "Log10")
.pre(system_math_log10_pre),
);
manager.register(
Hook::new("System.Math.Exp")
.match_name("System", "Math", "Exp")
.pre(system_math_exp_pre),
);
manager.register(
Hook::new("System.Math.Sin")
.match_name("System", "Math", "Sin")
.pre(system_math_sin_pre),
);
manager.register(
Hook::new("System.Math.Cos")
.match_name("System", "Math", "Cos")
.pre(system_math_cos_pre),
);
manager.register(
Hook::new("System.Math.Tan")
.match_name("System", "Math", "Tan")
.pre(system_math_tan_pre),
);
manager.register(
Hook::new("System.Math.Asin")
.match_name("System", "Math", "Asin")
.pre(system_math_asin_pre),
);
manager.register(
Hook::new("System.Math.Acos")
.match_name("System", "Math", "Acos")
.pre(system_math_acos_pre),
);
manager.register(
Hook::new("System.Math.Atan")
.match_name("System", "Math", "Atan")
.pre(system_math_atan_pre),
);
manager.register(
Hook::new("System.Math.Atan2")
.match_name("System", "Math", "Atan2")
.pre(system_math_atan2_pre),
);
manager.register(
Hook::new("System.Numerics.BitOperations.PopCount")
.match_name("System.Numerics", "BitOperations", "PopCount")
.pre(system_numerics_bitoperations_popcount_pre),
);
manager.register(
Hook::new("System.Numerics.BitOperations.LeadingZeroCount")
.match_name("System.Numerics", "BitOperations", "LeadingZeroCount")
.pre(system_numerics_bitoperations_leadingzerocount_pre),
);
manager.register(
Hook::new("System.Numerics.BitOperations.TrailingZeroCount")
.match_name("System.Numerics", "BitOperations", "TrailingZeroCount")
.pre(system_numerics_bitoperations_trailingzerocount_pre),
);
manager.register(
Hook::new("System.Numerics.BitOperations.RotateLeft")
.match_name("System.Numerics", "BitOperations", "RotateLeft")
.pre(system_numerics_bitoperations_rotateleft_pre),
);
manager.register(
Hook::new("System.Numerics.BitOperations.RotateRight")
.match_name("System.Numerics", "BitOperations", "RotateRight")
.pre(system_numerics_bitoperations_rotateright_pre),
);
}
fn system_math_abs_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(0_i32.into()));
}
let result = match &ctx.args[0] {
EmValue::I32(n) => n.abs().into(),
EmValue::I64(n) => n.abs().into(),
EmValue::F32(f) => f.abs().into(),
EmValue::F64(f) => f.abs().into(),
_ => 0_i32.into(),
};
PreHookResult::Bypass(Some(result))
}
fn system_math_min_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.len() < 2 {
return PreHookResult::Bypass(ctx.args.first().cloned());
}
let result = match (&ctx.args[0], &ctx.args[1]) {
(EmValue::I32(a), EmValue::I32(b)) => (*a.min(b)).into(),
(EmValue::I64(a), EmValue::I64(b)) => (*a.min(b)).into(),
(EmValue::F32(a), EmValue::F32(b)) => a.min(*b).into(),
(EmValue::F64(a), EmValue::F64(b)) => a.min(*b).into(),
_ => ctx.args[0].clone(),
};
PreHookResult::Bypass(Some(result))
}
fn system_math_max_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.len() < 2 {
return PreHookResult::Bypass(ctx.args.first().cloned());
}
let result = match (&ctx.args[0], &ctx.args[1]) {
(EmValue::I32(a), EmValue::I32(b)) => EmValue::I32(*a.max(b)),
(EmValue::I64(a), EmValue::I64(b)) => EmValue::I64(*a.max(b)),
(EmValue::F32(a), EmValue::F32(b)) => EmValue::F32(a.max(*b)),
(EmValue::F64(a), EmValue::F64(b)) => EmValue::F64(a.max(*b)),
_ => ctx.args[0].clone(),
};
PreHookResult::Bypass(Some(result))
}
fn system_math_sign_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
}
let result = match &ctx.args[0] {
EmValue::I32(n) => n.signum(),
EmValue::I64(n) => n.signum() as i32,
EmValue::F32(f) => {
if f.is_nan() {
0
} else if *f > 0.0 {
1
} else if *f < 0.0 {
-1
} else {
0
}
}
EmValue::F64(f) => {
if f.is_nan() {
0
} else if *f > 0.0 {
1
} else if *f < 0.0 {
-1
} else {
0
}
}
_ => 0,
};
PreHookResult::Bypass(Some(EmValue::I32(result)))
}
fn system_math_clamp_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.len() < 3 {
return PreHookResult::Bypass(ctx.args.first().cloned());
}
let result = match (&ctx.args[0], &ctx.args[1], &ctx.args[2]) {
(EmValue::I32(val), EmValue::I32(min), EmValue::I32(max)) => {
EmValue::I32(*val.max(min).min(max))
}
(EmValue::I64(val), EmValue::I64(min), EmValue::I64(max)) => {
EmValue::I64(*val.max(min).min(max))
}
(EmValue::F32(val), EmValue::F32(min), EmValue::F32(max)) => {
EmValue::F32(val.max(*min).min(*max))
}
(EmValue::F64(val), EmValue::F64(min), EmValue::F64(max)) => {
EmValue::F64(val.max(*min).min(*max))
}
_ => ctx.args[0].clone(),
};
PreHookResult::Bypass(Some(result))
}
fn system_math_divrem_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.len() < 2 {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
}
let (quotient, remainder) = match (&ctx.args[0], &ctx.args[1]) {
(EmValue::I32(a), EmValue::I32(b)) => {
if *b == 0 {
(EmValue::I32(0), EmValue::I32(0))
} else {
(EmValue::I32(a / b), EmValue::I32(a % b))
}
}
(EmValue::I64(a), EmValue::I64(b)) => {
if *b == 0 {
(EmValue::I64(0), EmValue::I64(0))
} else {
(EmValue::I64(a / b), EmValue::I64(a % b))
}
}
_ => (EmValue::I32(0), EmValue::I32(0)),
};
if ctx.args.len() >= 3 {
if let Some(ptr) = ctx.args[2].as_managed_ptr() {
let _ = thread.store_through_pointer(ptr, remainder);
}
}
PreHookResult::Bypass(Some(quotient))
}
fn system_math_floor_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F64(0.0)));
}
let result = match &ctx.args[0] {
EmValue::F32(f) => EmValue::F64(f64::from(f.floor())),
EmValue::F64(f) => EmValue::F64(f.floor()),
_ => EmValue::F64(0.0),
};
PreHookResult::Bypass(Some(result))
}
fn system_math_ceiling_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F64(0.0)));
}
let result = match &ctx.args[0] {
EmValue::F32(f) => EmValue::F64(f64::from(f.ceil())),
EmValue::F64(f) => EmValue::F64(f.ceil()),
_ => EmValue::F64(0.0),
};
PreHookResult::Bypass(Some(result))
}
fn system_math_round_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F64(0.0)));
}
let value = match &ctx.args[0] {
EmValue::F32(f) => f64::from(*f),
EmValue::F64(f) => *f,
_ => return PreHookResult::Bypass(Some(EmValue::F64(0.0))),
};
let decimals = if ctx.args.len() > 1 {
match &ctx.args[1] {
EmValue::I32(n) => (*n).clamp(0, 15),
_ => 0,
}
} else {
0
};
let multiplier = 10_f64.powi(decimals);
let rounded = (value * multiplier).round() / multiplier;
PreHookResult::Bypass(Some(EmValue::F64(rounded)))
}
fn system_math_truncate_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F64(0.0)));
}
let result = match &ctx.args[0] {
EmValue::F32(f) => EmValue::F64(f64::from(f.trunc())),
EmValue::F64(f) => EmValue::F64(f.trunc()),
_ => EmValue::F64(0.0),
};
PreHookResult::Bypass(Some(result))
}
fn system_math_pow_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.len() < 2 {
return PreHookResult::Bypass(Some(EmValue::F64(0.0)));
}
let base = to_f64(&ctx.args[0]);
let exp = to_f64(&ctx.args[1]);
PreHookResult::Bypass(Some(EmValue::F64(base.powf(exp))))
}
fn system_math_sqrt_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F64(0.0)));
}
let value = to_f64(&ctx.args[0]);
PreHookResult::Bypass(Some(EmValue::F64(value.sqrt())))
}
fn system_math_log_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F64(f64::NEG_INFINITY)));
}
let value = to_f64(&ctx.args[0]);
let result = if ctx.args.len() > 1 {
let base = to_f64(&ctx.args[1]);
value.log(base)
} else {
value.ln()
};
PreHookResult::Bypass(Some(EmValue::F64(result)))
}
fn system_math_log10_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F64(f64::NEG_INFINITY)));
}
let value = to_f64(&ctx.args[0]);
PreHookResult::Bypass(Some(EmValue::F64(value.log10())))
}
fn system_math_exp_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F64(1.0)));
}
let value = to_f64(&ctx.args[0]);
PreHookResult::Bypass(Some(EmValue::F64(value.exp())))
}
fn system_math_sin_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F64(0.0)));
}
let value = to_f64(&ctx.args[0]);
PreHookResult::Bypass(Some(EmValue::F64(value.sin())))
}
fn system_math_cos_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F64(1.0)));
}
let value = to_f64(&ctx.args[0]);
PreHookResult::Bypass(Some(EmValue::F64(value.cos())))
}
fn system_math_tan_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F64(0.0)));
}
let value = to_f64(&ctx.args[0]);
PreHookResult::Bypass(Some(EmValue::F64(value.tan())))
}
fn system_math_asin_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F64(0.0)));
}
let value = to_f64(&ctx.args[0]);
PreHookResult::Bypass(Some(EmValue::F64(value.asin())))
}
fn system_math_acos_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F64(std::f64::consts::FRAC_PI_2)));
}
let value = to_f64(&ctx.args[0]);
PreHookResult::Bypass(Some(EmValue::F64(value.acos())))
}
fn system_math_atan_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F64(0.0)));
}
let value = to_f64(&ctx.args[0]);
PreHookResult::Bypass(Some(EmValue::F64(value.atan())))
}
fn system_math_atan2_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.len() < 2 {
return PreHookResult::Bypass(Some(EmValue::F64(0.0)));
}
let y = to_f64(&ctx.args[0]);
let x = to_f64(&ctx.args[1]);
PreHookResult::Bypass(Some(EmValue::F64(y.atan2(x))))
}
#[allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_possible_wrap
)]
fn system_numerics_bitoperations_popcount_pre(
ctx: &HookContext<'_>,
_thread: &mut EmulationThread,
) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
}
let count = match &ctx.args[0] {
EmValue::I32(n) => (*n as u32).count_ones() as i32,
EmValue::I64(n) => (*n as u64).count_ones() as i32,
_ => 0,
};
PreHookResult::Bypass(Some(EmValue::I32(count)))
}
#[allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_possible_wrap
)]
fn system_numerics_bitoperations_leadingzerocount_pre(
ctx: &HookContext<'_>,
_thread: &mut EmulationThread,
) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::I32(32)));
}
let count = match &ctx.args[0] {
EmValue::I32(n) => (*n as u32).leading_zeros() as i32,
EmValue::I64(n) => (*n as u64).leading_zeros() as i32,
_ => 32,
};
PreHookResult::Bypass(Some(EmValue::I32(count)))
}
#[allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_possible_wrap
)]
fn system_numerics_bitoperations_trailingzerocount_pre(
ctx: &HookContext<'_>,
_thread: &mut EmulationThread,
) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::I32(32)));
}
let count = match &ctx.args[0] {
EmValue::I32(n) => (*n as u32).trailing_zeros() as i32,
EmValue::I64(n) => (*n as u64).trailing_zeros() as i32,
_ => 32,
};
PreHookResult::Bypass(Some(EmValue::I32(count)))
}
#[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap)]
fn system_numerics_bitoperations_rotateleft_pre(
ctx: &HookContext<'_>,
_thread: &mut EmulationThread,
) -> PreHookResult {
if ctx.args.len() < 2 {
return PreHookResult::Bypass(ctx.args.first().cloned());
}
let shift = match &ctx.args[1] {
EmValue::I32(n) => *n as u32,
_ => return PreHookResult::Bypass(Some(ctx.args[0].clone())),
};
let result = match &ctx.args[0] {
EmValue::I32(n) => EmValue::I32((*n as u32).rotate_left(shift) as i32),
EmValue::I64(n) => EmValue::I64((*n as u64).rotate_left(shift) as i64),
_ => ctx.args[0].clone(),
};
PreHookResult::Bypass(Some(result))
}
#[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap)]
fn system_numerics_bitoperations_rotateright_pre(
ctx: &HookContext<'_>,
_thread: &mut EmulationThread,
) -> PreHookResult {
if ctx.args.len() < 2 {
return PreHookResult::Bypass(ctx.args.first().cloned());
}
let shift = match &ctx.args[1] {
EmValue::I32(n) => *n as u32,
_ => return PreHookResult::Bypass(Some(ctx.args[0].clone())),
};
let result = match &ctx.args[0] {
EmValue::I32(n) => EmValue::I32((*n as u32).rotate_right(shift) as i32),
EmValue::I64(n) => EmValue::I64((*n as u64).rotate_right(shift) as i64),
_ => ctx.args[0].clone(),
};
PreHookResult::Bypass(Some(result))
}
#[allow(clippy::cast_precision_loss)]
fn to_f64(value: &EmValue) -> f64 {
match value {
EmValue::I32(n) => f64::from(*n),
EmValue::I64(n) => *n as f64,
EmValue::F32(f) => f64::from(*f),
EmValue::F64(f) => *f,
_ => 0.0,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
emulation::runtime::HookManager, metadata::typesystem::PointerSize,
test::emulation::create_test_thread,
};
#[test]
fn test_register_hooks() {
let mut manager = HookManager::new();
register(&mut manager);
assert!(manager.len() > 20);
}
#[test]
fn test_abs_hook() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::I32(-5)]);
let result = system_math_abs_pre(&ctx, &mut thread);
match result {
PreHookResult::Bypass(Some(EmValue::I32(5))) => {}
_ => panic!("Expected Bypass(Some(I32(5)))"),
}
}
#[test]
fn test_min_hook() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::I32(3), EmValue::I32(7)]);
let result = system_math_min_pre(&ctx, &mut thread);
match result {
PreHookResult::Bypass(Some(EmValue::I32(3))) => {}
_ => panic!("Expected Bypass(Some(I32(3)))"),
}
}
#[test]
fn test_max_hook() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::I32(3), EmValue::I32(7)]);
let result = system_math_max_pre(&ctx, &mut thread);
match result {
PreHookResult::Bypass(Some(EmValue::I32(7))) => {}
_ => panic!("Expected Bypass(Some(I32(7)))"),
}
}
#[test]
fn test_pow_hook() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::F64(2.0), EmValue::F64(3.0)]);
let result = system_math_pow_pre(&ctx, &mut thread);
match result {
PreHookResult::Bypass(Some(EmValue::F64(val))) if (val - 8.0).abs() < 0.001 => {}
_ => panic!("Expected Bypass(Some(F64(8.0)))"),
}
}
#[test]
fn test_rotate_left_hook() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::I32(1), EmValue::I32(4)]);
let result = system_numerics_bitoperations_rotateleft_pre(&ctx, &mut thread);
match result {
PreHookResult::Bypass(Some(EmValue::I32(16))) => {}
_ => panic!("Expected Bypass(Some(I32(16)))"),
}
}
fn create_test_context(args: &[EmValue]) -> HookContext<'_> {
use crate::metadata::token::Token;
HookContext::new(
Token::new(0x06000001),
"System",
"Math",
"Abs",
PointerSize::Bit64,
)
.with_args(args)
}
}