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.
239 lines
7.8 KiB
Rust
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(())
|
|
}
|