use crate::{system_info, InstallError}; /// Apply registry entries using native Windows registry APIs. /// /// The entries are in .reg file format (without the header line). /// Supported syntax: /// - `[HKEY_xxx\path]` — create/open key /// - `[-HKEY_xxx\path]` — delete entire key tree /// - `"name"="string_value"` — set REG_SZ value /// - `"name"=dword:XXXXXXXX` — set REG_DWORD value /// - `"name"=-` — delete a value /// - `@="string_value"` — set the default (unnamed) value #[cfg(windows)] pub fn apply_registry(entries: &[&str]) -> Result<(), InstallError> { let combined = entries.join("\n\n"); let filled = system_info::fill_string(&combined); apply_reg_content(&filled) } #[cfg(windows)] fn apply_reg_content(content: &str) -> Result<(), InstallError> { use windows_sys::Win32::System::Registry::{ RegCloseKey, RegCreateKeyExW, RegDeleteValueW, RegSetValueExW, KEY_WOW64_64KEY, KEY_WRITE, REG_DWORD, REG_OPTION_NON_VOLATILE, REG_SZ, }; let mut current_hkey: Option = None; for line in content.lines() { let line = line.trim(); if line.is_empty() { continue; } // Delete key: [-HKEY_xxx\path] if line.starts_with("[-") && line.ends_with(']') { if let Some(hk) = current_hkey.take() { unsafe { RegCloseKey(hk) }; } let key_path = &line[2..line.len() - 1]; if let Some((root, subkey)) = parse_root_and_subkey(key_path) { delete_key_tree(root, subkey); } continue; } // Open/create key: [HKEY_xxx\path] if line.starts_with('[') && line.ends_with(']') { if let Some(hk) = current_hkey.take() { unsafe { RegCloseKey(hk) }; } let key_path = &line[1..line.len() - 1]; if let Some((root, subkey)) = parse_root_and_subkey(key_path) { let subkey_wide: Vec = subkey.encode_utf16().chain(std::iter::once(0)).collect(); unsafe { let mut hkey = std::ptr::null_mut(); let mut disposition: u32 = 0; let result = RegCreateKeyExW( root, subkey_wide.as_ptr(), 0, std::ptr::null(), REG_OPTION_NON_VOLATILE, KEY_WRITE | KEY_WOW64_64KEY, std::ptr::null(), &mut hkey, &mut disposition, ); if result == 0 { current_hkey = Some(hkey); } } } continue; } // Value operations require an open key. let Some(hkey) = current_hkey else { continue; }; // Default value: @="value" if let Some(rest) = line.strip_prefix("@=") { if let Some(val) = parse_reg_string(rest) { let val_wide: Vec = val.encode_utf16().chain(std::iter::once(0)).collect(); unsafe { RegSetValueExW( hkey, std::ptr::null(), 0, REG_SZ as u32, val_wide.as_ptr() as *const u8, (val_wide.len() * 2) as u32, ); } } continue; } // Named value: "name"=... if !line.starts_with('"') { continue; } let Some((name, value_part)) = parse_name_and_value(line) else { continue; }; let name_wide: Vec = name.encode_utf16().chain(std::iter::once(0)).collect(); // Delete value: "name"=- if value_part == "-" { unsafe { RegDeleteValueW(hkey, name_wide.as_ptr()); } continue; } // DWORD value: "name"=dword:XXXXXXXX if let Some(hex_str) = value_part.strip_prefix("dword:") { if let Ok(dword_val) = u32::from_str_radix(hex_str, 16) { unsafe { RegSetValueExW( hkey, name_wide.as_ptr(), 0, REG_DWORD as u32, &dword_val as *const u32 as *const u8, 4, ); } } continue; } // String value: "name"="value" if let Some(val) = parse_reg_string(value_part) { let val_wide: Vec = val.encode_utf16().chain(std::iter::once(0)).collect(); unsafe { RegSetValueExW( hkey, name_wide.as_ptr(), 0, REG_SZ as u32, val_wide.as_ptr() as *const u8, (val_wide.len() * 2) as u32, ); } continue; } } if let Some(hk) = current_hkey { unsafe { RegCloseKey(hk) }; } Ok(()) } /// Parse a root key name and subkey path from a combined string like /// `HKEY_LOCAL_MACHINE\Software\Something`. #[cfg(windows)] fn parse_root_and_subkey( path: &str, ) -> Option<(windows_sys::Win32::System::Registry::HKEY, &str)> { use windows_sys::Win32::System::Registry::{ HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, HKEY_USERS, }; let (root_name, subkey) = path.split_once('\\')?; let root = match root_name { "HKEY_LOCAL_MACHINE" => HKEY_LOCAL_MACHINE, "HKEY_CURRENT_USER" => HKEY_CURRENT_USER, "HKEY_CLASSES_ROOT" => HKEY_CLASSES_ROOT, "HKEY_USERS" => HKEY_USERS, _ => return None, }; Some((root, subkey)) } /// Delete an entire registry key tree. #[cfg(windows)] fn delete_key_tree(root: windows_sys::Win32::System::Registry::HKEY, subkey: &str) { use windows_sys::Win32::System::Registry::{ RegCloseKey, RegDeleteTreeW, RegOpenKeyExW, KEY_WOW64_64KEY, KEY_WRITE, }; if let Some((parent, leaf)) = subkey.rsplit_once('\\') { let parent_wide: Vec = parent.encode_utf16().chain(std::iter::once(0)).collect(); let leaf_wide: Vec = leaf.encode_utf16().chain(std::iter::once(0)).collect(); unsafe { let mut hkey = std::ptr::null_mut(); let result = RegOpenKeyExW(root, parent_wide.as_ptr(), 0, KEY_WRITE | KEY_WOW64_64KEY, &mut hkey); if result == 0 { RegDeleteTreeW(hkey, leaf_wide.as_ptr()); RegCloseKey(hkey); } } } else { let subkey_wide: Vec = subkey.encode_utf16().chain(std::iter::once(0)).collect(); unsafe { RegDeleteTreeW(root, subkey_wide.as_ptr()); } } } /// Parse a quoted string value: `"some value"` → `some value`. /// Handles escaped backslashes (`\\` → `\`) inside the string. #[cfg(windows)] fn parse_reg_string(s: &str) -> Option { let s = s.trim(); if !s.starts_with('"') || !s.ends_with('"') || s.len() < 2 { return None; } let inner = &s[1..s.len() - 1]; Some(inner.replace("\\\\", "\\")) } /// Parse `"name"=value` into `(name, value)`. #[cfg(windows)] fn parse_name_and_value(line: &str) -> Option<(String, &str)> { let rest = &line[1..]; // skip opening " let end_quote = rest.find('"')?; let name = &rest[..end_quote]; let after_name = &rest[end_quote + 1..]; // skip closing " let value_part = after_name.strip_prefix('=')?; Some((name.to_string(), value_part)) } #[cfg(not(windows))] pub fn apply_registry(_entries: &[&str]) -> Result<(), InstallError> { Ok(()) }