mirror of
https://github.com/Novattz/creamlinux-installer.git
synced 2026-05-02 04:52:03 -04:00
184 lines
5.5 KiB
Rust
184 lines
5.5 KiB
Rust
use log::{info, warn};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::fs;
|
|
use std::path::{Path, PathBuf};
|
|
use walkdir::WalkDir;
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct EpicGame {
|
|
pub app_name: String,
|
|
pub title: String,
|
|
pub install_path: String,
|
|
pub executable: String,
|
|
pub box_art_url: Option<String>,
|
|
pub scream_installed: bool,
|
|
pub koaloader_installed: bool,
|
|
/// True when Koaloader was installed using version.dll as a fallback
|
|
/// because no matching proxy import was detected in the game's PE files.
|
|
pub proxy_fallback_used: bool,
|
|
}
|
|
|
|
/// Minimal fields we need from installed.json entries.
|
|
#[derive(Debug, Deserialize)]
|
|
struct InstalledEntry {
|
|
title: String,
|
|
install_path: String,
|
|
executable: String,
|
|
#[serde(default)]
|
|
is_dlc: bool,
|
|
}
|
|
|
|
fn legendary_config_dir() -> Option<PathBuf> {
|
|
let home = std::env::var("HOME").ok()?;
|
|
let path = PathBuf::from(&home)
|
|
.join(".config")
|
|
.join("heroic")
|
|
.join("legendaryConfig")
|
|
.join("legendary");
|
|
if path.exists() {
|
|
Some(path)
|
|
} else {
|
|
warn!("Heroic legendary config dir not found at: {}", path.display());
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn scan_epic_games() -> Vec<EpicGame> {
|
|
let legendary_dir = match legendary_config_dir() {
|
|
Some(d) => d,
|
|
None => return Vec::new(),
|
|
};
|
|
|
|
let installed_path = legendary_dir.join("installed.json");
|
|
if !installed_path.exists() {
|
|
warn!("installed.json not found at: {}", installed_path.display());
|
|
return Vec::new();
|
|
}
|
|
|
|
let content = match fs::read_to_string(&installed_path) {
|
|
Ok(c) => c,
|
|
Err(e) => {
|
|
warn!("Failed to read installed.json: {}", e);
|
|
return Vec::new();
|
|
}
|
|
};
|
|
|
|
let installed: serde_json::Value = match serde_json::from_str(&content) {
|
|
Ok(v) => v,
|
|
Err(e) => {
|
|
warn!("Failed to parse installed.json: {}", e);
|
|
return Vec::new();
|
|
}
|
|
};
|
|
|
|
let metadata_dir = legendary_dir.join("metadata");
|
|
let mut games = Vec::new();
|
|
|
|
if let Some(obj) = installed.as_object() {
|
|
for (app_name, entry_val) in obj {
|
|
let entry: InstalledEntry = match serde_json::from_value(entry_val.clone()) {
|
|
Ok(e) => e,
|
|
Err(e) => {
|
|
warn!("Failed to parse installed entry {}: {}", app_name, e);
|
|
continue;
|
|
}
|
|
};
|
|
|
|
if entry.is_dlc {
|
|
continue;
|
|
}
|
|
|
|
let install_path = PathBuf::from(&entry.install_path);
|
|
if !install_path.exists() {
|
|
warn!(
|
|
"Install path does not exist for {}: {}",
|
|
app_name, entry.install_path
|
|
);
|
|
continue;
|
|
}
|
|
|
|
let box_art_url = get_box_art(&metadata_dir, app_name);
|
|
let scream_installed = check_screamapi_installed(&install_path);
|
|
let koaloader_installed = check_koaloader_installed(&install_path);
|
|
|
|
info!(
|
|
"Found Epic game: {} ({}), ScreamAPI={}, Koaloader={}",
|
|
entry.title, app_name, scream_installed, koaloader_installed
|
|
);
|
|
|
|
games.push(EpicGame {
|
|
app_name: app_name.clone(),
|
|
title: entry.title,
|
|
install_path: entry.install_path,
|
|
executable: entry.executable,
|
|
box_art_url,
|
|
scream_installed,
|
|
koaloader_installed,
|
|
proxy_fallback_used: false,
|
|
});
|
|
}
|
|
}
|
|
|
|
info!("Found {} Epic games", games.len());
|
|
games
|
|
}
|
|
|
|
/// Extract the "DieselGameBox" image URL from a game's metadata JSON.
|
|
/// We read the top-level keyImages array directly from the JSON value,
|
|
/// which avoids pulling in DLC images from dlcItemList.
|
|
fn get_box_art(metadata_dir: &Path, app_name: &str) -> Option<String> {
|
|
let meta_path = metadata_dir.join(format!("{}.json", app_name));
|
|
if !meta_path.exists() {
|
|
return None;
|
|
}
|
|
|
|
let content = fs::read_to_string(&meta_path).ok()?;
|
|
let val: serde_json::Value = serde_json::from_str(&content).ok()?;
|
|
|
|
let key_images = val
|
|
.get("metadata")
|
|
.and_then(|m| m.get("keyImages"))
|
|
.and_then(|k| k.as_array())?;
|
|
|
|
// Prefer landscape (DieselGameBox), fall back to portrait or logo
|
|
for preferred in &["DieselGameBox", "DieselGameBoxTall", "DieselGameBoxLogo"] {
|
|
if let Some(url) = key_images.iter().find_map(|img| {
|
|
if img.get("type").and_then(|t| t.as_str()) == Some(preferred) {
|
|
img.get("url").and_then(|u| u.as_str()).map(str::to_owned)
|
|
} else {
|
|
None
|
|
}
|
|
}) {
|
|
return Some(url);
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
pub fn check_screamapi_installed(install_path: &Path) -> bool {
|
|
for entry in WalkDir::new(install_path)
|
|
.max_depth(8)
|
|
.into_iter()
|
|
.filter_map(Result::ok)
|
|
{
|
|
let filename = entry.file_name().to_string_lossy().to_lowercase();
|
|
if filename.starts_with("eossdk-win") && filename.ends_with("_o.dll") {
|
|
return true;
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
pub fn check_koaloader_installed(install_path: &Path) -> bool {
|
|
for entry in WalkDir::new(install_path)
|
|
.max_depth(4)
|
|
.into_iter()
|
|
.filter_map(Result::ok)
|
|
{
|
|
if entry.file_name().to_string_lossy() == "Koaloader.config.json" {
|
|
return true;
|
|
}
|
|
}
|
|
false
|
|
} |