summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/SMAPI.Tests/Core/ModResolverTests.cs43
-rw-r--r--src/SMAPI.Toolkit.CoreInterfaces/IManifest.cs8
-rw-r--r--src/SMAPI.Toolkit.CoreInterfaces/IManifestContentPackFor.cs4
-rw-r--r--src/SMAPI.Toolkit.CoreInterfaces/IManifestDependency.cs4
-rw-r--r--src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs15
-rw-r--r--src/SMAPI.Toolkit/Serialization/Models/Manifest.cs102
-rw-r--r--src/SMAPI.Toolkit/Serialization/Models/ManifestContentPackFor.cs33
-rw-r--r--src/SMAPI.Toolkit/Serialization/Models/ManifestDependency.cs44
-rw-r--r--src/SMAPI/Framework/ModLoading/ModResolver.cs2
9 files changed, 164 insertions, 91 deletions
diff --git a/src/SMAPI.Tests/Core/ModResolverTests.cs b/src/SMAPI.Tests/Core/ModResolverTests.cs
index 2ce1c74e..e1b56559 100644
--- a/src/SMAPI.Tests/Core/ModResolverTests.cs
+++ b/src/SMAPI.Tests/Core/ModResolverTests.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.IO;
@@ -52,11 +50,11 @@ namespace SMAPI.Tests.Core
// act
IModMetadata[] mods = new ModResolver().ReadManifests(new ModToolkit(), rootFolder, new ModDatabase()).ToArray();
- IModMetadata mod = mods.FirstOrDefault();
+ IModMetadata? mod = mods.FirstOrDefault();
// assert
Assert.AreEqual(1, mods.Length, 0, $"Expected to find one manifest, found {mods.Length} instead.");
- Assert.AreEqual(ModMetadataStatus.Failed, mod.Status, "The mod metadata was not marked failed.");
+ Assert.AreEqual(ModMetadataStatus.Failed, mod!.Status, "The mod metadata was not marked failed.");
Assert.IsNotNull(mod.Error, "The mod metadata did not have an error message set.");
}
@@ -91,12 +89,12 @@ namespace SMAPI.Tests.Core
// act
IModMetadata[] mods = new ModResolver().ReadManifests(new ModToolkit(), rootFolder, new ModDatabase()).ToArray();
- IModMetadata mod = mods.FirstOrDefault();
+ IModMetadata? mod = mods.FirstOrDefault();
// assert
Assert.AreEqual(1, mods.Length, 0, "Expected to find one manifest.");
Assert.IsNotNull(mod, "The loaded manifest shouldn't be null.");
- Assert.AreEqual(null, mod.DataRecord, "The data record should be null since we didn't provide one.");
+ Assert.AreEqual(null, mod!.DataRecord, "The data record should be null since we didn't provide one.");
Assert.AreEqual(modFolder, mod.DirectoryPath, "The directory path doesn't match.");
Assert.AreEqual(null, mod.Error, "The error should be null since parsing should have succeeded.");
Assert.AreEqual(ModMetadataStatus.Found, mod.Status, "The status doesn't match.");
@@ -215,7 +213,7 @@ namespace SMAPI.Tests.Core
// create DLL
string modFolder = Path.Combine(this.GetTempFolderPath(), Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(modFolder);
- File.WriteAllText(Path.Combine(modFolder, manifest.EntryDll), "");
+ File.WriteAllText(Path.Combine(modFolder, manifest.EntryDll!), "");
// arrange
Mock<IModMetadata> mock = new Mock<IModMetadata>(MockBehavior.Strict);
@@ -480,21 +478,20 @@ namespace SMAPI.Tests.Core
/// <param name="contentPackForID">The <see cref="IManifest.ContentPackFor"/> value.</param>
/// <param name="minimumApiVersion">The <see cref="IManifest.MinimumApiVersion"/> value.</param>
/// <param name="dependencies">The <see cref="IManifest.Dependencies"/> value.</param>
- private Manifest GetManifest(string id = null, string name = null, string version = null, string entryDll = null, string contentPackForID = null, string minimumApiVersion = null, IManifestDependency[] dependencies = null)
+ private Manifest GetManifest(string? id = null, string? name = null, string? version = null, string? entryDll = null, string? contentPackForID = null, string? minimumApiVersion = null, IManifestDependency[]? dependencies = null)
{
- return new Manifest
- {
- UniqueID = id ?? $"{Sample.String()}.{Sample.String()}",
- Name = name ?? id ?? Sample.String(),
- Author = Sample.String(),
- Description = Sample.String(),
- Version = version != null ? new SemanticVersion(version) : new SemanticVersion(Sample.Int(), Sample.Int(), Sample.Int(), Sample.String()),
- EntryDll = entryDll ?? $"{Sample.String()}.dll",
- ContentPackFor = contentPackForID != null ? new ManifestContentPackFor { UniqueID = contentPackForID } : null,
- MinimumApiVersion = minimumApiVersion != null ? new SemanticVersion(minimumApiVersion) : null,
- Dependencies = dependencies ?? Array.Empty<IManifestDependency>(),
- UpdateKeys = Array.Empty<string>()
- };
+ return new Manifest(
+ uniqueId: id ?? $"{Sample.String()}.{Sample.String()}",
+ name: name ?? id ?? Sample.String(),
+ author: Sample.String(),
+ description: Sample.String(),
+ version: version != null ? new SemanticVersion(version) : new SemanticVersion(Sample.Int(), Sample.Int(), Sample.Int(), Sample.String()),
+ entryDll: entryDll ?? $"{Sample.String()}.dll",
+ contentPackFor: contentPackForID != null ? new ManifestContentPackFor(contentPackForID, null) : null,
+ minimumApiVersion: minimumApiVersion != null ? new SemanticVersion(minimumApiVersion) : null,
+ dependencies: dependencies ?? Array.Empty<IManifestDependency>(),
+ updateKeys: Array.Empty<string>()
+ );
}
/// <summary>Get a randomized basic manifest.</summary>
@@ -510,7 +507,7 @@ namespace SMAPI.Tests.Core
/// <param name="allowStatusChange">Whether the code being tested is allowed to change the mod status.</param>
private Mock<IModMetadata> GetMetadata(string uniqueID, string[] dependencies, bool allowStatusChange = false)
{
- IManifest manifest = this.GetManifest(id: uniqueID, version: "1.0", dependencies: dependencies?.Select(dependencyID => (IManifestDependency)new ManifestDependency(dependencyID, null)).ToArray());
+ IManifest manifest = this.GetManifest(id: uniqueID, version: "1.0", dependencies: dependencies?.Select(dependencyID => (IManifestDependency)new ManifestDependency(dependencyID, null as ISemanticVersion)).ToArray());
return this.GetMetadata(manifest, allowStatusChange);
}
@@ -538,7 +535,7 @@ namespace SMAPI.Tests.Core
/// <summary>Set up a mock mod metadata for <see cref="ModResolver.ValidateManifests"/>.</summary>
/// <param name="mod">The mock mod metadata.</param>
/// <param name="modRecord">The extra metadata about the mod from SMAPI's internal data (if any).</param>
- private void SetupMetadataForValidation(Mock<IModMetadata> mod, ModDataRecordVersionedFields modRecord = null)
+ private void SetupMetadataForValidation(Mock<IModMetadata> mod, ModDataRecordVersionedFields? modRecord = null)
{
mod.Setup(p => p.Status).Returns(ModMetadataStatus.Found);
mod.Setup(p => p.DataRecord).Returns(() => null);
diff --git a/src/SMAPI.Toolkit.CoreInterfaces/IManifest.cs b/src/SMAPI.Toolkit.CoreInterfaces/IManifest.cs
index a9251446..ee6cc0b6 100644
--- a/src/SMAPI.Toolkit.CoreInterfaces/IManifest.cs
+++ b/src/SMAPI.Toolkit.CoreInterfaces/IManifest.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Collections.Generic;
namespace StardewModdingAPI
@@ -23,16 +21,16 @@ namespace StardewModdingAPI
ISemanticVersion Version { get; }
/// <summary>The minimum SMAPI version required by this mod, if any.</summary>
- ISemanticVersion MinimumApiVersion { get; }
+ ISemanticVersion? MinimumApiVersion { get; }
/// <summary>The unique mod ID.</summary>
string UniqueID { get; }
/// <summary>The name of the DLL in the directory that has the <c>Entry</c> method. Mutually exclusive with <see cref="ContentPackFor"/>.</summary>
- string EntryDll { get; }
+ string? EntryDll { get; }
/// <summary>The mod which will read this as a content pack. Mutually exclusive with <see cref="EntryDll"/>.</summary>
- IManifestContentPackFor ContentPackFor { get; }
+ IManifestContentPackFor? ContentPackFor { get; }
/// <summary>The other mods that must be loaded before this mod.</summary>
IManifestDependency[] Dependencies { get; }
diff --git a/src/SMAPI.Toolkit.CoreInterfaces/IManifestContentPackFor.cs b/src/SMAPI.Toolkit.CoreInterfaces/IManifestContentPackFor.cs
index d898b716..52ac8f1c 100644
--- a/src/SMAPI.Toolkit.CoreInterfaces/IManifestContentPackFor.cs
+++ b/src/SMAPI.Toolkit.CoreInterfaces/IManifestContentPackFor.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
namespace StardewModdingAPI
{
/// <summary>Indicates which mod can read the content pack represented by the containing manifest.</summary>
@@ -9,6 +7,6 @@ namespace StardewModdingAPI
string UniqueID { get; }
/// <summary>The minimum required version (if any).</summary>
- ISemanticVersion MinimumVersion { get; }
+ ISemanticVersion? MinimumVersion { get; }
}
}
diff --git a/src/SMAPI.Toolkit.CoreInterfaces/IManifestDependency.cs b/src/SMAPI.Toolkit.CoreInterfaces/IManifestDependency.cs
index 49b7aed6..58425eb2 100644
--- a/src/SMAPI.Toolkit.CoreInterfaces/IManifestDependency.cs
+++ b/src/SMAPI.Toolkit.CoreInterfaces/IManifestDependency.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
namespace StardewModdingAPI
{
/// <summary>A mod dependency listed in a mod manifest.</summary>
@@ -12,7 +10,7 @@ namespace StardewModdingAPI
string UniqueID { get; }
/// <summary>The minimum required version (if any).</summary>
- ISemanticVersion MinimumVersion { get; }
+ ISemanticVersion? MinimumVersion { get; }
/// <summary>Whether the dependency must be installed to use the mod.</summary>
bool IsRequired { get; }
diff --git a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs
index 4deaf19b..621f1e28 100644
--- a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs
+++ b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs
@@ -171,14 +171,6 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
}
}
- // normalize display fields
- if (manifest != null)
- {
- manifest.Name = this.StripNewlines(manifest.Name);
- manifest.Description = this.StripNewlines(manifest.Description);
- manifest.Author = this.StripNewlines(manifest.Author);
- }
-
// get mod type
ModType type;
{
@@ -365,12 +357,5 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
return hasVortexMarker;
}
-
- /// <summary>Strip newlines from a string.</summary>
- /// <param name="input">The input to strip.</param>
- private string StripNewlines(string input)
- {
- return input?.Replace("\r", "").Replace("\n", "");
- }
}
}
diff --git a/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs b/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs
index a5dbf604..01010602 100644
--- a/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs
+++ b/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs
@@ -1,8 +1,6 @@
-#nullable disable
-
using System;
using System.Collections.Generic;
-using System.Runtime.Serialization;
+using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
using StardewModdingAPI.Toolkit.Serialization.Converters;
@@ -15,48 +13,45 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models
** Accessors
*********/
/// <summary>The mod name.</summary>
- public string Name { get; set; }
+ public string Name { get; }
/// <summary>A brief description of the mod.</summary>
- public string Description { get; set; }
+ public string Description { get; }
/// <summary>The mod author's name.</summary>
- public string Author { get; set; }
+ public string Author { get; }
/// <summary>The mod version.</summary>
- public ISemanticVersion Version { get; set; }
+ public ISemanticVersion Version { get; }
/// <summary>The minimum SMAPI version required by this mod, if any.</summary>
- public ISemanticVersion MinimumApiVersion { get; set; }
+ public ISemanticVersion? MinimumApiVersion { get; }
/// <summary>The name of the DLL in the directory that has the <c>Entry</c> method. Mutually exclusive with <see cref="ContentPackFor"/>.</summary>
- public string EntryDll { get; set; }
+ public string? EntryDll { get; }
/// <summary>The mod which will read this as a content pack. Mutually exclusive with <see cref="Manifest.EntryDll"/>.</summary>
[JsonConverter(typeof(ManifestContentPackForConverter))]
- public IManifestContentPackFor ContentPackFor { get; set; }
+ public IManifestContentPackFor? ContentPackFor { get; }
/// <summary>The other mods that must be loaded before this mod.</summary>
[JsonConverter(typeof(ManifestDependencyArrayConverter))]
- public IManifestDependency[] Dependencies { get; set; }
+ public IManifestDependency[] Dependencies { get; }
/// <summary>The namespaced mod IDs to query for updates (like <c>Nexus:541</c>).</summary>
- public string[] UpdateKeys { get; set; }
+ public string[] UpdateKeys { get; private set; }
/// <summary>The unique mod ID.</summary>
- public string UniqueID { get; set; }
+ public string UniqueID { get; }
/// <summary>Any manifest fields which didn't match a valid field.</summary>
[JsonExtensionData]
- public IDictionary<string, object> ExtraFields { get; set; }
+ public IDictionary<string, object> ExtraFields { get; set; } = new Dictionary<string, object>();
/*********
** Public methods
*********/
- /// <summary>Construct an instance.</summary>
- public Manifest() { }
-
/// <summary>Construct an instance for a transitional content pack.</summary>
/// <param name="uniqueID">The unique mod ID.</param>
/// <param name="name">The mod name.</param>
@@ -64,24 +59,71 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models
/// <param name="description">A brief description of the mod.</param>
/// <param name="version">The mod version.</param>
/// <param name="contentPackFor">The modID which will read this as a content pack.</param>
- public Manifest(string uniqueID, string name, string author, string description, ISemanticVersion version, string contentPackFor = null)
+ public Manifest(string uniqueID, string name, string author, string description, ISemanticVersion version, string? contentPackFor = null)
+ : this(
+ uniqueId: uniqueID,
+ name: name,
+ author: author,
+ description: description,
+ version: version,
+ minimumApiVersion: null,
+ entryDll: null,
+ contentPackFor: contentPackFor != null
+ ? new ManifestContentPackFor(contentPackFor, null)
+ : null,
+ dependencies: null,
+ updateKeys: null
+ )
+ { }
+
+ /// <summary>Construct an instance for a transitional content pack.</summary>
+ /// <param name="uniqueId">The unique mod ID.</param>
+ /// <param name="name">The mod name.</param>
+ /// <param name="author">The mod author's name.</param>
+ /// <param name="description">A brief description of the mod.</param>
+ /// <param name="version">The mod version.</param>
+ /// <param name="minimumApiVersion">The minimum SMAPI version required by this mod, if any.</param>
+ /// <param name="entryDll">The name of the DLL in the directory that has the <c>Entry</c> method. Mutually exclusive with <see cref="ContentPackFor"/>.</param>
+ /// <param name="contentPackFor">The modID which will read this as a content pack.</param>
+ /// <param name="dependencies">The other mods that must be loaded before this mod.</param>
+ /// <param name="updateKeys">The namespaced mod IDs to query for updates (like <c>Nexus:541</c>).</param>
+ [JsonConstructor]
+ public Manifest(string uniqueId, string name, string author, string description, ISemanticVersion version, ISemanticVersion? minimumApiVersion, string? entryDll, IManifestContentPackFor? contentPackFor, IManifestDependency[]? dependencies, string[]? updateKeys)
{
- this.Name = name;
- this.Author = author;
- this.Description = description;
+ this.UniqueID = this.NormalizeWhitespace(uniqueId);
+ this.Name = this.NormalizeWhitespace(name);
+ this.Author = this.NormalizeWhitespace(author);
+ this.Description = this.NormalizeWhitespace(description);
this.Version = version;
- this.UniqueID = uniqueID;
- this.UpdateKeys = Array.Empty<string>();
- this.ContentPackFor = new ManifestContentPackFor { UniqueID = contentPackFor };
+ this.MinimumApiVersion = minimumApiVersion;
+ this.EntryDll = this.NormalizeWhitespace(entryDll);
+ this.ContentPackFor = contentPackFor;
+ this.Dependencies = dependencies ?? Array.Empty<IManifestDependency>();
+ this.UpdateKeys = updateKeys ?? Array.Empty<string>();
+ }
+
+ /// <summary>Override the update keys loaded from the mod info.</summary>
+ /// <param name="updateKeys">The new update keys to set.</param>
+ internal void OverrideUpdateKeys(params string[] updateKeys)
+ {
+ this.UpdateKeys = updateKeys;
}
- /// <summary>Normalize the model after it's deserialized.</summary>
- /// <param name="context">The deserialization context.</param>
- [OnDeserialized]
- public void OnDeserialized(StreamingContext context)
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Normalize whitespace in a raw string.</summary>
+ /// <param name="input">The input to strip.</param>
+#if NET5_0_OR_GREATER
+ [return: NotNullIfNotNull("input")]
+#endif
+ private string? NormalizeWhitespace(string? input)
{
- this.Dependencies ??= Array.Empty<IManifestDependency>();
- this.UpdateKeys ??= Array.Empty<string>();
+ return input
+ ?.Trim()
+ .Replace("\r", "")
+ .Replace("\n", "");
}
}
}
diff --git a/src/SMAPI.Toolkit/Serialization/Models/ManifestContentPackFor.cs b/src/SMAPI.Toolkit/Serialization/Models/ManifestContentPackFor.cs
index ea5f0e6c..f7dc8aa8 100644
--- a/src/SMAPI.Toolkit/Serialization/Models/ManifestContentPackFor.cs
+++ b/src/SMAPI.Toolkit/Serialization/Models/ManifestContentPackFor.cs
@@ -1,4 +1,4 @@
-#nullable disable
+using System.Diagnostics.CodeAnalysis;
namespace StardewModdingAPI.Toolkit.Serialization.Models
{
@@ -9,9 +9,36 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models
** Accessors
*********/
/// <summary>The unique ID of the mod which can read this content pack.</summary>
- public string UniqueID { get; set; }
+ public string UniqueID { get; }
/// <summary>The minimum required version (if any).</summary>
- public ISemanticVersion MinimumVersion { get; set; }
+ public ISemanticVersion? MinimumVersion { get; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="uniqueId">The unique ID of the mod which can read this content pack.</param>
+ /// <param name="minimumVersion">The minimum required version (if any).</param>
+ public ManifestContentPackFor(string uniqueId, ISemanticVersion? minimumVersion)
+ {
+ this.UniqueID = this.NormalizeWhitespace(uniqueId);
+ this.MinimumVersion = minimumVersion;
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Normalize whitespace in a raw string.</summary>
+ /// <param name="input">The input to strip.</param>
+#if NET5_0_OR_GREATER
+ [return: NotNullIfNotNull("input")]
+#endif
+ private string? NormalizeWhitespace(string? input)
+ {
+ return input?.Trim();
+ }
}
}
diff --git a/src/SMAPI.Toolkit/Serialization/Models/ManifestDependency.cs b/src/SMAPI.Toolkit/Serialization/Models/ManifestDependency.cs
index f52dd5ee..e7acf71d 100644
--- a/src/SMAPI.Toolkit/Serialization/Models/ManifestDependency.cs
+++ b/src/SMAPI.Toolkit/Serialization/Models/ManifestDependency.cs
@@ -1,4 +1,5 @@
-#nullable disable
+using System.Diagnostics.CodeAnalysis;
+using Newtonsoft.Json;
namespace StardewModdingAPI.Toolkit.Serialization.Models
{
@@ -9,13 +10,13 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models
** Accessors
*********/
/// <summary>The unique mod ID to require.</summary>
- public string UniqueID { get; set; }
+ public string UniqueID { get; }
/// <summary>The minimum required version (if any).</summary>
- public ISemanticVersion MinimumVersion { get; set; }
+ public ISemanticVersion? MinimumVersion { get; }
/// <summary>Whether the dependency must be installed to use the mod.</summary>
- public bool IsRequired { get; set; }
+ public bool IsRequired { get; }
/*********
@@ -26,12 +27,39 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models
/// <param name="minimumVersion">The minimum required version (if any).</param>
/// <param name="required">Whether the dependency must be installed to use the mod.</param>
public ManifestDependency(string uniqueID, string minimumVersion, bool required = true)
+ : this(
+ uniqueID: uniqueID,
+ minimumVersion: !string.IsNullOrWhiteSpace(minimumVersion)
+ ? new SemanticVersion(minimumVersion)
+ : null,
+ required: required
+ )
+ { }
+
+ /// <summary>Construct an instance.</summary>
+ /// <param name="uniqueID">The unique mod ID to require.</param>
+ /// <param name="minimumVersion">The minimum required version (if any).</param>
+ /// <param name="required">Whether the dependency must be installed to use the mod.</param>
+ [JsonConstructor]
+ public ManifestDependency(string uniqueID, ISemanticVersion? minimumVersion, bool required = true)
{
- this.UniqueID = uniqueID;
- this.MinimumVersion = !string.IsNullOrWhiteSpace(minimumVersion)
- ? new SemanticVersion(minimumVersion)
- : null;
+ this.UniqueID = this.NormalizeWhitespace(uniqueID);
+ this.MinimumVersion = minimumVersion;
this.IsRequired = required;
}
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Normalize whitespace in a raw string.</summary>
+ /// <param name="input">The input to strip.</param>
+#if NET5_0_OR_GREATER
+ [return: NotNullIfNotNull("input")]
+#endif
+ private string? NormalizeWhitespace(string? input)
+ {
+ return input?.Trim();
+ }
}
}
diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs
index 51463048..2842c11a 100644
--- a/src/SMAPI/Framework/ModLoading/ModResolver.cs
+++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs
@@ -35,7 +35,7 @@ namespace StardewModdingAPI.Framework.ModLoading
// apply defaults
if (manifest != null && dataRecord?.UpdateKey is not null)
- manifest.UpdateKeys = new[] { dataRecord.UpdateKey };
+ manifest.OverrideUpdateKeys(dataRecord.UpdateKey);
// build metadata
bool shouldIgnore = folder.Type == ModType.Ignored;