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,
utils::write_7bit_encoded_int,
Result,
};
fn get_binary_writer_stream(writer_ref: HeapRef, thread: &EmulationThread) -> Option<HeapRef> {
let field_value = thread
.heap()
.get_field(writer_ref, io_fields::BINARY_WRITER_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.BinaryWriter..ctor")
.match_name("System.IO", "BinaryWriter", ".ctor")
.pre(binary_writer_ctor_pre),
)?;
manager.register(
Hook::new("System.IO.BinaryWriter.get_BaseStream")
.match_name("System.IO", "BinaryWriter", "get_BaseStream")
.pre(binary_writer_get_base_stream_pre),
)?;
manager.register(
Hook::new("System.IO.BinaryWriter.Write")
.match_name("System.IO", "BinaryWriter", "Write")
.pre(binary_writer_write_pre),
)?;
manager.register(
Hook::new("System.IO.BinaryWriter.Seek")
.match_name("System.IO", "BinaryWriter", "Seek")
.pre(binary_writer_seek_pre),
)?;
manager.register(
Hook::new("System.IO.BinaryWriter.Flush")
.match_name("System.IO", "BinaryWriter", "Flush")
.pre(stream_close_pre),
)?;
manager.register(
Hook::new("System.IO.BinaryWriter.Close")
.match_name("System.IO", "BinaryWriter", "Close")
.pre(stream_close_pre),
)?;
manager.register(
Hook::new("System.IO.BinaryWriter.Dispose")
.match_name("System.IO", "BinaryWriter", "Dispose")
.pre(stream_dispose_pre),
)?;
Ok(())
}
fn binary_writer_ctor_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let writer_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(
writer_ref,
io_fields::BINARY_WRITER_STREAM,
EmValue::ObjectRef(stream_ref),
));
PreHookResult::Bypass(None)
}
fn binary_writer_get_base_stream_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let writer_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::Bypass(Some(EmValue::Null)),
};
match get_binary_writer_stream(writer_ref, thread) {
Some(stream_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(stream_ref))),
None => PreHookResult::Bypass(Some(EmValue::Null)),
}
}
fn binary_writer_write_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let writer_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::Bypass(None),
};
let Some(stream_ref) = get_binary_writer_stream(writer_ref, thread) else {
return PreHookResult::Bypass(None);
};
if let Some(param_types) = ctx.param_types {
if param_types.len() == 1 {
match param_types[0] {
CilFlavor::Boolean => {
let v = match ctx.args.first() {
Some(EmValue::I32(v)) if *v != 0 => 1u8,
_ => 0u8,
};
try_hook!(thread.heap_mut().write_to_stream(stream_ref, &[v]));
return PreHookResult::Bypass(None);
}
CilFlavor::U1 => {
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let v = match ctx.args.first() {
Some(EmValue::I32(v)) => *v as u8,
_ => 0u8,
};
try_hook!(thread.heap_mut().write_to_stream(stream_ref, &[v]));
return PreHookResult::Bypass(None);
}
CilFlavor::I1 => {
#[allow(clippy::cast_possible_truncation)]
let v = match ctx.args.first() {
Some(EmValue::I32(v)) => *v as i8 as u8,
_ => 0u8,
};
try_hook!(thread.heap_mut().write_to_stream(stream_ref, &[v]));
return PreHookResult::Bypass(None);
}
CilFlavor::Char => {
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
if let Some(EmValue::I32(v)) = ctx.args.first() {
if let Some(ch) = char::from_u32(*v as u32) {
let mut buf = [0u8; 4];
let encoded = ch.encode_utf8(&mut buf);
try_hook!(thread
.heap_mut()
.write_to_stream(stream_ref, encoded.as_bytes()));
}
}
return PreHookResult::Bypass(None);
}
CilFlavor::I2 => {
#[allow(clippy::cast_possible_truncation)]
let v = match ctx.args.first() {
Some(EmValue::I32(v)) => *v as i16,
_ => 0i16,
};
try_hook!(thread
.heap_mut()
.write_to_stream(stream_ref, &v.to_le_bytes()));
return PreHookResult::Bypass(None);
}
CilFlavor::U2 => {
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let v = match ctx.args.first() {
Some(EmValue::I32(v)) => *v as u16,
_ => 0u16,
};
try_hook!(thread
.heap_mut()
.write_to_stream(stream_ref, &v.to_le_bytes()));
return PreHookResult::Bypass(None);
}
CilFlavor::I4 => {
let v = match ctx.args.first() {
Some(EmValue::I32(v)) => *v,
_ => 0i32,
};
try_hook!(thread
.heap_mut()
.write_to_stream(stream_ref, &v.to_le_bytes()));
return PreHookResult::Bypass(None);
}
CilFlavor::U4 => {
#[allow(clippy::cast_sign_loss)]
let v = match ctx.args.first() {
Some(EmValue::I32(v)) => *v as u32,
_ => 0u32,
};
try_hook!(thread
.heap_mut()
.write_to_stream(stream_ref, &v.to_le_bytes()));
return PreHookResult::Bypass(None);
}
CilFlavor::I8 => {
let v = match ctx.args.first() {
Some(EmValue::I64(v)) => *v,
Some(EmValue::I32(v)) => i64::from(*v),
_ => 0i64,
};
try_hook!(thread
.heap_mut()
.write_to_stream(stream_ref, &v.to_le_bytes()));
return PreHookResult::Bypass(None);
}
CilFlavor::U8 => {
#[allow(clippy::cast_sign_loss)]
let v = match ctx.args.first() {
Some(EmValue::I64(v)) => *v as u64,
Some(EmValue::I32(v)) => *v as u64,
_ => 0u64,
};
try_hook!(thread
.heap_mut()
.write_to_stream(stream_ref, &v.to_le_bytes()));
return PreHookResult::Bypass(None);
}
CilFlavor::R4 => {
let v = match ctx.args.first() {
Some(EmValue::F32(v)) => *v,
Some(EmValue::F64(v)) => *v as f32,
_ => 0.0f32,
};
try_hook!(thread
.heap_mut()
.write_to_stream(stream_ref, &v.to_le_bytes()));
return PreHookResult::Bypass(None);
}
CilFlavor::R8 => {
let v = match ctx.args.first() {
Some(EmValue::F64(v)) => *v,
Some(EmValue::F32(v)) => f64::from(*v),
_ => 0.0f64,
};
try_hook!(thread
.heap_mut()
.write_to_stream(stream_ref, &v.to_le_bytes()));
return PreHookResult::Bypass(None);
}
CilFlavor::String => {
if let Some(EmValue::ObjectRef(obj_ref)) = ctx.args.first() {
if let Ok(s) = thread.heap().get_string(*obj_ref) {
let s_bytes = s.as_bytes().to_vec();
let mut len_prefix = Vec::new();
#[allow(clippy::cast_possible_truncation)]
write_7bit_encoded_int(s_bytes.len() as u32, &mut len_prefix);
try_hook!(thread.heap_mut().write_to_stream(stream_ref, &len_prefix));
try_hook!(thread.heap_mut().write_to_stream(stream_ref, &s_bytes));
}
}
return PreHookResult::Bypass(None);
}
_ => {} }
}
}
match (ctx.args.first(), ctx.args.len()) {
(Some(EmValue::ObjectRef(obj_ref)), 1) => {
if let Ok(s) = thread.heap().get_string(*obj_ref) {
let s_bytes = s.as_bytes().to_vec();
let mut len_prefix = Vec::new();
#[allow(clippy::cast_possible_truncation)]
write_7bit_encoded_int(s_bytes.len() as u32, &mut len_prefix);
try_hook!(thread.heap_mut().write_to_stream(stream_ref, &len_prefix));
try_hook!(thread.heap_mut().write_to_stream(stream_ref, &s_bytes));
} else if let Some(bytes) = try_hook!(thread.heap().get_byte_array(*obj_ref)) {
try_hook!(thread.heap_mut().write_to_stream(stream_ref, &bytes));
}
}
(Some(EmValue::ObjectRef(arr_ref)), 3) => {
#[allow(clippy::cast_sign_loss)]
if let (Some(EmValue::I32(offset)), Some(EmValue::I32(count))) =
(ctx.args.get(1), ctx.args.get(2))
{
let offset = *offset as usize;
let count = *count as usize;
if let Some(bytes) = try_hook!(thread.heap().get_byte_array(*arr_ref)) {
let end = (offset + count).min(bytes.len());
if offset < bytes.len() {
try_hook!(thread
.heap_mut()
.write_to_stream(stream_ref, &bytes[offset..end]));
}
}
}
}
(Some(EmValue::I32(v)), 1) => {
try_hook!(thread
.heap_mut()
.write_to_stream(stream_ref, &v.to_le_bytes()));
}
(Some(EmValue::I64(v)), 1) => {
try_hook!(thread
.heap_mut()
.write_to_stream(stream_ref, &v.to_le_bytes()));
}
(Some(EmValue::F32(v)), 1) => {
try_hook!(thread
.heap_mut()
.write_to_stream(stream_ref, &v.to_le_bytes()));
}
(Some(EmValue::F64(v)), 1) => {
try_hook!(thread
.heap_mut()
.write_to_stream(stream_ref, &v.to_le_bytes()));
}
_ => {}
}
PreHookResult::Bypass(None)
}
fn binary_writer_seek_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let writer_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::Bypass(Some(EmValue::I64(0))),
};
let Some(stream_ref) = get_binary_writer_stream(writer_ref, thread) else {
return PreHookResult::Bypass(Some(EmValue::I64(0)));
};
let offset = match ctx.args.first() {
Some(EmValue::I32(v)) => i64::from(*v),
Some(EmValue::I64(v)) => *v,
_ => 0,
};
let origin = match ctx.args.get(1) {
Some(EmValue::I32(v)) => *v,
_ => 0,
};
let length = match try_hook!(thread.heap().stream_len(stream_ref)) {
Some(len) => len,
None => return PreHookResult::Bypass(Some(EmValue::I64(0))),
};
let current_pos = match try_hook!(thread.heap().stream_position(stream_ref)) {
Some(pos) => 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);
try_hook!(thread
.heap_mut()
.set_stream_position(stream_ref, clamped_pos));
#[allow(clippy::cast_possible_wrap)]
PreHookResult::Bypass(Some(EmValue::I64(clamped_pos as i64)))
}