use crate::{
emulation::{
runtime::hook::{Hook, HookContext, HookManager, PreHookResult},
thread::EmulationThread,
EmValue, HeapRef,
},
metadata::token::Token,
};
fn binary_reader_stream_field() -> Token {
Token::new(0xFFFF_0001)
}
pub fn register(manager: &mut HookManager) {
manager.register(
Hook::new("System.IO.MemoryStream..ctor")
.match_name("System.IO", "MemoryStream", ".ctor")
.pre(memory_stream_ctor_pre),
);
manager.register(
Hook::new("System.IO.MemoryStream.Read")
.match_name("System.IO", "MemoryStream", "Read")
.pre(stream_read_pre),
);
manager.register(
Hook::new("System.IO.MemoryStream.ReadByte")
.match_name("System.IO", "MemoryStream", "ReadByte")
.pre(stream_read_byte_pre),
);
manager.register(
Hook::new("System.IO.MemoryStream.Write")
.match_name("System.IO", "MemoryStream", "Write")
.pre(stream_write_pre),
);
manager.register(
Hook::new("System.IO.MemoryStream.ToArray")
.match_name("System.IO", "MemoryStream", "ToArray")
.pre(memory_stream_to_array_pre),
);
manager.register(
Hook::new("System.IO.MemoryStream.get_Length")
.match_name("System.IO", "MemoryStream", "get_Length")
.pre(stream_get_length_pre),
);
manager.register(
Hook::new("System.IO.MemoryStream.get_Position")
.match_name("System.IO", "MemoryStream", "get_Position")
.pre(stream_get_position_pre),
);
manager.register(
Hook::new("System.IO.MemoryStream.set_Position")
.match_name("System.IO", "MemoryStream", "set_Position")
.pre(stream_set_position_pre),
);
manager.register(
Hook::new("System.IO.MemoryStream.Seek")
.match_name("System.IO", "MemoryStream", "Seek")
.pre(stream_seek_pre),
);
manager.register(
Hook::new("System.IO.Stream.Read")
.match_name("System.IO", "Stream", "Read")
.pre(stream_read_pre),
);
manager.register(
Hook::new("System.IO.Stream.ReadByte")
.match_name("System.IO", "Stream", "ReadByte")
.pre(stream_read_byte_pre),
);
manager.register(
Hook::new("System.IO.Stream.get_Length")
.match_name("System.IO", "Stream", "get_Length")
.pre(stream_get_length_pre),
);
manager.register(
Hook::new("System.IO.Stream.Close")
.match_name("System.IO", "Stream", "Close")
.pre(stream_close_pre),
);
manager.register(
Hook::new("System.IO.Stream.Dispose")
.match_name("System.IO", "Stream", "Dispose")
.pre(stream_dispose_pre),
);
manager.register(
Hook::new("System.IO.BinaryReader..ctor")
.match_name("System.IO", "BinaryReader", ".ctor")
.pre(binary_reader_ctor_pre),
);
manager.register(
Hook::new("System.IO.BinaryReader.ReadByte")
.match_name("System.IO", "BinaryReader", "ReadByte")
.pre(binary_reader_read_byte_pre),
);
manager.register(
Hook::new("System.IO.BinaryReader.ReadBytes")
.match_name("System.IO", "BinaryReader", "ReadBytes")
.pre(binary_reader_read_bytes_pre),
);
manager.register(
Hook::new("System.IO.BinaryReader.ReadInt16")
.match_name("System.IO", "BinaryReader", "ReadInt16")
.pre(binary_reader_read_int16_pre),
);
manager.register(
Hook::new("System.IO.BinaryReader.ReadInt32")
.match_name("System.IO", "BinaryReader", "ReadInt32")
.pre(binary_reader_read_int32_pre),
);
manager.register(
Hook::new("System.IO.BinaryReader.ReadInt64")
.match_name("System.IO", "BinaryReader", "ReadInt64")
.pre(binary_reader_read_int64_pre),
);
manager.register(
Hook::new("System.IO.BinaryReader.ReadString")
.match_name("System.IO", "BinaryReader", "ReadString")
.pre(binary_reader_read_string_pre),
);
manager.register(
Hook::new("System.IO.BinaryReader.Close")
.match_name("System.IO", "BinaryReader", "Close")
.pre(stream_close_pre),
);
}
fn memory_stream_ctor_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let data = if let Some(EmValue::ObjectRef(array_ref)) = ctx.args.first() {
thread
.heap()
.get_byte_array(*array_ref)
.or_else(|| {
thread
.heap()
.get_array_as_bytes(*array_ref, ctx.pointer_size)
})
.unwrap_or_default()
} else {
Vec::new()
};
match ctx.this {
Some(EmValue::ObjectRef(stream_ref)) => {
thread.heap_mut().replace_with_stream(*stream_ref, data);
PreHookResult::Bypass(None) }
_ => {
match thread.heap_mut().alloc_stream(data) {
Ok(stream_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(stream_ref))),
Err(_) => PreHookResult::Bypass(Some(EmValue::Null)),
}
}
}
}
fn stream_read_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let stream_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::Bypass(Some(EmValue::I32(0))),
};
#[allow(clippy::cast_sign_loss)]
let (buffer_ref, offset, count) = match (ctx.args.first(), ctx.args.get(1), ctx.args.get(2)) {
(Some(EmValue::ObjectRef(b)), Some(EmValue::I32(o)), Some(EmValue::I32(c))) => {
(*b, *o as usize, *c as usize)
}
_ => return PreHookResult::Bypass(Some(EmValue::I32(0))),
};
let Some((data, position)) = thread.heap().get_stream_data(stream_ref) else {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
};
let available = data.len().saturating_sub(position);
let to_read = count.min(available);
for i in 0..to_read {
if let Some(&byte) = data.get(position + i) {
let _ = thread.heap_mut().set_array_element(
buffer_ref,
offset + i,
EmValue::I32(i32::from(byte)),
);
}
}
thread
.heap_mut()
.set_stream_position(stream_ref, position + to_read);
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
PreHookResult::Bypass(Some(EmValue::I32(to_read as i32)))
}
fn stream_read_byte_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let stream_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::Bypass(Some(EmValue::I32(-1))),
};
let Some((data, position)) = thread.heap().get_stream_data(stream_ref) else {
return PreHookResult::Bypass(Some(EmValue::I32(-1)));
};
if position >= data.len() {
return PreHookResult::Bypass(Some(EmValue::I32(-1))); }
let byte = data[position];
thread
.heap_mut()
.set_stream_position(stream_ref, position + 1);
PreHookResult::Bypass(Some(EmValue::I32(i32::from(byte))))
}
fn stream_write_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let stream_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::Bypass(None),
};
#[allow(clippy::cast_sign_loss)]
let (buffer_ref, offset, count) = match (ctx.args.first(), ctx.args.get(1), ctx.args.get(2)) {
(Some(EmValue::ObjectRef(b)), Some(EmValue::I32(o)), Some(EmValue::I32(c))) => {
(*b, *o as usize, *c as usize)
}
_ => return PreHookResult::Bypass(None),
};
let Some(buffer_data) = thread.heap().get_byte_array(buffer_ref) else {
return PreHookResult::Bypass(None);
};
let end = (offset + count).min(buffer_data.len());
let bytes_to_write = if offset < buffer_data.len() {
&buffer_data[offset..end]
} else {
&[]
};
thread
.heap_mut()
.write_to_stream(stream_ref, bytes_to_write);
PreHookResult::Bypass(None)
}
fn memory_stream_to_array_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let stream_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => {
match thread.heap_mut().alloc_byte_array(&[]) {
Ok(array_ref) => return PreHookResult::Bypass(Some(EmValue::ObjectRef(array_ref))),
Err(_) => return PreHookResult::Bypass(Some(EmValue::Null)),
}
}
};
let data = match thread.heap().get_stream_data(stream_ref) {
Some((data, _)) => data,
None => Vec::new(),
};
match thread.heap_mut().alloc_byte_array(&data) {
Ok(array_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(array_ref))),
Err(_) => PreHookResult::Bypass(Some(EmValue::Null)),
}
}
fn stream_get_length_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let stream_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::Bypass(Some(EmValue::I64(0))),
};
let length = match thread.heap().get_stream_data(stream_ref) {
Some((data, _)) => data.len(),
None => 0,
};
#[allow(clippy::cast_possible_wrap)]
PreHookResult::Bypass(Some(EmValue::I64(length as i64)))
}
fn stream_get_position_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let stream_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::Bypass(Some(EmValue::I64(0))),
};
let position = match thread.heap().get_stream_data(stream_ref) {
Some((_, pos)) => pos,
None => 0,
};
#[allow(clippy::cast_possible_wrap)]
PreHookResult::Bypass(Some(EmValue::I64(position as i64)))
}
fn stream_set_position_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let stream_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::Bypass(None),
};
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let new_pos = match ctx.args.first() {
Some(EmValue::I64(v)) => *v as usize,
Some(EmValue::I32(v)) => *v as usize,
_ => return PreHookResult::Bypass(None),
};
thread.heap_mut().set_stream_position(stream_ref, new_pos);
PreHookResult::Bypass(None)
}
fn stream_seek_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let stream_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::Bypass(Some(EmValue::I64(0))),
};
let offset = match ctx.args.first() {
Some(EmValue::I64(v)) => *v,
Some(EmValue::I32(v)) => i64::from(*v),
_ => 0,
};
let origin = match ctx.args.get(1) {
Some(EmValue::I32(v)) => *v,
_ => 0, };
let (length, current_pos) = match thread.heap().get_stream_data(stream_ref) {
Some((data, pos)) => (data.len(), pos),
None => return PreHookResult::Bypass(Some(EmValue::I64(0))),
};
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let new_pos = match origin {
1 => {
#[allow(clippy::cast_possible_wrap)]
let pos = current_pos as i64 + offset;
pos.max(0) as usize
}
2 => {
#[allow(clippy::cast_possible_wrap)]
let pos = length as i64 + offset;
pos.max(0) as usize
}
_ => offset.max(0) as usize,
};
let clamped_pos = new_pos.min(length);
thread
.heap_mut()
.set_stream_position(stream_ref, clamped_pos);
#[allow(clippy::cast_possible_wrap)]
PreHookResult::Bypass(Some(EmValue::I64(clamped_pos as i64)))
}
fn stream_close_pre(_ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
PreHookResult::Bypass(None)
}
fn stream_dispose_pre(_ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
PreHookResult::Bypass(None)
}
fn get_binary_reader_stream(reader_ref: HeapRef, thread: &EmulationThread) -> Option<HeapRef> {
let field_value = thread
.heap()
.get_field(reader_ref, binary_reader_stream_field())
.ok()?;
match field_value {
EmValue::ObjectRef(stream_ref) => Some(stream_ref),
_ => None,
}
}
fn binary_reader_ctor_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let reader_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::Bypass(None),
};
let stream_ref = match ctx.args.first() {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::Bypass(None),
};
let _ = thread.heap_mut().set_field(
reader_ref,
binary_reader_stream_field(),
EmValue::ObjectRef(stream_ref),
);
PreHookResult::Bypass(None)
}
fn binary_reader_read_byte_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let reader_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::Bypass(Some(EmValue::I32(0))),
};
let Some(stream_ref) = get_binary_reader_stream(reader_ref, thread) else {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
};
let Some((data, position)) = thread.heap().get_stream_data(stream_ref) else {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
};
if position >= data.len() {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
}
let byte = data[position];
thread
.heap_mut()
.set_stream_position(stream_ref, position + 1);
PreHookResult::Bypass(Some(EmValue::I32(i32::from(byte))))
}
fn binary_reader_read_bytes_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
#[allow(clippy::cast_sign_loss)]
let count = match ctx.args.first() {
Some(EmValue::I32(v)) => *v as usize,
_ => 0,
};
let reader_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => match thread.heap_mut().alloc_byte_array(&vec![0u8; count]) {
Ok(array_ref) => return PreHookResult::Bypass(Some(EmValue::ObjectRef(array_ref))),
Err(_) => return PreHookResult::Bypass(Some(EmValue::Null)),
},
};
let Some(stream_ref) = get_binary_reader_stream(reader_ref, thread) else {
match thread.heap_mut().alloc_byte_array(&vec![0u8; count]) {
Ok(array_ref) => return PreHookResult::Bypass(Some(EmValue::ObjectRef(array_ref))),
Err(_) => return PreHookResult::Bypass(Some(EmValue::Null)),
}
};
let Some((data, position)) = thread.heap().get_stream_data(stream_ref) else {
match thread.heap_mut().alloc_byte_array(&vec![0u8; count]) {
Ok(array_ref) => return PreHookResult::Bypass(Some(EmValue::ObjectRef(array_ref))),
Err(_) => return PreHookResult::Bypass(Some(EmValue::Null)),
}
};
let available = data.len().saturating_sub(position);
let to_read = count.min(available);
let bytes: Vec<u8> = data[position..position + to_read].to_vec();
thread
.heap_mut()
.set_stream_position(stream_ref, position + to_read);
match thread.heap_mut().alloc_byte_array(&bytes) {
Ok(array_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(array_ref))),
Err(_) => PreHookResult::Bypass(Some(EmValue::Null)),
}
}
fn binary_reader_read_int16_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let reader_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::Bypass(Some(EmValue::I32(0))),
};
let Some(stream_ref) = get_binary_reader_stream(reader_ref, thread) else {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
};
let Some((data, position)) = thread.heap().get_stream_data(stream_ref) else {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
};
if position + 2 > data.len() {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
}
let value = i16::from_le_bytes([data[position], data[position + 1]]);
thread
.heap_mut()
.set_stream_position(stream_ref, position + 2);
PreHookResult::Bypass(Some(EmValue::I32(i32::from(value))))
}
fn binary_reader_read_int32_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let reader_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::Bypass(Some(EmValue::I32(0))),
};
let Some(stream_ref) = get_binary_reader_stream(reader_ref, thread) else {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
};
let Some((data, position)) = thread.heap().get_stream_data(stream_ref) else {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
};
if position + 4 > data.len() {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
}
let value = i32::from_le_bytes([
data[position],
data[position + 1],
data[position + 2],
data[position + 3],
]);
thread
.heap_mut()
.set_stream_position(stream_ref, position + 4);
PreHookResult::Bypass(Some(EmValue::I32(value)))
}
fn binary_reader_read_int64_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let reader_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::Bypass(Some(EmValue::I64(0))),
};
let Some(stream_ref) = get_binary_reader_stream(reader_ref, thread) else {
return PreHookResult::Bypass(Some(EmValue::I64(0)));
};
let Some((data, position)) = thread.heap().get_stream_data(stream_ref) else {
return PreHookResult::Bypass(Some(EmValue::I64(0)));
};
if position + 8 > data.len() {
return PreHookResult::Bypass(Some(EmValue::I64(0)));
}
let value = i64::from_le_bytes([
data[position],
data[position + 1],
data[position + 2],
data[position + 3],
data[position + 4],
data[position + 5],
data[position + 6],
data[position + 7],
]);
thread
.heap_mut()
.set_stream_position(stream_ref, position + 8);
PreHookResult::Bypass(Some(EmValue::I64(value)))
}
fn binary_reader_read_string_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let reader_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => match thread.heap_mut().alloc_string("") {
Ok(str_ref) => return PreHookResult::Bypass(Some(EmValue::ObjectRef(str_ref))),
Err(_) => return PreHookResult::Bypass(Some(EmValue::Null)),
},
};
let Some(stream_ref) = get_binary_reader_stream(reader_ref, thread) else {
match thread.heap_mut().alloc_string("") {
Ok(str_ref) => return PreHookResult::Bypass(Some(EmValue::ObjectRef(str_ref))),
Err(_) => return PreHookResult::Bypass(Some(EmValue::Null)),
}
};
let Some((data, mut position)) = thread.heap().get_stream_data(stream_ref) else {
match thread.heap_mut().alloc_string("") {
Ok(str_ref) => return PreHookResult::Bypass(Some(EmValue::ObjectRef(str_ref))),
Err(_) => return PreHookResult::Bypass(Some(EmValue::Null)),
}
};
let mut length: usize = 0;
let mut shift = 0;
loop {
if position >= data.len() {
match thread.heap_mut().alloc_string("") {
Ok(str_ref) => return PreHookResult::Bypass(Some(EmValue::ObjectRef(str_ref))),
Err(_) => return PreHookResult::Bypass(Some(EmValue::Null)),
}
}
let byte = data[position];
position += 1;
length |= ((byte & 0x7F) as usize) << shift;
if byte & 0x80 == 0 {
break;
}
shift += 7;
if shift > 35 {
break;
}
}
if position + length > data.len() {
let available = data.len().saturating_sub(position);
let str_bytes = &data[position..position + available];
let s = String::from_utf8_lossy(str_bytes).into_owned();
position += available;
thread.heap_mut().set_stream_position(stream_ref, position);
match thread.heap_mut().alloc_string(&s) {
Ok(str_ref) => return PreHookResult::Bypass(Some(EmValue::ObjectRef(str_ref))),
Err(_) => return PreHookResult::Bypass(Some(EmValue::Null)),
}
}
let str_bytes = &data[position..position + length];
let s = String::from_utf8_lossy(str_bytes).into_owned();
position += length;
thread.heap_mut().set_stream_position(stream_ref, position);
match thread.heap_mut().alloc_string(&s) {
Ok(str_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(str_ref))),
Err(_) => PreHookResult::Bypass(Some(EmValue::Null)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
emulation::runtime::hook::HookManager, metadata::typesystem::PointerSize,
test::emulation::create_test_thread,
};
#[test]
fn test_register_hooks() {
let mut manager = HookManager::new();
register(&mut manager);
assert_eq!(manager.len(), 22);
}
#[test]
fn test_stream_close_hook() {
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"Stream",
"Close",
PointerSize::Bit64,
);
let mut thread = create_test_thread();
let result = stream_close_pre(&ctx, &mut thread);
match result {
PreHookResult::Bypass(None) => {}
_ => panic!("Expected Bypass(None)"),
}
}
#[test]
fn test_stream_read_byte_eof_without_stream() {
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"Stream",
"ReadByte",
PointerSize::Bit64,
);
let mut thread = create_test_thread();
let result = stream_read_byte_pre(&ctx, &mut thread);
match result {
PreHookResult::Bypass(Some(EmValue::I32(-1))) => {}
_ => panic!("Expected Bypass with I32(-1) for EOF"),
}
}
#[test]
fn test_stream_read_byte_with_data() {
let mut thread = create_test_thread();
let data = vec![1, 2, 3, 4, 5];
let stream_ref = thread.heap_mut().alloc_stream(data).unwrap();
let this = EmValue::ObjectRef(stream_ref);
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"Stream",
"ReadByte",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = stream_read_byte_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(1)))
));
let result = stream_read_byte_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(2)))
));
for expected in [3, 4, 5] {
let result = stream_read_byte_pre(&ctx, &mut thread);
assert!(
matches!(result, PreHookResult::Bypass(Some(EmValue::I32(v))) if v == expected)
);
}
let result = stream_read_byte_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(-1)))
));
}
#[test]
fn test_stream_get_length_hook() {
let mut thread = create_test_thread();
let data = vec![1, 2, 3, 4, 5];
let stream_ref = thread.heap_mut().alloc_stream(data).unwrap();
let this = EmValue::ObjectRef(stream_ref);
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"Stream",
"get_Length",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = stream_get_length_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I64(5)))
));
}
#[test]
fn test_stream_get_position_hook() {
let mut thread = create_test_thread();
let data = vec![1, 2, 3, 4, 5];
let stream_ref = thread.heap_mut().alloc_stream(data).unwrap();
let this = EmValue::ObjectRef(stream_ref);
let get_pos_ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"Stream",
"get_Position",
PointerSize::Bit64,
)
.with_this(Some(&this));
let read_byte_ctx = HookContext::new(
Token::new(0x0A000002),
"System.IO",
"Stream",
"ReadByte",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = stream_get_position_pre(&get_pos_ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I64(0)))
));
stream_read_byte_pre(&read_byte_ctx, &mut thread);
let result = stream_get_position_pre(&get_pos_ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I64(1)))
));
}
#[test]
fn test_stream_set_position_hook() {
let mut thread = create_test_thread();
let data = vec![1, 2, 3, 4, 5];
let stream_ref = thread.heap_mut().alloc_stream(data).unwrap();
let this = EmValue::ObjectRef(stream_ref);
let args = [EmValue::I64(3)];
let set_pos_ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"Stream",
"set_Position",
PointerSize::Bit64,
)
.with_this(Some(&this))
.with_args(&args);
stream_set_position_pre(&set_pos_ctx, &mut thread);
let get_pos_ctx = HookContext::new(
Token::new(0x0A000002),
"System.IO",
"Stream",
"get_Position",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = stream_get_position_pre(&get_pos_ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I64(3)))
));
let read_byte_ctx = HookContext::new(
Token::new(0x0A000003),
"System.IO",
"Stream",
"ReadByte",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = stream_read_byte_pre(&read_byte_ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(4)))
));
}
#[test]
fn test_memory_stream_to_array_hook() {
let mut thread = create_test_thread();
let data = vec![10, 20, 30];
let stream_ref = thread.heap_mut().alloc_stream(data.clone()).unwrap();
let this = EmValue::ObjectRef(stream_ref);
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"MemoryStream",
"ToArray",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = memory_stream_to_array_pre(&ctx, &mut thread);
let array_ref = match result {
PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) => r,
_ => panic!("Expected Bypass with ObjectRef"),
};
let retrieved = thread.heap().get_byte_array(array_ref).unwrap();
assert_eq!(retrieved.as_slice(), &[10, 20, 30]);
}
#[test]
fn test_memory_stream_ctor_hook() {
let mut thread = create_test_thread();
let data = vec![1, 2, 3, 4, 5];
let array_ref = thread.heap_mut().alloc_byte_array(&data).unwrap();
let args = [EmValue::ObjectRef(array_ref)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"MemoryStream",
".ctor",
PointerSize::Bit64,
)
.with_args(&args);
let result = memory_stream_ctor_pre(&ctx, &mut thread);
let stream_ref = match result {
PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) => r,
_ => panic!("Expected Bypass with ObjectRef"),
};
let this = EmValue::ObjectRef(stream_ref);
let length_ctx = HookContext::new(
Token::new(0x0A000002),
"System.IO",
"MemoryStream",
"get_Length",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = stream_get_length_pre(&length_ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I64(5)))
));
let read_ctx = HookContext::new(
Token::new(0x0A000003),
"System.IO",
"MemoryStream",
"ReadByte",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = stream_read_byte_pre(&read_ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(1)))
));
}
#[test]
fn test_stream_seek_hook() {
let mut thread = create_test_thread();
let data = vec![10, 20, 30, 40, 50];
let stream_ref = thread.heap_mut().alloc_stream(data).unwrap();
let this = EmValue::ObjectRef(stream_ref);
let args = [EmValue::I64(2), EmValue::I32(0)];
let seek_ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"MemoryStream",
"Seek",
PointerSize::Bit64,
)
.with_this(Some(&this))
.with_args(&args);
let result = stream_seek_pre(&seek_ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I64(2)))
));
let read_ctx = HookContext::new(
Token::new(0x0A000002),
"System.IO",
"MemoryStream",
"ReadByte",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = stream_read_byte_pre(&read_ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(30)))
));
let args = [EmValue::I64(-1), EmValue::I32(2)];
let seek_ctx = HookContext::new(
Token::new(0x0A000003),
"System.IO",
"MemoryStream",
"Seek",
PointerSize::Bit64,
)
.with_this(Some(&this))
.with_args(&args);
let result = stream_seek_pre(&seek_ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I64(4)))
));
let result = stream_read_byte_pre(&read_ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(50)))
));
}
fn create_binary_reader(thread: &mut EmulationThread, data: Vec<u8>) -> HeapRef {
let stream_ref = thread.heap_mut().alloc_stream(data).unwrap();
let reader_token = Token::new(0x0200_0001); let reader_ref = thread.heap_mut().alloc_object(reader_token).unwrap();
let this = EmValue::ObjectRef(reader_ref);
let args = [EmValue::ObjectRef(stream_ref)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"BinaryReader",
".ctor",
PointerSize::Bit64,
)
.with_this(Some(&this))
.with_args(&args);
binary_reader_ctor_pre(&ctx, thread);
reader_ref
}
#[test]
fn test_binary_reader_read_byte_from_stream() {
let mut thread = create_test_thread();
let reader_ref = create_binary_reader(&mut thread, vec![0x42, 0x55, 0x88]);
let this = EmValue::ObjectRef(reader_ref);
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"BinaryReader",
"ReadByte",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = binary_reader_read_byte_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(0x42)))
));
let result = binary_reader_read_byte_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(0x55)))
));
let result = binary_reader_read_byte_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(0x88)))
));
}
#[test]
fn test_binary_reader_read_bytes_from_stream() {
let mut thread = create_test_thread();
let reader_ref = create_binary_reader(&mut thread, vec![1, 2, 3, 4, 5, 6, 7, 8]);
let this = EmValue::ObjectRef(reader_ref);
let args = [EmValue::I32(5)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"BinaryReader",
"ReadBytes",
PointerSize::Bit64,
)
.with_this(Some(&this))
.with_args(&args);
let result = binary_reader_read_bytes_pre(&ctx, &mut thread);
let array_ref = match result {
PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) => r,
_ => panic!("Expected Bypass with ObjectRef"),
};
let bytes = thread.heap().get_byte_array(array_ref).unwrap();
assert_eq!(bytes.as_slice(), &[1, 2, 3, 4, 5]);
}
#[test]
fn test_binary_reader_read_int16() {
let mut thread = create_test_thread();
let reader_ref = create_binary_reader(&mut thread, vec![0x34, 0x12]);
let this = EmValue::ObjectRef(reader_ref);
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"BinaryReader",
"ReadInt16",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = binary_reader_read_int16_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(0x1234)))
));
}
#[test]
fn test_binary_reader_read_int32() {
let mut thread = create_test_thread();
let reader_ref = create_binary_reader(&mut thread, vec![0x78, 0x56, 0x34, 0x12]);
let this = EmValue::ObjectRef(reader_ref);
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"BinaryReader",
"ReadInt32",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = binary_reader_read_int32_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(0x12345678)))
));
}
#[test]
fn test_binary_reader_read_int64() {
let mut thread = create_test_thread();
let reader_ref = create_binary_reader(
&mut thread,
vec![0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01],
);
let this = EmValue::ObjectRef(reader_ref);
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"BinaryReader",
"ReadInt64",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = binary_reader_read_int64_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I64(0x0102030405060708)))
));
}
#[test]
fn test_binary_reader_read_string() {
let mut thread = create_test_thread();
let reader_ref = create_binary_reader(&mut thread, vec![5, b'H', b'e', b'l', b'l', b'o']);
let this = EmValue::ObjectRef(reader_ref);
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"BinaryReader",
"ReadString",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = binary_reader_read_string_pre(&ctx, &mut thread);
let str_ref = match result {
PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) => r,
_ => panic!("Expected Bypass with ObjectRef"),
};
let s = thread.heap().get_string(str_ref).unwrap();
assert_eq!(s.as_ref(), "Hello");
}
#[test]
fn test_binary_reader_mixed_reads() {
let mut thread = create_test_thread();
let reader_ref = create_binary_reader(
&mut thread,
vec![
0xFF, 0x78, 0x56, 0x34, 0x12, 4, b'T', b'e', b's', b't', ],
);
let this = EmValue::ObjectRef(reader_ref);
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.IO",
"BinaryReader",
"ReadByte",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = binary_reader_read_byte_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(0xFF)))
));
let ctx = HookContext::new(
Token::new(0x0A000002),
"System.IO",
"BinaryReader",
"ReadInt32",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = binary_reader_read_int32_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(0x12345678)))
));
let ctx = HookContext::new(
Token::new(0x0A000003),
"System.IO",
"BinaryReader",
"ReadString",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result = binary_reader_read_string_pre(&ctx, &mut thread);
let str_ref = match result {
PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) => r,
_ => panic!("Expected Bypass with ObjectRef"),
};
let s = thread.heap().get_string(str_ref).unwrap();
assert_eq!(s.as_ref(), "Test");
}
}