rustclr/
clr.rs

1use obfstr::obfstr as s;
2use core::{ffi::c_void, ptr::null_mut};
3use windows_core::{IUnknown, Interface, PCWSTR};
4use windows_sys::{
5    Win32::{
6        System::{
7            Memory::PAGE_EXECUTE_READWRITE, 
8            Variant::{VariantClear, VARIANT}
9        },
10        UI::Shell::SHCreateMemStream 
11    }
12};
13use alloc::{
14    boxed::Box, format, 
15    string::{String, ToString}, 
16    vec::Vec, vec
17};
18use dinvk::{
19    data::NT_SUCCESS, 
20    NtCurrentProcess, 
21    NtProtectVirtualMemory
22};
23
24use super::{
25    WinStr, uuid, Result,
26    create_safe_array_args,
27    file::{validate_file, read_file}, 
28    Invocation, error::ClrError, 
29};
30use super::com::{
31    CLRCreateInstance, 
32    CLSID_CLRMETAHOST, 
33    CLSID_COR_RUNTIME_HOST
34};
35use super::data::{
36    ICLRMetaHost, ICLRRuntimeInfo, 
37    ICorRuntimeHost, _AppDomain, _Assembly
38};
39use super::{
40    host_control::RustClrControl, Variant,
41    com::{CLRIdentityManagerType, CLSID_ICLR_RUNTIME_HOST}, 
42    data::{ICLRAssemblyIdentityManager, ICLRuntimeHost, IHostControl}, 
43};
44
45/// Represents a Rust interface to the Common Language Runtime (CLR).
46/// 
47/// This structure allows loading and executing .NET assemblies with specific runtime versions, 
48/// application domains, and arguments.
49#[derive(Debug, Clone)]
50pub struct RustClr<'a> {
51    /// Buffer containing the .NET assembly in bytes.
52    buffer: &'a [u8],
53
54    /// Flag to indicate if output redirection is enabled.
55    redirect_output: bool,
56
57    /// Whether to patch `System.Environment.Exit` to prevent the process from terminating.
58    patch_exit: bool,
59
60    /// The identity name of the assembly being loaded or executed.
61    identity_assembly: String,
62
63    /// Name of the application domain to create or use.
64    domain_name: Option<String>,
65
66    /// .NET runtime version to use.
67    runtime_version: Option<RuntimeVersion>,
68
69    /// Arguments to pass to the .NET assembly's `Main` method.
70    args: Option<Vec<String>>,
71
72    /// Current application domain where the assembly is loaded.
73    app_domain: Option<_AppDomain>,
74
75    /// Host for the CLR runtime.
76    cor_runtime_host: Option<ICorRuntimeHost>,
77}
78
79impl<'a> Default for RustClr<'a> {
80    /// Provides a default-initialized `RustClr`.
81    ///
82    /// # Returns
83    ///
84    /// * A default-initialized `RustClr`.
85    fn default() -> Self {
86        Self { 
87            buffer: &[], 
88            runtime_version: None,
89            redirect_output: false,
90            patch_exit: false,
91            identity_assembly: String::new(),
92            domain_name: None,
93            args: None, 
94            app_domain: None,
95            cor_runtime_host: None
96        }
97    }
98}
99
100impl<'a> RustClr<'a> {
101    /// Creates a new [`RustClr`] instance with the specified assembly buffer.
102    /// 
103    /// # Arguments
104    /// 
105    /// * `source` - A value convertible into [`ClrSource`], representing either a file path or a byte buffer.
106    /// 
107    /// # Returns
108    /// 
109    /// * `Ok(Self)` - If the buffer is valid and the [`RustClr`] instance is created successfully.
110    /// * `Err(ClrError)` - If the buffer validation fails (e.g., not a valid .NET assembly).
111    /// 
112    /// # Example
113    /// 
114    /// ```rust,ignore
115    /// use rustclr::RustClr;
116    /// use std::fs;
117    ///
118    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
119    ///     // Load a sample .NET assembly into a buffer
120    ///     let buffer = fs::read("examples/sample.exe")?;
121    ///
122    ///     // Create a new RustClr instance
123    ///     let clr = RustClr::new(&buffer)?;
124    ///     println!("RustClr instance created successfully.");
125    /// 
126    ///     Ok(())
127    /// }
128    /// ```
129    pub fn new<T: Into<ClrSource<'a>>>(source: T) -> Result<Self> {
130        let buffer = match source.into() {
131            // Try reading the file
132            ClrSource::File(path) => {
133                Box::leak(read_file(path)?.into_boxed_slice())
134            },
135
136            // Creates the .NET directly from the buffer
137            ClrSource::Buffer(buffer) => buffer,
138        };
139
140        // Checks if it is a valid .NET and EXE file
141        validate_file(&buffer)?;
142
143        // Initializes the default instance and injects the read buffer
144        let mut clr = Self::default();
145        clr.buffer = buffer;
146        Ok(clr)
147    }
148
149    /// Sets the .NET runtime version to use.
150    /// 
151    /// # Arguments
152    /// 
153    /// * `version` - The `RuntimeVersion` enum representing the .NET version.
154    /// 
155    /// # Returns
156    /// 
157    /// * Returns the modified `RustClr` instance.
158    ///
159    /// # Example
160    /// 
161    /// ```rust,ignore
162    /// use rustclr::{RustClr, RuntimeVersion};
163    /// use std::fs;
164    ///
165    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
166    ///     let buffer = fs::read("examples/sample.exe")?;
167    ///
168    ///     // Set a specific .NET runtime version
169    ///     let clr = RustClr::new(&buffer)?
170    ///         .runtime_version(RuntimeVersion::V4);
171    ///
172    ///     println!("Runtime version set successfully.");
173    /// 
174    ///     Ok(())
175    /// }
176    /// ```
177    pub fn runtime_version(mut self, version: RuntimeVersion) -> Self {
178        self.runtime_version = Some(version);
179        self
180    }
181
182    /// Sets the application domain name to use.
183    /// 
184    /// # Arguments
185    /// 
186    /// * `domain_name` - A string representing the name of the application domain.
187    /// 
188    /// # Returns
189    /// 
190    /// * Returns the modified `RustClr` instance.
191    /// 
192    /// # Example
193    /// 
194    /// ```rust,ignore
195    /// use rustclr::RustClr;
196    /// use std::fs;
197    ///
198    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
199    ///     let buffer = fs::read("examples/sample.exe")?;
200    ///
201    ///     // Set a custom application domain name
202    ///     let clr = RustClr::new(&buffer)?
203    ///         .domain("CustomDomain");
204    ///
205    ///     println!("Domain set successfully.");
206    ///     Ok(())
207    /// }
208    /// ```
209    pub fn domain(mut self, domain_name: &str) -> Self {
210        self.domain_name = Some(domain_name.to_string());
211        self
212    }
213
214    /// Sets the arguments to pass to the .NET assembly's entry point.
215    /// 
216    /// # Arguments
217    /// 
218    /// * `args` - A vector of strings representing the arguments.
219    /// 
220    /// # Returns
221    /// 
222    /// * Returns the modified `RustClr` instance.
223    /// 
224    /// # Examples
225    /// 
226    /// ```rust,ignore
227    /// use rustclr::RustClr;
228    /// use std::fs;
229    ///
230    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
231    ///     let buffer = fs::read("examples/sample.exe")?;
232    ///
233    ///     // Pass arguments to the .NET assembly's entry point
234    ///     let clr = RustClr::new(&buffer)?
235    ///         .args(vec!["arg1", "arg2"]);
236    ///
237    ///     println!("Arguments set successfully.");
238    ///     Ok(())
239    /// }
240    /// ```
241    pub fn args(mut self, args: Vec<&str>) -> Self {
242        self.args = Some(args.iter().map(|&s| s.to_string()).collect());
243        self
244    }
245
246    /// Enables or disables output redirection.
247    ///
248    /// # Arguments
249    ///
250    /// * `redirect` - A boolean indicating whether to enable output redirection.
251    ///
252    /// # Returns
253    ///
254    /// * The modified `RustClr` instance with the updated output redirection setting.
255    /// 
256    /// # Example
257    ///
258    /// ```rust,ignore
259    /// use rustclr::RustClr;
260    /// use std::fs;
261    ///
262    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
263    ///     let buffer = fs::read("examples/sample.exe")?;
264    ///
265    ///     // Enable output redirection to capture console output
266    ///     let clr = RustClr::new(&buffer)?
267    ///         .output();
268    ///
269    ///     println!("Output redirection enabled.");
270    ///     Ok(())
271    /// }
272    /// ```
273    pub fn output(mut self) -> Self {
274        self.redirect_output = true;
275        self
276    }
277
278    /// Enables patching of the `System.Environment.Exit` method in `mscorlib`.
279    ///
280    /// # Returns
281    ///
282    /// * The modified `RustClr` instance with the patch setting enabled.
283    ///
284    /// # Example
285    ///
286    /// ```rust,ignore
287    /// use rustclr::RustClr;
288    /// use std::fs;
289    ///
290    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
291    ///     let buffer = fs::read("examples/sample.exe")?;
292    ///
293    ///     // Enable patching of Environment.Exit
294    ///     let clr = RustClr::new(&buffer)?
295    ///         .exit();
296    ///
297    ///     println!("Environment.Exit will be patched to prevent termination.");
298    ///     Ok(())
299    /// }
300    /// ```
301    pub fn exit(mut self) -> Self {
302        self.patch_exit = true;
303        self
304    }
305
306    /// Prepares the CLR environment by initializing the runtime and application domain.
307    /// 
308    /// # Returns
309    /// 
310    /// * `Ok(())` - If the environment is successfully prepared.
311    /// * `Err(ClrError)` - If any error occurs during the preparation process.
312    fn prepare(&mut self) -> Result<()> {
313        // Creates the MetaHost to access the available CLR versions
314        let meta_host = self.create_meta_host()?;
315
316        // Gets information about the specified (or default) runtime version
317        let runtime_info = self.get_runtime_info(&meta_host)?;
318
319        // Get ICLRAssemblyIdentityManager via GetProcAddress
320        let addr = runtime_info.GetProcAddress(s!("GetCLRIdentityManager"))?;
321        let GetCLRIdentityManager = unsafe { core::mem::transmute::<*mut c_void, CLRIdentityManagerType>(addr) };        
322        let mut ptr = null_mut();
323        GetCLRIdentityManager(&ICLRAssemblyIdentityManager::IID, &mut ptr);
324
325        // Create a stream for the in-memory assembly
326        let iclr_assembly = ICLRAssemblyIdentityManager::from_raw(ptr)?;
327        let stream = unsafe { SHCreateMemStream(self.buffer.as_ptr(), self.buffer.len() as u32) };
328
329        // Get the identity string from the stream
330        self.identity_assembly = iclr_assembly.get_identity_stream(stream, 0)?;
331
332        // Creates the `ICLRuntimeHost`
333        let iclr_runtime_host = self.get_clr_runtime_host(&runtime_info)?;
334
335        // Create and register IHostControl with custom assembly and identity
336        let host_control: IHostControl = RustClrControl::new(self.buffer, &self.identity_assembly).into();
337        iclr_runtime_host.SetHostControl(&host_control)?;
338
339        // Checks if the runtime is started
340        if runtime_info.IsLoadable().is_ok() && !runtime_info.is_started() {
341            // Starts the CLR runtime
342            self.start_runtime(&iclr_runtime_host)?;
343        }
344        
345        // Creates the `ICorRuntimeHost`
346        let cor_runtime_host = self.get_icor_runtime_host(&runtime_info)?;
347
348        // Initializes the specified application domain or the default
349        self.init_app_domain(&cor_runtime_host)?;
350
351        // Saves the runtime host for future use
352        self.cor_runtime_host = Some(self.get_icor_runtime_host(&runtime_info)?);
353        Ok(())
354    }
355
356    /// Runs the .NET assembly by loading it into the application domain and invoking its entry point.
357    /// 
358    /// # Returns
359    /// 
360    /// * `Ok(String)` - The output from the .NET assembly if executed successfully.
361    /// * `Err(ClrError)` - If an error occurs during execution.
362    /// 
363    /// # Examples
364    /// 
365    /// ```rust,ignore
366    /// use rustclr::{RustClr, RuntimeVersion};
367    /// use std::fs;
368    ///
369    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
370    ///     let buffer = fs::read("examples/sample.exe")?;
371    ///
372    ///     // Create and configure a RustClr instance
373    ///     let mut clr = RustClr::new(&buffer)?
374    ///         .runtime_version(RuntimeVersion::V4)
375    ///         .domain("CustomDomain")
376    ///         .args(vec!["arg1", "arg2"])
377    ///         .output();
378    ///
379    ///     // Run the .NET assembly and capture the output
380    ///     let output = clr.run()?;
381    ///     println!("Output: {}", output);
382    /// 
383    ///     Ok(())
384    /// }
385    /// ```
386    pub fn run(&mut self) -> Result<String> {
387        // Prepare the CLR environment
388        self.prepare()?;
389
390        // Gets the current application domain
391        let domain = self.get_app_domain()?;
392
393        // Loads the .NET assembly specified by the buffer
394        let assembly = domain.load_name(&self.identity_assembly)?;
395
396        // Prepares the parameters for the `Main` method
397        let parameters = self.args.as_ref().map_or_else(
398            || Ok(null_mut()),
399            |args| create_safe_array_args(args.to_vec())
400        )?;
401
402        // Loads the mscorlib library
403        let mscorlib = domain.get_assembly(s!("mscorlib"))?;
404
405        // If the exit patch is enabled, perform the patch in System.Environment.Exit
406        if self.patch_exit { self.patch_exit(&mscorlib)?; }
407
408        // Redirects output if enabled
409        let output = if self.redirect_output {
410            // Redirecting output
411            let mut output_manager = ClrOutput::new(&mscorlib);
412            output_manager.redirect()?;
413
414            // Invokes the `Main` method of the assembly
415            assembly.run(parameters)?;
416
417            // Restores output if redirected
418            let output = output_manager.capture()?;
419            output_manager.restore()?;
420            output
421        } else {
422            // Invokes the `Main` method of the assembly
423            assembly.run(parameters)?;
424
425            // Empty output
426            String::new()
427        };
428
429        // Unload Domain
430        self.unload_domain()?;
431        Ok(output)
432    }
433
434    /// Patches the `System.Environment.Exit` method in `mscorlib` to avoid process termination.
435    /// 
436    /// # Arguments
437    ///
438    /// * `mscorlib` - The `_Assembly` object representing the loaded `mscorlib.dll`.
439    ///
440    /// # Returns
441    ///
442    /// * `Ok(())` - If the patch was applied successfully.
443    /// * `Err(ClrError)` - If any COM error or memory protection error occurs during the process.
444    fn patch_exit(&self, mscorlib: &_Assembly) -> Result<()> {
445        // Resolve System.Environment type and the Exit method
446        let env = mscorlib.resolve_type(s!("System.Environment"))?;
447        let exit = env.method(s!("Exit"))?;
448
449        // Resolve System.Reflection.MethodInfo.MethodHandle property
450        let method_info = mscorlib.resolve_type(s!("System.Reflection.MethodInfo"))?;
451        let method_handle = method_info.property(s!("MethodHandle"))?;
452
453        // Convert the Exit method into a COM IUnknown pointer to pass into MethodHandle
454        let instance = exit
455            .cast::<IUnknown>()
456            .map_err(|_| ClrError::GenericError("Failed to cast to IUnknown"))?;
457
458        // Call MethodHandle.get_Value(instance) to retrieve the RuntimeMethodHandle
459        let method_handle_exit = method_handle.value(Some(instance.to_variant()), None)?;
460
461        // Resolve System.RuntimeMethodHandle.GetFunctionPointer
462        let runtime_method = mscorlib.resolve_type(s!("System.RuntimeMethodHandle"))?;
463        let get_function_pointer = runtime_method.method(s!("GetFunctionPointer"))?;
464
465        // Invoke GetFunctionPointer(handle) to get the native address of Environment.Exit
466        let ptr = get_function_pointer.invoke(Some(method_handle_exit), None)?;
467
468        // Extract pointer from VARIANT
469        let mut addr_exit = unsafe { ptr.Anonymous.Anonymous.Anonymous.byref };
470        let mut old = 0;
471        let mut size = 1;
472        
473        // Change memory protection to RWX for patching
474        if !NT_SUCCESS(NtProtectVirtualMemory(NtCurrentProcess(), &mut addr_exit, &mut size, PAGE_EXECUTE_READWRITE, &mut old)) {
475            return Err(ClrError::GenericError("Failed to change memory protection to RWX"));
476        }
477
478        // Overwrite first byte with RET (0xC3) to effectively no-op the function
479        unsafe { *(ptr.Anonymous.Anonymous.Anonymous.byref as *mut u8) = 0xC3 };
480
481        // Restore original protection
482        if !NT_SUCCESS(NtProtectVirtualMemory(NtCurrentProcess(), &mut addr_exit, &mut size, old, &mut old)) {
483            return Err(ClrError::GenericError("Failed to restore memory protection"));
484        }
485        
486        Ok(())
487    }
488    
489    /// Retrieves the current application domain.
490    /// 
491    /// # Returns
492    /// 
493    /// * `Ok(_AppDomain)` - If the application domain is available.
494    /// * `Err(ClrError)` - If no application domain is available.
495    fn get_app_domain(&mut self) -> Result<_AppDomain> {
496        self.app_domain.clone().ok_or(ClrError::NoDomainAvailable)
497    }
498
499    /// Creates an instance of `ICLRMetaHost`.
500    /// 
501    /// # Returns
502    /// 
503    /// * `Ok(ICLRMetaHost)` - If the instance is created successfully.
504    /// * `Err(ClrError)` - If the instance creation fails.
505    fn create_meta_host(&self) -> Result<ICLRMetaHost> {
506        CLRCreateInstance::<ICLRMetaHost>(&CLSID_CLRMETAHOST)
507            .map_err(|e| ClrError::MetaHostCreationError(format!("{e}")))
508    }
509
510    /// Retrieves runtime information based on the selected .NET version.
511    /// 
512    /// # Arguments
513    /// 
514    /// * `meta_host` - Reference to the `ICLRMetaHost` instance.
515    /// 
516    /// # Returns
517    /// 
518    /// * `Ok(ICLRRuntimeInfo)` - If runtime information is retrieved successfully.
519    /// * `Err(ClrError)` - If the retrieval fails.
520    fn get_runtime_info(&self, meta_host: &ICLRMetaHost) -> Result<ICLRRuntimeInfo> {
521        let runtime_version = self.runtime_version.unwrap_or(RuntimeVersion::V4);
522        let version_wide = runtime_version.to_vec();
523        let version = PCWSTR(version_wide.as_ptr());
524        meta_host.GetRuntime::<ICLRRuntimeInfo>(version)
525            .map_err(|e| ClrError::RuntimeInfoError(format!("{e}")))
526    }
527
528    /// Gets the runtime host interface from the provided runtime information.
529    /// 
530    /// # Arguments
531    /// 
532    /// * `runtime_info` - Reference to the `ICLRRuntimeInfo` instance.
533    /// 
534    /// # Returns
535    /// 
536    /// * `Ok(ICorRuntimeHost)` - If the interface is obtained successfully.
537    /// * `Err(ClrError)` - If the retrieval fails.
538    fn get_icor_runtime_host(&self, runtime_info: &ICLRRuntimeInfo) -> Result<ICorRuntimeHost> {
539        runtime_info.GetInterface::<ICorRuntimeHost>(&CLSID_COR_RUNTIME_HOST)
540            .map_err(|e| ClrError::RuntimeHostError(format!("{e}")))
541    }
542
543    /// Gets the runtime host interface from the provided runtime information.
544    /// 
545    /// # Arguments
546    /// 
547    /// * `runtime_info` - Reference to the `ICLRRuntimeInfo` instance.
548    /// 
549    /// # Returns
550    /// 
551    /// * `Ok(ICorRuntimeHost)` - If the interface is obtained successfully.
552    /// * `Err(ClrError)` - If the retrieval fails.
553    fn get_clr_runtime_host(&self, runtime_info: &ICLRRuntimeInfo) -> Result<ICLRuntimeHost> {
554        runtime_info.GetInterface::<ICLRuntimeHost>(&CLSID_ICLR_RUNTIME_HOST)
555            .map_err(|e| ClrError::RuntimeHostError(format!("{e}")))
556    }
557
558    /// Starts the CLR runtime using the provided runtime host.
559    /// 
560    /// # Arguments
561    /// 
562    /// * `iclr_runtime_host` - Reference to the `ICorRuntimeHost` instance.
563    /// 
564    /// # Returns
565    /// 
566    /// * `Ok(())` - If the runtime starts successfully.
567    /// * `Err(ClrError)` - If the runtime fails to start.
568    fn start_runtime(&self, iclr_runtime_host: &ICLRuntimeHost) -> Result<()> {
569        if iclr_runtime_host.Start() != 0 {
570            return Err(ClrError::RuntimeStartError);
571        }
572
573        Ok(())
574    }
575
576    /// Initializes the application domain with the specified name or uses the default domain.
577    /// 
578    /// # Arguments
579    /// 
580    /// * `cor_runtime_host` - Reference to the `ICorRuntimeHost` instance.
581    /// 
582    /// # Returns
583    /// 
584    /// * `Ok(())` - If the application domain is successfully initialized.
585    /// * `Err(ClrError)` - If the initialization fails.
586    fn init_app_domain(&mut self, cor_runtime_host: &ICorRuntimeHost) -> Result<()> {
587        // Creates the application domain based on the specified name or uses the default domain
588        let app_domain = if let Some(domain_name) = &self.domain_name {
589            let wide_domain_name = domain_name.encode_utf16().chain(Some(0)).collect::<Vec<u16>>();
590            cor_runtime_host.CreateDomain(PCWSTR(wide_domain_name.as_ptr()), null_mut())?
591        } else {
592            let uuid = uuid()
593                .to_string()
594                .encode_utf16()
595                .chain(Some(0))
596                .collect::<Vec<u16>>();
597            
598            cor_runtime_host.CreateDomain(PCWSTR(uuid.as_ptr()), null_mut())?
599        };
600
601        // Saves the created application domain
602        self.app_domain = Some(app_domain);
603        Ok(())
604    }
605
606    /// Unloads the current application domain.
607    ///
608    /// This method is used to properly unload a custom AppDomain created by `RustClr`.
609    ///
610    /// # Returns
611    ///
612    /// * `Ok(())` - If the AppDomain is unloaded or not present.
613    /// * `Err(ClrError)` - If unloading the domain fails.
614    fn unload_domain(&self) -> Result<()> {
615        if let (Some(cor_runtime_host), Some(app_domain)) = (
616            &self.cor_runtime_host,
617            &self.app_domain,
618        ) {
619            // Attempt to unload the AppDomain, log error if it fails
620            cor_runtime_host.UnloadDomain(app_domain.cast::<windows_core::IUnknown>()
621                .map(|i| i.as_raw().cast())
622                .unwrap_or(null_mut())
623            )?
624        }
625        
626        Ok(())
627    }
628}
629
630/// Implements the `Drop` trait to release memory when `RustClr` goes out of scope.
631impl<'a> Drop for RustClr<'a> {
632    fn drop(&mut self) {
633        if let Some(cor_runtime_host) = &self.cor_runtime_host {
634            // Attempt to stop the CLR runtime
635            cor_runtime_host.Stop();
636        }
637    }
638}
639
640/// Manages output redirection in the CLR by using a `StringWriter`.
641///
642/// This struct handles the redirection of standard output and error streams
643/// to a `StringWriter` instance, enabling the capture of output produced
644/// by the .NET code.
645pub struct ClrOutput<'a> {
646    /// The `StringWriter` instance used to capture output.
647    string_writer: Option<VARIANT>,
648
649    /// Reference to the `mscorlib` assembly for creating types.
650    mscorlib: &'a _Assembly,
651}
652
653impl<'a> ClrOutput<'a> {
654    /// Creates a new `ClrOutput`.
655    ///
656    /// # Arguments
657    ///
658    /// * `mscorlib` - An instance of the `_Assembly` representing `mscorlib`.
659    ///
660    /// # Returns
661    ///
662    /// * A new instance of `ClrOutput`.
663    pub fn new(mscorlib: &'a _Assembly) -> Self {
664        Self {
665            string_writer: None,
666            mscorlib
667        }
668    }
669
670    /// Redirects standard output and error streams to a `StringWriter`.
671    ///
672    /// # Returns
673    ///
674    /// * `Ok(())` - If the redirection is successful.
675    /// * `Err(ClrError)` - If an error occurs while attempting to redirect the streams.
676    pub fn redirect(&mut self) -> Result<()> {
677        let console = self.mscorlib.resolve_type(s!("System.Console"))?;
678        let string_writer = self.mscorlib.create_instance(s!("System.IO.StringWriter"))?;
679
680        // Invokes the methods
681        console.invoke(s!("SetOut"), None, Some(vec![string_writer]), Invocation::Static)?;
682        console.invoke(s!("SetError"), None, Some(vec![string_writer]), Invocation::Static)?;
683        
684        // Saves the StringWriter instance to retrieve the output later
685        self.string_writer = Some(string_writer);
686        Ok(())
687    }
688
689    /// Restores the original standard output and error streams.
690    ///
691    /// # Returns
692    ///
693    /// * `Ok(())` - If the restoration is successful.
694    /// * `Err(ClrError)` - If an error occurs while restoring the streams.
695    pub fn restore(&mut self) -> Result<()> {
696        let console = self.mscorlib.resolve_type(s!("System.Console"))?;
697        console.method_signature(s!("Void InitializeStdOutError(Boolean)"))?
698            .invoke(None, Some(crate::create_safe_args(vec![true.to_variant()])?))?;
699
700        Ok(())
701    }
702
703    /// Captures the content of the `StringWriter` as a `String`.
704    ///
705    /// # Returns
706    ///
707    /// * `Ok(String)` - The captured output as a string if successful.
708    /// * `Err(ClrError)` - If an error occurs while capturing the output.
709    pub fn capture(&self) -> Result<String> {
710        // Ensure that the StringWriter instance is available
711        let mut instance = self.string_writer.ok_or(ClrError::GenericError("No StringWriter instance found"))?;
712        
713        // Resolve the 'ToString' method on the StringWriter type
714        let string_writer = self.mscorlib.resolve_type(s!("System.IO.StringWriter"))?;
715        let to_string = string_writer.method(s!("ToString"))?;
716        
717        // Invoke 'ToString' on the StringWriter instance
718        let result = to_string.invoke(Some(instance), None)?;
719        
720        // Extract the BSTR from the result
721        let bstr = unsafe { result.Anonymous.Anonymous.Anonymous.bstrVal };
722
723        // Clean Variant
724        unsafe { VariantClear(&mut instance as *mut _) };
725
726        // Convert the BSTR to a UTF-8 String
727        Ok(bstr.to_string())
728    }
729}
730
731/// Represents a simplified interface to the CLR components without loading assemblies.
732#[derive(Debug)]
733pub struct RustClrEnv {
734    /// .NET runtime version to use.
735    pub runtime_version: RuntimeVersion,
736
737    /// MetaHost for accessing CLR components.
738    pub meta_host: ICLRMetaHost,
739
740    /// Runtime information for the specified CLR version.
741    pub runtime_info: ICLRRuntimeInfo,
742
743    /// Host for the CLR runtime.
744    pub cor_runtime_host: ICorRuntimeHost,
745
746    /// Current application domain.
747    pub app_domain: _AppDomain,
748}
749
750impl RustClrEnv {
751    /// Creates a new `RustClrEnv` instance with the specified runtime version.
752    ///
753    /// # Arguments
754    ///
755    /// * `runtime_version` - The .NET runtime version to use.
756    ///
757    /// # Returns
758    ///
759    /// * `Ok(Self)` - If the components are initialized successfully.
760    /// * `Err(ClrError)` - If initialization fails at any step.
761    ///
762    /// # Examples
763    ///
764    /// ```ignore
765    /// use rustclr::{RustClrEnv, RuntimeVersion};
766    ///
767    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
768    ///     // Create a new RustClrEnv with a specific runtime version
769    ///     let clr_env = RustClrEnv::new(Some(RuntimeVersion::V4))?;
770    ///
771    ///     println!("CLR initialized successfully.");
772    ///     Ok(())
773    /// }
774    /// ```
775    pub fn new(runtime_version: Option<RuntimeVersion>) -> Result<Self> {
776        // Initialize MetaHost
777        let meta_host = CLRCreateInstance::<ICLRMetaHost>(&CLSID_CLRMETAHOST)
778            .map_err(|e| ClrError::MetaHostCreationError(format!("{e}")))?;
779
780        // Initialize RuntimeInfo
781        let version_str = runtime_version.unwrap_or(RuntimeVersion::V4).to_vec();
782        let version = PCWSTR(version_str.as_ptr());
783
784        let runtime_info = meta_host.GetRuntime::<ICLRRuntimeInfo>(version)
785            .map_err(|e| ClrError::RuntimeInfoError(format!("{e}")))?;
786
787        // Initialize CorRuntimeHost
788        let cor_runtime_host = runtime_info.GetInterface::<ICorRuntimeHost>(&CLSID_COR_RUNTIME_HOST)
789            .map_err(|e| ClrError::RuntimeHostError(format!("{e}")))?;
790        
791        if cor_runtime_host.Start() != 0 {
792            return Err(ClrError::RuntimeStartError);
793        }
794
795        // Initialize AppDomain
796        let uuid = uuid()
797            .to_string()
798            .encode_utf16()
799            .chain(Some(0))
800            .collect::<Vec<u16>>();
801
802        let app_domain = cor_runtime_host.CreateDomain(PCWSTR(uuid.as_ptr()), null_mut())
803            .map_err(|_| ClrError::NoDomainAvailable)?;
804
805        // Return the initialized instance
806        Ok(Self {
807            runtime_version: runtime_version.unwrap_or(RuntimeVersion::V4),
808            meta_host,
809            runtime_info,
810            cor_runtime_host,
811            app_domain,
812        })
813    }
814}
815
816impl Drop for RustClrEnv {
817    fn drop(&mut self) {
818        // Attempt to unload the AppDomain, log error if it fails
819        if let Err(e) = self.cor_runtime_host.UnloadDomain(
820            self.app_domain.cast::<windows_core::IUnknown>()
821                        .map(|i| i.as_raw().cast())
822                        .unwrap_or(null_mut()))
823        {
824            dinvk::println!("Failed to unload AppDomain: {:?}", e);
825        }
826
827        // Attempt to stop the CLR runtime
828        self.cor_runtime_host.Stop();
829    }
830}
831
832/// Represents the .NET runtime versions supported by RustClr.
833#[derive(Debug, Clone, Copy)]
834pub enum RuntimeVersion {
835    /// .NET Framework 2.0, identified by version `v2.0.50727`.
836    V2,
837    
838    /// .NET Framework 3.0, identified by version `v3.0`.
839    V3,
840    
841    /// .NET Framework 4.0, identified by version `v4.0.30319`.
842    V4,
843
844    /// Represents an unknown or unsupported .NET runtime version.
845    UNKNOWN,
846}
847
848impl RuntimeVersion {
849    /// Converts the `RuntimeVersion` to a wide string representation as a `Vec<u16>`.
850    ///
851    /// # Returns
852    ///
853    /// A `Vec<u16>` containing the .NET runtime version as a null-terminated wide string.
854    fn to_vec(self) -> Vec<u16> {
855        let runtime_version = match self {
856            RuntimeVersion::V2 => "v2.0.50727",
857            RuntimeVersion::V3 => "v3.0",
858            RuntimeVersion::V4 => "v4.0.30319",
859            RuntimeVersion::UNKNOWN => "UNKNOWN",
860        };
861
862        runtime_version.encode_utf16().chain(Some(0)).collect::<Vec<u16>>()
863    }
864}
865
866/// Represents the COFF data source, which can be a file or a memory buffer.
867#[derive(Debug, Clone)]
868pub enum ClrSource<'a> {
869    /// COFF file indicated by a string representing the file path.
870    File(&'a str),
871
872    /// Memory buffer containing COFF data.
873    Buffer(&'a [u8]),
874}
875
876impl<'a> From<&'a str> for ClrSource<'a> {
877    /// Converts a file path (`&'a str`) to a COFF source (`ClrSource::File`).
878    ///
879    /// # Arguments
880    ///
881    /// * `file` - Path of the COFF file.
882    ///
883    /// # Returns
884    ///
885    /// * The input string will be treated as the path of a COFF file.
886    fn from(file: &'a str) -> Self {
887        ClrSource::File(file)
888    }
889}
890
891impl<'a, const N: usize> From<&'a [u8; N]> for ClrSource<'a> {
892    /// Converts a fixed-size byte array (`&[u8; N]`) to a COFF source (`ClrSource::Buffer`).
893    ///
894    /// # Arguments
895    ///
896    /// * `buffer` - A fixed-size byte array representing the COFF file data.
897    ///
898    /// # Returns
899    ///
900    /// * The input byte array will be treated as a COFF buffer in memory.
901    fn from(buffer: &'a [u8; N]) -> Self {
902        ClrSource::Buffer(buffer)
903    }
904}
905
906impl<'a> From<&'a [u8]> for ClrSource<'a> {
907    /// Converts a byte slice (`&[u8]`) to a COFF source (`ClrSource::Buffer`).
908    ///
909    /// # Arguments
910    ///
911    /// * `buffer` - A byte slice representing the COFF file data.
912    ///
913    /// # Returns
914    ///
915    /// * The input byte slice will be treated as a COFF buffer in memory.
916    fn from(buffer: &'a [u8]) -> Self {
917        ClrSource::Buffer(buffer)
918    }
919}
920
921impl<'a> From<&'a Vec<u8>> for ClrSource<'a> {
922    /// Converts a byte slice (`&Vec<u8>`) to a COFF source (`ClrSource::Buffer`).
923    ///
924    /// # Arguments
925    ///
926    /// * `buffer` - A byte slice representing the COFF file data.
927    ///
928    /// # Returns
929    ///
930    /// * The input byte slice will be treated as a COFF buffer in memory.
931    fn from(buffer: &'a Vec<u8>) -> Self {
932        ClrSource::Buffer(buffer.as_slice())
933    }
934}