use crate::{
emulation::{
runtime::{
bcl::io::stream::{stream_close_pre, stream_dispose_pre},
hook::{Hook, HookContext, HookManager, PreHookResult},
},
thread::EmulationThread,
tokens::io_fields,
EmValue, HeapRef,
},
metadata::typesystem::CilFlavor,
Result,
};
fn get_binary_reader_stream(reader_ref: HeapRef, thread: &EmulationThread) -> Option<HeapRef> {
let field_value = thread
.heap()
.get_field(reader_ref, io_fields::BINARY_READER_STREAM)
.ok()?;
match field_value {
EmValue::ObjectRef(stream_ref) => Some(stream_ref),
_ => None,
}
}
pub fn register(manager: &HookManager) -> Result<()> {
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.get_BaseStream")
.match_name("System.IO", "BinaryReader", "get_BaseStream")
.pre(binary_reader_get_base_stream_pre),
)?;
manager.register(
Hook::new("System.IO.BinaryReader.Read")
.match_name("System.IO", "BinaryReader", "Read")
.pre(binary_reader_read_pre),
)?;
manager.register(
Hook::new("System.IO.BinaryReader.ReadBoolean")
.match_name("System.IO", "BinaryReader", "ReadBoolean")
.pre(binary_reader_read_boolean_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.ReadSByte")
.match_name("System.IO", "BinaryReader", "ReadSByte")
.pre(binary_reader_read_sbyte_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.ReadChar")
.match_name("System.IO", "BinaryReader", "ReadChar")
.pre(binary_reader_read_char_pre),
)?;
manager.register(
Hook::new("System.IO.BinaryReader.ReadChars")
.match_name("System.IO", "BinaryReader", "ReadChars")
.pre(binary_reader_read_chars_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.ReadUInt16")
.match_name("System.IO", "BinaryReader", "ReadUInt16")
.pre(binary_reader_read_uint16_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.ReadUInt32")
.match_name("System.IO", "BinaryReader", "ReadUInt32")
.pre(binary_reader_read_uint32_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.ReadUInt64")
.match_name("System.IO", "BinaryReader", "ReadUInt64")
.pre(binary_reader_read_uint64_pre),
)?;
manager.register(
Hook::new("System.IO.BinaryReader.ReadSingle")
.match_name("System.IO", "BinaryReader", "ReadSingle")
.pre(binary_reader_read_single_pre),
)?;
manager.register(
Hook::new("System.IO.BinaryReader.ReadDouble")
.match_name("System.IO", "BinaryReader", "ReadDouble")
.pre(binary_reader_read_double_pre),
)?;
manager.register(
Hook::new("System.IO.BinaryReader.ReadDecimal")
.match_name("System.IO", "BinaryReader", "ReadDecimal")
.pre(binary_reader_read_decimal_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.Read7BitEncodedInt")
.match_name("System.IO", "BinaryReader", "Read7BitEncodedInt")
.pre(binary_reader_read_7bit_encoded_int_pre),
)?;
manager.register(
Hook::new("System.IO.BinaryReader.PeekChar")
.match_name("System.IO", "BinaryReader", "PeekChar")
.pre(binary_reader_peek_char_pre),
)?;
manager.register(
Hook::new("System.IO.BinaryReader.Close")
.match_name("System.IO", "BinaryReader", "Close")
.pre(stream_close_pre),
)?;
manager.register(
Hook::new("System.IO.BinaryReader.Dispose")
.match_name("System.IO", "BinaryReader", "Dispose")
.pre(stream_dispose_pre),
)?;
Ok(())
}
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);
}
};
try_hook!(thread.heap_mut().set_field(
reader_ref,
io_fields::BINARY_READER_STREAM,
EmValue::ObjectRef(stream_ref),
));
PreHookResult::Bypass(None)
}
fn binary_reader_get_base_stream_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let reader_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => {
return PreHookResult::Bypass(Some(EmValue::Null));
}
};
match get_binary_reader_stream(reader_ref, thread) {
Some(stream_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(stream_ref))),
None => PreHookResult::Bypass(Some(EmValue::Null)),
}
}
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::throw_end_of_stream(),
};
let Some(stream_ref) = get_binary_reader_stream(reader_ref, thread) else {
return PreHookResult::throw_end_of_stream();
};
let Some(byte) = try_hook!(thread.heap().stream_read_byte(stream_ref)) else {
return PreHookResult::throw_end_of_stream();
};
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,
_ => return PreHookResult::throw_object_disposed(),
};
let Some(stream_ref) = get_binary_reader_stream(reader_ref, thread) else {
return PreHookResult::throw_object_disposed();
};
let Some(bytes) = try_hook!(thread.heap().stream_read(stream_ref, count)) else {
return PreHookResult::throw_object_disposed();
};
match thread.heap_mut().alloc_byte_array(&bytes) {
Ok(array_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(array_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
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::throw_end_of_stream(),
};
let Some(stream_ref) = get_binary_reader_stream(reader_ref, thread) else {
return PreHookResult::throw_end_of_stream();
};
let Some(bytes) = try_hook!(thread.heap().stream_read_exact::<2>(stream_ref)) else {
return PreHookResult::throw_end_of_stream();
};
let value = i16::from_le_bytes(bytes);
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::throw_end_of_stream(),
};
let Some(stream_ref) = get_binary_reader_stream(reader_ref, thread) else {
return PreHookResult::throw_end_of_stream();
};
let Some(bytes) = try_hook!(thread.heap().stream_read_exact::<4>(stream_ref)) else {
return PreHookResult::throw_end_of_stream();
};
let value = i32::from_le_bytes(bytes);
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::throw_end_of_stream(),
};
let Some(stream_ref) = get_binary_reader_stream(reader_ref, thread) else {
return PreHookResult::throw_end_of_stream();
};
let Some(bytes) = try_hook!(thread.heap().stream_read_exact::<8>(stream_ref)) else {
return PreHookResult::throw_end_of_stream();
};
let value = i64::from_le_bytes(bytes);
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,
_ => return PreHookResult::throw_end_of_stream(),
};
let Some(stream_ref) = get_binary_reader_stream(reader_ref, thread) else {
return PreHookResult::throw_end_of_stream();
};
let Some(result) = try_hook!(thread.heap().with_stream(stream_ref, |data, position| {
let mut length: usize = 0;
let mut shift = 0;
loop {
if *position >= data.len() {
return None;
}
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() {
return None;
}
let s = String::from_utf8_lossy(&data[*position..*position + length]).into_owned();
*position += length;
Some(s)
})) else {
return PreHookResult::throw_end_of_stream();
};
let Some(s) = result else {
return PreHookResult::throw_end_of_stream();
};
match thread.heap_mut().alloc_string(&s) {
Ok(str_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(str_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn binary_reader_read_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let reader_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::throw_object_disposed(),
};
let Some(stream_ref) = get_binary_reader_stream(reader_ref, thread) else {
return PreHookResult::throw_object_disposed();
};
match ctx.args.len() {
0 => {
let Some(result) =
try_hook!(thread.heap().with_stream(stream_ref, |data, position| {
if *position >= data.len() {
return -1_i32;
}
let remaining = &data[*position..];
let s = String::from_utf8_lossy(remaining);
if let Some(ch) = s.chars().next() {
*position += ch.len_utf8();
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
{
ch as i32
}
} else {
-1_i32
}
}))
else {
return PreHookResult::Bypass(Some(EmValue::I32(-1)));
};
PreHookResult::Bypass(Some(EmValue::I32(result)))
}
3 => {
#[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(bytes) = try_hook!(thread.heap().stream_read(stream_ref, count)) else {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
};
for (i, &byte) in bytes.iter().enumerate() {
try_hook!(thread.heap_mut().set_array_element(
buffer_ref,
offset + i,
EmValue::I32(i32::from(byte)),
));
}
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
PreHookResult::Bypass(Some(EmValue::I32(bytes.len() as i32)))
}
_ => PreHookResult::Bypass(Some(EmValue::I32(0))),
}
}
fn binary_reader_read_boolean_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let reader_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::throw_end_of_stream(),
};
let Some(stream_ref) = get_binary_reader_stream(reader_ref, thread) else {
return PreHookResult::throw_end_of_stream();
};
let Some(byte) = try_hook!(thread.heap().stream_read_byte(stream_ref)) else {
return PreHookResult::throw_end_of_stream();
};
let value = if byte != 0 { 1 } else { 0 };
PreHookResult::Bypass(Some(EmValue::I32(value)))
}
fn binary_reader_read_sbyte_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let reader_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::throw_end_of_stream(),
};
let Some(stream_ref) = get_binary_reader_stream(reader_ref, thread) else {
return PreHookResult::throw_end_of_stream();
};
let Some(byte) = try_hook!(thread.heap().stream_read_byte(stream_ref)) else {
return PreHookResult::throw_end_of_stream();
};
let value = byte as i8;
PreHookResult::Bypass(Some(EmValue::I32(i32::from(value))))
}
fn binary_reader_read_char_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let reader_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::throw_end_of_stream(),
};
let Some(stream_ref) = get_binary_reader_stream(reader_ref, thread) else {
return PreHookResult::throw_end_of_stream();
};
let Some(result) = try_hook!(thread.heap().with_stream(stream_ref, |data, position| {
if *position >= data.len() {
return None;
}
let remaining = &data[*position..];
let s = String::from_utf8_lossy(remaining);
if let Some(ch) = s.chars().next() {
*position += ch.len_utf8();
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
Some(ch as i32)
} else {
None
}
})) else {
return PreHookResult::throw_end_of_stream();
};
match result {
Some(ch) => PreHookResult::Bypass(Some(EmValue::I32(ch))),
None => PreHookResult::Bypass(Some(EmValue::I32(0))),
}
}
fn binary_reader_read_chars_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,
_ => return PreHookResult::throw_object_disposed(),
};
let Some(stream_ref) = get_binary_reader_stream(reader_ref, thread) else {
return PreHookResult::throw_object_disposed();
};
let Some(chars) = try_hook!(thread.heap().with_stream(stream_ref, |data, position| {
let mut chars = Vec::with_capacity(count);
for _ in 0..count {
if *position >= data.len() {
break;
}
let remaining = &data[*position..];
let s = String::from_utf8_lossy(remaining);
if let Some(ch) = s.chars().next() {
chars.push(ch);
*position += ch.len_utf8();
} else {
break;
}
}
chars
})) else {
return PreHookResult::throw_object_disposed();
};
alloc_char_array_result(thread, &chars)
}
fn alloc_char_array_result(thread: &mut EmulationThread, chars: &[char]) -> PreHookResult {
let elements: Vec<EmValue> = chars
.iter()
.map(|&c| {
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
EmValue::I32(c as i32)
})
.collect();
match thread
.heap_mut()
.alloc_array_with_values(CilFlavor::Char, elements)
{
Ok(arr_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(arr_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn binary_reader_read_uint16_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let reader_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::throw_end_of_stream(),
};
let Some(stream_ref) = get_binary_reader_stream(reader_ref, thread) else {
return PreHookResult::throw_end_of_stream();
};
let Some(bytes) = try_hook!(thread.heap().stream_read_exact::<2>(stream_ref)) else {
return PreHookResult::throw_end_of_stream();
};
let value = u16::from_le_bytes(bytes);
PreHookResult::Bypass(Some(EmValue::I32(i32::from(value))))
}
fn binary_reader_read_uint32_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let reader_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::throw_end_of_stream(),
};
let Some(stream_ref) = get_binary_reader_stream(reader_ref, thread) else {
return PreHookResult::throw_end_of_stream();
};
let Some(bytes) = try_hook!(thread.heap().stream_read_exact::<4>(stream_ref)) else {
return PreHookResult::throw_end_of_stream();
};
let value = u32::from_le_bytes(bytes);
#[allow(clippy::cast_possible_wrap)]
PreHookResult::Bypass(Some(EmValue::I32(value as i32)))
}
fn binary_reader_read_uint64_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let reader_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::throw_end_of_stream(),
};
let Some(stream_ref) = get_binary_reader_stream(reader_ref, thread) else {
return PreHookResult::throw_end_of_stream();
};
let Some(bytes) = try_hook!(thread.heap().stream_read_exact::<8>(stream_ref)) else {
return PreHookResult::throw_end_of_stream();
};
let value = u64::from_le_bytes(bytes);
#[allow(clippy::cast_possible_wrap)]
PreHookResult::Bypass(Some(EmValue::I64(value as i64)))
}
fn binary_reader_read_single_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let reader_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::throw_end_of_stream(),
};
let Some(stream_ref) = get_binary_reader_stream(reader_ref, thread) else {
return PreHookResult::throw_end_of_stream();
};
let Some(bytes) = try_hook!(thread.heap().stream_read_exact::<4>(stream_ref)) else {
return PreHookResult::throw_end_of_stream();
};
let value = f32::from_le_bytes(bytes);
PreHookResult::Bypass(Some(EmValue::F32(value)))
}
fn binary_reader_read_double_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let reader_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::throw_end_of_stream(),
};
let Some(stream_ref) = get_binary_reader_stream(reader_ref, thread) else {
return PreHookResult::throw_end_of_stream();
};
let Some(bytes) = try_hook!(thread.heap().stream_read_exact::<8>(stream_ref)) else {
return PreHookResult::throw_end_of_stream();
};
let value = f64::from_le_bytes(bytes);
PreHookResult::Bypass(Some(EmValue::F64(value)))
}
fn binary_reader_read_decimal_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let reader_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::throw_end_of_stream(),
};
let Some(stream_ref) = get_binary_reader_stream(reader_ref, thread) else {
return PreHookResult::throw_end_of_stream();
};
let Some(bytes) = try_hook!(thread.heap().stream_read_exact::<16>(stream_ref)) else {
return PreHookResult::throw_end_of_stream();
};
let lo = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
let mid = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
let hi = u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
let flags = u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]);
let negative = (flags & 0x8000_0000) != 0;
let scale = (flags >> 16) & 0xFF;
let mantissa = f64::from(lo)
+ f64::from(mid) * 4_294_967_296.0
+ f64::from(hi) * 18_446_744_073_709_551_616.0;
let divisor = 10_f64.powi(scale as i32);
let value = if negative {
-mantissa / divisor
} else {
mantissa / divisor
};
PreHookResult::Bypass(Some(EmValue::F64(value)))
}
fn binary_reader_read_7bit_encoded_int_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let reader_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::throw_end_of_stream(),
};
let Some(stream_ref) = get_binary_reader_stream(reader_ref, thread) else {
return PreHookResult::throw_end_of_stream();
};
enum Read7BitResult {
Ok(u32),
EndOfStream,
FormatError,
}
let Some(result) = try_hook!(thread.heap().with_stream(stream_ref, |data, position| {
if *position >= data.len() {
return Read7BitResult::EndOfStream;
}
let mut value: u32 = 0;
let mut shift = 0;
loop {
if *position >= data.len() {
return Read7BitResult::EndOfStream;
}
if shift > 35 {
return Read7BitResult::FormatError;
}
let byte = data[*position];
*position += 1;
value |= u32::from(byte & 0x7F) << shift;
if byte & 0x80 == 0 {
break;
}
shift += 7;
}
Read7BitResult::Ok(value)
})) else {
return PreHookResult::throw_end_of_stream();
};
match result {
Read7BitResult::Ok(value) =>
{
#[allow(clippy::cast_possible_wrap)]
PreHookResult::Bypass(Some(EmValue::I32(value as i32)))
}
Read7BitResult::EndOfStream => PreHookResult::throw_end_of_stream(),
Read7BitResult::FormatError => PreHookResult::throw_format_exception(),
}
}
fn binary_reader_peek_char_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let reader_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::Bypass(Some(EmValue::I32(-1))),
};
let Some(stream_ref) = get_binary_reader_stream(reader_ref, thread) else {
return PreHookResult::Bypass(Some(EmValue::I32(-1)));
};
let Some(result) = try_hook!(thread.heap().with_stream(stream_ref, |data, position| {
if *position >= data.len() {
return -1_i32;
}
let remaining = &data[*position..];
let s = String::from_utf8_lossy(remaining);
if let Some(ch) = s.chars().next() {
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
{
ch as i32
}
} else {
-1_i32
}
})) else {
return PreHookResult::Bypass(Some(EmValue::I32(-1)));
};
PreHookResult::Bypass(Some(EmValue::I32(result)))
}
#[cfg(test)]
mod tests {
use crate::{
emulation::{
runtime::{
bcl::io::binaryreader::{
binary_reader_ctor_pre, binary_reader_read_byte_pre,
binary_reader_read_bytes_pre, binary_reader_read_int16_pre,
binary_reader_read_int32_pre, binary_reader_read_int64_pre,
binary_reader_read_string_pre,
},
hook::{HookContext, HookManager, PreHookResult},
},
EmValue, HeapRef,
},
metadata::{token::Token, typesystem::PointerSize},
test::emulation::create_test_thread,
};
use crate::emulation::thread::EmulationThread;
fn create_binary_reader(thread: &mut EmulationThread, data: Vec<u8>) -> HeapRef {
let stream_ref = thread.heap_mut().alloc_stream(data, None).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_register_hooks() {
let manager = HookManager::new();
super::register(&manager).unwrap();
assert_eq!(manager.len(), 23);
}
#[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().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");
}
}