diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-14 02:45:12 -0600 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-14 02:45:25 -0600 |
| commit | 817e8f5cd26739c69d930d21cd9dc4c0b6e4984e (patch) | |
| tree | 52aed11d6751bb7d20b06acf197d56fac113203d /libs/nak/src/game_finder/bottles.rs | |
| parent | 51a9f8f197727f00896e5de44569b098923527dd (diff) | |
Add FUSE external mapping support, BG3/Oblivion Remastered fixes, fomod-plus and NaK integration
FUSE VFS now deploys non-data-dir mod mappings (Paks, OBSE, UE4SS, etc.)
via real symlinks and injects file-level data-dir mappings (plugins.txt,
loadorder.txt) into the VFS tree. Fixes game launches for Oblivion
Remastered (Root Builder path resolution, script extender support) and
BG3 (Wine prefix documents directory, file mapper symlinks on Linux).
Vendors mo2-fomod-plus plugin and NaK crate for FOMOD installer and
game finder/runtime support.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'libs/nak/src/game_finder/bottles.rs')
| -rw-r--r-- | libs/nak/src/game_finder/bottles.rs | 147 |
1 files changed, 147 insertions, 0 deletions
diff --git a/libs/nak/src/game_finder/bottles.rs b/libs/nak/src/game_finder/bottles.rs new file mode 100644 index 0000000..7d974e8 --- /dev/null +++ b/libs/nak/src/game_finder/bottles.rs @@ -0,0 +1,147 @@ +//! Bottles prefix detection +//! +//! Detects games installed in Bottles prefixes by scanning registry files +//! for known game registry entries. + +use std::fs; +use std::path::{Path, PathBuf}; + +use super::known_games::KNOWN_GAMES; +use super::registry::{read_registry_value, wine_path_to_linux}; +use super::{Game, Launcher}; +use crate::logging::log_info; + +/// Possible Bottles data paths +const BOTTLES_PATHS: &[&str] = &[ + ".local/share/bottles/bottles", + ".var/app/com.usebottles.bottles/data/bottles/bottles", +]; + +/// Detect all games in Bottles prefixes +pub fn detect_bottles_games() -> Vec<Game> { + let mut games = Vec::new(); + let home = match std::env::var("HOME") { + Ok(h) => h, + Err(_) => return games, + }; + + for relative_path in BOTTLES_PATHS { + let bottles_path = PathBuf::from(&home).join(relative_path); + if !bottles_path.exists() { + continue; + } + + log_info(&format!("Found Bottles installation: {}", bottles_path.display())); + + // Scan each bottle + let Ok(entries) = fs::read_dir(&bottles_path) else { + continue; + }; + + for entry in entries.flatten() { + let bottle_path = entry.path(); + if !bottle_path.is_dir() { + continue; + } + + // Each bottle might have games + let bottle_games = scan_bottle(&bottle_path); + games.extend(bottle_games); + } + } + + log_info(&format!("Bottles: Found {} installed games", games.len())); + games +} + +/// Scan a single Bottles bottle for known games +fn scan_bottle(bottle_path: &Path) -> Vec<Game> { + let mut games = Vec::new(); + + // The prefix is directly in the bottle folder (not in pfx subfolder like Steam) + // Check for drive_c to confirm it's a valid Wine prefix + let drive_c = bottle_path.join("drive_c"); + if !drive_c.exists() { + return games; + } + + let bottle_name = bottle_path + .file_name() + .and_then(|n| n.to_str()) + .unwrap_or("Unknown"); + + // Check for each known game by registry entry + for known_game in KNOWN_GAMES { + if let Some(install_path_wine) = + read_registry_value(bottle_path, known_game.registry_path, known_game.registry_value) + { + // Convert Wine path to Linux path + let install_path = match wine_path_to_linux(&install_path_wine) { + Some(p) => p, + None => { + // Try as a relative path within drive_c + if install_path_wine.starts_with("C:") || install_path_wine.starts_with("c:") { + let relative = install_path_wine[2..].replace('\\', "/"); + drive_c.join(relative.trim_start_matches('/')) + } else { + continue; + } + } + }; + + if !install_path.exists() { + continue; + } + + log_info(&format!( + "Found {} in Bottles bottle '{}'", + known_game.name, bottle_name + )); + + games.push(Game { + name: known_game.name.to_string(), + app_id: format!("bottles-{}", known_game.steam_app_id), + install_path, + prefix_path: Some(bottle_path.to_path_buf()), + launcher: Launcher::Bottles, + my_games_folder: known_game.my_games_folder.map(String::from), + appdata_local_folder: known_game.appdata_local_folder.map(String::from), + appdata_roaming_folder: known_game.appdata_roaming_folder.map(String::from), + registry_path: Some(known_game.registry_path.to_string()), + registry_value: Some(known_game.registry_value.to_string()), + }); + } + } + + games +} + +/// Find all Bottles prefixes (for manual prefix selection) +pub fn find_bottles_prefixes() -> Vec<PathBuf> { + let mut prefixes = Vec::new(); + let home = match std::env::var("HOME") { + Ok(h) => h, + Err(_) => return prefixes, + }; + + for relative_path in BOTTLES_PATHS { + let bottles_path = PathBuf::from(&home).join(relative_path); + if !bottles_path.exists() { + continue; + } + + let Ok(entries) = fs::read_dir(&bottles_path) else { + continue; + }; + + for entry in entries.flatten() { + let bottle_path = entry.path(); + // Verify it's a valid Wine prefix + if bottle_path.is_dir() && bottle_path.join("drive_c").exists() { + prefixes.push(bottle_path); + } + } + } + + prefixes +} |
