using LANCommander.Launcher.Data; using LANCommander.Launcher.Data.Models; using LANCommander.Launcher.Models; using LANCommander.SDK; using LANCommander.SDK.Extensions; using LANCommander.SDK.Helpers; using LANCommander.SDK.Plugins; using LANCommander.SDK.Plugins.Events; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; using System.Diagnostics; using LANCommander.SDK.Services; namespace LANCommander.Launcher.Services { public class GameService( DatabaseContext dbContext, ILogger logger, AuthenticationService authenticationService, PlaySessionService playSessionService, ProfileClient profileClient, GameClient gameClient, ToolService toolService, ToolClient toolClient, IConnectionClient connectionClient, IPluginEventBus pluginEventBus, IServiceProvider serviceProvider) : BaseDatabaseService(dbContext, logger) { public Dictionary RunningProcesses = new Dictionary(); public async Task> GetImportedOnMapAsync(IEnumerable ids) { var idSet = ids.ToHashSet(); return await Context.Set() .Where(g => idSet.Contains(g.Id)) .Select(g => new { g.Id, g.ImportedOn }) .ToDictionaryAsync(g => g.Id, g => g.ImportedOn); } public delegate Task OnUninstallCompleteHandler(Game game); public event OnUninstallCompleteHandler OnUninstallComplete; public delegate Task OnUninstallHandler(Game game); public event OnUninstallHandler OnUninstall; public async Task UninstallAsync(Game game) { using (var operation = Logger.BeginOperation("Uninstalling game {GameTitle} ({GameId})", game.Title, game.Id)) { try { OnUninstall?.Invoke(game); await pluginEventBus.PublishAsync(new GameUninstallingEvent(game.Id, game.InstallDirectory)); var installService = serviceProvider.GetService(); installService?.ClearCompleted(game.Id); await gameClient.UninstallAsync(game.InstallDirectory, game.Id); if (game.BaseGameId.HasValue) { var libraryService = serviceProvider.GetService(); var isInstalled = await libraryService!.IsInstalledAsync(game.BaseGameId.Value); if (!isInstalled) { var baseGame = await GetAsync(game.BaseGameId.Value); await gameClient.UninstallAsync(game.InstallDirectory, baseGame?.Id ?? game.BaseGameId.Value); ClearGameState(baseGame!, skipAddons: true); } } // Uninstall any tools that were installed for this game. Tools are installed // into the game's own directory and tracked per game, so uninstalling this // game only removes its copy and leaves the tool intact for other games that // share it. This must run before ClearGameState clears the install directory. var installedTools = await toolService.GetInstalledToolsForGameAsync(game.Id); foreach (var gameTool in installedTools) { try { await toolClient.UninstallAsync(game.InstallDirectory, gameTool.ToolId); await toolService.SetToolUninstalledAsync(game.Id, gameTool.ToolId); } catch (Exception ex) { Logger?.LogError(ex, "Could not uninstall tool {ToolId} from game {GameId}", gameTool.ToolId, game.Id); } } ClearGameState(game); await UpdateAsync(game); OnUninstallComplete?.Invoke(game); await pluginEventBus.PublishAsync(new GameUninstalledEvent(game.Id)); operation.Complete(); } catch (Exception ex) { Logger?.LogError(ex, "Game {GameTitle} ({GameId}) could not be uninstalled", game.Title, game.Id); } } } public async Task Run(Game game, SDK.Models.Manifest.Action action) { Guid userId; if (connectionClient.IsConnected()) { var profile = await profileClient.GetAsync(); userId = profile.Id; } else { userId = authenticationService.GetUserId(); } try { var latestSession = await playSessionService.GetLatestSession(game.Id, userId); await playSessionService.StartSession(game.Id, userId); await gameClient.RunAsync(game.InstallDirectory, game.Id, action, latestSession?.End); } catch (Exception ex) { Logger?.LogError(ex, "Game failed to run"); throw; } finally { await playSessionService.EndSession(game.Id, userId); } } protected void ClearGameState(Game game, bool skipAddons = false) { if (game == null) return; game.InstallDirectory = null; game.Installed = false; game.InstalledOn = null; game.InstalledVersion = null; if (!skipAddons) { foreach (var addon in (game.DependentGames ?? [])) { ClearGameState(addon); } } } } }