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}