diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 7b738ef..19b8128 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -10,6 +10,7 @@ mod searcher; mod unlockers; mod smokeapi_config; +use crate::unlockers::{CreamLinux, SmokeAPI, Unlocker}; use dlc_manager::DlcInfoWithState; use installer::{Game, InstallerAction, InstallerType}; use log::{debug, error, info, warn}; @@ -456,6 +457,146 @@ fn delete_smokeapi_config(game_path: String) -> Result<(), String> { smokeapi_config::delete_config(&game_path) } +#[tauri::command] +async fn resolve_platform_conflict( + game_id: String, + conflict_type: String, // "cream-to-proton" or "smoke-to-native" + state: State<'_, AppState>, + app_handle: tauri::AppHandle, +) -> Result { + info!( + "Resolving platform conflict for game {}: {}", + game_id, conflict_type + ); + + let game = { + let games = state.games.lock(); + games + .get(&game_id) + .cloned() + .ok_or_else(|| format!("Game with ID {} not found", game_id))? + }; + + let game_title = game.title.clone(); + + // Emit progress + installer::emit_progress( + &app_handle, + &format!("Resolving Conflict: {}", game_title), + "Removing conflicting files...", + 50.0, + false, + false, + None, + ); + + // Perform the appropriate removal based on conflict type + match conflict_type.as_str() { + "cream-to-proton" => { + // Remove CreamLinux files (bypassing native check) + info!("Removing CreamLinux files from Proton game: {}", game_title); + + CreamLinux::uninstall_from_game(&game.path, &game.id) + .await + .map_err(|e| format!("Failed to remove CreamLinux files: {}", e))?; + + // Remove version from manifest + crate::cache::remove_creamlinux_version(&game.path)?; + } + "smoke-to-native" => { + // Remove SmokeAPI files (bypassing proton check) + info!("Removing SmokeAPI files from native game: {}", game_title); + + // For native games, we need to manually remove backup files since + // the main DLL might already be gone + // Look for and remove *_o.dll backup files + use walkdir::WalkDir; + let mut removed_files = false; + + for entry in WalkDir::new(&game.path) + .max_depth(5) + .into_iter() + .filter_map(Result::ok) + { + let path = entry.path(); + if !path.is_file() { + continue; + } + + let filename = path.file_name().unwrap_or_default().to_string_lossy(); + + // Remove steam_api*_o.dll backup files + if filename.starts_with("steam_api") && filename.ends_with("_o.dll") { + match std::fs::remove_file(path) { + Ok(_) => { + info!("Removed SmokeAPI backup file: {}", path.display()); + removed_files = true; + } + Err(e) => { + warn!("Failed to remove backup file {}: {}", path.display(), e); + } + } + } + } + + // Also try the normal uninstall if api_files are present + if !game.api_files.is_empty() { + let api_files_str = game.api_files.join(","); + if let Err(e) = SmokeAPI::uninstall_from_game(&game.path, &api_files_str).await { + // Don't fail if this errors - we might have already cleaned up manually above + warn!("SmokeAPI uninstall warning: {}", e); + } + } + + if !removed_files { + warn!("No SmokeAPI files found to remove for: {}", game_title); + } + + // Remove version from manifest + crate::cache::remove_smokeapi_version(&game.path)?; + } + _ => return Err(format!("Invalid conflict type: {}", conflict_type)), + } + + installer::emit_progress( + &app_handle, + &format!("Conflict Resolved: {}", game_title), + "Conflicting files have been removed successfully!", + 100.0, + true, + false, + None, + ); + + // Update game state + let updated_game = { + let mut games_map = state.games.lock(); + let game = games_map + .get_mut(&game_id) + .ok_or_else(|| format!("Game with ID {} not found after conflict resolution", game_id))?; + + match conflict_type.as_str() { + "cream-to-proton" => { + game.cream_installed = false; + } + "smoke-to-native" => { + game.smoke_installed = false; + } + _ => {} + } + + game.installing = false; + game.clone() + }; + + if let Err(e) = app_handle.emit("game-updated", &updated_game) { + warn!("Failed to emit game-updated event: {}", e); + } + + info!("Platform conflict resolved successfully for: {}", game_title); + Ok(updated_game) +} + fn setup_logging() -> Result<(), Box> { use log::LevelFilter; use log4rs::append::file::FileAppender; @@ -516,6 +657,7 @@ fn main() { read_smokeapi_config, write_smokeapi_config, delete_smokeapi_config, + resolve_platform_conflict, ]) .setup(|app| { info!("Tauri application setup"); diff --git a/src-tauri/src/searcher.rs b/src-tauri/src/searcher.rs index a806be5..652383b 100644 --- a/src-tauri/src/searcher.rs +++ b/src-tauri/src/searcher.rs @@ -256,11 +256,7 @@ fn check_creamlinux_installed(game_path: &Path) -> bool { // Check if a game has SmokeAPI installed fn check_smokeapi_installed(game_path: &Path, api_files: &[String]) -> bool { - if api_files.is_empty() { - return false; - } - - // SmokeAPI creates backups with _o.dll suffix + // First check the provided api_files for backup files for api_file in api_files { let api_path = game_path.join(api_file); let api_dir = api_path.parent().unwrap_or(game_path); @@ -275,6 +271,28 @@ fn check_smokeapi_installed(game_path: &Path, api_files: &[String]) -> bool { return true; } } + + // Also scan for orphaned backup files (in case the main DLL was removed) + // This handles the Proton->Native switch case where steam_api*.dll is gone + // but steam_api*_o.dll backup remains + for entry in WalkDir::new(game_path) + .max_depth(5) + .into_iter() + .filter_map(Result::ok) + { + let path = entry.path(); + if !path.is_file() { + continue; + } + + let filename = path.file_name().unwrap_or_default().to_string_lossy(); + + // Look for steam_api*_o.dll backup files (SmokeAPI pattern) + if filename.starts_with("steam_api") && filename.ends_with("_o.dll") { + debug!("Found orphaned SmokeAPI backup file: {}", path.display()); + return true; + } + } false } @@ -631,12 +649,10 @@ pub async fn find_installed_games(steamapps_paths: &[PathBuf]) -> Vec // Check for CreamLinux installation let cream_installed = check_creamlinux_installed(&game_path); - // Check for SmokeAPI installation (only for non-native games with Steam API DLLs) - let smoke_installed = if !is_native && !api_files.is_empty() { - check_smokeapi_installed(&game_path, &api_files) - } else { - false - }; + // Check for SmokeAPI installation + // For Proton games: check if api_files exist + // For Native games: ALSO check for orphaned backup files (proton->native switch) + let smoke_installed = check_smokeapi_installed(&game_path, &api_files); // Create the game info let game_info = GameInfo {