game_manager_lib/sources/
heroic.rs1use 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 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 fn detect_heroic_config_path() -> Result<PathBuf, AppError> {
86 #[cfg(target_os = "linux")]
88 {
89 let home = std::env::var("HOME")
90 .map_err(|_| AppError::ValidationError("HOME não encontrado".into()))?;
91
92 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 let native_path = Path::new(&home).join(".config/heroic");
101 if native_path.exists() {
102 return Ok(native_path);
103 }
104 }
105
106 #[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}