diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-04-08 03:30:04 -0500 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-04-08 03:30:04 -0500 |
| commit | 3de4056df4ea5d0afbcea388ad8d681daea1aac3 (patch) | |
| tree | fc5978451f9f51b84268f45bd2fa6f5d4a17baaf /libs/nak/src/installers/symlinks.rs | |
| parent | 5ba23a6d7a40052d9e1d4bd0cf018cfe7814d03a (diff) | |
Remove NaK/Rust dependency, port remaining functionality to native C++
Replace NaK Rust crate and nak_ffi with native C++ implementations:
- Game detection (Steam, Heroic, Bottles), VDF parser, icon extraction
- Prefix symlinks, SLR manager, Steam path detection
- Known games database
Add FUSE VFS optimizations: zero-copy reads via fuse_reply_data,
default_permissions mount option, FUSE_CAP_ASYNC_DIO, FUSE_CAP_EXPIRE_ONLY,
lookup cache parent index, cached mode bits, increased I/O buffer sizes.
Update build system, prefix setup, and various fixes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'libs/nak/src/installers/symlinks.rs')
| -rw-r--r-- | libs/nak/src/installers/symlinks.rs | 377 |
1 files changed, 0 insertions, 377 deletions
diff --git a/libs/nak/src/installers/symlinks.rs b/libs/nak/src/installers/symlinks.rs deleted file mode 100644 index e8e477b..0000000 --- a/libs/nak/src/installers/symlinks.rs +++ /dev/null @@ -1,377 +0,0 @@ -//! Symlink management for NaK prefixes -//! -//! Creates symlinks FROM NaK prefix TO game prefixes. -//! Data stays in the game prefix, NaK just has links pointing to it. -//! -//! This inverted approach means: -//! - Game saves remain in their original location (Steam cloud sync works) -//! - NaK prefix provides unified access to all game data -//! - No data duplication or sync issues -//! -//! NaK Tools folder contains convenience symlinks pointing INTO the prefix -//! for easy access to Documents, AppData, etc. - -// Allow unused items - some functions are public API for future use -#![allow(dead_code)] - -use std::fs; -use std::path::Path; - -use crate::game_finder::{detect_all_games, Game, GameScanResult}; -use crate::logging::{log_info, log_warning}; - -// ============================================================================ -// Public API -// ============================================================================ - -/// Create symlinks from NaK prefix to game prefixes for all detected games -/// -/// This creates symlinks in the NaK prefix pointing to the actual save/config -/// folders in each game's own prefix. The symlink direction is: -/// -/// NaK/Documents/My Games/Skyrim -> game_prefix/Documents/My Games/Skyrim -/// NaK/AppData/Local/Skyrim -> game_prefix/AppData/Local/Skyrim -/// -/// Data stays in the game prefix (preserving Steam Cloud sync), while NaK -/// provides unified access through symlinks. -pub fn create_game_symlinks(nak_prefix: &Path, games: &[Game]) { - let users_dir = nak_prefix.join("drive_c/users"); - let username = find_prefix_username(&users_dir); - let user_dir = users_dir.join(&username); - - let documents = user_dir.join("Documents"); - let my_games = documents.join("My Games"); - let appdata_local = user_dir.join("AppData/Local"); - let appdata_roaming = user_dir.join("AppData/Roaming"); - - // Ensure base directories exist (real folders in NaK prefix) - let _ = fs::create_dir_all(&my_games); - let _ = fs::create_dir_all(&appdata_local); - let _ = fs::create_dir_all(&appdata_roaming); - - let mut linked_count = 0; - - for game in games { - // Skip games without prefixes - let Some(game_prefix) = &game.prefix_path else { - log_info(&format!( - "Symlink skip '{}' (app_id {}): no prefix_path", - game.name, game.app_id - )); - continue; - }; - - // Discover the game prefix's user directory - let game_users_dir = game_prefix.join("drive_c/users"); - let game_username = find_prefix_username(&game_users_dir); - let game_user_dir = game_users_dir.join(&game_username); - - let my_games_dir = game_user_dir.join("Documents/My Games"); - log_info(&format!( - "Symlink checking '{}' (app_id {}): prefix={}, user='{}', my_games_exists={}", - game.name, game.app_id, game_prefix.display(), game_username, my_games_dir.is_dir() - )); - - // Scan and symlink ALL folders in the game prefix's Documents/My Games/ - linked_count += scan_and_link_all( - &my_games, - &game_user_dir.join("Documents/My Games"), - "Documents/My Games", - &game.name, - game_prefix, - ); - - // Also link the Documents folder itself for non-My Games entries - // (some games put saves directly in Documents/<GameName>) - linked_count += scan_and_link_all( - &documents, - &game_user_dir.join("Documents"), - "Documents", - &game.name, - game_prefix, - ); - - // Scan and symlink ALL folders in AppData/Local/ - linked_count += scan_and_link_all( - &appdata_local, - &game_user_dir.join("AppData/Local"), - "AppData/Local", - &game.name, - game_prefix, - ); - - // Scan and symlink ALL folders in AppData/Roaming/ - linked_count += scan_and_link_all( - &appdata_roaming, - &game_user_dir.join("AppData/Roaming"), - "AppData/Roaming", - &game.name, - game_prefix, - ); - } - - if linked_count > 0 { - log_info(&format!( - "Created {} symlinks to game prefixes", - linked_count - )); - } - - // Create "My Documents" symlink for compatibility - let my_documents = user_dir.join("My Documents"); - if !my_documents.exists() && fs::symlink_metadata(&my_documents).is_err() { - if let Err(e) = std::os::unix::fs::symlink("Documents", &my_documents) { - log_warning(&format!("Failed to create My Documents symlink: {}", e)); - } - } -} - -/// Create NaK Tools convenience symlinks pointing INTO the prefix -/// -/// Creates symlinks in NaK Tools folder for easy access: -/// - NaK Tools/Prefix Documents -> prefix/drive_c/users/<user>/Documents -/// - NaK Tools/Prefix AppData Local -> prefix/drive_c/users/<user>/AppData/Local -/// - NaK Tools/Prefix AppData Roaming -> prefix/drive_c/users/<user>/AppData/Roaming -pub fn create_nak_tools_symlinks(tools_dir: &Path, prefix_path: &Path) { - let users_dir = prefix_path.join("drive_c/users"); - let username = find_prefix_username(&users_dir); - let user_dir = users_dir.join(&username); - - // Symlink: NaK Tools/Prefix Documents -> prefix Documents - let documents_link = tools_dir.join("Prefix Documents"); - let documents_target = user_dir.join("Documents"); - create_or_update_symlink(&documents_link, &documents_target, "Prefix Documents"); - - // Symlink: NaK Tools/Prefix AppData Local -> prefix AppData/Local - let appdata_local_link = tools_dir.join("Prefix AppData Local"); - let appdata_local_target = user_dir.join("AppData/Local"); - create_or_update_symlink(&appdata_local_link, &appdata_local_target, "Prefix AppData Local"); - - // Symlink: NaK Tools/Prefix AppData Roaming -> prefix AppData/Roaming - let appdata_roaming_link = tools_dir.join("Prefix AppData Roaming"); - let appdata_roaming_target = user_dir.join("AppData/Roaming"); - create_or_update_symlink(&appdata_roaming_link, &appdata_roaming_target, "Prefix AppData Roaming"); - - log_info("Created NaK Tools convenience symlinks to prefix folders"); -} - -/// Create or update a symlink -fn create_or_update_symlink(link_path: &Path, target: &Path, name: &str) { - // Remove existing symlink or file - if link_path.exists() || fs::symlink_metadata(link_path).is_ok() { - let _ = fs::remove_file(link_path); - let _ = fs::remove_dir_all(link_path); - } - - // Create symlink - if let Err(e) = std::os::unix::fs::symlink(target, link_path) { - log_warning(&format!("Failed to create {} symlink: {}", name, e)); - } -} - -/// Create symlinks for all detected games -/// -/// Convenience function that detects games and creates symlinks in one call. -pub fn create_game_symlinks_auto(nak_prefix: &Path) -> GameScanResult { - let result = detect_all_games(); - create_game_symlinks(nak_prefix, &result.games); - result -} - -/// Ensure only the Temp directory exists in AppData/Local -/// -/// MO2 and other tools require AppData/Local/Temp to exist. -/// We create only this essential directory, leaving other game-specific -/// folders to be symlinked from game prefixes. -pub fn ensure_temp_directory(prefix_path: &Path) { - let users_dir = prefix_path.join("drive_c/users"); - let username = find_prefix_username(&users_dir); - let user_dir = users_dir.join(&username); - - let temp_dir = user_dir.join("AppData/Local/Temp"); - if let Err(e) = fs::create_dir_all(&temp_dir) { - log_warning(&format!("Failed to create Temp directory: {}", e)); - } else { - log_info("Ensured AppData/Local/Temp directory exists"); - } -} - -// ============================================================================ -// Internal Functions -// ============================================================================ - -/// Directories to skip when scanning prefix folders for symlinking. -/// These are Wine/Proton internal or system dirs, not game data. -const SKIP_DIRS: &[&str] = &[ - "Temp", "Microsoft", "wine", "Public", "root", - "Application Data", "Cookies", "Local Settings", - "NetHood", "PrintHood", "Recent", "SendTo", - "Start Menu", "Templates", "My Documents", "My Music", - "My Pictures", "My Videos", "Desktop", "Downloads", - "Favorites", "Links", "Searches", - "Contacts", "3D Objects", -]; - -/// Scan all subdirectories in a game prefix folder and create symlinks -/// for each one in the corresponding NaK prefix folder. -/// -/// Returns the number of symlinks created. -fn scan_and_link_all( - nak_base: &Path, - game_base: &Path, - label: &str, - game_name: &str, - _game_prefix: &Path, -) -> usize { - if !game_base.is_dir() { - log_info(&format!( - " scan_and_link_all: {} not a dir for '{}', skipping", - game_base.display(), game_name - )); - return 0; - } - - let Ok(entries) = fs::read_dir(game_base) else { - log_info(&format!( - " scan_and_link_all: cannot read {} for '{}'", - game_base.display(), game_name - )); - return 0; - }; - - let mut count = 0; - for entry in entries.flatten() { - // Only symlink directories (game folders), not loose files - if !entry.path().is_dir() { - continue; - } - - let folder_name = entry.file_name().to_string_lossy().to_string(); - - // Skip Wine/system internal directories - if SKIP_DIRS.iter().any(|&s| s.eq_ignore_ascii_case(&folder_name)) { - continue; - } - - // Skip if it's "My Games" and we're scanning Documents (handled separately) - if label == "Documents" && folder_name == "My Games" { - continue; - } - - let nak_path = nak_base.join(&folder_name); - let source_path = entry.path(); - - if create_symlink_if_needed(&nak_path, &source_path, game_name, label, &folder_name) { - count += 1; - } - } - - count -} - -/// Create a symlink if the target doesn't already exist or is already correct. -/// -/// Returns true if a symlink was created (or already existed correctly). -fn create_symlink_if_needed( - nak_path: &Path, - source_path: &Path, - game_name: &str, - label: &str, - folder_name: &str, -) -> bool { - // Check if target already exists - if nak_path.exists() || fs::symlink_metadata(nak_path).is_ok() { - // Check if it's already a symlink to the correct location - if let Ok(target) = fs::read_link(nak_path) { - if target == source_path { - return true; // Already correctly linked - } - } - // Something else exists here, don't overwrite - return false; - } - - // Ensure parent directory exists - if let Some(parent) = nak_path.parent() { - let _ = fs::create_dir_all(parent); - } - - // Create the symlink - match std::os::unix::fs::symlink(source_path, nak_path) { - Ok(()) => { - log_info(&format!( - "Linked {}/{} -> {} ({})", - label, - folder_name, - source_path.display(), - game_name, - )); - true - } - Err(e) => { - log_warning(&format!( - "Failed to create symlink for {} ({}/{}): {}", - game_name, label, folder_name, e - )); - false - } - } -} - -/// Find the username from a Wine prefix users directory -fn find_prefix_username(users_dir: &Path) -> String { - if let Ok(entries) = fs::read_dir(users_dir) { - for entry in entries.flatten() { - let name = entry.file_name().to_string_lossy().to_string(); - if name != "Public" && name != "root" { - return name; - } - } - } - "steamuser".to_string() -} - -// ============================================================================ -// Oblivion Lowercase INI Symlinks -// ============================================================================ - -/// Create lowercase INI symlinks for Oblivion (some tools expect lowercase) -pub fn create_oblivion_ini_symlinks(prefix_path: &Path) { - let users_dir = prefix_path.join("drive_c/users"); - let username = find_prefix_username(&users_dir); - let oblivion_dir = users_dir - .join(&username) - .join("Documents/My Games/Oblivion"); - - if !oblivion_dir.exists() { - return; - } - - create_lowercase_ini_symlink(&oblivion_dir, "Oblivion.ini", "oblivion.ini"); - create_lowercase_ini_symlink(&oblivion_dir, "OblivionPrefs.ini", "oblivionprefs.ini"); -} - -/// Create a lowercase symlink for an INI file -fn create_lowercase_ini_symlink(dir: &Path, original: &str, lowercase: &str) { - let original_path = dir.join(original); - let lowercase_path = dir.join(lowercase); - - // Only create if original exists and lowercase doesn't - if original_path.exists() - && !lowercase_path.exists() - && fs::symlink_metadata(&lowercase_path).is_err() - { - // Create relative symlink (just the filename) - if let Err(e) = std::os::unix::fs::symlink(original, &lowercase_path) { - log_warning(&format!( - "Failed to create lowercase symlink {} -> {}: {}", - lowercase, original, e - )); - } else { - log_info(&format!( - "Created lowercase INI symlink: {} -> {}", - lowercase, original - )); - } - } -} |
