hopefully an awesomesauce (vibecoded) gtk game launcher (very wip)
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104use ira_models::{AchievementStatus, AssetType, Game};use serde::Deserialize;use std::collections::HashMap;
#[derive(Deserialize)]struct AppDetailsName { #[serde(default)] name: String,}
pub fn read_app_name(save_dir: &str, app_id: &str) -> Option<String> { let path = super::paths::data_dir(save_dir, app_id).join("appdetails.json"); let data = std::fs::read(&path).ok()?; let details: AppDetailsName = serde_json::from_slice(&data).ok()?; let name = details.name.trim().to_string(); if name.is_empty() { None } else { Some(name) }}
pub fn populate_image_paths(image_dir: &std::path::Path, game: &mut Game) { let db_id = game.db_id; let _s = tracing::info_span!("populate_image_paths", db_id).entered();
let mut files: std::collections::HashSet<String> = std::fs::read_dir(image_dir) .map(|d| { d.filter_map(|e| e.ok()) .filter_map(|e| e.file_name().to_str().map(String::from)) .collect() }) .unwrap_or_default();
for &at in AssetType::all() { let base = at.file_base(); let (max_w, max_h) = at.thumb_dims(); let small_webp = format!("{}_small.webp", base); let small_jpg = format!("{}_small.jpg", base); if !files.contains(&small_webp) && !files.contains(&small_jpg) { super::paths::ensure_small_image(image_dir, base, max_w, max_h); if image_dir.join(&small_webp).is_file() { files.insert(small_webp); } if image_dir.join(&small_jpg).is_file() { files.insert(small_jpg); } } }
let find = |base: &str| -> Option<std::path::PathBuf> { for ext in &["webp", "jpg"] { let name = format!("{}.{}", base, ext); if files.contains(&name) { return Some(image_dir.join(name)); } } None };
let found: Vec<(AssetType, String)> = AssetType::all() .iter() .filter_map(|&at| { find(&format!("{}_small", at.file_base())) .or_else(|| find(at.file_base())) .map(|p| (at, p.to_string_lossy().into_owned())) }) .collect(); for (at, p) in found { *game.asset_path_mut(at) = p; }}
pub fn set_achievement_earned( save_dir: &str, trophy_source: ira_models::TrophySource, app_id: &str, native_id: &str, ach_name: &str, earned: bool,) -> Result<(), String> { let status_path = super::paths::unlock_status_path(save_dir, trophy_source, app_id, native_id); // A failed read or a corrupt file must refuse the write — otherwise // the map starts empty and the write below erases every recorded // unlock the moment one new achievement is recorded. let mut status_map: HashMap<String, AchievementStatus> = match std::fs::read(&status_path) { Ok(data) => serde_json::from_slice(&data) .map_err(|e| format!("corrupt unlock status {}: {e}", status_path.display()))?, Err(e) if e.kind() == std::io::ErrorKind::NotFound => HashMap::new(), Err(e) => return Err(format!("read {}: {e}", status_path.display())), }; status_map.insert( ach_name.to_string(), AchievementStatus { earned, earned_time: 0, }, ); let b = serde_json::to_string_pretty(&status_map).map_err(|e| e.to_string())?; std::fs::write(&status_path, b).map_err(|e| e.to_string())?; Ok(())}