aboutsummaryrefslogtreecommitdiff
path: root/libs/nak/src/game_finder/bottles.rs
blob: 7d974e8c5dad2cf3420e6c668faaf5d9e0eef6f5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
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
}