diff options
Diffstat (limited to 'libs/nak/src/game_finder/registry.rs')
| -rw-r--r-- | libs/nak/src/game_finder/registry.rs | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/libs/nak/src/game_finder/registry.rs b/libs/nak/src/game_finder/registry.rs new file mode 100644 index 0000000..e843b6e --- /dev/null +++ b/libs/nak/src/game_finder/registry.rs @@ -0,0 +1,227 @@ +//! Wine registry parsing utilities +//! +//! Parses Wine registry files (system.reg, user.reg) to find game install paths. + +use std::fs; +use std::path::{Path, PathBuf}; + +use crate::logging::log_warning; + +/// Read a registry value from a Wine prefix registry file +/// +/// # Arguments +/// * `prefix_path` - Path to the Wine prefix (containing system.reg, user.reg) +/// * `key_path` - Registry key path (e.g., "Software\\Bethesda Softworks\\Skyrim") +/// * `value_name` - Name of the value to read (e.g., "Installed Path") +/// +/// Returns the value as a string, or None if not found +pub fn read_registry_value( + prefix_path: &Path, + key_path: &str, + value_name: &str, +) -> Option<String> { + // Try system.reg first (HKEY_LOCAL_MACHINE) + let system_reg = prefix_path.join("system.reg"); + if let Some(value) = read_value_from_reg_file(&system_reg, key_path, value_name) { + return Some(value); + } + + // Try user.reg (HKEY_CURRENT_USER) + let user_reg = prefix_path.join("user.reg"); + if let Some(value) = read_value_from_reg_file(&user_reg, key_path, value_name) { + return Some(value); + } + + None +} + +/// Read a value from a specific .reg file +fn read_value_from_reg_file(reg_file: &Path, key_path: &str, value_name: &str) -> Option<String> { + let content = fs::read_to_string(reg_file).ok()?; + + // Convert the key path to Wine's format + // Wine uses lowercase keys with escaped backslashes + // e.g., [Software\\Bethesda Softworks\\Skyrim] becomes [software\\\\bethesda softworks\\\\skyrim] + let wine_key = format!( + "[{}]", + key_path.to_lowercase().replace('\\', "\\\\") + ); + + // Also try with the Wow6432Node variant for 32-bit apps on 64-bit Wine + let wine_key_wow64 = format!( + "[software\\\\wow6432node\\\\{}]", + key_path + .strip_prefix("Software\\") + .unwrap_or(key_path) + .to_lowercase() + .replace('\\', "\\\\") + ); + + // Find the key section and extract the value + for key_to_find in [&wine_key, &wine_key_wow64] { + if let Some(value) = find_value_in_content(&content, key_to_find, value_name) { + return Some(value); + } + } + + None +} + +/// Find a value within registry file content +fn find_value_in_content(content: &str, key: &str, value_name: &str) -> Option<String> { + let mut in_target_key = false; + let value_name_lower = value_name.to_lowercase(); + + for line in content.lines() { + let trimmed = line.trim(); + + // Check for key header + if trimmed.starts_with('[') && trimmed.ends_with(']') { + in_target_key = trimmed.to_lowercase() == key.to_lowercase(); + continue; + } + + // If we're in the target key, look for the value + if in_target_key { + // Empty line or new section means we've left the key + if trimmed.is_empty() { + continue; + } + if trimmed.starts_with('[') { + break; + } + + // Parse value line: "ValueName"="value" or @="default value" + if let Some((name, value)) = parse_reg_value_line(trimmed) { + if name.to_lowercase() == value_name_lower { + return Some(value); + } + } + } + } + + None +} + +/// Parse a registry value line like "ValueName"="value" +fn parse_reg_value_line(line: &str) -> Option<(String, String)> { + // Format: "name"="value" or "name"=dword:00000000 or @="default" + let line = line.trim(); + + // Find the = separator + let eq_pos = line.find('=')?; + let (name_part, value_part) = line.split_at(eq_pos); + let value_part = &value_part[1..]; // Skip the '=' + + // Extract name (remove quotes) + let name = if name_part == "@" { + "@".to_string() + } else { + name_part.trim().trim_matches('"').to_string() + }; + + // Extract value + let value = if value_part.starts_with('"') { + // String value - need to handle escapes + parse_quoted_reg_value(value_part)? + } else if value_part.starts_with("dword:") { + // DWORD value - convert to decimal string + let hex = value_part.strip_prefix("dword:")?; + let num = u32::from_str_radix(hex, 16).ok()?; + num.to_string() + } else { + // Other types - return as-is + value_part.to_string() + }; + + Some((name, value)) +} + +/// Parse a quoted registry value, handling Wine's escape sequences +fn parse_quoted_reg_value(s: &str) -> Option<String> { + let s = s.trim(); + if !s.starts_with('"') { + return None; + } + + let mut result = String::new(); + let chars = s[1..].chars(); + let mut prev_was_backslash = false; + + for c in chars { + if prev_was_backslash { + match c { + 'n' => result.push('\n'), + 'r' => result.push('\r'), + 't' => result.push('\t'), + '\\' => result.push('\\'), + '"' => result.push('"'), + _ => { + result.push('\\'); + result.push(c); + } + } + prev_was_backslash = false; + } else if c == '\\' { + prev_was_backslash = true; + } else if c == '"' { + break; // End of string + } else { + result.push(c); + } + } + + Some(result) +} + +/// Convert a Wine path (Z:\path\to\file) to a Linux path +pub fn wine_path_to_linux(wine_path: &str) -> Option<PathBuf> { + let path = wine_path.trim(); + + // Handle Z: drive (maps to /) + if path.starts_with("Z:") || path.starts_with("z:") { + let linux_path = path[2..].replace('\\', "/"); + return Some(PathBuf::from(linux_path)); + } + + // Handle C: drive (maps to prefix/drive_c) + // Note: This requires knowing the prefix path, so we can't convert it here + // For now, we'll just return None for C: paths + if path.starts_with("C:") || path.starts_with("c:") { + log_warning(&format!( + "Cannot convert C: drive path without prefix: {}", + path + )); + return None; + } + + None +} + +/// Check if a Wine prefix contains a specific game by registry key +pub fn has_game_registry( + prefix_path: &Path, + registry_path: &str, + registry_value: &str, +) -> bool { + read_registry_value(prefix_path, registry_path, registry_value).is_some() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_reg_value_line() { + let (name, value) = + parse_reg_value_line(r#""Installed Path"="Z:\\mnt\\games\\Skyrim""#).unwrap(); + assert_eq!(name, "Installed Path"); + assert_eq!(value, r"Z:\mnt\games\Skyrim"); + } + + #[test] + fn test_wine_path_to_linux() { + let linux = wine_path_to_linux(r"Z:\mnt\games\Skyrim").unwrap(); + assert_eq!(linux, PathBuf::from("/mnt/games/Skyrim")); + } +} |
