public void Setup() { ksp_dir = TestData.NewTempDir(); nullUser = new NullUser(); CKAN.Utilities.CopyDirectory(TestData.good_ksp_dir(), ksp_dir, true); ksp = new CKAN.GameInstance(new KerbalSpaceProgram(), ksp_dir, "test", nullUser); }
public void FakeInstance_ValidArgumentsWithDLCs_ManagerHasValidInstance() { string name = "testname"; GameVersion mhVersion = GameVersion.Parse("1.1.0"); GameVersion bgVersion = GameVersion.Parse("1.0.0"); string tempdir = TestData.NewTempDir(); GameVersion version = GameVersion.Parse("1.7.1"); Dictionary <CKAN.DLC.IDlcDetector, GameVersion> dlcs = new Dictionary <CKAN.DLC.IDlcDetector, GameVersion>() { { new CKAN.DLC.MakingHistoryDlcDetector(), mhVersion }, { new CKAN.DLC.BreakingGroundDlcDetector(), bgVersion } }; manager.FakeInstance(new KerbalSpaceProgram(), name, tempdir, version, dlcs); CKAN.GameInstance newKSP = new CKAN.GameInstance(new KerbalSpaceProgram(), tempdir, name, new NullUser()); CKAN.DLC.MakingHistoryDlcDetector mhDetector = new CKAN.DLC.MakingHistoryDlcDetector(); CKAN.DLC.BreakingGroundDlcDetector bgDetector = new CKAN.DLC.BreakingGroundDlcDetector(); Assert.IsTrue(manager.HasInstance(name)); Assert.IsTrue(mhDetector.IsInstalled(newKSP, out string _, out UnmanagedModuleVersion detectedMhVersion)); Assert.IsTrue(bgDetector.IsInstalled(newKSP, out string _, out UnmanagedModuleVersion detectedBgVersion)); Assert.IsTrue(detectedMhVersion == new UnmanagedModuleVersion(mhVersion.ToString())); Assert.IsTrue(detectedBgVersion == new UnmanagedModuleVersion(bgVersion.ToString())); // Tidy up. CKAN.RegistryManager.Instance(newKSP).ReleaseLock(); System.IO.Directory.Delete(tempdir, true); }
public void Constructor_NullMainCompatVer_NoCrash() { // Arrange string gamedir = TestData.NewTempDir(); string ckandir = Path.Combine(gamedir, "CKAN"); string buildid = Path.Combine(gamedir, "buildID.txt"); string readme = Path.Combine(gamedir, "readme.txt"); string jsonpath = Path.Combine(ckandir, "compatible_ksp_versions.json"); const string compatible_ksp_versions_json = @"{ ""VersionOfKspWhenWritten"": null, ""CompatibleGameVersions"": [""1.4""] }"; // Generate a valid game dir except for missing buildID.txt and readme.txt CKAN.Utilities.CopyDirectory(TestData.good_ksp_dir(), gamedir, true); File.Delete(buildid); File.Delete(readme); // Save GameDir/CKAN/compatible_ksp_versions.json Directory.CreateDirectory(ckandir); File.WriteAllText(jsonpath, compatible_ksp_versions_json); // Act & Assert Assert.DoesNotThrow(() => { CKAN.GameInstance my_ksp = new CKAN.GameInstance(new KerbalSpaceProgram(), gamedir, "null-compat-ver-test", nullUser); }); Directory.Delete(gamedir, true); }
public void CloneInstance_BadInstance_ThrowsNotKSPDirKraken() { string badName = "badInstance"; string tempdir = TestData.NewTempDir(); CKAN.GameInstance badKSP = new CKAN.GameInstance(new KerbalSpaceProgram(), TestData.bad_ksp_dirs().First(), "badDir", new NullUser()); Assert.Throws <NotKSPDirKraken>(() => manager.CloneInstance(badKSP, badName, tempdir)); Assert.IsFalse(manager.HasInstance(badName)); // Tidy up System.IO.Directory.Delete(tempdir, true); }
// We require our constructor to be private so we can // enforce this being an instance (via Instance() above) private RegistryManager(string path, GameInstance ksp) { this.ksp = ksp; this.path = Path.Combine(path, "registry.json"); lockfilePath = Path.Combine(path, "registry.locked"); // Create a lock for this registry, so we cannot touch it again. if (!GetLock()) { log.DebugFormat("Unable to acquire registry lock: {0}", lockfilePath); throw new RegistryInUseKraken(lockfilePath); } try { LoadOrCreate(); } catch { // Clean up the lock file Dispose(false); throw; } // We don't cause an inconsistency error to stop the registry from being loaded, // because then the user can't do anything to correct it. However we're // sure as hell going to complain if we spot one! try { registry.CheckSanity(); } catch (InconsistentKraken kraken) { log.ErrorFormat("Loaded registry with inconsistencies:\r\n\r\n{0}", kraken.InconsistenciesPretty); } }
/// <summary> /// Initialize the compatible game versions dialog /// </summary> /// <param name="inst">Game instance</param> /// <param name="centerScreen">true to center the dialog on the screen, false to center on the parent</param> public CompatibleGameVersionsDialog(GameInstance inst, bool centerScreen) { this._inst = inst; InitializeComponent(); if (centerScreen) { StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; } List <GameVersion> compatibleVersions = inst.GetCompatibleVersions(); GameVersionLabel.Text = inst.Version()?.ToString() ?? Properties.Resources.CompatibleGameVersionsDialogNone; GameLocationLabel.Text = inst.GameDir().Replace('/', Path.DirectorySeparatorChar); List <GameVersion> knownVersions = inst.game.KnownVersions; List <GameVersion> majorVersionsList = CreateMajorVersionsList(knownVersions); List <GameVersion> compatibleVersionsLeftOthers = new List <GameVersion>(compatibleVersions); compatibleVersionsLeftOthers.RemoveAll((el) => knownVersions.Contains(el) || majorVersionsList.Contains(el)); SortAndAddVersionsToList(compatibleVersionsLeftOthers, compatibleVersions); SortAndAddVersionsToList(majorVersionsList, compatibleVersions); SortAndAddVersionsToList(knownVersions, compatibleVersions); }
/// <summary> /// Initialize the row /// </summary> public PlayTimeRow(string name, GameInstance instance) { Name = name; PlayTime = instance.playTime; path = TimeLog.GetPath(instance.CkanDir()); }
/// <summary> /// User is done. Start cloning or faking, depending on the clicked radio button. /// Close the window if everything went right. /// </summary> private async void buttonOK_Click(object sender, EventArgs e) { string newName = textBoxNewName.Text; string newPath = textBoxNewPath.Text; // Do some basic checks. if (String.IsNullOrWhiteSpace(newName)) { user.RaiseError(Properties.Resources.CloneFakeKspDialogEnterName); return; } if (String.IsNullOrWhiteSpace(newPath)) { user.RaiseError(Properties.Resources.CloneFakeKspDialogEnterPath); return; } // Show progress bar and deactivate controls. progressBar.Style = ProgressBarStyle.Marquee; progressBar.Show(); foreach (Control ctrl in this.Controls) { ctrl.Enabled = false; } // Clone the specified instance. // Done in a new task to not block the GUI thread. if (radioButtonClone.Checked) { user.RaiseMessage(Properties.Resources.CloneFakeKspDialogCloningInstance); try { await Task.Run(() => { GameInstance sourceInstance = manager.Instances.Values .FirstOrDefault(i => i.GameDir() == textBoxClonePath.Text); GameInstance instanceToClone = new GameInstance( sourceInstance.game, textBoxClonePath.Text, "irrelevant", user ); if (instanceToClone.Valid) { manager.CloneInstance(instanceToClone, newName, newPath); } else { throw new NotKSPDirKraken(instanceToClone.GameDir()); } }); } catch (InstanceNameTakenKraken) { user.RaiseError(Properties.Resources.CloneFakeKspDialogNameAlreadyUsed); reactivateDialog(); return; } catch (NotKSPDirKraken kraken) { user.RaiseError(string.Format(Properties.Resources.CloneFakeKspDialogInstanceNotValid, kraken.path)); reactivateDialog(); return; } catch (PathErrorKraken kraken) { user.RaiseError(string.Format(Properties.Resources.CloneFakeKspDialogDestinationNotEmpty, kraken.path)); reactivateDialog(); return; } catch (IOException ex) { user.RaiseError(string.Format(Properties.Resources.CloneFakeKspDialogCloneFailed, ex.Message)); reactivateDialog(); return; } catch (Exception ex) { user.RaiseError(string.Format(Properties.Resources.CloneFakeKspDialogCloneFailed, ex.Message)); reactivateDialog(); return; } if (checkBoxSetAsDefault.Checked) { manager.SetAutoStart(newName); } if (checkBoxSwitchInstance.Checked) { manager.SetCurrentInstance(newName); } user.RaiseMessage(Properties.Resources.CloneFakeKspDialogSuccessfulClone); DialogResult = DialogResult.OK; this.Close(); } // Create a new dummy instance. // Also in a separate task. else if (radioButtonFake.Checked) { GameVersion GameVersion = GameVersion.Parse(comboBoxGameVersion.Text); Dictionary <DLC.IDlcDetector, GameVersion> dlcs = new Dictionary <DLC.IDlcDetector, GameVersion>(); if (!String.IsNullOrWhiteSpace(textBoxMHDlcVersion.Text) && textBoxMHDlcVersion.Text.ToLower() != "none") { if (GameVersion.TryParse(textBoxMHDlcVersion.Text, out GameVersion ver)) { dlcs.Add(new DLC.MakingHistoryDlcDetector(), ver); } else { user.RaiseError(Properties.Resources.CloneFakeKspDialogDlcVersionMalformatted, "Making History"); reactivateDialog(); return; } } if (!String.IsNullOrWhiteSpace(textBoxBGDlcVersion.Text) && textBoxBGDlcVersion.Text.ToLower() != "none") { if (GameVersion.TryParse(textBoxBGDlcVersion.Text, out GameVersion ver)) { dlcs.Add(new DLC.BreakingGroundDlcDetector(), ver); } else { user.RaiseError(Properties.Resources.CloneFakeKspDialogDlcVersionMalformatted, "Breaking Ground"); reactivateDialog(); return; } } user.RaiseMessage(Properties.Resources.CloneFakeKspDialogCreatingInstance); try { await Task.Run(() => { manager.FakeInstance(new KerbalSpaceProgram(), newName, newPath, GameVersion, dlcs); }); } catch (InstanceNameTakenKraken) { user.RaiseError(Properties.Resources.CloneFakeKspDialogNameAlreadyUsed); reactivateDialog(); return; } catch (BadInstallLocationKraken) { user.RaiseError(Properties.Resources.CloneFakeKspDialogDestinationNotEmpty, newPath); reactivateDialog(); return; } catch (Exception ex) { user.RaiseError(string.Format(Properties.Resources.CloneFakeKspDialogFakeFailed, ex.Message)); reactivateDialog(); return; } if (checkBoxSetAsDefault.Checked) { manager.SetAutoStart(newName); } if (checkBoxSwitchInstance.Checked) { manager.SetCurrentInstance(newName); } user.RaiseMessage(Properties.Resources.CloneFakeKspDialogSuccessfulCreate); DialogResult = DialogResult.OK; this.Close(); } }
/// <summary> /// Create a new fake KSP instance /// </summary> /// <param name="game">The game of the new instance.</param> /// <param name="newName">The name for the new instance.</param> /// <param name="newPath">The location of the new instance.</param> /// <param name="version">The version of the new instance. Should have a build number.</param> /// <param name="dlcs">The IDlcDetector implementations for the DLCs that should be faked and the requested dlc version as a dictionary.</param> /// <exception cref="InstanceNameTakenKraken">Thrown if the instance name is already in use.</exception> /// <exception cref="NotKSPDirKraken">Thrown by AddInstance() if created instance is not valid, e.g. if a write operation didn't complete for whatever reason.</exception> public void FakeInstance(IGame game, string newName, string newPath, GameVersion version, Dictionary <DLC.IDlcDetector, GameVersion> dlcs = null) { TxFileManager fileMgr = new TxFileManager(); using (TransactionScope transaction = CkanTransaction.CreateTransactionScope()) { if (HasInstance(newName)) { throw new InstanceNameTakenKraken(newName); } if (!version.InBuildMap(game)) { throw new BadGameVersionKraken(String.Format("The specified KSP version is not a known version: {0}", version.ToString())); } if (Directory.Exists(newPath) && (Directory.GetFiles(newPath).Length != 0 || Directory.GetDirectories(newPath).Length != 0)) { throw new BadInstallLocationKraken("The specified folder already exists and is not empty."); } log.DebugFormat("Creating folder structure and text files at {0} for KSP version {1}", Path.GetFullPath(newPath), version.ToString()); // Create a KSP root directory, containing a GameData folder, a buildID.txt/buildID64.txt and a readme.txt fileMgr.CreateDirectory(newPath); fileMgr.CreateDirectory(Path.Combine(newPath, "GameData")); fileMgr.CreateDirectory(Path.Combine(newPath, "Ships")); fileMgr.CreateDirectory(Path.Combine(newPath, "Ships", "VAB")); fileMgr.CreateDirectory(Path.Combine(newPath, "Ships", "SPH")); fileMgr.CreateDirectory(Path.Combine(newPath, "Ships", "@thumbs")); fileMgr.CreateDirectory(Path.Combine(newPath, "Ships", "@thumbs", "VAB")); fileMgr.CreateDirectory(Path.Combine(newPath, "Ships", "@thumbs", "SPH")); fileMgr.CreateDirectory(Path.Combine(newPath, "saves")); fileMgr.CreateDirectory(Path.Combine(newPath, "saves", "scenarios")); fileMgr.CreateDirectory(Path.Combine(newPath, "saves", "training")); // Don't write the buildID.txts if we have no build, otherwise it would be -1. if (version.IsBuildDefined) { fileMgr.WriteAllText(Path.Combine(newPath, "buildID.txt"), String.Format("build id = {0}", version.Build)); fileMgr.WriteAllText(Path.Combine(newPath, "buildID64.txt"), String.Format("build id = {0}", version.Build)); } // Create the readme.txt WITHOUT build number. fileMgr.WriteAllText(Path.Combine(newPath, "readme.txt"), String.Format("Version {0}", new GameVersion(version.Major, version.Minor, version.Patch).ToString())); // Create the needed folder structure and the readme.txt for DLCs that should be simulated. if (dlcs != null) { foreach (KeyValuePair <DLC.IDlcDetector, GameVersion> dlc in dlcs) { DLC.IDlcDetector dlcDetector = dlc.Key; GameVersion dlcVersion = dlc.Value; if (!dlcDetector.AllowedOnBaseVersion(version)) { throw new WrongGameVersionKraken( version, String.Format("KSP version {0} or above is needed for {1} DLC.", dlcDetector.ReleaseGameVersion, dlcDetector.IdentifierBaseName )); } string dlcDir = Path.Combine(newPath, dlcDetector.InstallPath()); fileMgr.CreateDirectory(dlcDir); fileMgr.WriteAllText( Path.Combine(dlcDir, "readme.txt"), String.Format("Version {0}", dlcVersion)); } } // Add the new instance to the config GameInstance new_instance = new GameInstance(game, newPath, newName, User, false); AddInstance(new_instance); transaction.Complete(); } }
/// <summary> /// Given an open zipfile, returns all files that would be installed /// for this stanza. /// /// If a KSP instance is provided, it will be used to generate output paths, otherwise these will be null. /// /// Throws a BadInstallLocationKraken if the install stanza targets an /// unknown install location (eg: not GameData, Ships, etc) /// /// Throws a BadMetadataKraken if the stanza resulted in no files being returned. /// </summary> /// <exception cref="BadInstallLocationKraken">Thrown when the installation path is not valid according to the spec.</exception> public List <InstallableFile> FindInstallableFiles(ZipFile zipfile, GameInstance ksp) { string installDir; var files = new List <InstallableFile>(); // Normalize the path before doing everything else string install_to = CKANPathUtils.NormalizePath(this.install_to); // The installation path cannot contain updirs if (install_to.Contains("/../") || install_to.EndsWith("/..")) { throw new BadInstallLocationKraken("Invalid installation path: " + install_to); } if (ksp == null) { installDir = null; } else if (install_to == ksp.game.PrimaryModDirectoryRelative || install_to.StartsWith($"{ksp.game.PrimaryModDirectoryRelative}/")) { // The installation path can be either "GameData" or a sub-directory of "GameData" string subDir = install_to.Substring(ksp.game.PrimaryModDirectoryRelative.Length); // remove "GameData" subDir = subDir.StartsWith("/") ? subDir.Substring(1) : subDir; // remove a "/" at the beginning, if present // Add the extracted subdirectory to the path of KSP's GameData installDir = CKANPathUtils.NormalizePath(ksp.game.PrimaryModDirectory(ksp) + "/" + subDir); } else { switch (install_to) { case "GameRoot": installDir = ksp.GameDir(); break; default: if (ksp.game.AllowInstallationIn(install_to, out string path)) { installDir = ksp.ToAbsoluteGameDir(path); } else { throw new BadInstallLocationKraken("Unknown install_to " + install_to); } break; } } EnsurePattern(); // `find` is supposed to match the "topmost" folder. Find it. var shortestMatch = find == null ? (int?)null : zipfile.Cast <ZipEntry>() .Select(entry => inst_pattern.Match(entry.Name.Replace('\\', '/'))) .Where(match => match.Success) .DefaultIfEmpty() .Min(match => match?.Index); // O(N^2) solution, as we're walking the zipfile for each stanza. // Surely there's a better way, although this is fast enough we may not care. foreach (ZipEntry entry in zipfile) { // Skips dirs and things not prescribed by our install stanza. if (!IsWanted(entry.Name, shortestMatch)) { continue; } // Prepare our file info. InstallableFile file_info = new InstallableFile { source = entry, makedir = false, destination = null }; // If we have a place to install it, fill that in... if (installDir != null) { // Get the full name of the file. // Update our file info with the install location file_info.destination = TransformOutputName( ksp.game, entry.Name, installDir, @as); file_info.makedir = AllowDirectoryCreation( ksp.game, ksp?.ToRelativeGameDir(file_info.destination) ?? file_info.destination); } files.Add(file_info); } // If we have no files, then something is wrong! (KSP-CKAN/CKAN#93) if (files.Count == 0) { // We have null as the first argument here, because we don't know which module we're installing throw new BadMetadataKraken(null, String.Format("No files found matching {0} to install!", DescribeMatch())); } return(files); }
/// <summary> /// Download and update the local CKAN meta-info. /// Optionally takes a URL to the zipfile repo to download. /// </summary> public static RepoUpdateResult UpdateAllRepositories(RegistryManager registry_manager, GameInstance ksp, NetModuleCache cache, IUser user) { SortedDictionary <string, Repository> sortedRepositories = registry_manager.registry.Repositories; user.RaiseProgress("Checking for updates", 0); if (sortedRepositories.Values.All(repo => !string.IsNullOrEmpty(repo.last_server_etag) && repo.last_server_etag == Net.CurrentETag(repo.uri))) { user.RaiseProgress("Already up to date", 100); user.RaiseMessage("No changes since last update"); return(RepoUpdateResult.NoChanges); } List <CkanModule> allAvail = new List <CkanModule>(); int index = 0; foreach (KeyValuePair <string, Repository> repository in sortedRepositories) { user.RaiseProgress($"Updating {repository.Value.name}", 10 + 80 * index / sortedRepositories.Count); SortedDictionary <string, int> downloadCounts; string newETag; List <CkanModule> avail = UpdateRegistry(repository.Value.uri, ksp, user, out downloadCounts, out newETag); registry_manager.registry.SetDownloadCounts(downloadCounts); if (avail == null) { // Report failure if any repo fails, rather than losing half the list. // UpdateRegistry will have alerted the user to specific errors already. return(RepoUpdateResult.Failed); } else { // Merge all the lists allAvail.AddRange(avail); repository.Value.last_server_etag = newETag; user.RaiseMessage("Updated {0} ({1} modules)", repository.Value.name, avail.Count); } ++index; } // Save allAvail to the registry if we found anything if (allAvail.Count > 0) { user.RaiseProgress("Saving modules to registry", 90); using (var transaction = CkanTransaction.CreateTransactionScope()) { // Save our changes. registry_manager.registry.SetAllAvailable(allAvail); registry_manager.Save(enforce_consistency: false); transaction.Complete(); } ShowUserInconsistencies(registry_manager.registry, user); List <CkanModule> metadataChanges = GetChangedInstalledModules(registry_manager.registry); if (metadataChanges.Count > 0) { HandleModuleChanges(metadataChanges, user, ksp, cache, registry_manager); } // Registry.CompatibleModules is slow, just return success, // caller can check it if it's really needed user.RaiseProgress("Registry saved", 100); user.RaiseMessage("Repositories updated"); return(RepoUpdateResult.Updated); } else { // Return failure user.RaiseMessage("No modules found!"); return(RepoUpdateResult.Failed); } }
/// <summary> /// Resolve differences between installed and available metadata for given ModuleInstaller /// </summary> /// <param name="metadataChanges">List of modules that changed</param> /// <param name="user">Object for user interaction callbacks</param> /// <param name="ksp">Game instance</param> /// <param name="cache">Cacne object for mod downloads</param> /// <param name="registry_manager">Manager that holds our game instances</param> private static void HandleModuleChanges(List <CkanModule> metadataChanges, IUser user, GameInstance ksp, NetModuleCache cache, RegistryManager registry_manager) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < metadataChanges.Count; i++) { CkanModule module = metadataChanges[i]; sb.AppendLine(string.Format("- {0} {1}", module.identifier, module.version)); } if (user.RaiseYesNoDialog(string.Format(@"The following mods have had their metadata changed since last update: {0} You should reinstall them in order to preserve consistency with the repository. Do you wish to reinstall now?", sb))) { ModuleInstaller installer = ModuleInstaller.GetInstance(ksp, cache, new NullUser()); // New upstream metadata may break the consistency of already installed modules // e.g. if user installs modules A and B and then later up A is made to conflict with B // This is perfectly normal and shouldn't produce an error, therefore we skip enforcing // consistency. However, we will show the user any inconsistencies later on. // Do each changed module one at a time so a failure of one doesn't cause all the others to fail foreach (CkanModule mod in metadataChanges) { try { HashSet <string> possibleConfigOnlyDirs = null; installer.Upgrade( new CkanModule[] { mod }, new NetAsyncModulesDownloader(new NullUser(), cache), ref possibleConfigOnlyDirs, registry_manager, enforceConsistency: false, resolveRelationships: true ); } // Thrown when a dependency couldn't be satisfied catch (ModuleNotFoundKraken) { log.WarnFormat("Skipping installation of {0} due to relationship error.", mod.identifier); user.RaiseMessage("Skipping installation of {0} due to relationship error.", mod.identifier); } // Thrown when a conflicts relationship is violated catch (InconsistentKraken) { log.WarnFormat("Skipping installation of {0} due to relationship error.", mod.identifier); user.RaiseMessage("Skipping installation of {0} due to relationship error.", mod.identifier); } } } }
public InstalledModuleFile(string path, GameInstance ksp) { string absolute_path = ksp.ToAbsoluteGameDir(path); sha1_sum = Sha1Sum(absolute_path); }
/// <summary> /// Resolve differences between installed and available metadata for given ModuleInstaller /// </summary> /// <param name="metadataChanges">List of modules that changed</param> /// <param name="user">Object for user interaction callbacks</param> /// <param name="ksp">Game instance</param> /// <param name="cache">Cacne object for mod downloads</param> /// <param name="registry_manager">Manager that holds our game instances</param> private static void HandleModuleChanges(List <CkanModule> metadataChanges, IUser user, GameInstance ksp, NetModuleCache cache, RegistryManager registry_manager) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < metadataChanges.Count; i++) { CkanModule module = metadataChanges[i]; sb.AppendLine(string.Format("- {0} {1}", module.identifier, module.version)); } if (user.RaiseYesNoDialog(string.Format(@"The following mods have had their metadata changed since last update: {0} You should reinstall them in order to preserve consistency with the repository. Do you wish to reinstall now?", sb))) { throw new ReinstallModuleKraken(metadataChanges); } }
public InstallFiltersDialog(IConfiguration globalConfig, GameInstance instance) { InitializeComponent(); this.globalConfig = globalConfig; this.instance = instance; }
private void GameExit(GameInstance inst) { inst.playTime.Stop(inst.CkanDir()); UpdateStatusBar(); }
public void LaunchGame() { var split = configuration.CommandLineArguments.Split(' '); if (split.Length == 0) { return; } var registry = RegistryManager.Instance(CurrentInstance).registry; var suppressedIdentifiers = CurrentInstance.GetSuppressedCompatWarningIdentifiers; var incomp = registry.IncompatibleInstalled(CurrentInstance.VersionCriteria()) .Where(m => !m.Module.IsDLC && !suppressedIdentifiers.Contains(m.identifier)) .ToList(); if (incomp.Any()) { // Warn that it might not be safe to run Game with incompatible modules installed string incompatDescrip = incomp .Select(m => $"{m.Module} ({registry.CompatibleGameVersions(CurrentInstance.game, m.Module)})") .Aggregate((a, b) => $"{a}{Environment.NewLine}{b}"); var ver = CurrentInstance.Version(); var result = SuppressableYesNoDialog( string.Format(Properties.Resources.MainLaunchWithIncompatible, incompatDescrip), string.Format(Properties.Resources.MainLaunchDontShow, CurrentInstance.game.ShortName, new GameVersion(ver.Major, ver.Minor, ver.Patch)), Properties.Resources.MainLaunch, Properties.Resources.MainGoBack ); if (result.Item1 != DialogResult.Yes) { return; } else if (result.Item2) { CurrentInstance.AddSuppressedCompatWarningIdentifiers( incomp.Select(m => m.identifier).ToHashSet() ); } } split = CurrentInstance.game.AdjustCommandLine(split, Main.Instance.CurrentInstance.Version()); var binary = split[0]; var args = string.Join(" ", split.Skip(1)); try { Directory.SetCurrentDirectory(CurrentInstance.GameDir()); Process p = new Process() { StartInfo = new ProcessStartInfo() { FileName = binary, Arguments = args }, EnableRaisingEvents = true }; GameInstance inst = CurrentInstance; p.Exited += (sender, e) => GameExit(inst); p.Start(); CurrentInstance.playTime.Start(); } catch (Exception exception) { currentUser.RaiseError(Properties.Resources.MainLaunchFailed, exception.Message); } }