use crate::{
emulation::{
memory::HeapObject,
runtime::hook::{Hook, HookContext, HookManager, PreHookResult},
thread::EmulationThread,
EmValue,
},
metadata::typesystem::CilFlavor,
Result,
};
pub fn register(manager: &HookManager) -> Result<()> {
manager.register(
Hook::new("System.String.Concat")
.match_name("System", "String", "Concat")
.pre(string_concat_pre),
)?;
manager.register(
Hook::new("System.String.IsNullOrEmpty")
.match_name("System", "String", "IsNullOrEmpty")
.pre(string_is_null_or_empty_pre),
)?;
manager.register(
Hook::new("System.String.Join")
.match_name("System", "String", "Join")
.pre(string_join_pre),
)?;
manager.register(
Hook::new("System.String.Format")
.match_name("System", "String", "Format")
.pre(string_format_pre),
)?;
manager.register(
Hook::new("System.String.Intern")
.match_name("System", "String", "Intern")
.pre(string_intern_pre),
)?;
manager.register(
Hook::new("System.String.IsInterned")
.match_name("System", "String", "IsInterned")
.pre(string_is_interned_pre),
)?;
manager.register(
Hook::new("System.String.op_Equality")
.match_name("System", "String", "op_Equality")
.pre(string_op_equality_pre),
)?;
manager.register(
Hook::new("System.String.op_Inequality")
.match_name("System", "String", "op_Inequality")
.pre(string_op_inequality_pre),
)?;
manager.register(
Hook::new("System.String.get_Length")
.match_name("System", "String", "get_Length")
.pre(string_get_length_pre),
)?;
manager.register(
Hook::new("System.String.get_Chars")
.match_name("System", "String", "get_Chars")
.pre(string_get_chars_pre),
)?;
manager.register(
Hook::new("System.String.Substring")
.match_name("System", "String", "Substring")
.pre(string_substring_pre),
)?;
manager.register(
Hook::new("System.String.ToCharArray")
.match_name("System", "String", "ToCharArray")
.pre(string_to_char_array_pre),
)?;
manager.register(
Hook::new("System.String.ToUpper")
.match_name("System", "String", "ToUpper")
.pre(string_to_upper_pre),
)?;
manager.register(
Hook::new("System.String.ToLower")
.match_name("System", "String", "ToLower")
.pre(string_to_lower_pre),
)?;
manager.register(
Hook::new("System.String.Trim")
.match_name("System", "String", "Trim")
.pre(string_trim_pre),
)?;
manager.register(
Hook::new("System.String.Replace")
.match_name("System", "String", "Replace")
.pre(string_replace_pre),
)?;
manager.register(
Hook::new("System.String.Split")
.match_name("System", "String", "Split")
.pre(string_split_pre),
)?;
manager.register(
Hook::new("System.String.Contains")
.match_name("System", "String", "Contains")
.pre(string_contains_pre),
)?;
manager.register(
Hook::new("System.String.StartsWith")
.match_name("System", "String", "StartsWith")
.pre(string_starts_with_pre),
)?;
manager.register(
Hook::new("System.String.EndsWith")
.match_name("System", "String", "EndsWith")
.pre(string_ends_with_pre),
)?;
manager.register(
Hook::new("System.String.IndexOf")
.match_name("System", "String", "IndexOf")
.pre(string_index_of_pre),
)?;
manager.register(
Hook::new("System.String.PadLeft")
.match_name("System", "String", "PadLeft")
.pre(string_pad_left_pre),
)?;
manager.register(
Hook::new("System.String.PadRight")
.match_name("System", "String", "PadRight")
.pre(string_pad_right_pre),
)?;
Ok(())
}
fn string_concat_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let mut result = String::new();
for arg in ctx.args {
match arg {
EmValue::ObjectRef(href) => {
if let Ok(s) = thread.heap().get_string(*href) {
result.push_str(&s);
}
}
EmValue::Null => {}
EmValue::I32(v) => result.push_str(&v.to_string()),
EmValue::I64(v) => result.push_str(&v.to_string()),
EmValue::Bool(v) => result.push_str(if *v { "True" } else { "False" }),
EmValue::Char(c) => result.push(*c),
_ => result.push_str("[object]"),
}
}
match thread.heap_mut().alloc_string(&result) {
Ok(href) => PreHookResult::Bypass(Some(EmValue::ObjectRef(href))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn string_get_length_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let Some(EmValue::ObjectRef(href)) = ctx.this else {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
};
match thread.heap().get_string(*href) {
Ok(s) => {
let len = i32::try_from(s.chars().count()).unwrap_or(i32::MAX);
PreHookResult::Bypass(Some(EmValue::I32(len)))
}
Err(_) => PreHookResult::Bypass(Some(EmValue::I32(0))),
}
}
fn string_get_chars_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let Some(EmValue::ObjectRef(href)) = ctx.this else {
return PreHookResult::Bypass(Some(EmValue::Char('\0')));
};
let index = ctx
.args
.first()
.map(usize::try_from)
.and_then(|r| r.ok())
.unwrap_or(0);
match thread.heap().get_string(*href) {
Ok(s) => match s.chars().nth(index) {
Some(c) => PreHookResult::Bypass(Some(EmValue::Char(c))),
None => PreHookResult::Bypass(Some(EmValue::Char('\0'))),
},
Err(_) => PreHookResult::Bypass(Some(EmValue::Char('\0'))),
}
}
fn string_substring_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let Some(EmValue::ObjectRef(href)) = ctx.this else {
return PreHookResult::Bypass(Some(EmValue::Null));
};
let s = match thread.heap().get_string(*href) {
Ok(s) => s.to_string(),
Err(e) => return PreHookResult::Error(format!("heap allocation failed: {e}")),
};
let start = ctx
.args
.first()
.map(usize::try_from)
.and_then(|r| r.ok())
.unwrap_or(0);
let substring: String = if ctx.args.len() > 1 {
let length = usize::try_from(&ctx.args[1]).unwrap_or(0);
s.chars().skip(start).take(length).collect()
} else {
s.chars().skip(start).collect()
};
match thread.heap_mut().alloc_string(&substring) {
Ok(href) => PreHookResult::Bypass(Some(EmValue::ObjectRef(href))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn string_to_char_array_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let Some(EmValue::ObjectRef(href)) = ctx.this else {
return PreHookResult::Bypass(Some(EmValue::Null));
};
let s = match thread.heap().get_string(*href) {
Ok(s) => s.to_string(),
Err(e) => return PreHookResult::Error(format!("heap allocation failed: {e}")),
};
let chars: Vec<EmValue> = s.chars().map(EmValue::Char).collect();
match thread
.heap_mut()
.alloc_array_with_values(CilFlavor::Char, chars)
{
Ok(href) => PreHookResult::Bypass(Some(EmValue::ObjectRef(href))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn string_to_upper_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let Some(EmValue::ObjectRef(href)) = ctx.this else {
return PreHookResult::Bypass(Some(EmValue::Null));
};
match thread.heap().get_string(*href) {
Ok(s) => {
let upper = s.to_uppercase();
match thread.heap_mut().alloc_string(&upper) {
Ok(href) => PreHookResult::Bypass(Some(EmValue::ObjectRef(href))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn string_to_lower_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let Some(EmValue::ObjectRef(href)) = ctx.this else {
return PreHookResult::Bypass(Some(EmValue::Null));
};
match thread.heap().get_string(*href) {
Ok(s) => {
let lower = s.to_lowercase();
match thread.heap_mut().alloc_string(&lower) {
Ok(href) => PreHookResult::Bypass(Some(EmValue::ObjectRef(href))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn string_trim_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let Some(EmValue::ObjectRef(href)) = ctx.this else {
return PreHookResult::Bypass(Some(EmValue::Null));
};
match thread.heap().get_string(*href) {
Ok(s) => {
let trimmed = s.trim().to_string();
match thread.heap_mut().alloc_string(&trimmed) {
Ok(href) => PreHookResult::Bypass(Some(EmValue::ObjectRef(href))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn string_replace_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let Some(EmValue::ObjectRef(href)) = ctx.this else {
return PreHookResult::Bypass(Some(EmValue::Null));
};
if ctx.args.len() < 2 {
return PreHookResult::Bypass(Some(EmValue::ObjectRef(*href)));
}
let s = match thread.heap().get_string(*href) {
Ok(s) => s.to_string(),
Err(e) => return PreHookResult::Error(format!("heap allocation failed: {e}")),
};
let result = match (&ctx.args[0], &ctx.args[1]) {
(EmValue::Char(old), EmValue::Char(new)) => s.replace(*old, &new.to_string()),
(EmValue::ObjectRef(old_ref), EmValue::ObjectRef(new_ref)) => {
let old_str = thread
.heap()
.get_string(*old_ref)
.map(|s| s.to_string())
.unwrap_or_default();
let new_str = thread
.heap()
.get_string(*new_ref)
.map(|s| s.to_string())
.unwrap_or_default();
s.replace(&old_str, &new_str)
}
_ => s,
};
match thread.heap_mut().alloc_string(&result) {
Ok(href) => PreHookResult::Bypass(Some(EmValue::ObjectRef(href))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn string_split_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let Some(EmValue::ObjectRef(href)) = ctx.this else {
return PreHookResult::Bypass(Some(EmValue::Null));
};
let s = match thread.heap().get_string(*href) {
Ok(s) => s.to_string(),
Err(e) => return PreHookResult::Error(format!("heap allocation failed: {e}")),
};
let separator = match ctx.args.first() {
Some(EmValue::Char(c)) => *c,
Some(EmValue::ObjectRef(href)) => {
if let Ok(HeapObject::Array { elements, .. }) = thread.heap().get(*href) {
elements
.first()
.and_then(|e| {
if let EmValue::Char(c) = e {
Some(*c)
} else {
None
}
})
.unwrap_or(' ')
} else {
' '
}
}
_ => ' ',
};
let parts: Vec<&str> = s.split(separator).collect();
let mut string_refs = Vec::with_capacity(parts.len());
for part in parts {
match thread.heap_mut().alloc_string(part) {
Ok(string_ref) => string_refs.push(EmValue::ObjectRef(string_ref)),
Err(e) => return PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
match thread
.heap_mut()
.alloc_array_with_values(CilFlavor::String, string_refs)
{
Ok(array_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(array_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn string_contains_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let Some(EmValue::ObjectRef(href)) = ctx.this else {
return PreHookResult::Bypass(Some(EmValue::Bool(false)));
};
let s = match thread.heap().get_string(*href) {
Ok(s) => s.to_string(),
Err(_) => return PreHookResult::Bypass(Some(EmValue::Bool(false))),
};
let contains = match ctx.args.first() {
Some(EmValue::Char(c)) => s.contains(*c),
Some(EmValue::ObjectRef(href)) => thread
.heap()
.get_string(*href)
.map(|substr| s.contains(&*substr))
.unwrap_or(false),
_ => false,
};
PreHookResult::Bypass(Some(EmValue::Bool(contains)))
}
fn string_starts_with_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let Some(EmValue::ObjectRef(href)) = ctx.this else {
return PreHookResult::Bypass(Some(EmValue::Bool(false)));
};
let s = match thread.heap().get_string(*href) {
Ok(s) => s.to_string(),
Err(_) => return PreHookResult::Bypass(Some(EmValue::Bool(false))),
};
let starts_with = match ctx.args.first() {
Some(EmValue::Char(c)) => s.starts_with(*c),
Some(EmValue::ObjectRef(href)) => thread
.heap()
.get_string(*href)
.map(|prefix| s.starts_with(&*prefix))
.unwrap_or(false),
_ => false,
};
PreHookResult::Bypass(Some(EmValue::Bool(starts_with)))
}
fn string_ends_with_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let Some(EmValue::ObjectRef(href)) = ctx.this else {
return PreHookResult::Bypass(Some(EmValue::Bool(false)));
};
let s = match thread.heap().get_string(*href) {
Ok(s) => s.to_string(),
Err(_) => return PreHookResult::Bypass(Some(EmValue::Bool(false))),
};
let ends_with = match ctx.args.first() {
Some(EmValue::Char(c)) => s.ends_with(*c),
Some(EmValue::ObjectRef(href)) => thread
.heap()
.get_string(*href)
.map(|suffix| s.ends_with(&*suffix))
.unwrap_or(false),
_ => false,
};
PreHookResult::Bypass(Some(EmValue::Bool(ends_with)))
}
fn string_index_of_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let Some(EmValue::ObjectRef(href)) = ctx.this else {
return PreHookResult::Bypass(Some(EmValue::I32(-1)));
};
let s = match thread.heap().get_string(*href) {
Ok(s) => s.to_string(),
Err(_) => return PreHookResult::Bypass(Some(EmValue::I32(-1))),
};
let index = match ctx.args.first() {
Some(EmValue::Char(c)) => s
.find(*c)
.map_or(-1, |i| i32::try_from(i).unwrap_or(i32::MAX)),
Some(EmValue::ObjectRef(href)) => thread
.heap()
.get_string(*href)
.map(|substr| {
s.find(&*substr)
.map_or(-1, |i| i32::try_from(i).unwrap_or(i32::MAX))
})
.unwrap_or(-1),
_ => -1,
};
PreHookResult::Bypass(Some(EmValue::I32(index)))
}
fn string_pad_left_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let Some(EmValue::ObjectRef(href)) = ctx.this else {
return PreHookResult::Bypass(Some(EmValue::Null));
};
let s = match thread.heap().get_string(*href) {
Ok(s) => s.to_string(),
Err(e) => return PreHookResult::Error(format!("heap allocation failed: {e}")),
};
let total_width = ctx
.args
.first()
.map(usize::try_from)
.and_then(|r| r.ok())
.unwrap_or(0);
let pad_char = ctx
.args
.get(1)
.and_then(|v| {
if let EmValue::Char(c) = v {
Some(*c)
} else {
None
}
})
.unwrap_or(' ');
let result = if s.len() >= total_width {
s
} else {
let padding: String = std::iter::repeat_n(pad_char, total_width - s.len()).collect();
format!("{padding}{s}")
};
match thread.heap_mut().alloc_string(&result) {
Ok(href) => PreHookResult::Bypass(Some(EmValue::ObjectRef(href))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn string_pad_right_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let Some(EmValue::ObjectRef(href)) = ctx.this else {
return PreHookResult::Bypass(Some(EmValue::Null));
};
let s = match thread.heap().get_string(*href) {
Ok(s) => s.to_string(),
Err(e) => return PreHookResult::Error(format!("heap allocation failed: {e}")),
};
let total_width = ctx
.args
.first()
.map(usize::try_from)
.and_then(|r| r.ok())
.unwrap_or(0);
let pad_char = ctx
.args
.get(1)
.and_then(|v| {
if let EmValue::Char(c) = v {
Some(*c)
} else {
None
}
})
.unwrap_or(' ');
let result = if s.len() >= total_width {
s
} else {
let padding: String = std::iter::repeat_n(pad_char, total_width - s.len()).collect();
format!("{s}{padding}")
};
match thread.heap_mut().alloc_string(&result) {
Ok(href) => PreHookResult::Bypass(Some(EmValue::ObjectRef(href))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn string_is_null_or_empty_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let is_null_or_empty = if let Some(EmValue::ObjectRef(href)) = ctx.args.first() {
thread
.heap()
.get_string(*href)
.map(|s| s.is_empty())
.unwrap_or(true)
} else {
true
};
PreHookResult::Bypass(Some(EmValue::Bool(is_null_or_empty)))
}
fn string_join_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.len() < 2 {
return PreHookResult::Bypass(Some(EmValue::Null));
}
let separator = if let EmValue::ObjectRef(href) = &ctx.args[0] {
thread
.heap()
.get_string(*href)
.map(|s| s.to_string())
.unwrap_or_default()
} else {
String::new()
};
let array_ref = match &ctx.args[1] {
EmValue::ObjectRef(r) => *r,
_ => return PreHookResult::Bypass(Some(EmValue::Null)),
};
let Ok(obj) = thread.heap().get(array_ref) else {
return PreHookResult::Bypass(Some(EmValue::Null));
};
let parts: Vec<String> = match obj {
HeapObject::Array { elements, .. } => elements
.iter()
.filter_map(|e| match e {
EmValue::ObjectRef(href) => {
thread.heap().get_string(*href).ok().map(|s| s.to_string())
}
EmValue::Null => Some(String::new()),
_ => None,
})
.collect(),
_ => Vec::new(),
};
let result = parts.join(&separator);
match thread.heap_mut().alloc_string(&result) {
Ok(href) => PreHookResult::Bypass(Some(EmValue::ObjectRef(href))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn string_format_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::Null));
}
let format_str = match &ctx.args[0] {
EmValue::ObjectRef(href) => match thread.heap().get_string(*href) {
Ok(s) => s.to_string(),
Err(e) => return PreHookResult::Error(format!("heap allocation failed: {e}")),
},
_ => return PreHookResult::Bypass(Some(EmValue::Null)),
};
let mut result = format_str;
for (i, arg) in ctx.args.iter().skip(1).enumerate() {
let placeholder = format!("{{{i}}}");
let value_str = match arg {
EmValue::ObjectRef(href) => thread
.heap()
.get_string(*href)
.map_or_else(|_| "[object]".to_string(), |s| s.to_string()),
EmValue::Null => String::new(),
EmValue::I32(v) => v.to_string(),
EmValue::I64(v) => v.to_string(),
EmValue::F32(v) => v.to_string(),
EmValue::F64(v) => v.to_string(),
EmValue::Bool(v) => if *v { "True" } else { "False" }.to_string(),
EmValue::Char(c) => c.to_string(),
_ => "[value]".to_string(),
};
result = result.replace(&placeholder, &value_str);
}
match thread.heap_mut().alloc_string(&result) {
Ok(href) => PreHookResult::Bypass(Some(EmValue::ObjectRef(href))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn string_intern_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if let Some(s) = ctx.args.first() {
PreHookResult::Bypass(Some(s.clone()))
} else {
PreHookResult::Bypass(Some(EmValue::Null))
}
}
fn string_is_interned_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if let Some(s) = ctx.args.first() {
PreHookResult::Bypass(Some(s.clone()))
} else {
PreHookResult::Bypass(Some(EmValue::Null))
}
}
fn string_op_equality_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let equal = string_values_equal(ctx, thread);
PreHookResult::Bypass(Some(EmValue::I32(i32::from(equal))))
}
fn string_op_inequality_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let equal = string_values_equal(ctx, thread);
PreHookResult::Bypass(Some(EmValue::I32(i32::from(!equal))))
}
fn string_values_equal(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> bool {
let lhs = ctx.args.first().unwrap_or(&EmValue::Null);
let rhs = ctx.args.get(1).unwrap_or(&EmValue::Null);
match (lhs, rhs) {
(EmValue::Null, EmValue::Null) => true,
(EmValue::Null, _) | (_, EmValue::Null) => false,
(EmValue::ObjectRef(a), EmValue::ObjectRef(b)) => {
if a.id() == b.id() {
return true;
}
match (
thread.heap().get_string_opt(*a),
thread.heap().get_string_opt(*b),
) {
(Some(sa), Some(sb)) => *sa == *sb,
_ => false,
}
}
_ => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
emulation::runtime::hook::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(), 23);
}
#[test]
fn test_string_concat_hook() {
let mut thread = create_test_thread();
let s1 = thread.heap_mut().alloc_string("Hello ").unwrap();
let s2 = thread.heap_mut().alloc_string("World").unwrap();
let args = [EmValue::ObjectRef(s1), EmValue::ObjectRef(s2)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"String",
"Concat",
PointerSize::Bit64,
)
.with_args(&args);
let result = string_concat_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
assert_eq!(&*thread.heap().get_string(r).unwrap(), "Hello World");
} else {
panic!("Expected Bypass with ObjectRef");
}
}
#[test]
fn test_string_length_hook() {
let mut thread = create_test_thread();
let s = thread.heap_mut().alloc_string("Hello").unwrap();
let this = EmValue::ObjectRef(s);
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"String",
"get_Length",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = string_get_length_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(5)))
));
}
#[test]
fn test_string_to_upper_hook() {
let mut thread = create_test_thread();
let s = thread.heap_mut().alloc_string("hello").unwrap();
let this = EmValue::ObjectRef(s);
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"String",
"ToUpper",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = string_to_upper_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
assert_eq!(&*thread.heap().get_string(r).unwrap(), "HELLO");
} else {
panic!("Expected Bypass with ObjectRef");
}
}
#[test]
fn test_string_to_lower_hook() {
let mut thread = create_test_thread();
let s = thread.heap_mut().alloc_string("HELLO").unwrap();
let this = EmValue::ObjectRef(s);
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"String",
"ToLower",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = string_to_lower_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
assert_eq!(&*thread.heap().get_string(r).unwrap(), "hello");
} else {
panic!("Expected Bypass with ObjectRef");
}
}
#[test]
fn test_string_substring_one_arg() {
let mut thread = create_test_thread();
let s = thread.heap_mut().alloc_string("Hello World").unwrap();
let this = EmValue::ObjectRef(s);
let args = [EmValue::I32(6)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"String",
"Substring",
PointerSize::Bit64,
)
.with_this(Some(&this))
.with_args(&args);
let result = string_substring_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
assert_eq!(&*thread.heap().get_string(r).unwrap(), "World");
} else {
panic!("Expected Bypass with ObjectRef");
}
}
#[test]
fn test_string_substring_two_args() {
let mut thread = create_test_thread();
let s = thread.heap_mut().alloc_string("Hello World").unwrap();
let this = EmValue::ObjectRef(s);
let args = [EmValue::I32(0), EmValue::I32(5)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"String",
"Substring",
PointerSize::Bit64,
)
.with_this(Some(&this))
.with_args(&args);
let result = string_substring_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
assert_eq!(&*thread.heap().get_string(r).unwrap(), "Hello");
} else {
panic!("Expected Bypass with ObjectRef");
}
}
#[test]
fn test_string_get_chars() {
let mut thread = create_test_thread();
let s = thread.heap_mut().alloc_string("ABC").unwrap();
let this = EmValue::ObjectRef(s);
let args = [EmValue::I32(1)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"String",
"get_Chars",
PointerSize::Bit64,
)
.with_this(Some(&this))
.with_args(&args);
let result = string_get_chars_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::Char('B')))
));
}
#[test]
fn test_string_get_chars_oob() {
let mut thread = create_test_thread();
let s = thread.heap_mut().alloc_string("AB").unwrap();
let this = EmValue::ObjectRef(s);
let args = [EmValue::I32(5)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"String",
"get_Chars",
PointerSize::Bit64,
)
.with_this(Some(&this))
.with_args(&args);
let result = string_get_chars_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::Char('\0')))
));
}
#[test]
fn test_string_to_char_array() {
let mut thread = create_test_thread();
let s = thread.heap_mut().alloc_string("AB").unwrap();
let this = EmValue::ObjectRef(s);
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"String",
"ToCharArray",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = string_to_char_array_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(arr))) = result {
assert_eq!(
thread.heap().get_array_element(arr, 0).unwrap(),
EmValue::Char('A')
);
assert_eq!(
thread.heap().get_array_element(arr, 1).unwrap(),
EmValue::Char('B')
);
} else {
panic!("Expected Bypass with ObjectRef");
}
}
#[test]
fn test_string_trim() {
let mut thread = create_test_thread();
let s = thread.heap_mut().alloc_string(" hello ").unwrap();
let this = EmValue::ObjectRef(s);
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"String",
"Trim",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = string_trim_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
assert_eq!(&*thread.heap().get_string(r).unwrap(), "hello");
} else {
panic!("Expected Bypass with ObjectRef");
}
}
#[test]
fn test_string_replace_string() {
let mut thread = create_test_thread();
let s = thread.heap_mut().alloc_string("Hello World").unwrap();
let this = EmValue::ObjectRef(s);
let old = thread.heap_mut().alloc_string("World").unwrap();
let new = thread.heap_mut().alloc_string("Rust").unwrap();
let args = [EmValue::ObjectRef(old), EmValue::ObjectRef(new)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"String",
"Replace",
PointerSize::Bit64,
)
.with_this(Some(&this))
.with_args(&args);
let result = string_replace_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
assert_eq!(&*thread.heap().get_string(r).unwrap(), "Hello Rust");
} else {
panic!("Expected Bypass with ObjectRef");
}
}
#[test]
fn test_string_replace_char() {
let mut thread = create_test_thread();
let s = thread.heap_mut().alloc_string("a.b.c").unwrap();
let this = EmValue::ObjectRef(s);
let args = [EmValue::Char('.'), EmValue::Char('_')];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"String",
"Replace",
PointerSize::Bit64,
)
.with_this(Some(&this))
.with_args(&args);
let result = string_replace_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
assert_eq!(&*thread.heap().get_string(r).unwrap(), "a_b_c");
} else {
panic!("Expected Bypass with ObjectRef");
}
}
#[test]
fn test_string_split_by_char() {
let mut thread = create_test_thread();
let s = thread.heap_mut().alloc_string("a,b,c").unwrap();
let this = EmValue::ObjectRef(s);
let args = [EmValue::Char(',')];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"String",
"Split",
PointerSize::Bit64,
)
.with_this(Some(&this))
.with_args(&args);
let result = string_split_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(arr))) = result {
let len = thread.heap().get_array_length(arr).unwrap_or(0);
assert_eq!(len, 3);
} else {
panic!("Expected Bypass with ObjectRef");
}
}
#[test]
fn test_string_contains_true() {
let mut thread = create_test_thread();
let s = thread.heap_mut().alloc_string("Hello World").unwrap();
let this = EmValue::ObjectRef(s);
let sub = thread.heap_mut().alloc_string("World").unwrap();
let args = [EmValue::ObjectRef(sub)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"String",
"Contains",
PointerSize::Bit64,
)
.with_this(Some(&this))
.with_args(&args);
let result = string_contains_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::Bool(true)))
));
}
#[test]
fn test_string_contains_false() {
let mut thread = create_test_thread();
let s = thread.heap_mut().alloc_string("Hello").unwrap();
let this = EmValue::ObjectRef(s);
let sub = thread.heap_mut().alloc_string("xyz").unwrap();
let args = [EmValue::ObjectRef(sub)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"String",
"Contains",
PointerSize::Bit64,
)
.with_this(Some(&this))
.with_args(&args);
let result = string_contains_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::Bool(false)))
));
}
#[test]
fn test_string_starts_with() {
let mut thread = create_test_thread();
let s = thread.heap_mut().alloc_string("Hello World").unwrap();
let this = EmValue::ObjectRef(s);
let prefix = thread.heap_mut().alloc_string("Hello").unwrap();
let args = [EmValue::ObjectRef(prefix)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"String",
"StartsWith",
PointerSize::Bit64,
)
.with_this(Some(&this))
.with_args(&args);
let result = string_starts_with_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::Bool(true)))
));
}
#[test]
fn test_string_ends_with() {
let mut thread = create_test_thread();
let s = thread.heap_mut().alloc_string("Hello World").unwrap();
let this = EmValue::ObjectRef(s);
let suffix = thread.heap_mut().alloc_string("World").unwrap();
let args = [EmValue::ObjectRef(suffix)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"String",
"EndsWith",
PointerSize::Bit64,
)
.with_this(Some(&this))
.with_args(&args);
let result = string_ends_with_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::Bool(true)))
));
}
#[test]
fn test_string_index_of_found() {
let mut thread = create_test_thread();
let s = thread.heap_mut().alloc_string("Hello World").unwrap();
let this = EmValue::ObjectRef(s);
let args = [EmValue::Char('W')];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"String",
"IndexOf",
PointerSize::Bit64,
)
.with_this(Some(&this))
.with_args(&args);
let result = string_index_of_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(6)))
));
}
#[test]
fn test_string_index_of_not_found() {
let mut thread = create_test_thread();
let s = thread.heap_mut().alloc_string("Hello").unwrap();
let this = EmValue::ObjectRef(s);
let args = [EmValue::Char('Z')];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"String",
"IndexOf",
PointerSize::Bit64,
)
.with_this(Some(&this))
.with_args(&args);
let result = string_index_of_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(-1)))
));
}
#[test]
fn test_string_pad_left() {
let mut thread = create_test_thread();
let s = thread.heap_mut().alloc_string("42").unwrap();
let this = EmValue::ObjectRef(s);
let args = [EmValue::I32(5), EmValue::Char('0')];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"String",
"PadLeft",
PointerSize::Bit64,
)
.with_this(Some(&this))
.with_args(&args);
let result = string_pad_left_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
assert_eq!(&*thread.heap().get_string(r).unwrap(), "00042");
} else {
panic!("Expected Bypass with ObjectRef");
}
}
#[test]
fn test_string_pad_right() {
let mut thread = create_test_thread();
let s = thread.heap_mut().alloc_string("hi").unwrap();
let this = EmValue::ObjectRef(s);
let args = [EmValue::I32(5)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"String",
"PadRight",
PointerSize::Bit64,
)
.with_this(Some(&this))
.with_args(&args);
let result = string_pad_right_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
assert_eq!(&*thread.heap().get_string(r).unwrap(), "hi ");
} else {
panic!("Expected Bypass with ObjectRef");
}
}
#[test]
fn test_string_join() {
let mut thread = create_test_thread();
let sep = thread.heap_mut().alloc_string(", ").unwrap();
let s1 = thread.heap_mut().alloc_string("a").unwrap();
let s2 = thread.heap_mut().alloc_string("b").unwrap();
let s3 = thread.heap_mut().alloc_string("c").unwrap();
let arr = thread
.heap_mut()
.alloc_array_with_values(
CilFlavor::String,
vec![
EmValue::ObjectRef(s1),
EmValue::ObjectRef(s2),
EmValue::ObjectRef(s3),
],
)
.unwrap();
let args = [EmValue::ObjectRef(sep), EmValue::ObjectRef(arr)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"String",
"Join",
PointerSize::Bit64,
)
.with_args(&args);
let result = string_join_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
assert_eq!(&*thread.heap().get_string(r).unwrap(), "a, b, c");
} else {
panic!("Expected Bypass with ObjectRef");
}
}
#[test]
fn test_string_format_basic() {
let mut thread = create_test_thread();
let fmt = thread.heap_mut().alloc_string("Hello {0}!").unwrap();
let name = thread.heap_mut().alloc_string("World").unwrap();
let args = [EmValue::ObjectRef(fmt), EmValue::ObjectRef(name)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"String",
"Format",
PointerSize::Bit64,
)
.with_args(&args);
let result = string_format_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
assert_eq!(&*thread.heap().get_string(r).unwrap(), "Hello World!");
} else {
panic!("Expected Bypass with ObjectRef");
}
}
#[test]
fn test_string_is_null_or_empty_hook() {
let mut thread = create_test_thread();
let empty = thread.heap_mut().alloc_string("").unwrap();
let non_empty = thread.heap_mut().alloc_string("test").unwrap();
let args = [EmValue::Null];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"String",
"IsNullOrEmpty",
PointerSize::Bit64,
)
.with_args(&args);
let result = string_is_null_or_empty_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::Bool(true)))
));
let args = [EmValue::ObjectRef(empty)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"String",
"IsNullOrEmpty",
PointerSize::Bit64,
)
.with_args(&args);
let result = string_is_null_or_empty_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::Bool(true)))
));
let args = [EmValue::ObjectRef(non_empty)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"String",
"IsNullOrEmpty",
PointerSize::Bit64,
)
.with_args(&args);
let result = string_is_null_or_empty_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::Bool(false)))
));
}
}