use crate::{
emulation::{
runtime::hook::{Hook, HookContext, HookManager, PreHookResult},
thread::EmulationThread,
EmValue,
},
Result,
};
pub fn register(manager: &HookManager) -> Result<()> {
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.Math.Log2")
.match_name("System", "Math", "Log2")
.pre(system_math_log2_pre),
)?;
manager.register(
Hook::new("System.MathF.Abs")
.match_name("System", "MathF", "Abs")
.pre(system_mathf_abs_pre),
)?;
manager.register(
Hook::new("System.MathF.Sin")
.match_name("System", "MathF", "Sin")
.pre(system_mathf_sin_pre),
)?;
manager.register(
Hook::new("System.MathF.Cos")
.match_name("System", "MathF", "Cos")
.pre(system_mathf_cos_pre),
)?;
manager.register(
Hook::new("System.MathF.Sqrt")
.match_name("System", "MathF", "Sqrt")
.pre(system_mathf_sqrt_pre),
)?;
manager.register(
Hook::new("System.MathF.Floor")
.match_name("System", "MathF", "Floor")
.pre(system_mathf_floor_pre),
)?;
manager.register(
Hook::new("System.MathF.Ceiling")
.match_name("System", "MathF", "Ceiling")
.pre(system_mathf_ceiling_pre),
)?;
manager.register(
Hook::new("System.MathF.Round")
.match_name("System", "MathF", "Round")
.pre(system_mathf_round_pre),
)?;
manager.register(
Hook::new("System.MathF.Min")
.match_name("System", "MathF", "Min")
.pre(system_mathf_min_pre),
)?;
manager.register(
Hook::new("System.MathF.Max")
.match_name("System", "MathF", "Max")
.pre(system_mathf_max_pre),
)?;
manager.register(
Hook::new("System.MathF.Pow")
.match_name("System", "MathF", "Pow")
.pre(system_mathf_pow_pre),
)?;
manager.register(
Hook::new("System.MathF.Log")
.match_name("System", "MathF", "Log")
.pre(system_mathf_log_pre),
)?;
manager.register(
Hook::new("System.MathF.Log2")
.match_name("System", "MathF", "Log2")
.pre(system_mathf_log2_pre),
)?;
manager.register(
Hook::new("System.MathF.Log10")
.match_name("System", "MathF", "Log10")
.pre(system_mathf_log10_pre),
)?;
manager.register(
Hook::new("System.MathF.Tan")
.match_name("System", "MathF", "Tan")
.pre(system_mathf_tan_pre),
)?;
manager.register(
Hook::new("System.MathF.Asin")
.match_name("System", "MathF", "Asin")
.pre(system_mathf_asin_pre),
)?;
manager.register(
Hook::new("System.MathF.Acos")
.match_name("System", "MathF", "Acos")
.pre(system_mathf_acos_pre),
)?;
manager.register(
Hook::new("System.MathF.Atan")
.match_name("System", "MathF", "Atan")
.pre(system_mathf_atan_pre),
)?;
manager.register(
Hook::new("System.MathF.Atan2")
.match_name("System", "MathF", "Atan2")
.pre(system_mathf_atan2_pre),
)?;
manager.register(
Hook::new("System.MathF.Exp")
.match_name("System", "MathF", "Exp")
.pre(system_mathf_exp_pre),
)?;
manager.register(
Hook::new("System.MathF.Truncate")
.match_name("System", "MathF", "Truncate")
.pre(system_mathf_truncate_pre),
)?;
manager.register(
Hook::new("System.MathF.Sign")
.match_name("System", "MathF", "Sign")
.pre(system_mathf_sign_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),
)?;
Ok(())
}
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() {
try_hook!(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 = ctx.args[0].to_f64_cil();
let exp = ctx.args[1].to_f64_cil();
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 = ctx.args[0].to_f64_cil();
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 = ctx.args[0].to_f64_cil();
let result = if ctx.args.len() > 1 {
let base = ctx.args[1].to_f64_cil();
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 = ctx.args[0].to_f64_cil();
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 = ctx.args[0].to_f64_cil();
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 = ctx.args[0].to_f64_cil();
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 = ctx.args[0].to_f64_cil();
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 = ctx.args[0].to_f64_cil();
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 = ctx.args[0].to_f64_cil();
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 = ctx.args[0].to_f64_cil();
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 = ctx.args[0].to_f64_cil();
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 = ctx.args[0].to_f64_cil();
let x = ctx.args[1].to_f64_cil();
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))
}
fn system_math_log2_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F64(f64::NEG_INFINITY)));
}
let value = ctx.args[0].to_f64_cil();
PreHookResult::Bypass(Some(EmValue::F64(value.log2())))
}
fn system_mathf_abs_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F32(0.0)));
}
PreHookResult::Bypass(Some(EmValue::F32(ctx.args[0].to_f32_cil().abs())))
}
fn system_mathf_sin_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F32(0.0)));
}
PreHookResult::Bypass(Some(EmValue::F32(ctx.args[0].to_f32_cil().sin())))
}
fn system_mathf_cos_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F32(1.0)));
}
PreHookResult::Bypass(Some(EmValue::F32(ctx.args[0].to_f32_cil().cos())))
}
fn system_mathf_sqrt_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F32(0.0)));
}
PreHookResult::Bypass(Some(EmValue::F32(ctx.args[0].to_f32_cil().sqrt())))
}
fn system_mathf_floor_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F32(0.0)));
}
PreHookResult::Bypass(Some(EmValue::F32(ctx.args[0].to_f32_cil().floor())))
}
fn system_mathf_ceiling_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F32(0.0)));
}
PreHookResult::Bypass(Some(EmValue::F32(ctx.args[0].to_f32_cil().ceil())))
}
fn system_mathf_round_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F32(0.0)));
}
PreHookResult::Bypass(Some(EmValue::F32(ctx.args[0].to_f32_cil().round())))
}
fn system_mathf_min_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.len() < 2 {
return PreHookResult::Bypass(ctx.args.first().cloned());
}
let a = ctx.args[0].to_f32_cil();
let b = ctx.args[1].to_f32_cil();
PreHookResult::Bypass(Some(EmValue::F32(a.min(b))))
}
fn system_mathf_max_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.len() < 2 {
return PreHookResult::Bypass(ctx.args.first().cloned());
}
let a = ctx.args[0].to_f32_cil();
let b = ctx.args[1].to_f32_cil();
PreHookResult::Bypass(Some(EmValue::F32(a.max(b))))
}
fn system_mathf_pow_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.len() < 2 {
return PreHookResult::Bypass(Some(EmValue::F32(0.0)));
}
let base = ctx.args[0].to_f32_cil();
let exp = ctx.args[1].to_f32_cil();
PreHookResult::Bypass(Some(EmValue::F32(base.powf(exp))))
}
fn system_mathf_log_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F32(f32::NEG_INFINITY)));
}
let value = ctx.args[0].to_f32_cil();
let result = if ctx.args.len() > 1 {
let base = ctx.args[1].to_f32_cil();
value.log(base)
} else {
value.ln()
};
PreHookResult::Bypass(Some(EmValue::F32(result)))
}
fn system_mathf_log2_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F32(f32::NEG_INFINITY)));
}
PreHookResult::Bypass(Some(EmValue::F32(ctx.args[0].to_f32_cil().log2())))
}
fn system_mathf_log10_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F32(f32::NEG_INFINITY)));
}
PreHookResult::Bypass(Some(EmValue::F32(ctx.args[0].to_f32_cil().log10())))
}
fn system_mathf_tan_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F32(0.0)));
}
PreHookResult::Bypass(Some(EmValue::F32(ctx.args[0].to_f32_cil().tan())))
}
fn system_mathf_asin_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F32(0.0)));
}
PreHookResult::Bypass(Some(EmValue::F32(ctx.args[0].to_f32_cil().asin())))
}
fn system_mathf_acos_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F32(std::f32::consts::FRAC_PI_2)));
}
PreHookResult::Bypass(Some(EmValue::F32(ctx.args[0].to_f32_cil().acos())))
}
fn system_mathf_atan_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F32(0.0)));
}
PreHookResult::Bypass(Some(EmValue::F32(ctx.args[0].to_f32_cil().atan())))
}
fn system_mathf_atan2_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.len() < 2 {
return PreHookResult::Bypass(Some(EmValue::F32(0.0)));
}
let y = ctx.args[0].to_f32_cil();
let x = ctx.args[1].to_f32_cil();
PreHookResult::Bypass(Some(EmValue::F32(y.atan2(x))))
}
fn system_mathf_exp_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F32(1.0)));
}
PreHookResult::Bypass(Some(EmValue::F32(ctx.args[0].to_f32_cil().exp())))
}
fn system_mathf_truncate_pre(
ctx: &HookContext<'_>,
_thread: &mut EmulationThread,
) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::F32(0.0)));
}
PreHookResult::Bypass(Some(EmValue::F32(ctx.args[0].to_f32_cil().trunc())))
}
#[allow(clippy::cast_possible_truncation)]
fn system_mathf_sign_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
}
let f = ctx.args[0].to_f32_cil();
let result = if f.is_nan() {
0
} else if f > 0.0 {
1
} else if f < 0.0 {
-1
} else {
0
};
PreHookResult::Bypass(Some(EmValue::I32(result)))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
emulation::runtime::HookManager,
metadata::{token::Token, typesystem::PointerSize},
test::emulation::create_test_thread,
};
#[test]
fn test_register_hooks() {
let manager = HookManager::new();
register(&manager).unwrap();
assert_eq!(manager.len(), 49);
}
#[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<'_> {
HookContext::new(
Token::new(0x06000001),
"System",
"Math",
"Abs",
PointerSize::Bit64,
)
.with_args(args)
}
#[test]
fn test_abs_f64() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::F64(-3.125)]);
let result = system_math_abs_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::F64(v))) = result {
assert!((v - 3.125).abs() < 0.001);
} else {
panic!("Expected Bypass with F64");
}
}
#[test]
fn test_abs_i64() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::I64(-100)]);
let result = system_math_abs_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I64(100)))
));
}
#[test]
fn test_sign_positive() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::I32(42)]);
let result = system_math_sign_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(1)))
));
}
#[test]
fn test_sign_negative() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::I32(-42)]);
let result = system_math_sign_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(-1)))
));
}
#[test]
fn test_sign_zero() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::I32(0)]);
let result = system_math_sign_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(0)))
));
}
#[test]
fn test_clamp() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::I32(15), EmValue::I32(0), EmValue::I32(10)]);
let result = system_math_clamp_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(10)))
));
}
#[test]
fn test_floor() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::F64(3.7)]);
let result = system_math_floor_pre(&ctx, &mut thread);
assert!(
matches!(result, PreHookResult::Bypass(Some(EmValue::F64(v))) if (v - 3.0).abs() < 0.001)
);
}
#[test]
fn test_ceiling() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::F64(3.2)]);
let result = system_math_ceiling_pre(&ctx, &mut thread);
assert!(
matches!(result, PreHookResult::Bypass(Some(EmValue::F64(v))) if (v - 4.0).abs() < 0.001)
);
}
#[test]
fn test_round() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::F64(3.5)]);
let result = system_math_round_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::F64(v))) = result {
assert!((v - 4.0).abs() < 0.001);
} else {
panic!("Expected Bypass with F64");
}
}
#[test]
fn test_truncate() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::F64(3.9)]);
let result = system_math_truncate_pre(&ctx, &mut thread);
assert!(
matches!(result, PreHookResult::Bypass(Some(EmValue::F64(v))) if (v - 3.0).abs() < 0.001)
);
}
#[test]
fn test_sqrt() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::F64(16.0)]);
let result = system_math_sqrt_pre(&ctx, &mut thread);
assert!(
matches!(result, PreHookResult::Bypass(Some(EmValue::F64(v))) if (v - 4.0).abs() < 0.001)
);
}
#[test]
fn test_log_natural() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::F64(std::f64::consts::E)]);
let result = system_math_log_pre(&ctx, &mut thread);
assert!(
matches!(result, PreHookResult::Bypass(Some(EmValue::F64(v))) if (v - 1.0).abs() < 0.001)
);
}
#[test]
fn test_log10() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::F64(100.0)]);
let result = system_math_log10_pre(&ctx, &mut thread);
assert!(
matches!(result, PreHookResult::Bypass(Some(EmValue::F64(v))) if (v - 2.0).abs() < 0.001)
);
}
#[test]
fn test_exp() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::F64(1.0)]);
let result = system_math_exp_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::F64(v))) = result {
assert!((v - std::f64::consts::E).abs() < 0.001);
} else {
panic!("Expected Bypass with F64");
}
}
#[test]
fn test_sin() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::F64(0.0)]);
let result = system_math_sin_pre(&ctx, &mut thread);
assert!(matches!(result, PreHookResult::Bypass(Some(EmValue::F64(v))) if v.abs() < 0.001));
}
#[test]
fn test_cos() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::F64(0.0)]);
let result = system_math_cos_pre(&ctx, &mut thread);
assert!(
matches!(result, PreHookResult::Bypass(Some(EmValue::F64(v))) if (v - 1.0).abs() < 0.001)
);
}
#[test]
fn test_tan() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::F64(0.0)]);
let result = system_math_tan_pre(&ctx, &mut thread);
assert!(matches!(result, PreHookResult::Bypass(Some(EmValue::F64(v))) if v.abs() < 0.001));
}
#[test]
fn test_atan2() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::F64(1.0), EmValue::F64(1.0)]);
let result = system_math_atan2_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::F64(v))) = result {
assert!((v - std::f64::consts::FRAC_PI_4).abs() < 0.001);
} else {
panic!("Expected Bypass with F64");
}
}
#[test]
fn test_popcount() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::I32(0b1010_1010)]);
let result = system_numerics_bitoperations_popcount_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(4)))
));
}
#[test]
fn test_leading_zero_count() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::I32(1)]);
let result = system_numerics_bitoperations_leadingzerocount_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(31)))
));
}
#[test]
fn test_rotate_right() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::I32(16), EmValue::I32(4)]);
let result = system_numerics_bitoperations_rotateright_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(1)))
));
}
#[test]
fn test_min_with_f64() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::F64(3.125), EmValue::F64(2.75)]);
let result = system_math_min_pre(&ctx, &mut thread);
assert!(
matches!(result, PreHookResult::Bypass(Some(EmValue::F64(v))) if (v - 2.75).abs() < 0.001)
);
}
#[test]
fn test_max_with_f64() {
let mut thread = create_test_thread();
let ctx = create_test_context(&[EmValue::F64(3.125), EmValue::F64(2.75)]);
let result = system_math_max_pre(&ctx, &mut thread);
assert!(
matches!(result, PreHookResult::Bypass(Some(EmValue::F64(v))) if (v - 3.125).abs() < 0.001)
);
}
}