rustclr/
clr.rs

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