diff --git a/ModAPI.Common/InstalledMods.cs b/ModAPI.Common/InstalledMods.cs index 516c0be..fbb5aa9 100644 --- a/ModAPI.Common/InstalledMods.cs +++ b/ModAPI.Common/InstalledMods.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; +using System.Linq; using System.Xml; namespace ModAPI.Common @@ -52,8 +53,11 @@ public class ModConfiguration : IEquatable public string Name; public string Unique; + public Version Version; public string DisplayName; public string ConfiguratorPath; + public string[] Dependencies; + public Version[] DependenciesVersions; public List InstalledFiles = new List(); public ModConfiguration(string name, string unique) @@ -98,6 +102,27 @@ public void Save(XmlDocument document, XmlElement parent) element.Attributes.Append(uniqueAttribute); //} + if (Version != null) + { + var versionAttribute = document.CreateAttribute("version"); + versionAttribute.Value = Version.ToString(); + element.Attributes.Append(versionAttribute); + } + + if (Dependencies != null && Dependencies.Length > 0) + { + var dependenciesAttribute = document.CreateAttribute("dependencies"); + dependenciesAttribute.Value = String.Join("?", Dependencies); + element.Attributes.Append(dependenciesAttribute); + } + + if (DependenciesVersions != null && DependenciesVersions.Length > 0) + { + var dependenciesVersionsAttribute = document.CreateAttribute("dependenciesVersions"); + dependenciesVersionsAttribute.Value = String.Join("?", DependenciesVersions.Select(x => x.ToString())); + element.Attributes.Append(dependenciesVersionsAttribute); + } + if (this.ConfiguratorPath != null) { attribute = document.CreateAttribute("configurator"); @@ -132,6 +157,46 @@ public void Load(XmlNode node) else Unique = nameAttribute.Value; + var versionAttribute = node.Attributes.GetNamedItem("version"); + if (versionAttribute != null && + Version.TryParse(versionAttribute.Value, out Version parsedVersion)) + { + Version = parsedVersion; + } + else + { + Version = null; + } + + var dependenciesAttribute = node.Attributes.GetNamedItem("dependencies"); + if (dependenciesAttribute != null) + { + Dependencies = dependenciesAttribute.Value.Split('?'); + } + + var dependenciesVersionsAttribute = node.Attributes.GetNamedItem("dependenciesVersions"); + if (dependenciesVersionsAttribute != null) + { + List versions = new List(); + foreach (string version in dependenciesVersionsAttribute.Value.Split('?')) + { + if (Version.TryParse(version, out Version parsedDependencyVersion)) + { + versions.Add(parsedDependencyVersion); + } + else + { + versions.Clear(); + break; + } + } + + if (versions.Count() != 0) + { + DependenciesVersions = versions.ToArray(); + } + } + var displayNameAttribute = node.Attributes.GetNamedItem("displayName"); if (displayNameAttribute != null) this.DisplayName = displayNameAttribute.Value; diff --git a/Spore ModAPI Easy Installer/XmlInstallerWindow.xaml.cs b/Spore ModAPI Easy Installer/XmlInstallerWindow.xaml.cs index 53d61da..4636396 100644 --- a/Spore ModAPI Easy Installer/XmlInstallerWindow.xaml.cs +++ b/Spore ModAPI Easy Installer/XmlInstallerWindow.xaml.cs @@ -37,6 +37,9 @@ public partial class XmlInstallerWindow : DecoratableWindow string ModSettingsStoragePath = string.Empty; string ModConfigPath = string.Empty; string ModUnique = string.Empty; + Version ModVersion = null; + List ModDependencies = new List(); + List ModDependenciesVersions = new List(); XmlDocument Document = new XmlDocument(); List Prerequisites = new List(); List CompatFiles = new List(); @@ -154,7 +157,8 @@ private void PrepareInstaller() if ((ModInfoVersion == new Version(1, 0, 0, 0)) || (ModInfoVersion == new Version(1, 0, 1, 0)) || (ModInfoVersion == new Version(1, 0, 1, 1)) || - (ModInfoVersion == new Version(1, 0, 1, 2))) + (ModInfoVersion == new Version(1, 0, 1, 2)) || + (ModInfoVersion == new Version(1, 0, 1, 3))) { if (File.Exists(Path.Combine(ModConfigPath, "Branding.png"))) { @@ -182,7 +186,8 @@ private void PrepareInstaller() } else if ((ModInfoVersion == new Version(1, 0, 1, 0)) || (ModInfoVersion == new Version(1, 0, 1, 1)) || - (ModInfoVersion == new Version(1, 0, 1, 2))) + (ModInfoVersion == new Version(1, 0, 1, 2)) || + (ModInfoVersion == new Version(1, 0, 1, 3))) { _dontUseLegacyPackagePlacement = true; _installerMode = 1; @@ -243,6 +248,101 @@ private void PrepareInstaller() else ModDescription = string.Empty; + // installerSystemVersion 1.0.1.3 introduced the version, depends and dependsVersion attributes + if (ModInfoVersion == new Version(1, 0, 1, 3)) + { + if (Document.SelectSingleNode("/mod").Attributes["version"] != null) + { + if (Version.TryParse(Document.SelectSingleNode("/mod").Attributes["version"].Value, out Version parsedVersion)) + ModVersion = parsedVersion; + else + throw new Exception("This mod has an invalid version. Please inform the mod's developer of this."); + } + else + ModVersion = null; + + if (Document.SelectSingleNode("/mod").Attributes["depends"] != null) + { + string dependsString = Document.SelectSingleNode("/mod").Attributes["depends"].Value; + string dependsVersionString = null; + if (Document.SelectSingleNode("/mod").Attributes["dependsVersions"] != null) + dependsVersionString = Document.SelectSingleNode("/mod").Attributes["dependsVersions"].Value; + string[] dependsUniqueNames = dependsString.Split('?'); + string[] dependsVersions = dependsVersionString?.Split('?'); + bool[] foundDepends = new bool[dependsUniqueNames.Length]; + + ModDependencies = dependsUniqueNames.ToList(); + if (dependsVersions != null) + { + foreach (string dependsVersion in dependsVersions) + { + if (String.IsNullOrEmpty(dependsVersion)) + { // 0.0.0.0 is interpreted as having no version attribute + // but we need to pad the list to match the amount of items + // that ModDependencies has + ModDependenciesVersions.Add(new Version(0, 0, 0, 0)); + } + else + { + if (Version.TryParse(dependsVersion, out Version parsedDependsVersion)) + { + ModDependenciesVersions.Add(parsedDependsVersion); + } + else + { + throw new Exception($"This mod has an invalid dependsVersions entry: \"{dependsVersion}\". Please inform the mod's developer of this."); + } + } + } + } + + foreach (ModConfiguration mod in configs) + { + for (int i = 0; i < dependsUniqueNames.Length; i++) + { + if (mod.Unique == dependsUniqueNames[i]) + { + if (mod.Version != null && + (ModDependenciesVersions.Count() > i) && + (mod.Version >= ModDependenciesVersions[i])) + { // name + version match + foundDepends[i] = true; + break; + } + else if (ModDependenciesVersions.Count() <= i || + ModDependenciesVersions[i] == new Version(0, 0, 0, 0)) + { // name only match + foundDepends[i] = true; + break; + } + } + } + } + + bool satisfiedDepends = true; + List missingDepends = new List(); + for (int i = 0; i < foundDepends.Length; i++) + { + if (!foundDepends[i]) + { + satisfiedDepends = false; + string missingDependsText = dependsUniqueNames[i]; + if (dependsVersions != null && + dependsVersions.Length > i) + { + missingDependsText += " " + dependsVersions[i]; + } + missingDepends.Add(missingDependsText); + } + } + + if (!satisfiedDepends) + { + throw new Exception($"This mod has dependencies that you don't have installed: {String.Join(", ", missingDepends)}"); + } + } + } + NameTextBlock.Text = displayName; DescriptionTextBlock.Text = ModDescription; @@ -757,6 +857,9 @@ await Dispatcher.BeginInvoke(new Action(() => ModConfiguration mod = new ModConfiguration(ModName, ModUnique) { DisplayName = ModDisplayName, + Version = ModVersion, + Dependencies = ModDependencies.ToArray(), + DependenciesVersions = ModDependenciesVersions.ToArray(), ConfiguratorPath = Path.Combine(ModConfigPath, "ModInfo.xml") }; @@ -780,7 +883,10 @@ await Dispatcher.BeginInvoke(new Action(() => { ModConfiguration mod = new ModConfiguration(ModName, ModUnique) { - DisplayName = ModDisplayName + DisplayName = ModDisplayName, + Version = ModVersion, + Dependencies = ModDependencies.ToArray(), + DependenciesVersions = ModDependenciesVersions.ToArray(), }; foreach (ComponentInfo c in Prerequisites) diff --git a/Spore ModAPI Easy Uninstaller/EasyUninstaller.cs b/Spore ModAPI Easy Uninstaller/EasyUninstaller.cs index f7f0f0a..d5ec06f 100644 --- a/Spore ModAPI Easy Uninstaller/EasyUninstaller.cs +++ b/Spore ModAPI Easy Uninstaller/EasyUninstaller.cs @@ -75,6 +75,31 @@ public static void UninstallMods(Dictionary mods) try { + // ensure we don't remove a mod which + // is a dependency of another mod + List modDependencies = new List(); + List reliantMods = new List(); + foreach (var mod in Mods.ModConfigurations) + { + foreach (var uninstallMod in mods) + { + if (mod.Dependencies != null && + mod.Dependencies.Contains(uninstallMod.Key.Unique) && + !mods.Select(x => x.Key.Unique).Contains(mod.Unique)) + { + modDependencies.Add(uninstallMod.Key.DisplayName); + if (!reliantMods.Contains(mod.DisplayName)) + reliantMods.Add(mod.DisplayName); + } + } + } + + if (modDependencies.Count() > 0) + { + MessageBox.Show($"Cannot uninstall {String.Join(", ", modDependencies.ToArray())} because {String.Join(", ", reliantMods.ToArray())} relies on them being installed!", CommonStrings.Error, MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + foreach (var mod in mods) { ResultType result = ResultType.Success; diff --git a/Spore ModAPI Easy Uninstaller/UninstallerForm.Designer.cs b/Spore ModAPI Easy Uninstaller/UninstallerForm.Designer.cs index f21f126..d7bac35 100644 --- a/Spore ModAPI Easy Uninstaller/UninstallerForm.Designer.cs +++ b/Spore ModAPI Easy Uninstaller/UninstallerForm.Designer.cs @@ -39,6 +39,7 @@ private void InitializeComponent() this.MainColumn = new System.Windows.Forms.DataGridViewCheckBoxColumn(); this.ModNames = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.ModDisplayNames = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.ModVersions = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.ModConfiguration = new System.Windows.Forms.DataGridViewCheckBoxColumn(); ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit(); this.SuspendLayout(); @@ -98,6 +99,7 @@ private void InitializeComponent() this.MainColumn, this.ModNames, this.ModDisplayNames, + this.ModVersions, this.ModConfiguration}); dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; dataGridViewCellStyle2.BackColor = System.Drawing.SystemColors.Window; @@ -150,6 +152,12 @@ private void InitializeComponent() this.ModDisplayNames.Name = "ModDisplayNames"; this.ModDisplayNames.ReadOnly = true; // + // ModVersions + // + this.ModVersions.HeaderText = "Mod Versions"; + this.ModVersions.Name = "ModVersions"; + this.ModVersions.ReadOnly = true; + // // ModConfiguration // this.ModConfiguration.HeaderText = "Mod Configuration"; @@ -186,6 +194,7 @@ private void InitializeComponent() private System.Windows.Forms.DataGridViewCheckBoxColumn MainColumn; private System.Windows.Forms.DataGridViewTextBoxColumn ModNames; private System.Windows.Forms.DataGridViewTextBoxColumn ModDisplayNames; + private System.Windows.Forms.DataGridViewTextBoxColumn ModVersions; private System.Windows.Forms.DataGridViewCheckBoxColumn ModConfiguration; } } diff --git a/Spore ModAPI Easy Uninstaller/UninstallerForm.cs b/Spore ModAPI Easy Uninstaller/UninstallerForm.cs index e3c7242..a309625 100644 --- a/Spore ModAPI Easy Uninstaller/UninstallerForm.cs +++ b/Spore ModAPI Easy Uninstaller/UninstallerForm.cs @@ -46,11 +46,16 @@ public void AddMods(HashSet mods) // add mods foreach (var mod in mods) { + string versionString = ""; + if (mod.Version != null) + { + versionString = mod.Version.ToString(); + } - int index = this.dataGridView1.Rows.Add(new object[] { false, mod, mod.DisplayName }); + int index = this.dataGridView1.Rows.Add(new object[] { false, mod, mod.DisplayName, versionString }); if (GetConfiguratorPath(index) != null) - this.dataGridView1.Rows[index].Cells[3].ReadOnly = true; + this.dataGridView1.Rows[index].Cells[4].ReadOnly = true; } // sort mods @@ -67,7 +72,7 @@ private void btnCancel_Click(object sender, EventArgs e) private void dataGridView_CellDoubleClick(Object sender, DataGridViewCellEventArgs e) { - if (e.RowIndex != 0 && e.ColumnIndex == 3 && GetConfiguratorPath(e.RowIndex) != null) + if (e.RowIndex != 0 && e.ColumnIndex == 4 && GetConfiguratorPath(e.RowIndex) != null) { // execute configurator and close uninstaller ExecuteConfigurator(GetModConfiguration(e.RowIndex)); @@ -80,7 +85,7 @@ private void dataGridView_CellDoubleClick(Object sender, DataGridViewCellEventAr private void dataGridView_CellContentClick(object sender, DataGridViewCellEventArgs e) { - if (e.RowIndex != 0 && e.ColumnIndex == 3 && GetConfiguratorPath(e.RowIndex) != null) + if (e.RowIndex != 0 && e.ColumnIndex == 4 && GetConfiguratorPath(e.RowIndex) != null) { // execute configurator and close uninstaller ExecuteConfigurator(GetModConfiguration(e.RowIndex)); @@ -153,7 +158,7 @@ private void btnUninstall_Click(object sender, EventArgs e) private void dataGridView_CellPainting(object sender, DataGridViewCellPaintingEventArgs e) { - if (e.ColumnIndex == 3 && this.dataGridView1.SelectedRows.Count > 0) + if (e.ColumnIndex == 4 && this.dataGridView1.SelectedRows.Count > 0) { if (e.RowIndex > 0) { diff --git a/Spore ModAPI Easy Uninstaller/UninstallerForm.resx b/Spore ModAPI Easy Uninstaller/UninstallerForm.resx index 896a66c..39e23a7 100644 --- a/Spore ModAPI Easy Uninstaller/UninstallerForm.resx +++ b/Spore ModAPI Easy Uninstaller/UninstallerForm.resx @@ -126,6 +126,9 @@ True + + True + True