clroxide 1.1.1

A library that allows you to host the CLR and execute dotnet binaries.
Documentation
use crate::primitives::{
    Class, ICLRRuntimeInfo, IEnumUnknown, IUnknown, IUnknownVtbl, Interface, RuntimeVersion, GUID,
    HRESULT,
};
use std::{collections::HashMap, ffi::c_void, ops::Deref, ptr};

#[repr(C)]
pub struct ICLRMetaHostVtbl {
    pub parent: IUnknownVtbl,
    pub GetRuntime: unsafe extern "system" fn(
        this: *mut c_void,
        pwzVersion: *mut u16,
        riid: *const GUID,
        ppRuntime: *mut *mut c_void,
    ) -> HRESULT,
    pub GetVersionFromFile: unsafe extern "system" fn(this: *mut c_void) -> HRESULT,
    pub EnumerateInstalledRuntimes: unsafe extern "system" fn(
        this: *mut c_void,
        ppEnumerator: *mut *mut IEnumUnknown,
    ) -> HRESULT,
    pub EnumerateLoadedRuntimes: unsafe extern "system" fn(this: *mut c_void) -> HRESULT,
    pub RequestRuntimeLoadedNotification: unsafe extern "system" fn(this: *mut c_void) -> HRESULT,
    pub QueryLegacyV2RuntimeBinding: unsafe extern "system" fn(this: *mut c_void) -> HRESULT,
    pub ExitProcess: unsafe extern "system" fn(this: *mut c_void) -> HRESULT,
}

#[derive(Debug, Clone)]
#[repr(C)]
pub struct ICLRMetaHost {
    pub vtable: *const ICLRMetaHostVtbl,
}

pub type CLRCreateInstance =
    fn(class_id: *const GUID, interface_id: *const GUID, interface: *mut *mut c_void) -> HRESULT;

impl ICLRMetaHost {
    pub fn new(clr_create_instance: CLRCreateInstance) -> Result<*mut ICLRMetaHost, String> {
        let mut ppv: *mut ICLRMetaHost = ptr::null_mut();

        let hr = clr_create_instance(
            &ICLRMetaHost::CLSID,
            &ICLRMetaHost::IID,
            &mut ppv as *mut *mut _ as *mut *mut c_void,
        );

        if hr.is_err() {
            return Err(format!("{:?}", hr));
        }

        if ppv.is_null() {
            return Err("Could not retrieve ICLRMetaHost".into());
        }

        return Ok(ppv);
    }

    pub fn get_first_available_runtime(
        &self,
        prefer_version: Option<RuntimeVersion>,
    ) -> Result<*mut ICLRRuntimeInfo, String> {
        let runtimes = self.get_installed_runtimes()?;

        if let Some(prefer_version) = prefer_version {
            if let Some(runtime) = runtimes.get(&prefer_version) {
                return Ok(*runtime);
            }
        }

        for runtime in runtimes.values() {
            return Ok(*runtime);
        }

        Err("Could not find any runtimes".into())
    }

    pub fn get_runtime(&self, version: RuntimeVersion) -> Result<*mut ICLRRuntimeInfo, String> {
        let version_ptr = version.clone().to_bstr();
        let mut ppv: *mut ICLRRuntimeInfo = ptr::null_mut();

        let hr = unsafe {
            (*self).GetRuntime(
                version_ptr.into_raw() as *mut _,
                &ICLRRuntimeInfo::IID,
                &mut ppv as *mut *mut _ as *mut *mut c_void,
            )
        };

        return match hr.is_ok() {
            true => Ok(ppv),
            false => Err(format!(
                "Could not find a runtime for version `{}`: {:?}",
                version, hr
            )),
        };
    }

    pub fn get_installed_runtimes(
        &self,
    ) -> Result<HashMap<RuntimeVersion, *mut ICLRRuntimeInfo>, String> {
        let mut ieu_ptr: *mut IEnumUnknown = ptr::null_mut();

        let hr = unsafe { (*self).EnumerateInstalledRuntimes(&mut ieu_ptr) };

        if hr.is_err() {
            return Err(format!("Could not enumerate installed runtimes: {:?}", hr));
        }

        if ieu_ptr.is_null() {
            return Err("Could not enumerate installed runtimes.".into());
        }

        let mut hmri: HashMap<RuntimeVersion, *mut ICLRRuntimeInfo> = HashMap::new();

        loop {
            let mut iu_ptr: *mut IUnknown = ptr::null_mut();
            let mut cfetched: u32 = 0;

            let next_hr = unsafe { (*ieu_ptr).Next(1, &mut iu_ptr, &mut cfetched) };

            if next_hr.is_err() || iu_ptr.is_null() {
                break;
            }

            let mut ri_ptr: *mut ICLRRuntimeInfo = ptr::null_mut();

            let inner_hr = unsafe {
                (*iu_ptr).QueryInterface(
                    &ICLRRuntimeInfo::IID,
                    &mut ri_ptr as *mut _ as *mut *mut c_void,
                )
            };

            if inner_hr.is_err() || ri_ptr.is_null() {
                break;
            }

            let version = unsafe { (*ri_ptr).get_version()? };

            hmri.insert(version, ri_ptr);
        }

        Ok(hmri)
    }

    #[inline]
    pub unsafe fn GetRuntime(
        &self,
        pwzVersion: *mut u16,
        riid: *const GUID,
        ppRuntime: *mut *mut c_void,
    ) -> HRESULT {
        ((*self.vtable).GetRuntime)(self as *const _ as *mut _, pwzVersion, riid, ppRuntime)
    }

    #[inline]
    pub unsafe fn EnumerateInstalledRuntimes(
        &self,
        ppEnumerator: *mut *mut IEnumUnknown,
    ) -> HRESULT {
        ((*self.vtable).EnumerateInstalledRuntimes)(self as *const _ as *mut _, ppEnumerator)
    }
}

impl Interface for ICLRMetaHost {
    const IID: GUID = GUID {
        data1: 0xD332DB9E,
        data2: 0xB9B3,
        data3: 0x4125,
        data4: [0x82, 0x07, 0xA1, 0x48, 0x84, 0xF5, 0x32, 0x16],
    };

    fn vtable(&self) -> *const c_void {
        self.vtable as *const _ as *const c_void
    }
}

impl Class for ICLRMetaHost {
    const CLSID: GUID = GUID {
        data1: 0x9280188d,
        data2: 0xe8e,
        data3: 0x4867,
        data4: [0xb3, 0xc, 0x7f, 0xa8, 0x38, 0x84, 0xe8, 0xde],
    };
}

impl Deref for ICLRMetaHost {
    type Target = IUnknown;

    #[inline]
    fn deref(&self) -> &IUnknown {
        unsafe { &*(self as *const ICLRMetaHost as *const IUnknown) }
    }
}