extern crate alloc;
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt::Write as _;
use windows::Win32::Foundation::ERROR_FILE_NOT_FOUND;
use windows::Win32::System::Registry::{
RegCloseKey, RegCreateKeyExW, RegDeleteTreeW, RegSetValueExW, HKEY, HKEY_CURRENT_USER,
KEY_WRITE, REG_OPTION_NON_VOLATILE, REG_SZ,
};
use windows_core::PCWSTR;
use crate::clsid::Clsid;
use crate::raw::class_factory::ApoVTable;
pub fn write_registry(vtable: &ApoVTable, dll_path_wide: &[u16]) -> windows_core::Result<()> {
write_registry_with(vtable.clsid, vtable.name, dll_path_wide)
}
pub fn write_registry_with(
clsid: Clsid,
name: &str,
dll_path_wide: &[u16],
) -> windows_core::Result<()> {
let clsid_path = clsid_subkey(&clsid);
let key = create_subkey(HKEY_CURRENT_USER, &clsid_path)?;
set_default_value(key, name);
close(key);
let inproc_path = inproc_subkey(&clsid);
let inproc = create_subkey(HKEY_CURRENT_USER, &inproc_path)?;
let set_path = set_default_wide(inproc, dll_path_wide);
let set_threading = set_named_value(inproc, "ThreadingModel", "Both");
close(inproc);
set_path?;
set_threading?;
Ok(())
}
pub fn clear_registry(clsid: &Clsid) -> windows_core::Result<()> {
let path = clsid_subkey(clsid);
let path_wide = encode_wide(&path);
let err = unsafe { RegDeleteTreeW(HKEY_CURRENT_USER, PCWSTR(path_wide.as_ptr())) };
if err.is_ok() || err == ERROR_FILE_NOT_FOUND {
Ok(())
} else {
Err(err.into())
}
}
fn clsid_subkey(clsid: &Clsid) -> String {
let mut s = String::with_capacity(64);
let _ = write!(s, "Software\\Classes\\CLSID\\{clsid}");
s
}
fn inproc_subkey(clsid: &Clsid) -> String {
let mut s = String::with_capacity(80);
let _ = write!(s, "Software\\Classes\\CLSID\\{clsid}\\InprocServer32");
s
}
fn encode_wide(s: &str) -> Vec<u16> {
s.encode_utf16().chain(core::iter::once(0)).collect()
}
fn create_subkey(parent: HKEY, path: &str) -> windows_core::Result<HKEY> {
let path_wide = encode_wide(path);
let mut key = HKEY::default();
let err = unsafe {
RegCreateKeyExW(
parent,
PCWSTR(path_wide.as_ptr()),
None,
PCWSTR::null(),
REG_OPTION_NON_VOLATILE,
KEY_WRITE,
None,
&mut key,
None,
)
};
err.ok().map(|()| key)
}
fn close(key: HKEY) {
let _ = unsafe { RegCloseKey(key) };
}
fn set_default_value(key: HKEY, value: &str) {
let wide = encode_wide(value);
let _ = unsafe { RegSetValueExW(key, PCWSTR::null(), None, REG_SZ, Some(wide_bytes(&wide))) };
}
fn set_default_wide(key: HKEY, wide: &[u16]) -> windows_core::Result<()> {
unsafe { RegSetValueExW(key, PCWSTR::null(), None, REG_SZ, Some(wide_bytes(wide))).ok() }
}
fn set_named_value(key: HKEY, name: &str, value: &str) -> windows_core::Result<()> {
let name_wide = encode_wide(name);
let value_wide = encode_wide(value);
unsafe {
RegSetValueExW(
key,
PCWSTR(name_wide.as_ptr()),
None,
REG_SZ,
Some(wide_bytes(&value_wide)),
)
.ok()
}
}
fn wide_bytes(w: &[u16]) -> &[u8] {
unsafe { core::slice::from_raw_parts(w.as_ptr().cast::<u8>(), core::mem::size_of_val(w)) }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clsid_subkey_uses_canonical_dash_separated_brace_form() {
let c = Clsid::from_u128(0x12345678_1234_5678_1234_567812345678);
assert_eq!(
clsid_subkey(&c),
"Software\\Classes\\CLSID\\{12345678-1234-5678-1234-567812345678}"
);
}
#[test]
fn inproc_subkey_extends_the_clsid_path() {
let c = Clsid::from_u128(0xAABBCCDD_EEFF_0011_2233_445566778899);
assert_eq!(
inproc_subkey(&c),
"Software\\Classes\\CLSID\\{AABBCCDD-EEFF-0011-2233-445566778899}\\InprocServer32"
);
}
#[test]
fn encode_wide_null_terminates() {
let w = encode_wide("ab");
assert_eq!(w, [b'a' as u16, b'b' as u16, 0]);
}
#[test]
fn wide_bytes_doubles_length() {
let w = [u16::from(b'a'), u16::from(b'b'), 0];
let b = wide_bytes(&w);
assert_eq!(b.len(), 6);
}
}