use std::sync::Arc;
use crate::{
emulation::{
process::{EmulationConfig, UnknownMethodBehavior},
runtime::{bcl, hook::HookManager, native, AppDomainState, Hook},
},
metadata::token::Token,
};
#[derive(Debug)]
pub struct RuntimeState {
hooks: HookManager,
app_domain: AppDomainState,
unknown_method_behavior: UnknownMethodBehavior,
config: Arc<EmulationConfig>,
}
impl RuntimeState {
#[must_use]
pub fn new() -> Self {
Self::with_config(Arc::new(EmulationConfig::default()))
}
#[must_use]
pub fn with_config(config: Arc<EmulationConfig>) -> Self {
let mut hooks = HookManager::new();
if config.stubs.bcl_stubs {
bcl::register(&mut hooks);
}
if config.stubs.pinvoke_stubs {
native::register(&mut hooks);
}
Self {
hooks,
app_domain: AppDomainState::new(),
unknown_method_behavior: UnknownMethodBehavior::default(),
config,
}
}
#[must_use]
pub fn config(&self) -> &EmulationConfig {
&self.config
}
#[must_use]
pub fn hooks(&self) -> &HookManager {
&self.hooks
}
pub fn hooks_mut(&mut self) -> &mut HookManager {
&mut self.hooks
}
pub fn register_hook(&mut self, hook: Hook) {
self.hooks.register(hook);
}
#[must_use]
pub fn unknown_method_behavior(&self) -> UnknownMethodBehavior {
self.unknown_method_behavior
}
pub fn set_unknown_method_behavior(&mut self, behavior: UnknownMethodBehavior) {
self.unknown_method_behavior = behavior;
}
#[must_use]
pub fn app_domain(&self) -> &AppDomainState {
&self.app_domain
}
pub fn app_domain_mut(&mut self) -> &mut AppDomainState {
&mut self.app_domain
}
pub fn set_executing_assembly(&mut self, token: Token) {
self.app_domain.set_executing_assembly(token);
}
pub fn set_entry_assembly(&mut self, token: Token) {
self.app_domain.set_entry_assembly(token);
}
}
impl Default for RuntimeState {
fn default() -> Self {
Self::new()
}
}
pub struct RuntimeStateBuilder {
config: EmulationConfig,
hooks: Vec<Hook>,
register_defaults: bool,
unknown_method_behavior: UnknownMethodBehavior,
}
impl RuntimeStateBuilder {
#[must_use]
pub fn new() -> Self {
Self {
config: EmulationConfig::default(),
hooks: Vec::new(),
register_defaults: true,
unknown_method_behavior: UnknownMethodBehavior::default(),
}
}
#[must_use]
pub fn config(mut self, config: EmulationConfig) -> Self {
self.config = config;
self
}
#[must_use]
pub fn hook(mut self, hook: Hook) -> Self {
self.hooks.push(hook);
self
}
#[must_use]
pub fn unknown_method_behavior(mut self, behavior: UnknownMethodBehavior) -> Self {
self.unknown_method_behavior = behavior;
self
}
#[must_use]
pub fn no_defaults(mut self) -> Self {
self.register_defaults = false;
self
}
#[must_use]
pub fn build(self) -> RuntimeState {
let mut hooks = HookManager::new();
if self.register_defaults && self.config.stubs.bcl_stubs {
bcl::register(&mut hooks);
}
if self.register_defaults && self.config.stubs.pinvoke_stubs {
native::register(&mut hooks);
}
for hook in self.hooks {
hooks.register(hook);
}
RuntimeState {
hooks,
app_domain: AppDomainState::new(),
unknown_method_behavior: self.unknown_method_behavior,
config: Arc::new(self.config),
}
}
}
impl Default for RuntimeStateBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::emulation::runtime::{HookPriority, PreHookResult};
#[test]
fn test_runtime_state_creation() {
let state = RuntimeState::new();
assert!(!state.hooks().is_empty()); }
#[test]
fn test_runtime_state_with_config() {
let config = EmulationConfig::minimal();
let state = RuntimeState::with_config(Arc::new(config));
assert!(!state.hooks().is_empty());
}
#[test]
fn test_register_custom_hook() {
let mut state = RuntimeState::new();
let initial_count = state.hooks().len();
state.register_hook(
Hook::new("test-hook")
.with_priority(HookPriority::HIGH)
.match_name("Custom", "Type", "Method")
.pre(|_ctx, _thread| PreHookResult::Continue),
);
assert_eq!(state.hooks().len(), initial_count + 1);
}
#[test]
fn test_runtime_state_builder() {
let state = RuntimeStateBuilder::new()
.config(EmulationConfig::minimal())
.hook(
Hook::new("test-hook")
.match_method_name("Test")
.pre(|_ctx, _thread| PreHookResult::Continue),
)
.build();
assert!(!state.hooks().is_empty());
}
#[test]
fn test_builder_no_defaults() {
let state = RuntimeStateBuilder::new()
.no_defaults()
.hook(
Hook::new("only-hook")
.match_method_name("Test")
.pre(|_ctx, _thread| PreHookResult::Continue),
)
.build();
assert_eq!(state.hooks().len(), 1);
}
#[test]
fn test_unknown_method_behavior() {
let mut state = RuntimeState::new();
assert_eq!(
state.unknown_method_behavior(),
UnknownMethodBehavior::Emulate
);
state.set_unknown_method_behavior(UnknownMethodBehavior::Fail);
assert_eq!(state.unknown_method_behavior(), UnknownMethodBehavior::Fail);
}
#[test]
fn test_app_domain_integration() {
let mut state = RuntimeState::new();
state.set_executing_assembly(Token::new(0x20000001));
state.set_entry_assembly(Token::new(0x20000002));
assert_eq!(
state.app_domain().executing_assembly(),
Some(Token::new(0x20000001))
);
assert_eq!(
state.app_domain().entry_assembly(),
Some(Token::new(0x20000002))
);
}
#[test]
fn test_native_hooks() {
let config = EmulationConfig {
stubs: crate::emulation::process::StubConfig {
pinvoke_stubs: true,
bcl_stubs: false, ..Default::default()
},
..Default::default()
};
let state = RuntimeState::with_config(Arc::new(config));
assert!(state.hooks().len() >= 12);
}
}