diff options
-rw-r--r-- | src/StardewModdingAPI.Tests/Core/ModResolverTests.cs | 20 | ||||
-rw-r--r-- | src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs | 21 |
2 files changed, 41 insertions, 0 deletions
diff --git a/src/StardewModdingAPI.Tests/Core/ModResolverTests.cs b/src/StardewModdingAPI.Tests/Core/ModResolverTests.cs index e6ec632a..9b46c5d2 100644 --- a/src/StardewModdingAPI.Tests/Core/ModResolverTests.cs +++ b/src/StardewModdingAPI.Tests/Core/ModResolverTests.cs @@ -179,6 +179,26 @@ namespace StardewModdingAPI.Tests.Core mock.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<string>()), Times.Once, "The validation did not fail the metadata."); } +#if SMAPI_2_0 + [Test(Description = "Assert that validation fails when multiple mods have the same unique ID.")] + public void ValidateManifests_DuplicateUniqueID_Fails() + { + // arrange + Mock<IModMetadata> modA = this.GetMetadata("Mod A", new string[0], allowStatusChange: true); + Mock<IModMetadata> modB = this.GetMetadata(this.GetManifest("Mod A", "1.0", manifest => manifest.Name = "Mod B"), allowStatusChange: true); + Mock<IModMetadata> modC = this.GetMetadata("Mod C", new string[0], allowStatusChange: false); + foreach (Mock<IModMetadata> mod in new[] { modA, modB, modC }) + this.SetupMetadataForValidation(mod); + + // act + new ModResolver().ValidateManifests(new[] { modA.Object, modB.Object }, apiVersion: new SemanticVersion("1.0")); + + // assert + modA.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<string>()), Times.Once, "The validation did not fail the first mod with a unique ID."); + modB.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<string>()), Times.Once, "The validation did not fail the second mod with a unique ID."); + } +#endif + [Test(Description = "Assert that validation fails when the manifest references a DLL that does not exist.")] public void ValidateManifests_Valid_Passes() { diff --git a/src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs b/src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs index 38dddce7..69ef0b63 100644 --- a/src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs +++ b/src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs @@ -95,6 +95,9 @@ namespace StardewModdingAPI.Framework.ModLoading /// <param name="apiVersion">The current SMAPI version.</param> public void ValidateManifests(IEnumerable<IModMetadata> mods, ISemanticVersion apiVersion) { + mods = mods.ToArray(); + + // validate each manifest foreach (IModMetadata mod in mods) { // skip if already failed @@ -153,6 +156,24 @@ namespace StardewModdingAPI.Framework.ModLoading } #endif } + + // validate IDs are unique +#if SMAPI_2_0 + { + var duplicatesByID = mods + .GroupBy(mod => mod.Manifest.UniqueID?.Trim(), mod => mod, StringComparer.InvariantCultureIgnoreCase) + .Where(p => p.Count() > 1); + foreach (var group in duplicatesByID) + { + foreach (IModMetadata mod in group) + { + if (mod.Status == ModMetadataStatus.Failed) + continue; // don't replace metadata error + mod.SetStatus(ModMetadataStatus.Failed, $"its unique ID '{mod.Manifest.UniqueID}' is used by multiple mods ({string.Join(", ", group.Select(p => p.DisplayName))})."); + } + } + } +#endif } /// <summary>Sort the given mods by the order they should be loaded.</summary> |