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}