Implement CLI and silent install / uninstall

This commit is contained in:
Disyer
2026-03-31 22:47:13 +03:00
parent 9547be7f16
commit 4683064637
20 changed files with 2953 additions and 69 deletions
+219 -31
View File
@@ -1,50 +1,238 @@
use crate::{process_utils, system_info, InstallError};
use std::fs;
use std::io::Write;
use crate::{system_info, InstallError};
/// Apply registry contents by writing a .reg file and importing with reg.exe.
/// 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)
}
let content = format!("Windows Registry Editor Version 5.00\n\n{}", 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 temp_dir = std::env::temp_dir();
// Include the process ID to avoid collisions when two instances run concurrently,
// matching the unique-file guarantee of C#'s Path.GetTempFileName().
let reg_file = temp_dir.join(format!("cleanflash_reg_{}.tmp", std::process::id()));
let mut current_hkey: Option<windows_sys::Win32::System::Registry::HKEY> = None;
// Write as UTF-16LE with BOM (Windows .reg format).
{
let mut f = fs::File::create(&reg_file)
.map_err(|e| InstallError::new(format!("Failed to create temp reg file: {}", e)))?;
let utf16: Vec<u16> = content.encode_utf16().collect();
// BOM
f.write_all(&[0xFF, 0xFE])
.map_err(|e| InstallError::new(format!("Failed to write BOM: {}", e)))?;
for word in &utf16 {
f.write_all(&word.to_le_bytes())
.map_err(|e| InstallError::new(format!("Failed to write reg data: {}", e)))?;
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;
}
}
let reg_filename = reg_file.to_string_lossy().to_string();
let result = process_utils::run_process("reg.exe", &["import", &reg_filename]);
let _ = fs::remove_file(&reg_file);
if !result.is_successful() {
return Err(InstallError::new(format!(
"Failed to apply changes to registry: error code {}\n\n{}",
result.exit_code, result.output
)));
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(())
}
}
@@ -6,7 +6,6 @@ use std::fs;
/// This removes the native messaging host binary, the Pepper (pp64) files
/// installed alongside it, and the browser manifests.
pub fn uninstall(form: &dyn ProgressCallback) -> Result<(), InstallError> {
form.update_progress_label("Removing native messaging host...", true);
native_host::uninstall_native_host(form);
// Remove the entire install directory (host binary + pp64 files).