use crate::{
emulation::{
runtime::{
bcl::io::stream::{stream_close_pre, stream_dispose_pre},
hook::{Hook, HookContext, HookManager, PreHookResult},
},
thread::EmulationThread,
EmValue,
},
utils::{decompress_deflate, decompress_gzip},
Result,
};
pub fn register(manager: &HookManager) -> Result<()> {
manager.register(
Hook::new("System.IO.Compression.DeflateStream..ctor")
.match_name("System.IO.Compression", "DeflateStream", ".ctor")
.pre(deflate_stream_ctor_pre),
)?;
manager.register(
Hook::new("System.IO.Compression.DeflateStream.Read")
.match_name("System.IO.Compression", "DeflateStream", "Read")
.pre(compressed_stream_read_pre),
)?;
manager.register(
Hook::new("System.IO.Compression.DeflateStream.Close")
.match_name("System.IO.Compression", "DeflateStream", "Close")
.pre(stream_close_pre),
)?;
manager.register(
Hook::new("System.IO.Compression.DeflateStream.Dispose")
.match_name("System.IO.Compression", "DeflateStream", "Dispose")
.pre(stream_dispose_pre),
)?;
manager.register(
Hook::new("System.IO.Compression.GZipStream..ctor")
.match_name("System.IO.Compression", "GZipStream", ".ctor")
.pre(gzip_stream_ctor_pre),
)?;
manager.register(
Hook::new("System.IO.Compression.GZipStream.Read")
.match_name("System.IO.Compression", "GZipStream", "Read")
.pre(compressed_stream_read_pre),
)?;
manager.register(
Hook::new("System.IO.Compression.GZipStream.Close")
.match_name("System.IO.Compression", "GZipStream", "Close")
.pre(stream_close_pre),
)?;
manager.register(
Hook::new("System.IO.Compression.GZipStream.Dispose")
.match_name("System.IO.Compression", "GZipStream", "Dispose")
.pre(stream_dispose_pre),
)?;
Ok(())
}
fn deflate_stream_ctor_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
compressed_stream_ctor(ctx, thread, 0) }
fn gzip_stream_ctor_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
compressed_stream_ctor(ctx, thread, 1) }
fn compressed_stream_ctor(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
compression_type: u8,
) -> PreHookResult {
let stream_ref = match ctx.this {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::Bypass(None),
};
let underlying_stream = match ctx.args.first() {
Some(EmValue::ObjectRef(r)) => *r,
_ => return PreHookResult::Bypass(None),
};
#[allow(clippy::cast_sign_loss)]
let mode = match ctx.args.get(1) {
Some(EmValue::I32(v)) => *v as u8,
_ => 0, };
try_hook!(thread.heap().replace_with_compressed_stream(
stream_ref,
underlying_stream,
compression_type,
mode,
));
PreHookResult::Bypass(None)
}
fn compressed_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))),
};
if !try_hook!(thread.heap().has_compressed_stream_data(stream_ref)) {
let Some((underlying, compression_type, _mode)) =
try_hook!(thread.heap().get_compressed_stream_info(stream_ref))
else {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
};
let Some((compressed_data, underlying_pos)) =
try_hook!(thread.heap().get_stream_data(underlying))
else {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
};
let effective_data = if underlying_pos < compressed_data.len() {
&compressed_data[underlying_pos..]
} else {
&[]
};
let decompressed = match compression_type {
0 => decompress_deflate(effective_data).ok(),
1 => decompress_gzip(effective_data).ok(),
_ => None,
};
let data = decompressed.unwrap_or_default();
try_hook!(thread.heap().set_compressed_stream_data(stream_ref, data));
}
let Some(bytes) = try_hook!(thread.heap().read_compressed_stream(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)))
}