static void Main(string[] args) { var arguments = new Arguments(args); Log.AddChannel("debug", "dedicated-debug.log"); Log.AddChannel("perf", "dedicated-perf.log"); Log.AddChannel("server", "dedicated-server.log"); Log.AddChannel("nat", "dedicated-nat.log"); // Special case handling of Game.Mod argument: if it matches a real filesystem path // then we use this to override the mod search path, and replace it with the mod id var modArgument = arguments.GetValue("Game.Mod", null); string customModPath = null; if (modArgument != null && (File.Exists(modArgument) || Directory.Exists(modArgument))) { customModPath = modArgument; arguments.ReplaceValue("Game.Mod", Path.GetFileNameWithoutExtension(modArgument)); } // HACK: The engine code assumes that Game.Settings is set. // This isn't nearly as bad as ModData, but is still not very nice. Game.InitializeSettings(arguments); var settings = Game.Settings.Server; var mod = Game.Settings.Game.Mod; var mods = new InstalledMods(customModPath); // HACK: The engine code *still* assumes that Game.ModData is set var modData = Game.ModData = new ModData(mods[mod], mods); modData.MapCache.LoadMaps(); settings.Map = modData.MapCache.ChooseInitialMap(settings.Map, new MersenneTwister()); Console.WriteLine("[{0}] Starting dedicated server for mod: {1}", DateTime.Now.ToString(settings.TimestampFormat), mod); while (true) { var server = new Server(new IPEndPoint(IPAddress.Any, settings.ListenPort), settings, modData, true); while (true) { Thread.Sleep(1000); if (server.State == ServerState.GameStarted && server.Conns.Count < 1) { Console.WriteLine("[{0}] No one is playing, shutting down...", DateTime.Now.ToString(settings.TimestampFormat)); server.Shutdown(); break; } } Console.WriteLine("[{0}] Starting a new server instance...", DateTime.Now.ToString(settings.TimestampFormat)); } }
/// <summary> /// Installs a mod into the game. /// </summary> public async Task <bool> Install(Mod m) { string mPath = Mod.InstallPath + m.Slug; string mBackupPath = Mod.BackupPath + m.Slug; if (!Directory.Exists(mBackupPath)) { Directory.CreateDirectory(mBackupPath); } Console.WriteLine("Install Mod"); // Before installing get the list of affected ice files and make sure no installed // mod uses the same files if (!ModCollision(m)) { m.ContentsMD5 = new Dictionary <string, string>(); foreach (var f in Directory.GetFiles(mPath)) { var fileName = Path.GetFileName(f); string from = mPath + "\\" + fileName; string destin = settings.PSO2Dir + "\\" + fileName; string backup = mBackupPath + "\\" + fileName; if (!File.Exists(from)) { MessageBox.Show("Mod file(s) are not found. please reinstall mod.", "Error", MessageBoxButton.OK, MessageBoxImage.Error); return(false); } if (!File.Exists(destin)) { // backup target no match continue; } //Console.WriteLine ("Backup FROM: " + settings.PSO2Dir + "\\" + fileName + " TO: " + mBackupPath + "\\" + fileName); //Console.WriteLine ("Replace FROM: " + mPath + "\\" + fileName + " TO: " + settings.PSO2Dir + "\\" + fileName); if (!Mod.modSettingsFiles.Contains(fileName)) { var BackupTask = new Helpers.FileCopy(destin, backup); var ApplyModTask = new Helpers.FileCopy(from, destin); // This is done here because some mods will have dynamic/setup content so we create // the md5 hash over the file we copy on the pso2 dir m.ContentsMD5.Add(fileName, Helpers.CheckMD5(destin)); Console.WriteLine("Backup Task"); await Task.Run(() => BackupTask.StartCopy()); Console.WriteLine("Apply Mod Task"); await Task.Run(() => ApplyModTask.StartCopy()); } } InstalledMods.Add(m); AvailableMods.Remove(m); UpdateSettings(); } else { MessageBox.Show("Another installed mod is using the same files this mod will try to overwrite. Installation cancelled", "Error", MessageBoxButton.OK, MessageBoxImage.Error); return(false); } return(true); }
/// <summary> /// Saves the settings file. /// </summary> private void UpdateSettings() { settings.AvailableMods = AvailableMods.ToList(); settings.InstalledMods = InstalledMods.ToList(); File.WriteAllText(Settings.SettingsPath, JsonSerializer.SerializeToString(settings)); }
/// <summary> /// Returns true if the mod is installed /// </summary> public bool IsInstalled(Mod m) { return(InstalledMods.Contains(m)); }
private async Task DownloadSelectedModRelease() { string token; if (GlobalCredentials.Instance.LogIn(Window, out token)) { var progressWindow = new ProgressWindow { Owner = Window }; var progressViewModel = (ProgressViewModel)progressWindow.ViewModel; progressViewModel.ActionName = App.Instance.GetLocalizedResourceString("DownloadingAction"); progressViewModel.ProgressDescription = string.Format(App.Instance.GetLocalizedResourceString("DownloadingDescription"), selectedRelease.FileName); progressViewModel.CanCancel = true; var cancellationSource = new CancellationTokenSource(); progressViewModel.CancelRequested += (sender, e) => cancellationSource.Cancel(); var progress = new Progress <double>(p => progressViewModel.Progress = p); Mod newMod; try { Task closeWindowTask = null; try { Task <Mod> downloadTask = ModWebsite.DownloadReleaseAsync(selectedRelease, GlobalCredentials.Instance.Username, token, progress, cancellationSource.Token, InstalledMods, MainViewModel.Instance.Modpacks); closeWindowTask = downloadTask.ContinueWith(t => progressWindow.Dispatcher.Invoke(progressWindow.Close)); progressWindow.ShowDialog(); newMod = await downloadTask; } finally { if (closeWindowTask != null) { await closeWindowTask; } } } catch (HttpRequestException) { MessageBox.Show(Window, App.Instance.GetLocalizedMessage("InternetConnection", MessageType.Error), App.Instance.GetLocalizedMessageTitle("InternetConnection", MessageType.Error), MessageBoxButton.OK, MessageBoxImage.Error); return; } if (!cancellationSource.IsCancellationRequested) { if (newMod != null) { InstalledMods.Add(newMod); } UpdateSelectedReleases(); } } }
public Utility(ModData modData, InstalledMods mods) { ModData = modData; Mods = mods; }
/// <summary> /// Backend input /// </summary> /// <param name="inputargs">Input</param> public static void doCommand(string[] inputargs) { switch (inputargs[0]) { case "reload": JsonModList.GetModLists(true); break; case "wipe": File.Delete(Directory.GetCurrentDirectory() + @"\installedmods.json"); Console.WriteLine("Wiped!"); break; case "modlists": listmodlists(); break; case "check": var ml = JsonModList.GetModLists(); for (var i = 0; i < ml.Length; i++) { for (var x = 0; x < ml[i].Modlist.Length; x++) { if (ml[i].Modlist[x].ModId == inputargs[1]) { var link = ml[i].Modlist[x].Website; var psi = new ProcessStartInfo { FileName = link, UseShellExecute = true }; Process.Start(psi); } } } break; case "dl": Downloader.DownloadModDirector(inputargs[1]); break; case "install": Downloader.DownloadModDirector(inputargs[1], true); break; case "list": if (inputargs[1] == "installedmods") { var mf = InstalledMods.GetInstalledMods(); for (var i = 0; i < mf.Length; i++) { Console.WriteLine(mf[i].Name); } } else { list(inputargs[1]); } break; case "exit": return; case "help": Console.WriteLine("wipe - Wipes the installed registry (DOES NOT DELETE MODS)"); Console.WriteLine("modlists - Lists all modlists, which are lists of mods."); Console.WriteLine("dl [modname] - Downloads mod listed."); Console.WriteLine("check [modname] - Opens browser to modpage."); Console.WriteLine("list [modlist] - Lists all mods contained in a modlist."); Console.WriteLine("list installedmods - Lists all mods registered as installed."); Console.WriteLine("exit - Close H3VRModInstaller."); break; case "toggledebugging": ModInstallerCommon.enableDebugging = !ModInstallerCommon.enableDebugging; Console.WriteLine("Debugging is now " + ModInstallerCommon.enableDebugging); break; //deletion case "rm": Console.WriteLine($"Deleting {inputargs[1]}"); Uninstaller.DeleteMod(inputargs[1]); break; default: Console.WriteLine("Invalid command!"); break; } }
static void Main(string[] args) { var arguments = new Arguments(args); var engineDirArg = arguments.GetValue("Engine.EngineDir", null); if (!string.IsNullOrEmpty(engineDirArg)) { Platform.OverrideEngineDir(engineDirArg); } var supportDirArg = arguments.GetValue("Engine.SupportDir", null); if (!string.IsNullOrEmpty(supportDirArg)) { Platform.OverrideSupportDir(supportDirArg); } Log.AddChannel("debug", "dedicated-debug.log", true); Log.AddChannel("perf", "dedicated-perf.log", true); Log.AddChannel("server", "dedicated-server.log", true); Log.AddChannel("nat", "dedicated-nat.log", true); Log.AddChannel("geoip", "dedicated-geoip.log", true); // Special case handling of Game.Mod argument: if it matches a real filesystem path // then we use this to override the mod search path, and replace it with the mod id var modID = arguments.GetValue("Game.Mod", null); var explicitModPaths = new string[0]; if (modID != null && (File.Exists(modID) || Directory.Exists(modID))) { explicitModPaths = new[] { modID }; modID = Path.GetFileNameWithoutExtension(modID); } if (modID == null) { throw new InvalidOperationException("Game.Mod argument missing or mod could not be found."); } // HACK: The engine code assumes that Game.Settings is set. // This isn't nearly as bad as ModData, but is still not very nice. Game.InitializeSettings(arguments); var settings = Game.Settings.Server; var envModSearchPaths = Environment.GetEnvironmentVariable("MOD_SEARCH_PATHS"); var modSearchPaths = !string.IsNullOrWhiteSpace(envModSearchPaths) ? FieldLoader.GetValue <string[]>("MOD_SEARCH_PATHS", envModSearchPaths) : new[] { Path.Combine(Platform.EngineDir, "mods") }; var mods = new InstalledMods(modSearchPaths, explicitModPaths); Console.WriteLine("[{0}] Starting dedicated server for mod: {1}", DateTime.Now.ToString(settings.TimestampFormat), modID); while (true) { // HACK: The engine code *still* assumes that Game.ModData is set var modData = Game.ModData = new ModData(mods[modID], mods); modData.MapCache.LoadMaps(); settings.Map = modData.MapCache.ChooseInitialMap(settings.Map, new MersenneTwister()); var endpoints = new List <IPEndPoint> { new IPEndPoint(IPAddress.IPv6Any, settings.ListenPort), new IPEndPoint(IPAddress.Any, settings.ListenPort) }; var server = new Server(endpoints, settings, modData, ServerType.Dedicated); GC.Collect(); while (true) { Thread.Sleep(1000); if (server.State == ServerState.GameStarted && server.Conns.Count < 1) { Console.WriteLine("[{0}] No one is playing, shutting down...", DateTime.Now.ToString(settings.TimestampFormat)); server.Shutdown(); break; } } modData.Dispose(); Console.WriteLine("[{0}] Starting a new server instance...", DateTime.Now.ToString(settings.TimestampFormat)); } }
/// <summary> /// Called when [activated]. /// </summary> /// <param name="disposables">The disposables.</param> protected override void OnActivated(CompositeDisposable disposables) { Task.Run(() => EvalResumeAvailabilityLoopAsync().ConfigureAwait(false)); ShowAdvancedFeatures = (gameService.GetSelected()?.AdvancedFeaturesSupported).GetValueOrDefault(); AnalyzeClass = string.Empty; var allowModSelectionEnabled = this.WhenAnyValue(v => v.AllowModSelection); var applyEnabled = Observable.Merge(this.WhenAnyValue(v => v.ApplyingCollection, v => !v), allowModSelectionEnabled); this.WhenAnyValue(v => v.CollectionMods.SelectedModCollection).Subscribe(s => { if (s != null) { AllowModSelection = true; InstalledMods.AllowModSelection = true; CollectionMods.AllowModSelection = true; } else { AllowModSelection = false; InstalledMods.AllowModSelection = false; CollectionMods.AllowModSelection = false; } InstallModsAsync().ConfigureAwait(true); }).DisposeWith(disposables); this.WhenAnyValue(v => v.InstalledMods.Mods).Subscribe(v => { CollectionMods.SetMods(v, InstalledMods.ActiveGame); }); this.WhenAnyValue(v => v.InstalledMods.RefreshingMods).Subscribe(s => { CollectionMods.HandleModRefresh(s, InstalledMods.Mods, InstalledMods.ActiveGame); }).DisposeWith(disposables); this.WhenAnyValue(v => v.CollectionMods.NeedsModListRefresh).Where(x => x).Subscribe(s => { InstalledMods.RefreshMods(); }).DisposeWith(disposables); this.WhenAnyValue(p => p.InstalledMods.PerformingEnableAll).Subscribe(s => { CollectionMods.HandleEnableAllToggled(s, InstalledMods.AllModsEnabled, InstalledMods.FilteredMods); }).DisposeWith(disposables); ApplyCommand = ReactiveCommand.Create(() => { ApplyCollectionAsync(idGenerator.GetNextId()).ConfigureAwait(true); }, applyEnabled).DisposeWith(disposables); AnalyzeCommand = ReactiveCommand.CreateFromTask(async() => { var game = gameService.GetSelected(); if (game != null && CollectionMods.SelectedMods?.Count > 0 && CollectionMods.SelectedModCollection != null) { var id = idGenerator.GetNextId(); await TriggerOverlayAsync(id, true, localizationManager.GetResource(LocalizationResources.App.WaitBackgroundOperationMessage)); await shutDownState.WaitUntilFreeAsync(); modPatchCollectionService.ResetPatchStateCache(); var mode = await modPatchCollectionService.GetPatchStateModeAsync(CollectionMods.SelectedModCollection.Name); if (mode == PatchStateMode.None) { await TriggerOverlayAsync(id, false); await Task.Delay(50); IsModeOpen = true; } else { await AnalyzeModsAsync(id, mode); } } }, allowModSelectionEnabled).DisposeWith(disposables); async Task <bool> ensureSteamIsRunning(IGameSettings args) { if (gameService.IsSteamGame(args)) { // Check if process is running var processes = Process.GetProcesses(); if (!processes.Any(p => p.ProcessName.Equals(SteamProcess, StringComparison.OrdinalIgnoreCase))) { await appAction.OpenAsync(SteamLaunch); var attempts = 0; while (!processes.Any(p => p.ProcessName.Equals(SteamProcess, StringComparison.OrdinalIgnoreCase))) { if (attempts > 3) { break; } await Task.Delay(3000); processes = Process.GetProcesses(); attempts++; } } } return(true); } async Task launchGame(bool continueGame) { var game = gameService.GetSelected(); if (game != null) { var args = gameService.GetLaunchSettings(game, continueGame); if (!string.IsNullOrWhiteSpace(args.ExecutableLocation)) { var id = idGenerator.GetNextId(); await TriggerOverlayAsync(id, true, localizationManager.GetResource(LocalizationResources.Mod_Actions.LaunchGame.Overlay)); if (game.RefreshDescriptors) { await modService.DeleteDescriptorsAsync(InstalledMods.Mods); await modService.InstallModsAsync(InstalledMods.Mods); } await ApplyCollectionAsync(id, false); await MessageBus.PublishAsync(new LaunchingGameEvent(game.Type)); if (gameService.IsSteamLaunchPath(args)) { if (await appAction.OpenAsync(args.ExecutableLocation)) { if (game.CloseAppAfterGameLaunch) { await appAction.ExitAppAsync(); } } else { notificationAction.ShowNotification(localizationManager.GetResource(LocalizationResources.Mod_Actions.LaunchGame.LaunchError.Title), localizationManager.GetResource(LocalizationResources.Mod_Actions.LaunchGame.LaunchError.Message), NotificationType.Error, 10); await TriggerOverlayAsync(id, false); } } else { await ensureSteamIsRunning(args); if (await appAction.RunAsync(args.ExecutableLocation, args.LaunchArguments)) { if (game.CloseAppAfterGameLaunch) { await appAction.ExitAppAsync(); } else { await TriggerOverlayAsync(id, false); } } else { notificationAction.ShowNotification(localizationManager.GetResource(LocalizationResources.Mod_Actions.LaunchGame.LaunchError.Title), localizationManager.GetResource(LocalizationResources.Mod_Actions.LaunchGame.LaunchError.Message), NotificationType.Error, 10); await TriggerOverlayAsync(id, false); } } } else { notificationAction.ShowNotification(localizationManager.GetResource(LocalizationResources.Mod_Actions.LaunchGame.NotSet.Title), localizationManager.GetResource(LocalizationResources.Mod_Actions.LaunchGame.NotSet.Message), NotificationType.Warning, 10); } } } LaunchGameCommand = ReactiveCommand.CreateFromTask(async() => { await launchGame(false); }, allowModSelectionEnabled).DisposeWith(disposables); ResumeGameCommand = ReactiveCommand.CreateFromTask(async() => { await launchGame(true); }, allowModSelectionEnabled).DisposeWith(disposables); AdvancedModeCommand = ReactiveCommand.CreateFromTask(async() => { await AnalyzeModsAsync(idGenerator.GetNextId(), PatchStateMode.Advanced); }).DisposeWith(disposables); DefaultModeCommand = ReactiveCommand.CreateFromTask(async() => { await AnalyzeModsAsync(idGenerator.GetNextId(), PatchStateMode.Default); }).DisposeWith(disposables); CloseModeCommand = ReactiveCommand.Create(() => { ForceClosePopups(); }).DisposeWith(disposables); var previousCollectionNotification = string.Empty; CollectionMods.ConflictSolverStateChanged += (collectionName, state) => { AnalyzeClass = !state ? InvalidConflictSolverClass : string.Empty; if (!state && previousCollectionNotification != collectionName) { notificationAction.ShowNotification(localizationManager.GetResource(LocalizationResources.Notifications.ConflictSolverUpdate.Title), localizationManager.GetResource(LocalizationResources.Notifications.ConflictSolverUpdate.Message), NotificationType.Warning, 30); previousCollectionNotification = collectionName; } }; gameDirectoryChangedHandler.Message.Subscribe(s => { InstalledMods.RefreshMods(); EvalResumeAvailability(s.Game); }).DisposeWith(disposables); this.WhenAnyValue(v => v.InstalledMods.ModFilePopulationInCompleted).Subscribe(s => { CollectionMods.CanExportModHashReport = s; }).DisposeWith(disposables); base.OnActivated(disposables); }
public void UpdateModList(string dispcat = "n/a") { DownloadableModsList.Items.Clear(); InstalledModsList.Items.Clear(); if (dispcat == "n/a") { dispcat = publicdispcat; } publicdispcat = dispcat; var totalmods = JsonCommon.GetAllMods(); if (dispcat == "n/a") { dispcat = "dependencies"; } Console.WriteLine(dispcat); var dispmods = JsonModList.GetDeserializedModListFormatOnline(dispcat).Modlist; var installedMods = InstalledMods.GetInstalledMods(); //f**k you ModFile[] list = null; var relevantint = 0; for (var i = 0; i < totalmods.Length; i++) { //this just checks if the mod we're working with is an installedmod, or a dl mod in isinstldmod var isinstldmod = false; var x = 0; for (x = 0; x < installedMods.Length; x++) { if (totalmods[i].ModId == installedMods[x].ModId) { isinstldmod = true; break; } } var isdispmod = false; for (var y = 0; y < dispmods.Length; y++) { if (totalmods[i].ModId == dispmods[y].ModId) { isdispmod = true; break; } } //sets vars to installedmods or input if (isinstldmod) { list = installedMods; relevantint = x; } else { if (publicdispcat == "n/a") { goto Finish; } list = totalmods; relevantint = i; } var mod = new ListViewItem(list[relevantint].Name, 0); //0 mod.SubItems.Add(list[relevantint].Version); //1 mod.SubItems.Add(string.Join(", ", list[relevantint].Author)); //2 mod.SubItems.Add(list[relevantint].Description); //3 mod.SubItems.Add(list[relevantint].ModId); //4 if (!isinstldmod && isdispmod) { DownloadableModsList.Items.Add(mod); } if (isinstldmod) { InstalledModsList.Items.Add(mod); } Finish :; } for (var i = 0; i < InstalledModsList.Items.Count; i++) { //if cached installed mod is a older version than the database if (new Version(InstalledModsList.Items[i].SubItems[1].Text).CompareTo( new Version(ModParsing.GetSpecificMod(InstalledModsList.Items[i].SubItems[4].Text).Version)) < 0) { InstalledModsList.Items[i].BackColor = Color.Yellow; } } }
private async Task UpdateSelectedModRelease() { string token; if (GlobalCredentials.Instance.LogIn(Window, out token)) { ModRelease newestRelease = GetNewestRelease(ExtendedInfo, SelectedRelease); Mod mod = InstalledMods.FindByFactorioVersion(SelectedMod.Name, newestRelease.FactorioVersion); var zippedMod = mod as ZippedMod; var extractedMod = mod as ExtractedMod; var cancellationSource = new CancellationTokenSource(); var progressWindow = new ProgressWindow { Owner = Window }; var progressViewModel = (ProgressViewModel)progressWindow.ViewModel; progressViewModel.ActionName = App.Instance.GetLocalizedResourceString("UpdatingAction"); progressViewModel.ProgressDescription = string.Format(App.Instance.GetLocalizedResourceString("DownloadingDescription"), newestRelease.FileName); progressViewModel.CanCancel = true; progressViewModel.CancelRequested += (sender, e) => cancellationSource.Cancel(); IProgress <double> progress = new Progress <double>(p => { if (p > 1) { progressViewModel.ProgressDescription = App.Instance.GetLocalizedResourceString("ExtractingDescription"); progressViewModel.IsIndeterminate = true; progressViewModel.CanCancel = false; } else { progressViewModel.Progress = p; } }); try { Task closeWindowTask = null; try { Task downloadTask = ModWebsite.UpdateReleaseAsync(newestRelease, GlobalCredentials.Instance.Username, token, progress, cancellationSource.Token); if (extractedMod != null) { downloadTask = downloadTask.ContinueWith(t => { progress.Report(2); FileInfo modFile = ((Task <FileInfo>)t).Result; DirectoryInfo modDirectory = App.Instance.Settings.GetModDirectory(newestRelease.FactorioVersion); ZipFile.ExtractToDirectory(modFile.FullName, modDirectory.FullName); modFile.Delete(); return(new DirectoryInfo(Path.Combine(modDirectory.FullName, modFile.NameWithoutExtension()))); }, TaskContinuationOptions.NotOnFaulted | TaskContinuationOptions.NotOnCanceled); } closeWindowTask = downloadTask.ContinueWith(t => progressWindow.Dispatcher.Invoke(progressWindow.Close)); progressWindow.ShowDialog(); if (zippedMod != null) { FileInfo newModFile = await(Task <FileInfo>) downloadTask; if (zippedMod.FactorioVersion == newestRelease.FactorioVersion) { zippedMod.Update(newModFile, newestRelease.Version); } else { var newMod = new ZippedMod(zippedMod.Name, newestRelease.Version, newestRelease.FactorioVersion, newModFile, InstalledMods, MainViewModel.Instance.Modpacks); InstalledMods.Add(newMod); foreach (var modpack in MainViewModel.Instance.Modpacks) { ModReference reference; if (modpack.Contains(zippedMod, out reference)) { modpack.Mods.Remove(reference); modpack.Mods.Add(new ModReference(newMod, modpack)); } } zippedMod.File.Delete(); InstalledMods.Remove(extractedMod); } } if (extractedMod != null) { DirectoryInfo newModDirectory = await(Task <DirectoryInfo>) downloadTask; if (extractedMod.FactorioVersion == newestRelease.FactorioVersion) { extractedMod.Update(newModDirectory, newestRelease.Version); } else { var newMod = new ExtractedMod(extractedMod.Name, newestRelease.Version, newestRelease.FactorioVersion, newModDirectory, InstalledMods, MainViewModel.Instance.Modpacks); InstalledMods.Add(newMod); foreach (var modpack in MainViewModel.Instance.Modpacks) { ModReference reference; if (modpack.Contains(extractedMod, out reference)) { modpack.Mods.Remove(reference); modpack.Mods.Add(new ModReference(newMod, modpack)); } } extractedMod.Directory.Delete(true); InstalledMods.Remove(extractedMod); ModpackTemplateList.Instance.Update(MainViewModel.Instance.Modpacks); ModpackTemplateList.Instance.Save(); } } } finally { if (closeWindowTask != null) { await closeWindowTask; } } } catch (HttpRequestException) { MessageBox.Show(Window, App.Instance.GetLocalizedMessage("InternetConnection", MessageType.Error), App.Instance.GetLocalizedMessageTitle("InternetConnection", MessageType.Error), MessageBoxButton.OK, MessageBoxImage.Error); return; } SelectedRelease = null; foreach (var release in SelectedReleases) { release.IsInstalled = InstalledMods.Contains(selectedMod.Name, release.Version); release.IsVersionInstalled = !release.IsInstalled && InstalledMods.ContainsByFactorioVersion(selectedMod.Name, release.FactorioVersion); } } }