Skip to main content

game_manager_lib/sources/
heroic.rs

1//! Módulo para integração com o Heroic Games Launcher
2//!
3//! Fornece funcionalidade para detectar jogos instalados via Heroic, lendo os arquivos de
4//! configuração do Heroic e mapeando-os para o formato genérico de jogos usado pela aplicação.
5//!
6//! Suporta **Linux** (instalação nativa e Flatpak) e **Windows** (detecção automática via
7//! `%APPDATA%\heroic` ou caminho personalizado fornecido pelo usuário).
8//!
9//! ## Caminhos de configuração detectados automaticamente
10//!
11//! **Linux (nativo):** `~/.config/heroic/installed.json`
12//! **Linux (Flatpak):** `~/.var/app/com.heroicgameslauncher.hgl/config/heroic/installed.json`
13//! **Windows:** `%APPDATA%\heroic\installed.json`
14//!
15//! ## Aviso sobre duplicatas
16//!
17//! Se o usuário importar jogos via Heroic **e** via um launcher nativo (Epic, GOG, etc.),
18//! os mesmos títulos aparecerão duas vezes na biblioteca com plataformas diferentes
19//! (ex.: "Heroic" e "Epic Games"). Isso é esperado — cada entrada representa uma
20//! instalação/plataforma distinta.
21
22use std::fs;
23use std::path::{Path, PathBuf};
24
25use serde::Deserialize;
26
27use crate::errors::AppError;
28use crate::sources::providers::SourceGame;
29
30#[derive(Debug, Deserialize)]
31struct HeroicInstalledGame {
32    app_name: Option<String>,
33    title: Option<String>,
34    install_path: Option<String>,
35    executable: Option<String>,
36}
37
38#[derive(Debug, Deserialize)]
39struct HeroicInstalledFile {
40    installed: Vec<HeroicInstalledGame>,
41}
42
43pub struct HeroicSource;
44
45impl HeroicSource {
46    /// Importa os jogos instalados via Heroic.
47    ///
48    /// `config_path_override` — permite fornecer um caminho personalizado para o diretório de
49    /// configuração do Heroic. Quando `None`, a detecção automática é usada.
50    pub async fn import_installed(
51        config_path_override: Option<PathBuf>,
52    ) -> Result<Vec<SourceGame>, AppError> {
53        let config_path = match config_path_override {
54            Some(path) => path,
55            None => Self::detect_heroic_config_path()?,
56        };
57
58        let installed_file = config_path.join("installed.json");
59
60        if !installed_file.exists() {
61            return Ok(vec![]);
62        }
63
64        let content = fs::read_to_string(&installed_file)?;
65
66        let parsed: HeroicInstalledFile = serde_json::from_str(&content)?;
67
68        let mut games = Vec::new();
69
70        for game in parsed.installed {
71            if let Some(source_game) = Self::map_to_source_game(game) {
72                games.push(source_game);
73            }
74        }
75
76        Ok(games)
77    }
78
79    /// Detecta automaticamente o diretório de configuração do Heroic.
80    ///
81    /// Ordem de verificação:
82    /// 1. **Linux Flatpak:** `~/.var/app/com.heroicgameslauncher.hgl/config/heroic`
83    /// 2. **Linux nativo:** `~/.config/heroic`
84    /// 3. **Windows:** `%APPDATA%\heroic`
85    fn detect_heroic_config_path() -> Result<PathBuf, AppError> {
86        // === LINUX ===
87        #[cfg(target_os = "linux")]
88        {
89            let home = std::env::var("HOME")
90                .map_err(|_| AppError::ValidationError("HOME não encontrado".into()))?;
91
92            // Flatpak path
93            let flatpak_path =
94                Path::new(&home).join(".var/app/com.heroicgameslauncher.hgl/config/heroic");
95            if flatpak_path.exists() {
96                return Ok(flatpak_path);
97            }
98
99            // Native installation path
100            let native_path = Path::new(&home).join(".config/heroic");
101            if native_path.exists() {
102                return Ok(native_path);
103            }
104        }
105
106        // === WINDOWS ===
107        #[cfg(target_os = "windows")]
108        {
109            if let Ok(appdata) = std::env::var("APPDATA") {
110                let path = Path::new(&appdata).join("heroic");
111                if path.exists() {
112                    return Ok(path);
113                }
114            }
115        }
116
117        Err(AppError::ValidationError(
118            "Heroic não encontrado no sistema. Verifique se está instalado ou informe o diretório manualmente.".into(),
119        ))
120    }
121
122    fn map_to_source_game(game: HeroicInstalledGame) -> Option<SourceGame> {
123        let title = game.title?;
124        let install_path = game.install_path?;
125        let executable = game.executable?;
126        let app_name = game.app_name?;
127
128        let full_executable_path = Path::new(&install_path)
129            .join(&executable)
130            .to_string_lossy()
131            .to_string();
132
133        Some(SourceGame {
134            platform: "Heroic".to_string(),
135            platform_game_id: app_name,
136            name: Some(title),
137            installed: true,
138            executable_path: Some(full_executable_path),
139            install_path: Some(install_path),
140            playtime_minutes: None,
141            last_played: None,
142        })
143    }
144}