use crate::{
emulation::{
runtime::hook::{Hook, HookContext, HookManager, PreHookResult},
thread::EmulationThread,
EmValue,
},
Result,
};
pub fn register(manager: &HookManager) -> Result<()> {
manager.register(
Hook::new("System.Environment.get_ProcessorCount")
.match_name("System", "Environment", "get_ProcessorCount")
.pre(get_processor_count_pre),
)?;
manager.register(
Hook::new("System.Environment.get_Is64BitOperatingSystem")
.match_name("System", "Environment", "get_Is64BitOperatingSystem")
.pre(get_is_64bit_os_pre),
)?;
manager.register(
Hook::new("System.Environment.get_Is64BitProcess")
.match_name("System", "Environment", "get_Is64BitProcess")
.pre(get_is_64bit_process_pre),
)?;
manager.register(
Hook::new("System.Environment.get_UserName")
.match_name("System", "Environment", "get_UserName")
.pre(get_user_name_pre),
)?;
manager.register(
Hook::new("System.Environment.get_MachineName")
.match_name("System", "Environment", "get_MachineName")
.pre(get_machine_name_pre),
)?;
manager.register(
Hook::new("System.Environment.get_CurrentDirectory")
.match_name("System", "Environment", "get_CurrentDirectory")
.pre(get_current_directory_pre),
)?;
manager.register(
Hook::new("System.Environment.GetEnvironmentVariable")
.match_name("System", "Environment", "GetEnvironmentVariable")
.pre(get_environment_variable_pre),
)?;
manager.register(
Hook::new("System.Environment.get_TickCount")
.match_name("System", "Environment", "get_TickCount")
.pre(get_tick_count_pre),
)?;
manager.register(
Hook::new("System.Environment.get_TickCount64")
.match_name("System", "Environment", "get_TickCount64")
.pre(get_tick_count64_pre),
)?;
manager.register(
Hook::new("System.Environment.Exit")
.match_name("System", "Environment", "Exit")
.pre(exit_pre),
)?;
manager.register(
Hook::new("System.Environment.get_StackTrace")
.match_name("System", "Environment", "get_StackTrace")
.pre(get_stack_trace_pre),
)?;
manager.register(
Hook::new("System.Environment.GetFolderPath")
.match_name("System", "Environment", "GetFolderPath")
.pre(get_folder_path_pre),
)?;
manager.register(
Hook::new("System.Environment.get_OSVersion")
.match_name("System", "Environment", "get_OSVersion")
.pre(get_os_version_pre),
)?;
Ok(())
}
fn get_processor_count_pre(_ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
PreHookResult::Bypass(Some(EmValue::I32(
thread.config().environment.processor_count,
)))
}
fn get_is_64bit_os_pre(_ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
PreHookResult::Bypass(Some(EmValue::I32(
thread.config().environment.is_64bit_os as i32,
)))
}
fn get_is_64bit_process_pre(_ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
PreHookResult::Bypass(Some(EmValue::I32(
thread.config().environment.is_64bit_process as i32,
)))
}
fn get_user_name_pre(_ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let name = &thread.config().environment.user_name;
match thread.heap_mut().alloc_string(name) {
Ok(str_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(str_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn get_machine_name_pre(_ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let name = &thread.config().environment.machine_name;
match thread.heap_mut().alloc_string(name) {
Ok(str_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(str_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn get_current_directory_pre(
_ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let dir = &thread.config().environment.current_directory;
match thread.heap_mut().alloc_string(dir) {
Ok(str_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(str_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn get_environment_variable_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let var_name = match ctx.args.first() {
Some(EmValue::ObjectRef(r)) => match thread.heap().get_string(*r).map(|s| s.to_string()) {
Ok(s) => s,
Err(e) => return PreHookResult::Error(format!("heap allocation failed: {e}")),
},
_ => return PreHookResult::Bypass(Some(EmValue::Null)),
};
match thread
.config()
.environment
.environment_variables
.get(&var_name)
{
Some(value) => match thread.heap_mut().alloc_string(value) {
Ok(str_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(str_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
},
None => PreHookResult::Bypass(Some(EmValue::Null)),
}
}
fn get_tick_count_pre(_ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let config = &thread.config().environment;
let divisor = config.tick_count_divisor.max(1); #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
let ticks = config.tick_count_base + (thread.instructions_executed() / divisor) as i64;
PreHookResult::Bypass(Some(EmValue::I32(ticks as i32)))
}
fn get_tick_count64_pre(_ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let config = &thread.config().environment;
let divisor = config.tick_count_divisor.max(1); #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
let ticks = config.tick_count_base + (thread.instructions_executed() / divisor) as i64;
PreHookResult::Bypass(Some(EmValue::I64(ticks)))
}
fn exit_pre(_ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
PreHookResult::Bypass(None)
}
fn get_stack_trace_pre(_ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
match thread.heap_mut().alloc_string("") {
Ok(str_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(str_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn get_folder_path_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let folder_id = match ctx.args.first() {
Some(EmValue::I32(id)) => *id,
_ => return PreHookResult::Bypass(Some(EmValue::Null)),
};
let path = thread
.config()
.environment
.folder_paths
.get(&folder_id)
.cloned()
.unwrap_or_default();
match thread.heap_mut().alloc_string(&path) {
Ok(str_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(str_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn get_os_version_pre(_ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let version = &thread.config().environment.os_version;
match thread.heap_mut().alloc_string(version) {
Ok(str_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(str_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
#[cfg(test)]
mod tests {
use crate::{
emulation::runtime::hook::{HookContext, HookManager, PreHookResult},
metadata::{token::Token, typesystem::PointerSize},
test::emulation::create_test_thread,
};
use super::*;
#[test]
fn test_register_hooks() {
let manager = HookManager::new();
register(&manager).unwrap();
assert_eq!(manager.len(), 13);
}
#[test]
fn test_get_processor_count() {
let mut thread = create_test_thread();
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"Environment",
"get_ProcessorCount",
PointerSize::Bit64,
);
let result = get_processor_count_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(4)))
));
}
#[test]
fn test_get_tick_count_advances() {
let mut thread = create_test_thread();
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"Environment",
"get_TickCount",
PointerSize::Bit64,
);
let result = get_tick_count_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(300_000)))
));
}
#[test]
fn test_get_folder_path() {
let mut thread = create_test_thread();
let args = [EmValue::I32(0)]; let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"Environment",
"GetFolderPath",
PointerSize::Bit64,
)
.with_args(&args);
let result = get_folder_path_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
let s = thread.heap().get_string(r).unwrap();
assert_eq!(s.as_ref(), "C:\\Users\\user\\Desktop");
} else {
panic!("Expected ObjectRef");
}
}
fn ctx<'a>(method: &'a str, args: &'a [EmValue]) -> HookContext<'a> {
HookContext::new(
Token::new(0x0A000001),
"System",
"Environment",
method,
PointerSize::Bit64,
)
.with_args(args)
}
#[test]
fn test_is_64bit_os() {
let mut thread = create_test_thread();
let result = get_is_64bit_os_pre(&ctx("get_Is64BitOperatingSystem", &[]), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(1)))
));
}
#[test]
fn test_is_64bit_process() {
let mut thread = create_test_thread();
let result = get_is_64bit_process_pre(&ctx("get_Is64BitProcess", &[]), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(1)))
));
}
#[test]
fn test_get_user_name() {
let mut thread = create_test_thread();
let result = get_user_name_pre(&ctx("get_UserName", &[]), &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
let s = thread.heap().get_string(r).unwrap();
assert!(!s.is_empty());
} else {
panic!("Expected ObjectRef");
}
}
#[test]
fn test_get_machine_name() {
let mut thread = create_test_thread();
let result = get_machine_name_pre(&ctx("get_MachineName", &[]), &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
let s = thread.heap().get_string(r).unwrap();
assert!(!s.is_empty());
} else {
panic!("Expected ObjectRef");
}
}
#[test]
fn test_get_current_directory() {
let mut thread = create_test_thread();
let result = get_current_directory_pre(&ctx("get_CurrentDirectory", &[]), &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
let s = thread.heap().get_string(r).unwrap();
assert!(!s.is_empty());
} else {
panic!("Expected ObjectRef");
}
}
#[test]
fn test_tick_count64() {
let mut thread = create_test_thread();
let result = get_tick_count64_pre(&ctx("get_TickCount64", &[]), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I64(_)))
));
}
#[test]
fn test_exit() {
let mut thread = create_test_thread();
let result = exit_pre(&ctx("Exit", &[EmValue::I32(0)]), &mut thread);
assert!(matches!(result, PreHookResult::Bypass(None)));
}
#[test]
fn test_get_stack_trace() {
let mut thread = create_test_thread();
let result = get_stack_trace_pre(&ctx("get_StackTrace", &[]), &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
let s = thread.heap().get_string(r).unwrap();
assert_eq!(s.as_ref(), "");
} else {
panic!("Expected ObjectRef");
}
}
#[test]
fn test_get_os_version() {
let mut thread = create_test_thread();
let result = get_os_version_pre(&ctx("get_OSVersion", &[]), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::ObjectRef(_)))
));
}
#[test]
fn test_get_environment_variable_missing() {
let mut thread = create_test_thread();
let var_ref = thread.heap_mut().alloc_string("NONEXISTENT").unwrap();
let args = [EmValue::ObjectRef(var_ref)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"Environment",
"GetEnvironmentVariable",
PointerSize::Bit64,
)
.with_args(&args);
let result = get_environment_variable_pre(&ctx, &mut thread);
assert!(matches!(result, PreHookResult::Bypass(Some(EmValue::Null))));
}
}