use log::debug;
use crate::{
emulation::{
runtime::hook::{Hook, HookContext, HookManager, PreHookResult},
thread::EmulationThread,
tokens::io_fields,
EmValue, HeapRef,
},
utils::apply_crypto_transform,
Result,
};
pub fn register(manager: &HookManager) -> Result<()> {
manager.register(
Hook::new("System.IO.FileStream..ctor")
.match_name("System.IO", "FileStream", ".ctor")
.pre(filestream_ctor_pre),
)?;
manager.register(
Hook::new("System.IO.File.Exists")
.match_name("System.IO", "File", "Exists")
.pre(file_exists_pre),
)?;
manager.register(
Hook::new("System.IO.File.ReadAllBytes")
.match_name("System.IO", "File", "ReadAllBytes")
.pre(file_read_all_bytes_pre),
)?;
manager.register(
Hook::new("System.IO.File.ReadAllText")
.match_name("System.IO", "File", "ReadAllText")
.pre(file_read_all_text_pre),
)?;
manager.register(
Hook::new("System.IO.File.OpenRead")
.match_name("System.IO", "File", "OpenRead")
.pre(file_open_read_pre),
)?;
manager.register(
Hook::new("System.IO.File.Open")
.match_name("System.IO", "File", "Open")
.pre(file_open_pre),
)?;
manager.register(
Hook::new("System.IO.File.Create")
.match_name("System.IO", "File", "Create")
.pre(file_create_pre),
)?;
manager.register(
Hook::new("System.IO.File.WriteAllBytes")
.match_name("System.IO", "File", "WriteAllBytes")
.pre(file_noop_pre),
)?;
manager.register(
Hook::new("System.IO.File.WriteAllText")
.match_name("System.IO", "File", "WriteAllText")
.pre(file_noop_pre),
)?;
manager.register(
Hook::new("System.IO.File.Copy")
.match_name("System.IO", "File", "Copy")
.pre(file_noop_pre),
)?;
manager.register(
Hook::new("System.IO.File.Delete")
.match_name("System.IO", "File", "Delete")
.pre(file_noop_pre),
)?;
manager.register(
Hook::new("System.IO.Path.GetDirectoryName")
.match_name("System.IO", "Path", "GetDirectoryName")
.pre(path_get_directory_name_pre),
)?;
manager.register(
Hook::new("System.IO.Path.Combine")
.match_name("System.IO", "Path", "Combine")
.pre(path_combine_pre),
)?;
manager.register(
Hook::new("System.IO.Path.GetFileName")
.match_name("System.IO", "Path", "GetFileName")
.pre(path_get_file_name_pre),
)?;
manager.register(
Hook::new("System.IO.Path.GetFileNameWithoutExtension")
.match_name("System.IO", "Path", "GetFileNameWithoutExtension")
.pre(path_get_file_name_without_extension_pre),
)?;
manager.register(
Hook::new("System.IO.Path.GetExtension")
.match_name("System.IO", "Path", "GetExtension")
.pre(path_get_extension_pre),
)?;
manager.register(
Hook::new("System.IO.Path.GetFullPath")
.match_name("System.IO", "Path", "GetFullPath")
.pre(path_get_full_path_pre),
)?;
manager.register(
Hook::new("System.IO.Path.GetTempPath")
.match_name("System.IO", "Path", "GetTempPath")
.pre(path_get_temp_path_pre),
)?;
manager.register(
Hook::new("System.IO.Path.HasExtension")
.match_name("System.IO", "Path", "HasExtension")
.pre(path_has_extension_pre),
)?;
manager.register(
Hook::new("System.IO.Path.ChangeExtension")
.match_name("System.IO", "Path", "ChangeExtension")
.pre(path_change_extension_pre),
)?;
manager.register(
Hook::new("System.IO.Path.GetPathRoot")
.match_name("System.IO", "Path", "GetPathRoot")
.pre(path_get_path_root_pre),
)?;
manager.register(
Hook::new("System.IO.Path.IsPathRooted")
.match_name("System.IO", "Path", "IsPathRooted")
.pre(path_is_path_rooted_pre),
)?;
manager.register(
Hook::new("System.IO.FileInfo..ctor")
.match_name("System.IO", "FileInfo", ".ctor")
.pre(fileinfo_ctor_pre),
)?;
manager.register(
Hook::new("System.IO.FileInfo.get_Exists")
.match_name("System.IO", "FileInfo", "get_Exists")
.pre(fileinfo_get_exists_pre),
)?;
manager.register(
Hook::new("System.IO.FileInfo.get_Length")
.match_name("System.IO", "FileInfo", "get_Length")
.pre(fileinfo_get_length_pre),
)?;
manager.register(
Hook::new("System.IO.FileInfo.get_FullName")
.match_name("System.IO", "FileInfo", "get_FullName")
.pre(fileinfo_get_fullname_pre),
)?;
manager.register(
Hook::new("System.IO.FileInfo.get_Name")
.match_name("System.IO", "FileInfo", "get_Name")
.pre(fileinfo_get_name_pre),
)?;
manager.register(
Hook::new("System.IO.FileInfo.get_Extension")
.match_name("System.IO", "FileInfo", "get_Extension")
.pre(fileinfo_get_extension_pre),
)?;
manager.register(
Hook::new("System.IO.FileInfo.get_DirectoryName")
.match_name("System.IO", "FileInfo", "get_DirectoryName")
.pre(fileinfo_get_directory_name_pre),
)?;
manager.register(
Hook::new("System.IO.FileInfo.OpenRead")
.match_name("System.IO", "FileInfo", "OpenRead")
.pre(fileinfo_open_read_pre),
)?;
manager.register(
Hook::new("System.IO.StreamReader..ctor")
.match_name("System.IO", "StreamReader", ".ctor")
.pre(streamreader_ctor_pre),
)?;
manager.register(
Hook::new("System.IO.StreamReader.ReadToEnd")
.match_name("System.IO", "StreamReader", "ReadToEnd")
.pre(streamreader_read_to_end_pre),
)?;
manager.register(
Hook::new("System.IO.StreamReader.ReadLine")
.match_name("System.IO", "StreamReader", "ReadLine")
.pre(streamreader_read_line_pre),
)?;
manager.register(
Hook::new("System.IO.StreamReader.Read")
.match_name("System.IO", "StreamReader", "Read")
.pre(streamreader_read_pre),
)?;
manager.register(
Hook::new("System.IO.StreamReader.Peek")
.match_name("System.IO", "StreamReader", "Peek")
.pre(streamreader_peek_pre),
)?;
manager.register(
Hook::new("System.IO.StreamReader.get_EndOfStream")
.match_name("System.IO", "StreamReader", "get_EndOfStream")
.pre(streamreader_get_end_of_stream_pre),
)?;
manager.register(
Hook::new("System.IO.StreamReader.Close")
.match_name("System.IO", "StreamReader", "Close")
.pre(streamreader_noop_pre),
)?;
manager.register(
Hook::new("System.IO.StreamReader.Dispose")
.match_name("System.IO", "StreamReader", "Dispose")
.pre(streamreader_noop_pre),
)?;
Ok(())
}
fn extract_path_arg(ctx: &HookContext<'_>, thread: &EmulationThread) -> Option<String> {
match ctx.args.first() {
Some(EmValue::ObjectRef(r)) => thread.heap().get_string(*r).ok().map(|s| s.to_string()),
_ => None,
}
}
fn extract_nth_string_arg(
ctx: &HookContext<'_>,
thread: &EmulationThread,
n: usize,
) -> Option<String> {
match ctx.args.get(n) {
Some(EmValue::ObjectRef(r)) => thread.heap().get_string(*r).ok().map(|s| s.to_string()),
_ => None,
}
}
fn path_filename(path: &str) -> &str {
path.rfind(['\\', '/']).map_or(path, |pos| &path[pos + 1..])
}
fn is_rooted(path: &str) -> bool {
let bytes = path.as_bytes();
if bytes.is_empty() {
return false;
}
bytes[0] == b'\\' || bytes[0] == b'/' || (bytes.len() >= 2 && bytes[1] == b':')
}
fn alloc_string_result(thread: &mut EmulationThread, s: &str) -> PreHookResult {
match thread.heap_mut().alloc_string(s) {
Ok(r) => PreHookResult::Bypass(Some(EmValue::ObjectRef(r))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn get_fileinfo_path(this: Option<&EmValue>, thread: &EmulationThread) -> Option<String> {
let this_ref = match this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return None,
};
let field_value = thread
.heap()
.get_field(this_ref, io_fields::FILEINFO_PATH)
.ok()?;
match field_value {
EmValue::ObjectRef(str_ref) => thread
.heap()
.get_string(str_ref)
.ok()
.map(|s| s.to_string()),
_ => None,
}
}
fn get_streamreader_stream(this: Option<&EmValue>, thread: &EmulationThread) -> Option<HeapRef> {
let this_ref = match this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return None,
};
let field_value = thread
.heap()
.get_field(this_ref, io_fields::STREAMREADER_STREAM)
.ok()?;
match field_value {
EmValue::ObjectRef(stream_ref) => Some(stream_ref),
_ => None,
}
}
fn filestream_ctor_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let path = match extract_path_arg(ctx, thread) {
Some(p) => p,
None => return PreHookResult::throw_file_not_found("<null>"),
};
let data = match thread.virtual_fs().get(&path) {
Some(cow) => cow.data().to_vec(),
None => {
debug!("FileStream: file not found in VirtualFs: {}", path);
return PreHookResult::throw_file_not_found(&path);
}
};
debug!(
"FileStream: opened {} from VirtualFs ({} bytes)",
path,
data.len()
);
match ctx.this {
Some(EmValue::ObjectRef(stream_ref)) => {
try_hook!(thread.heap_mut().replace_with_stream(*stream_ref, data));
PreHookResult::Bypass(None)
}
_ => {
let type_token = thread.resolve_type_token("System.IO", "FileStream");
match thread.heap_mut().alloc_stream(data, type_token) {
Ok(stream_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(stream_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
}
}
fn file_exists_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(r)) = ctx.args.first() {
if let Ok(path) = thread.heap().get_string(*r) {
if thread.virtual_fs().exists(&path) {
return PreHookResult::Bypass(Some(EmValue::I32(1)));
}
}
}
PreHookResult::Bypass(Some(EmValue::I32(1)))
}
fn file_read_all_bytes_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let path = match extract_path_arg(ctx, thread) {
Some(p) => p,
None => return PreHookResult::throw_file_not_found("<null>"),
};
let data = match thread.virtual_fs().get(&path) {
Some(cow) => cow.data().to_vec(),
None => {
debug!("File.ReadAllBytes: file not found in VirtualFs: {}", path);
return PreHookResult::throw_file_not_found(&path);
}
};
debug!(
"File.ReadAllBytes: read {} from VirtualFs ({} bytes)",
path,
data.len()
);
match thread.heap_mut().alloc_byte_array(&data) {
Ok(array_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(array_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn file_read_all_text_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let path = match extract_path_arg(ctx, thread) {
Some(p) => p,
None => return PreHookResult::throw_file_not_found("<null>"),
};
let data = match thread.virtual_fs().get(&path) {
Some(cow) => cow.data().to_vec(),
None => {
debug!("File.ReadAllText: file not found in VirtualFs: {}", path);
return PreHookResult::throw_file_not_found(&path);
}
};
debug!(
"File.ReadAllText: read {} from VirtualFs ({} bytes)",
path,
data.len()
);
let text = String::from_utf8_lossy(&data);
alloc_string_result(thread, &text)
}
fn file_open_read_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let path = match extract_path_arg(ctx, thread) {
Some(p) => p,
None => return PreHookResult::throw_file_not_found("<null>"),
};
let data = match thread.virtual_fs().get(&path) {
Some(cow) => cow.data().to_vec(),
None => {
debug!("File.OpenRead: file not found in VirtualFs: {}", path);
return PreHookResult::throw_file_not_found(&path);
}
};
debug!(
"File.OpenRead: opened {} from VirtualFs ({} bytes)",
path,
data.len()
);
let type_token = thread.resolve_type_token("System.IO", "FileStream");
match thread.heap_mut().alloc_stream(data, type_token) {
Ok(stream_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(stream_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn file_open_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
file_open_read_pre(ctx, thread)
}
fn file_create_pre(_ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let type_token = thread.resolve_type_token("System.IO", "FileStream");
match thread.heap_mut().alloc_stream(Vec::new(), type_token) {
Ok(stream_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(stream_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn file_noop_pre(_ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
PreHookResult::Bypass(None)
}
fn path_get_directory_name_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let path = match extract_path_arg(ctx, thread) {
Some(p) => p,
None => return PreHookResult::Bypass(Some(EmValue::Null)),
};
let dir = if let Some(pos) = path.rfind(['\\', '/']) {
&path[..pos]
} else {
""
};
alloc_string_result(thread, dir)
}
fn path_combine_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let mut parts = Vec::new();
for arg in ctx.args.iter() {
if let EmValue::ObjectRef(r) = arg {
if let Ok(s) = thread.heap().get_string(*r) {
parts.push(s.to_string());
}
}
}
let result = parts.iter().fold(String::new(), |acc, part| {
if is_rooted(part) || acc.is_empty() {
part.clone()
} else if acc.ends_with('\\') || acc.ends_with('/') {
format!("{acc}{part}")
} else {
format!("{acc}\\{part}")
}
});
alloc_string_result(thread, &result)
}
fn path_get_file_name_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let path = match extract_path_arg(ctx, thread) {
Some(p) => p,
None => return PreHookResult::Bypass(Some(EmValue::Null)),
};
alloc_string_result(thread, path_filename(&path))
}
fn path_get_file_name_without_extension_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let path = match extract_path_arg(ctx, thread) {
Some(p) => p,
None => return PreHookResult::Bypass(Some(EmValue::Null)),
};
let filename = path_filename(&path);
let without_ext = if let Some(pos) = filename.rfind('.') {
&filename[..pos]
} else {
filename
};
alloc_string_result(thread, without_ext)
}
fn path_get_extension_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let path = match extract_path_arg(ctx, thread) {
Some(p) => p,
None => return PreHookResult::Bypass(Some(EmValue::Null)),
};
let filename = path_filename(&path);
let ext = if let Some(pos) = filename.rfind('.') {
&filename[pos..]
} else {
""
};
alloc_string_result(thread, ext)
}
fn path_get_full_path_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let path = match extract_path_arg(ctx, thread) {
Some(p) => p,
None => return PreHookResult::Bypass(Some(EmValue::Null)),
};
alloc_string_result(thread, &path)
}
fn path_get_temp_path_pre(_ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let base = &thread.config().environment.assembly_location_base;
let temp = format!("{base}\\");
alloc_string_result(thread, &temp)
}
fn path_has_extension_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let path = match extract_path_arg(ctx, thread) {
Some(p) => p,
None => return PreHookResult::Bypass(Some(EmValue::I32(0))),
};
let filename = path_filename(&path);
let has = filename.contains('.');
PreHookResult::Bypass(Some(EmValue::I32(has as i32)))
}
fn path_change_extension_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let path = match extract_path_arg(ctx, thread) {
Some(p) => p,
None => return PreHookResult::Bypass(Some(EmValue::Null)),
};
let new_ext = extract_nth_string_arg(ctx, thread, 1).unwrap_or_default();
let base = if let Some(pos) = path.rfind('.') {
let last_sep = path.rfind(['\\', '/']).unwrap_or(0);
if pos > last_sep {
&path[..pos]
} else {
&path
}
} else {
&path
};
let result = if new_ext.is_empty() {
base.to_string()
} else if new_ext.starts_with('.') {
format!("{base}{new_ext}")
} else {
format!("{base}.{new_ext}")
};
alloc_string_result(thread, &result)
}
fn path_get_path_root_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let path = match extract_path_arg(ctx, thread) {
Some(p) => p,
None => return PreHookResult::Bypass(Some(EmValue::Null)),
};
let bytes = path.as_bytes();
let root = if bytes.len() >= 3 && bytes[1] == b':' && (bytes[2] == b'\\' || bytes[2] == b'/') {
&path[..3]
} else if bytes.len() >= 2 && bytes[1] == b':' {
&path[..2]
} else if !bytes.is_empty() && (bytes[0] == b'\\' || bytes[0] == b'/') {
&path[..1]
} else {
""
};
alloc_string_result(thread, root)
}
fn path_is_path_rooted_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let path = match extract_path_arg(ctx, thread) {
Some(p) => p,
None => return PreHookResult::Bypass(Some(EmValue::I32(0))),
};
PreHookResult::Bypass(Some(EmValue::I32(is_rooted(&path) as i32)))
}
fn fileinfo_ctor_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let path_ref = match ctx.args.first() {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::Bypass(None),
};
if let Some(EmValue::ObjectRef(this_ref)) = ctx.this {
try_hook!(thread.heap_mut().set_field(
*this_ref,
io_fields::FILEINFO_PATH,
EmValue::ObjectRef(path_ref),
));
}
PreHookResult::Bypass(None)
}
fn fileinfo_get_exists_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let path = match get_fileinfo_path(ctx.this, thread) {
Some(p) => p,
None => return PreHookResult::Bypass(Some(EmValue::I32(1))),
};
let _exists = thread.virtual_fs().exists(&path);
PreHookResult::Bypass(Some(EmValue::I32(1)))
}
fn fileinfo_get_length_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let path = match get_fileinfo_path(ctx.this, thread) {
Some(p) => p,
None => return PreHookResult::Bypass(Some(EmValue::I64(0))),
};
let len = thread
.virtual_fs()
.get(&path)
.map_or(0i64, |cow| cow.data().len() as i64);
PreHookResult::Bypass(Some(EmValue::I64(len)))
}
fn fileinfo_get_fullname_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(this_ref)) = ctx.this {
if let Ok(field) = thread.heap().get_field(*this_ref, io_fields::FILEINFO_PATH) {
return PreHookResult::Bypass(Some(field));
}
}
PreHookResult::Bypass(Some(EmValue::Null))
}
fn fileinfo_get_name_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let path = match get_fileinfo_path(ctx.this, thread) {
Some(p) => p,
None => return PreHookResult::Bypass(Some(EmValue::Null)),
};
alloc_string_result(thread, path_filename(&path))
}
fn fileinfo_get_extension_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let path = match get_fileinfo_path(ctx.this, thread) {
Some(p) => p,
None => return PreHookResult::Bypass(Some(EmValue::Null)),
};
let filename = path_filename(&path);
let ext = filename.rfind('.').map_or("", |pos| &filename[pos..]);
alloc_string_result(thread, ext)
}
fn fileinfo_get_directory_name_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let path = match get_fileinfo_path(ctx.this, thread) {
Some(p) => p,
None => return PreHookResult::Bypass(Some(EmValue::Null)),
};
let dir = if let Some(pos) = path.rfind(['\\', '/']) {
&path[..pos]
} else {
""
};
alloc_string_result(thread, dir)
}
fn fileinfo_open_read_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let path = match get_fileinfo_path(ctx.this, thread) {
Some(p) => p,
None => return PreHookResult::throw_file_not_found("<FileInfo>"),
};
let data = match thread.virtual_fs().get(&path) {
Some(cow) => cow.data().to_vec(),
None => {
debug!("FileInfo.OpenRead: file not found in VirtualFs: {}", path);
return PreHookResult::throw_file_not_found(&path);
}
};
let type_token = thread.resolve_type_token("System.IO", "FileStream");
match thread.heap_mut().alloc_stream(data, type_token) {
Ok(stream_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(stream_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn streamreader_ctor_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let this_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::Bypass(None),
};
let stream_ref = match ctx.args.first() {
Some(EmValue::ObjectRef(r)) => {
if let Ok(path) = thread.heap().get_string(*r) {
let path = path.to_string();
let data = match thread.virtual_fs().get(&path) {
Some(cow) => cow.data().to_vec(),
None => {
debug!("StreamReader: file not found in VirtualFs: {}", path);
return PreHookResult::throw_file_not_found(&path);
}
};
let type_token = thread.resolve_type_token("System.IO", "FileStream");
match thread.heap_mut().alloc_stream(data, type_token) {
Ok(sr) => sr,
Err(_) => return PreHookResult::Bypass(None),
}
} else {
*r
}
}
_ => return PreHookResult::Bypass(None),
};
try_hook!(thread.heap_mut().set_field(
this_ref,
io_fields::STREAMREADER_STREAM,
EmValue::ObjectRef(stream_ref),
));
PreHookResult::Bypass(None)
}
fn streamreader_read_to_end_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let stream_ref = match get_streamreader_stream(ctx.this, thread) {
Some(r) => r,
None => return PreHookResult::Bypass(Some(EmValue::Null)),
};
if let Some(text) = try_hook!(thread.heap().with_stream(stream_ref, |data, position| {
let remaining = if *position < data.len() {
&data[*position..]
} else {
&[]
};
let text = String::from_utf8_lossy(remaining).into_owned();
*position = data.len(); text
})) {
return alloc_string_result(thread, &text);
}
if let Some((underlying_stream, transform_ref, mode)) =
try_hook!(thread.heap().get_crypto_stream_info(stream_ref))
{
if mode != 0 {
return PreHookResult::Bypass(Some(EmValue::Null));
}
let decrypted = if let Some((data, pos)) =
try_hook!(thread.heap().get_crypto_stream_transformed(stream_ref))
{
if pos < data.len() {
data[pos..].to_vec()
} else {
vec![]
}
} else {
let Some((stream_data, underlying_pos)) =
try_hook!(thread.heap().get_stream_data(underlying_stream))
else {
return PreHookResult::Bypass(Some(EmValue::Null));
};
let effective_data = if underlying_pos < stream_data.len() {
&stream_data[underlying_pos..]
} else {
&[]
};
let transformed = if let Some((algorithm, key, iv, is_encryptor, cmode, padding)) =
try_hook!(thread.heap().get_crypto_transform_info(transform_ref))
{
apply_crypto_transform(
&algorithm,
&key,
&iv,
is_encryptor,
effective_data,
cmode,
padding,
)
.unwrap_or_else(|| effective_data.to_vec())
} else {
effective_data.to_vec()
};
let _ = thread
.heap()
.set_crypto_stream_transformed(stream_ref, transformed.clone());
transformed
};
let text = String::from_utf8_lossy(&decrypted).into_owned();
return alloc_string_result(thread, &text);
}
PreHookResult::Bypass(Some(EmValue::Null))
}
fn streamreader_read_line_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let stream_ref = match get_streamreader_stream(ctx.this, thread) {
Some(r) => r,
None => return PreHookResult::Bypass(Some(EmValue::Null)),
};
let line_text = match try_hook!(thread.heap().with_stream(stream_ref, |data, position| {
if *position >= data.len() {
return None; }
let remaining = &data[*position..];
let (line_bytes, advance) = if let Some(nl_pos) = remaining.iter().position(|&b| b == b'\n')
{
let line_end = if nl_pos > 0 && remaining[nl_pos - 1] == b'\r' {
nl_pos - 1 } else {
nl_pos
};
(&remaining[..line_end], nl_pos + 1) } else {
(remaining, remaining.len())
};
*position += advance;
Some(String::from_utf8_lossy(line_bytes).into_owned())
})) {
Some(opt) => opt,
None => return PreHookResult::Bypass(Some(EmValue::Null)),
};
match line_text {
Some(text) => alloc_string_result(thread, &text),
None => PreHookResult::Bypass(Some(EmValue::Null)), }
}
fn streamreader_read_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let stream_ref = match get_streamreader_stream(ctx.this, thread) {
Some(r) => r,
None => return PreHookResult::Bypass(Some(EmValue::I32(-1))),
};
match try_hook!(thread.heap().stream_read_byte(stream_ref)) {
Some(byte) => PreHookResult::Bypass(Some(EmValue::I32(byte as i32))),
None => PreHookResult::Bypass(Some(EmValue::I32(-1))),
}
}
fn streamreader_peek_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let stream_ref = match get_streamreader_stream(ctx.this, thread) {
Some(r) => r,
None => return PreHookResult::Bypass(Some(EmValue::I32(-1))),
};
let value = try_hook!(thread.heap().with_stream(stream_ref, |data, position| {
if *position < data.len() {
data[*position] as i32
} else {
-1
}
}));
PreHookResult::Bypass(Some(EmValue::I32(value.unwrap_or(-1))))
}
fn streamreader_get_end_of_stream_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let stream_ref = match get_streamreader_stream(ctx.this, thread) {
Some(r) => r,
None => return PreHookResult::Bypass(Some(EmValue::I32(1))),
};
let len = match try_hook!(thread.heap().stream_len(stream_ref)) {
Some(l) => l,
None => return PreHookResult::Bypass(Some(EmValue::I32(1))),
};
let pos = match try_hook!(thread.heap().stream_position(stream_ref)) {
Some(p) => p,
None => return PreHookResult::Bypass(Some(EmValue::I32(1))),
};
PreHookResult::Bypass(Some(EmValue::I32((pos >= len) as i32)))
}
fn streamreader_noop_pre(_ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
PreHookResult::Bypass(None)
}
#[cfg(test)]
mod tests {
use std::sync::{Arc, RwLock};
use crate::{
emulation::{
filesystem::VirtualFs,
runtime::{
hook::{HookContext, HookManager, PreHookResult},
RuntimeState,
},
AddressSpace, CaptureContext, EmValue, EmulationConfig, EmulationThread,
SharedFakeObjects, ThreadContext, ThreadId,
},
metadata::{token::Token, typesystem::PointerSize},
test::emulation::create_test_thread,
};
use super::*;
fn create_thread_with_vfs() -> EmulationThread {
let mut vfs = VirtualFs::new();
vfs.map_data("test.exe", vec![0x4D, 0x5A, 0x90, 0x00]);
let address_space = Arc::new(AddressSpace::new());
let fake_objects = SharedFakeObjects::new(address_space.managed_heap());
let ctx = Arc::new(ThreadContext::new(
address_space,
Arc::new(RwLock::new(RuntimeState::new())),
Arc::new(CaptureContext::new()),
Arc::new(EmulationConfig::default()),
None,
fake_objects,
Arc::new(vfs),
));
EmulationThread::new(ThreadId::MAIN, ctx)
}
#[test]
fn test_register_hooks() {
let manager = HookManager::new();
register(&manager).unwrap();
assert_eq!(manager.len(), 38);
}
#[test]
fn test_filestream_ctor_found() {
let mut thread = create_thread_with_vfs();
let path_ref = thread.heap_mut().alloc_string("test.exe").unwrap();
let this_ref = thread
.heap_mut()
.alloc_object(Token::new(0x02000001))
.unwrap();
let args = [EmValue::ObjectRef(path_ref), EmValue::I32(3)]; let this = EmValue::ObjectRef(this_ref);
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"FileStream",
".ctor",
PointerSize::Bit64,
)
.with_args(&args)
.with_this(Some(&this));
let result = filestream_ctor_pre(&ctx, &mut thread);
assert!(matches!(result, PreHookResult::Bypass(None)));
let (data, _pos) = thread.heap().get_stream_data(this_ref).unwrap().unwrap();
assert_eq!(data, vec![0x4D, 0x5A, 0x90, 0x00]);
}
#[test]
fn test_filestream_ctor_not_found() {
let mut thread = create_thread_with_vfs();
let path_ref = thread.heap_mut().alloc_string("nonexistent.dll").unwrap();
let args = [EmValue::ObjectRef(path_ref), EmValue::I32(3)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"FileStream",
".ctor",
PointerSize::Bit64,
)
.with_args(&args);
let result = filestream_ctor_pre(&ctx, &mut thread);
assert!(matches!(result, PreHookResult::Throw { .. }));
}
#[test]
fn test_file_read_all_bytes() {
let mut thread = create_thread_with_vfs();
let path_ref = thread.heap_mut().alloc_string("test.exe").unwrap();
let args = [EmValue::ObjectRef(path_ref)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"File",
"ReadAllBytes",
PointerSize::Bit64,
)
.with_args(&args);
let result = file_read_all_bytes_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(array_ref))) = result {
let bytes = thread.heap().get_byte_array(array_ref).unwrap().unwrap();
assert_eq!(bytes, vec![0x4D, 0x5A, 0x90, 0x00]);
} else {
panic!("Expected byte array result");
}
}
#[test]
fn test_file_read_all_text() {
let mut vfs = VirtualFs::new();
vfs.map_data("hello.txt", b"Hello, World!".to_vec());
let address_space = Arc::new(AddressSpace::new());
let fake_objects = SharedFakeObjects::new(address_space.managed_heap());
let ctx = Arc::new(ThreadContext::new(
address_space,
Arc::new(RwLock::new(RuntimeState::new())),
Arc::new(CaptureContext::new()),
Arc::new(EmulationConfig::default()),
None,
fake_objects,
Arc::new(vfs),
));
let mut thread = EmulationThread::new(ThreadId::MAIN, ctx);
let path_ref = thread.heap_mut().alloc_string("hello.txt").unwrap();
let args = [EmValue::ObjectRef(path_ref)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"File",
"ReadAllText",
PointerSize::Bit64,
)
.with_args(&args);
let result = file_read_all_text_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(str_ref))) = result {
let text = thread.heap().get_string(str_ref).unwrap();
assert_eq!(text.as_ref(), "Hello, World!");
} else {
panic!("Expected string result");
}
}
#[test]
fn test_file_open_read() {
let mut thread = create_thread_with_vfs();
let path_ref = thread.heap_mut().alloc_string("test.exe").unwrap();
let args = [EmValue::ObjectRef(path_ref)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"File",
"OpenRead",
PointerSize::Bit64,
)
.with_args(&args);
let result = file_open_read_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(stream_ref))) = result {
let (data, _) = thread.heap().get_stream_data(stream_ref).unwrap().unwrap();
assert_eq!(data, vec![0x4D, 0x5A, 0x90, 0x00]);
} else {
panic!("Expected stream result");
}
}
#[test]
fn test_file_exists() {
let mut thread = create_thread_with_vfs();
let path_ref = thread.heap_mut().alloc_string("test.exe").unwrap();
let args = [EmValue::ObjectRef(path_ref)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"File",
"Exists",
PointerSize::Bit64,
)
.with_args(&args);
let result = file_exists_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(1)))
));
}
#[test]
fn test_file_create_returns_empty_stream() {
let mut thread = create_test_thread();
let path_ref = thread.heap_mut().alloc_string("new.txt").unwrap();
let args = [EmValue::ObjectRef(path_ref)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"File",
"Create",
PointerSize::Bit64,
)
.with_args(&args);
let result = file_create_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(stream_ref))) = result {
let (data, pos) = thread.heap().get_stream_data(stream_ref).unwrap().unwrap();
assert!(data.is_empty());
assert_eq!(pos, 0);
} else {
panic!("Expected stream result");
}
}
#[test]
fn test_file_noop_methods() {
let mut thread = create_test_thread();
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"File",
"Delete",
PointerSize::Bit64,
);
let result = file_noop_pre(&ctx, &mut thread);
assert!(matches!(result, PreHookResult::Bypass(None)));
}
#[test]
fn test_path_get_directory_name() {
let mut thread = create_test_thread();
let path_ref = thread
.heap_mut()
.alloc_string(r"C:\Users\test\file.exe")
.unwrap();
let args = [EmValue::ObjectRef(path_ref)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"Path",
"GetDirectoryName",
PointerSize::Bit64,
)
.with_args(&args);
let result = path_get_directory_name_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(str_ref))) = result {
let dir = thread.heap().get_string(str_ref).unwrap();
assert_eq!(dir.as_ref(), r"C:\Users\test");
} else {
panic!("Expected string result");
}
}
#[test]
fn test_path_combine() {
let mut thread = create_test_thread();
let a = thread.heap_mut().alloc_string(r"C:\Users").unwrap();
let b = thread.heap_mut().alloc_string("test").unwrap();
let args = [EmValue::ObjectRef(a), EmValue::ObjectRef(b)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"Path",
"Combine",
PointerSize::Bit64,
)
.with_args(&args);
let result = path_combine_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(), r"C:\Users\test");
} else {
panic!("Expected string result");
}
}
#[test]
fn test_path_combine_rooted_resets() {
let mut thread = create_test_thread();
let a = thread.heap_mut().alloc_string(r"C:\Users").unwrap();
let b = thread.heap_mut().alloc_string(r"D:\Other").unwrap();
let args = [EmValue::ObjectRef(a), EmValue::ObjectRef(b)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"Path",
"Combine",
PointerSize::Bit64,
)
.with_args(&args);
let result = path_combine_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(), r"D:\Other");
} else {
panic!("Expected string result");
}
}
#[test]
fn test_path_get_file_name() {
let mut thread = create_test_thread();
let path_ref = thread
.heap_mut()
.alloc_string(r"C:\Users\test\file.exe")
.unwrap();
let args = [EmValue::ObjectRef(path_ref)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"Path",
"GetFileName",
PointerSize::Bit64,
)
.with_args(&args);
let result = path_get_file_name_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(), "file.exe");
} else {
panic!("Expected string result");
}
}
#[test]
fn test_path_get_extension() {
let mut thread = create_test_thread();
let path_ref = thread.heap_mut().alloc_string("file.exe").unwrap();
let args = [EmValue::ObjectRef(path_ref)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"Path",
"GetExtension",
PointerSize::Bit64,
)
.with_args(&args);
let result = path_get_extension_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(), ".exe");
} else {
panic!("Expected string result");
}
}
#[test]
fn test_path_get_file_name_without_extension() {
let mut thread = create_test_thread();
let path_ref = thread.heap_mut().alloc_string(r"C:\dir\mylib.dll").unwrap();
let args = [EmValue::ObjectRef(path_ref)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"Path",
"GetFileNameWithoutExtension",
PointerSize::Bit64,
)
.with_args(&args);
let result = path_get_file_name_without_extension_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(), "mylib");
} else {
panic!("Expected string result");
}
}
#[test]
fn test_path_is_path_rooted() {
let mut thread = create_test_thread();
let rooted_ref = thread.heap_mut().alloc_string(r"C:\test").unwrap();
let args = [EmValue::ObjectRef(rooted_ref)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"Path",
"IsPathRooted",
PointerSize::Bit64,
)
.with_args(&args);
let result = path_is_path_rooted_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(1)))
));
let rel_ref = thread.heap_mut().alloc_string("relative/path").unwrap();
let args2 = [EmValue::ObjectRef(rel_ref)];
let ctx2 = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"Path",
"IsPathRooted",
PointerSize::Bit64,
)
.with_args(&args2);
let result2 = path_is_path_rooted_pre(&ctx2, &mut thread);
assert!(matches!(
result2,
PreHookResult::Bypass(Some(EmValue::I32(0)))
));
}
#[test]
fn test_path_get_temp_path() {
let mut thread = create_test_thread();
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"Path",
"GetTempPath",
PointerSize::Bit64,
);
let result = path_get_temp_path_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
let s = thread.heap().get_string(r).unwrap();
assert!(s.ends_with('\\'));
} else {
panic!("Expected string result");
}
}
#[test]
fn test_path_change_extension() {
let mut thread = create_test_thread();
let path_ref = thread.heap_mut().alloc_string("file.txt").unwrap();
let ext_ref = thread.heap_mut().alloc_string(".log").unwrap();
let args = [EmValue::ObjectRef(path_ref), EmValue::ObjectRef(ext_ref)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"Path",
"ChangeExtension",
PointerSize::Bit64,
)
.with_args(&args);
let result = path_change_extension_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(), "file.log");
} else {
panic!("Expected string result");
}
}
#[test]
fn test_path_get_path_root() {
let mut thread = create_test_thread();
let path_ref = thread
.heap_mut()
.alloc_string(r"C:\Users\test\file.exe")
.unwrap();
let args = [EmValue::ObjectRef(path_ref)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"Path",
"GetPathRoot",
PointerSize::Bit64,
)
.with_args(&args);
let result = path_get_path_root_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(), r"C:\");
} else {
panic!("Expected string result");
}
}
#[test]
fn test_filestream_ctor_case_insensitive() {
let mut thread = create_thread_with_vfs();
let path_ref = thread.heap_mut().alloc_string("TEST.EXE").unwrap();
let this_ref = thread
.heap_mut()
.alloc_object(Token::new(0x02000001))
.unwrap();
let args = [EmValue::ObjectRef(path_ref), EmValue::I32(3)];
let this = EmValue::ObjectRef(this_ref);
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"FileStream",
".ctor",
PointerSize::Bit64,
)
.with_args(&args)
.with_this(Some(&this));
let result = filestream_ctor_pre(&ctx, &mut thread);
assert!(matches!(result, PreHookResult::Bypass(None)));
}
#[test]
fn test_filestream_ctor_full_path_match() {
let mut thread = create_thread_with_vfs();
let path_ref = thread
.heap_mut()
.alloc_string(r"C:\Program Files\test.exe")
.unwrap();
let this_ref = thread
.heap_mut()
.alloc_object(Token::new(0x02000001))
.unwrap();
let args = [EmValue::ObjectRef(path_ref), EmValue::I32(3)];
let this = EmValue::ObjectRef(this_ref);
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"FileStream",
".ctor",
PointerSize::Bit64,
)
.with_args(&args)
.with_this(Some(&this));
let result = filestream_ctor_pre(&ctx, &mut thread);
assert!(matches!(result, PreHookResult::Bypass(None)));
}
#[test]
fn test_fileinfo_ctor_and_properties() {
let mut thread = create_thread_with_vfs();
let path_ref = thread.heap_mut().alloc_string(r"C:\temp\test.exe").unwrap();
let this_ref = thread
.heap_mut()
.alloc_object(Token::new(0x02000001))
.unwrap();
let args = [EmValue::ObjectRef(path_ref)];
let this = EmValue::ObjectRef(this_ref);
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"FileInfo",
".ctor",
PointerSize::Bit64,
)
.with_args(&args)
.with_this(Some(&this));
let result = fileinfo_ctor_pre(&ctx, &mut thread);
assert!(matches!(result, PreHookResult::Bypass(None)));
let ctx_name = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"FileInfo",
"get_Name",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = fileinfo_get_name_pre(&ctx_name, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
let s = thread.heap().get_string(r).unwrap();
assert_eq!(s.as_ref(), "test.exe");
} else {
panic!("Expected name string");
}
let ctx_ext = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"FileInfo",
"get_Extension",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = fileinfo_get_extension_pre(&ctx_ext, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
let s = thread.heap().get_string(r).unwrap();
assert_eq!(s.as_ref(), ".exe");
} else {
panic!("Expected extension string");
}
let ctx_dir = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"FileInfo",
"get_DirectoryName",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = fileinfo_get_directory_name_pre(&ctx_dir, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
let s = thread.heap().get_string(r).unwrap();
assert_eq!(s.as_ref(), r"C:\temp");
} else {
panic!("Expected directory string");
}
let ctx_len = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"FileInfo",
"get_Length",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = fileinfo_get_length_pre(&ctx_len, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I64(4)))
));
}
#[test]
fn test_streamreader_read_to_end() {
let mut thread = create_test_thread();
let stream_ref = thread
.heap_mut()
.alloc_stream(b"Hello, World!".to_vec(), None)
.unwrap();
let this_ref = thread
.heap_mut()
.alloc_object(Token::new(0x02000001))
.unwrap();
thread
.heap_mut()
.set_field(
this_ref,
io_fields::STREAMREADER_STREAM,
EmValue::ObjectRef(stream_ref),
)
.unwrap();
let this = EmValue::ObjectRef(this_ref);
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"StreamReader",
"ReadToEnd",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = streamreader_read_to_end_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(), "Hello, World!");
} else {
panic!("Expected string result");
}
let ctx_eof = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"StreamReader",
"get_EndOfStream",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = streamreader_get_end_of_stream_pre(&ctx_eof, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(1)))
));
}
#[test]
fn test_streamreader_read_line() {
let mut thread = create_test_thread();
let stream_ref = thread
.heap_mut()
.alloc_stream(b"line1\r\nline2\nline3".to_vec(), None)
.unwrap();
let this_ref = thread
.heap_mut()
.alloc_object(Token::new(0x02000001))
.unwrap();
thread
.heap_mut()
.set_field(
this_ref,
io_fields::STREAMREADER_STREAM,
EmValue::ObjectRef(stream_ref),
)
.unwrap();
let this = EmValue::ObjectRef(this_ref);
let ctx1 = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"StreamReader",
"ReadLine",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = streamreader_read_line_pre(&ctx1, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
assert_eq!(thread.heap().get_string(r).unwrap().as_ref(), "line1");
} else {
panic!("Expected line1");
}
let ctx2 = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"StreamReader",
"ReadLine",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = streamreader_read_line_pre(&ctx2, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
assert_eq!(thread.heap().get_string(r).unwrap().as_ref(), "line2");
} else {
panic!("Expected line2");
}
let ctx3 = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"StreamReader",
"ReadLine",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = streamreader_read_line_pre(&ctx3, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) = result {
assert_eq!(thread.heap().get_string(r).unwrap().as_ref(), "line3");
} else {
panic!("Expected line3");
}
let ctx4 = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"StreamReader",
"ReadLine",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = streamreader_read_line_pre(&ctx4, &mut thread);
assert!(matches!(result, PreHookResult::Bypass(Some(EmValue::Null))));
}
#[test]
fn test_streamreader_read_and_peek() {
let mut thread = create_test_thread();
let stream_ref = thread
.heap_mut()
.alloc_stream(b"AB".to_vec(), None)
.unwrap();
let this_ref = thread
.heap_mut()
.alloc_object(Token::new(0x02000001))
.unwrap();
thread
.heap_mut()
.set_field(
this_ref,
io_fields::STREAMREADER_STREAM,
EmValue::ObjectRef(stream_ref),
)
.unwrap();
let this = EmValue::ObjectRef(this_ref);
let ctx_peek = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"StreamReader",
"Peek",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = streamreader_peek_pre(&ctx_peek, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(65))) ));
let ctx_read = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"StreamReader",
"Read",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = streamreader_read_pre(&ctx_read, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(65)))
));
let ctx_read2 = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"StreamReader",
"Read",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = streamreader_read_pre(&ctx_read2, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(66))) ));
let ctx_read3 = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"StreamReader",
"Read",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = streamreader_read_pre(&ctx_read3, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(-1)))
));
}
}