You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
CleanFlashInstaller/rust/crates/clean_flash_common/src/registry.rs

239 lines
7.8 KiB
Rust

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<windows_sys::Win32::System::Registry::HKEY> = 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<u16> =
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<u16> =
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<u16> = 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<u16> = 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<u16> = parent.encode_utf16().chain(std::iter::once(0)).collect();
let leaf_wide: Vec<u16> = 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<u16> = 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<String> {
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(())
}