From 69b17f1db87d9aeb5dd6d6f9c81ac9ac62f2a6d3 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 25 May 2018 02:06:28 -0400 Subject: move PathUtilities into toolkit (#532) --- src/SMAPI/Framework/ContentPack.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/SMAPI/Framework/ContentPack.cs') diff --git a/src/SMAPI/Framework/ContentPack.cs b/src/SMAPI/Framework/ContentPack.cs index 071fb872..ee6df1ec 100644 --- a/src/SMAPI/Framework/ContentPack.cs +++ b/src/SMAPI/Framework/ContentPack.cs @@ -3,7 +3,7 @@ using System.IO; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Framework.Serialisation; -using StardewModdingAPI.Framework.Utilities; +using StardewModdingAPI.Toolkit.Utilities; using xTile; namespace StardewModdingAPI.Framework -- cgit From 625c538f244519700f3942b2b2969845db9a99b0 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 5 Jun 2018 20:22:46 -0400 Subject: move manifest parsing into toolkit (#532) --- src/SMAPI.Tests/Core/ModResolverTests.cs | 84 +++++-------- src/SMAPI.Tests/Utilities/SemanticVersionTests.cs | 1 + src/SMAPI/Framework/ContentPack.cs | 2 +- src/SMAPI/Framework/Exceptions/SParseException.cs | 17 --- src/SMAPI/Framework/InternalExtensions.cs | 16 --- src/SMAPI/Framework/LegacyManifestVersion.cs | 26 ---- src/SMAPI/Framework/ModHelpers/ModHelper.cs | 20 ++- src/SMAPI/Framework/ModLoading/ModResolver.cs | 37 +++--- src/SMAPI/Framework/Models/Manifest.cs | 74 +++++++++-- .../Framework/Models/ManifestContentPackFor.cs | 25 +++- src/SMAPI/Framework/Models/ManifestDependency.cs | 11 +- .../Framework/Serialisation/ColorConverter.cs | 47 +++++++ .../CrossplatformConverters/ColorConverter.cs | 46 ------- .../CrossplatformConverters/PointConverter.cs | 42 ------- .../CrossplatformConverters/RectangleConverter.cs | 51 -------- src/SMAPI/Framework/Serialisation/JsonHelper.cs | 139 --------------------- .../Framework/Serialisation/PointConverter.cs | 43 +++++++ .../Framework/Serialisation/RectangleConverter.cs | 52 ++++++++ .../Serialisation/SimpleReadOnlyConverter.cs | 77 ------------ .../ManifestContentPackForConverter.cs | 50 -------- .../ManifestDependencyArrayConverter.cs | 60 --------- .../SmapiConverters/SemanticVersionConverter.cs | 36 ------ .../SmapiConverters/StringEnumConverter.cs | 22 ---- src/SMAPI/IManifest.cs | 2 +- src/SMAPI/Program.cs | 18 ++- src/SMAPI/StardewModdingAPI.csproj | 20 +-- .../Converters/ManifestContentPackForConverter.cs | 50 ++++++++ .../Converters/ManifestDependencyArrayConverter.cs | 60 +++++++++ .../Converters/SemanticVersionConverter.cs | 36 ++++++ .../Converters/SimpleReadOnlyConverter.cs | 76 +++++++++++ .../Converters/StringEnumConverter.cs | 22 ++++ .../Serialisation/InternalExtensions.cs | 21 ++++ .../Serialisation/JsonHelper.cs | 123 ++++++++++++++++++ .../Serialisation/Models/LegacyManifestVersion.cs | 26 ++++ .../Serialisation/Models/Manifest.cs | 49 ++++++++ .../Serialisation/Models/ManifestContentPackFor.cs | 15 +++ .../Serialisation/Models/ManifestDependency.cs | 35 ++++++ .../Serialisation/SParseException.cs | 17 +++ 38 files changed, 851 insertions(+), 697 deletions(-) delete mode 100644 src/SMAPI/Framework/Exceptions/SParseException.cs delete mode 100644 src/SMAPI/Framework/LegacyManifestVersion.cs create mode 100644 src/SMAPI/Framework/Serialisation/ColorConverter.cs delete mode 100644 src/SMAPI/Framework/Serialisation/CrossplatformConverters/ColorConverter.cs delete mode 100644 src/SMAPI/Framework/Serialisation/CrossplatformConverters/PointConverter.cs delete mode 100644 src/SMAPI/Framework/Serialisation/CrossplatformConverters/RectangleConverter.cs delete mode 100644 src/SMAPI/Framework/Serialisation/JsonHelper.cs create mode 100644 src/SMAPI/Framework/Serialisation/PointConverter.cs create mode 100644 src/SMAPI/Framework/Serialisation/RectangleConverter.cs delete mode 100644 src/SMAPI/Framework/Serialisation/SimpleReadOnlyConverter.cs delete mode 100644 src/SMAPI/Framework/Serialisation/SmapiConverters/ManifestContentPackForConverter.cs delete mode 100644 src/SMAPI/Framework/Serialisation/SmapiConverters/ManifestDependencyArrayConverter.cs delete mode 100644 src/SMAPI/Framework/Serialisation/SmapiConverters/SemanticVersionConverter.cs delete mode 100644 src/SMAPI/Framework/Serialisation/SmapiConverters/StringEnumConverter.cs create mode 100644 src/StardewModdingAPI.Toolkit/Serialisation/Converters/ManifestContentPackForConverter.cs create mode 100644 src/StardewModdingAPI.Toolkit/Serialisation/Converters/ManifestDependencyArrayConverter.cs create mode 100644 src/StardewModdingAPI.Toolkit/Serialisation/Converters/SemanticVersionConverter.cs create mode 100644 src/StardewModdingAPI.Toolkit/Serialisation/Converters/SimpleReadOnlyConverter.cs create mode 100644 src/StardewModdingAPI.Toolkit/Serialisation/Converters/StringEnumConverter.cs create mode 100644 src/StardewModdingAPI.Toolkit/Serialisation/InternalExtensions.cs create mode 100644 src/StardewModdingAPI.Toolkit/Serialisation/JsonHelper.cs create mode 100644 src/StardewModdingAPI.Toolkit/Serialisation/Models/LegacyManifestVersion.cs create mode 100644 src/StardewModdingAPI.Toolkit/Serialisation/Models/Manifest.cs create mode 100644 src/StardewModdingAPI.Toolkit/Serialisation/Models/ManifestContentPackFor.cs create mode 100644 src/StardewModdingAPI.Toolkit/Serialisation/Models/ManifestDependency.cs create mode 100644 src/StardewModdingAPI.Toolkit/Serialisation/SParseException.cs (limited to 'src/SMAPI/Framework/ContentPack.cs') diff --git a/src/SMAPI.Tests/Core/ModResolverTests.cs b/src/SMAPI.Tests/Core/ModResolverTests.cs index d63eb1a2..2fbeb9b6 100644 --- a/src/SMAPI.Tests/Core/ModResolverTests.cs +++ b/src/SMAPI.Tests/Core/ModResolverTests.cs @@ -9,7 +9,7 @@ using StardewModdingAPI.Framework; using StardewModdingAPI.Framework.ModData; using StardewModdingAPI.Framework.Models; using StardewModdingAPI.Framework.ModLoading; -using StardewModdingAPI.Framework.Serialisation; +using StardewModdingAPI.Toolkit.Serialisation; namespace StardewModdingAPI.Tests.Core { @@ -93,8 +93,8 @@ namespace StardewModdingAPI.Tests.Core 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(modFolder, mod.DirectoryPath, "The directory path doesn't match."); - Assert.AreEqual(ModMetadataStatus.Found, mod.Status, "The status 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."); Assert.AreEqual(original[nameof(IManifest.Name)], mod.DisplayName, "The display name should use the manifest name."); Assert.AreEqual(original[nameof(IManifest.Name)], mod.Manifest.Name, "The manifest's name doesn't match."); @@ -160,7 +160,7 @@ namespace StardewModdingAPI.Tests.Core { // arrange Mock mock = this.GetMetadata("Mod A", new string[0], allowStatusChange: true); - mock.Setup(p => p.Manifest).Returns(this.GetManifest(m => m.MinimumApiVersion = new SemanticVersion("1.1"))); + mock.Setup(p => p.Manifest).Returns(this.GetManifest(minimumApiVersion: "1.1")); this.SetupMetadataForValidation(mock); // act @@ -174,7 +174,7 @@ namespace StardewModdingAPI.Tests.Core public void ValidateManifests_MissingEntryDLL_Fails() { // arrange - Mock mock = this.GetMetadata(this.GetManifest("Mod A", "1.0", manifest => manifest.EntryDll = "Missing.dll"), allowStatusChange: true); + Mock mock = this.GetMetadata(this.GetManifest(id: "Mod A", version: "1.0", entryDll: "Missing.dll"), allowStatusChange: true); this.SetupMetadataForValidation(mock); // act @@ -189,7 +189,7 @@ namespace StardewModdingAPI.Tests.Core { // arrange Mock modA = this.GetMetadata("Mod A", new string[0], allowStatusChange: true); - Mock modB = this.GetMetadata(this.GetManifest("Mod A", "1.0", manifest => manifest.Name = "Mod B"), allowStatusChange: true); + Mock modB = this.GetMetadata(this.GetManifest(id: "Mod A", name: "Mod B", version: "1.0"), allowStatusChange: true); Mock modC = this.GetMetadata("Mod C", new string[0], allowStatusChange: false); foreach (Mock mod in new[] { modA, modB, modC }) this.SetupMetadataForValidation(mod); @@ -398,8 +398,8 @@ namespace StardewModdingAPI.Tests.Core { // arrange // A 1.0 ◀── B (need A 1.1) - Mock modA = this.GetMetadata(this.GetManifest("Mod A", "1.0")); - Mock modB = this.GetMetadata(this.GetManifest("Mod B", "1.0", new ManifestDependency("Mod A", "1.1")), allowStatusChange: true); + Mock modA = this.GetMetadata(this.GetManifest(id: "Mod A", version: "1.0")); + Mock modB = this.GetMetadata(this.GetManifest(id: "Mod B", version: "1.0", dependencies: new IManifestDependency[] { new ManifestDependency("Mod A", "1.1") }), allowStatusChange: true); // act IModMetadata[] mods = new ModResolver().ProcessDependencies(new[] { modA.Object, modB.Object }, new ModDatabase()).ToArray(); @@ -414,8 +414,8 @@ namespace StardewModdingAPI.Tests.Core { // arrange // A 1.0 ◀── B (need A 1.0-beta) - Mock modA = this.GetMetadata(this.GetManifest("Mod A", "1.0")); - Mock modB = this.GetMetadata(this.GetManifest("Mod B", "1.0", new ManifestDependency("Mod A", "1.0-beta")), allowStatusChange: false); + Mock modA = this.GetMetadata(this.GetManifest(id: "Mod A", version: "1.0")); + Mock modB = this.GetMetadata(this.GetManifest(id: "Mod B", version: "1.0", dependencies: new IManifestDependency[] { new ManifestDependency("Mod A", "1.0-beta") }), allowStatusChange: false); // act IModMetadata[] mods = new ModResolver().ProcessDependencies(new[] { modA.Object, modB.Object }, new ModDatabase()).ToArray(); @@ -431,8 +431,8 @@ namespace StardewModdingAPI.Tests.Core { // arrange // A ◀── B - Mock modA = this.GetMetadata(this.GetManifest("Mod A", "1.0")); - Mock modB = this.GetMetadata(this.GetManifest("Mod B", "1.0", new ManifestDependency("Mod A", "1.0", required: false)), allowStatusChange: false); + Mock modA = this.GetMetadata(this.GetManifest(id: "Mod A", version: "1.0")); + Mock modB = this.GetMetadata(this.GetManifest(id: "Mod B", version: "1.0", dependencies: new IManifestDependency[] { new ManifestDependency("Mod A", "1.0", required: false) }), allowStatusChange: false); // act IModMetadata[] mods = new ModResolver().ProcessDependencies(new[] { modB.Object, modA.Object }, new ModDatabase()).ToArray(); @@ -448,7 +448,7 @@ namespace StardewModdingAPI.Tests.Core { // arrange // A ◀── B where A doesn't exist - Mock modB = this.GetMetadata(this.GetManifest("Mod B", "1.0", new ManifestDependency("Mod A", "1.0", required: false)), allowStatusChange: false); + Mock modB = this.GetMetadata(this.GetManifest(id: "Mod B", version: "1.0", dependencies: new IManifestDependency[] { new ManifestDependency("Mod A", "1.0", required: false) }), allowStatusChange: false); // act IModMetadata[] mods = new ModResolver().ProcessDependencies(new[] { modB.Object }, new ModDatabase()).ToArray(); @@ -463,46 +463,26 @@ namespace StardewModdingAPI.Tests.Core ** Private methods *********/ /// Get a randomised basic manifest. - /// Adjust the generated manifest. - private Manifest GetManifest(Action adjust = null) - { - Manifest manifest = new Manifest - { - Name = Sample.String(), - Author = Sample.String(), - Version = new SemanticVersion(Sample.Int(), Sample.Int(), Sample.Int(), Sample.String()), - Description = Sample.String(), - UniqueID = $"{Sample.String()}.{Sample.String()}", - EntryDll = $"{Sample.String()}.dll" - }; - adjust?.Invoke(manifest); - return manifest; - } - - /// Get a randomised basic manifest. - /// The mod's name and unique ID. - /// The mod version. - /// Adjust the generated manifest. - /// The dependencies this mod requires. - private IManifest GetManifest(string uniqueID, string version, Action adjust, params IManifestDependency[] dependencies) - { - return this.GetManifest(manifest => - { - manifest.Name = uniqueID; - manifest.UniqueID = uniqueID; - manifest.Version = new SemanticVersion(version); - manifest.Dependencies = dependencies; - adjust?.Invoke(manifest); - }); - } - - /// Get a randomised basic manifest. - /// The mod's name and unique ID. - /// The mod version. - /// The dependencies this mod requires. - private IManifest GetManifest(string uniqueID, string version, params IManifestDependency[] dependencies) + /// The value, or null for a generated value. + /// The value, or null for a generated value. + /// The value, or null for a generated value. + /// The value, or null for a generated value. + /// The value. + /// The value. + /// The value. + 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 this.GetManifest(uniqueID, version, null, dependencies); + 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, + minimumApiVersion: minimumApiVersion != null ? new SemanticVersion(minimumApiVersion) : null, + dependencies: dependencies + ); } /// Get a randomised basic manifest. @@ -518,7 +498,7 @@ namespace StardewModdingAPI.Tests.Core /// Whether the code being tested is allowed to change the mod status. private Mock GetMetadata(string uniqueID, string[] dependencies, bool allowStatusChange = false) { - IManifest manifest = this.GetManifest(uniqueID, "1.0", 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)).ToArray()); return this.GetMetadata(manifest, allowStatusChange); } diff --git a/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs b/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs index f1a72012..b797393b 100644 --- a/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs +++ b/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using Newtonsoft.Json; using NUnit.Framework; using StardewModdingAPI.Framework; +using StardewModdingAPI.Toolkit.Serialisation.Models; namespace StardewModdingAPI.Tests.Utilities { diff --git a/src/SMAPI/Framework/ContentPack.cs b/src/SMAPI/Framework/ContentPack.cs index ee6df1ec..4a4adb90 100644 --- a/src/SMAPI/Framework/ContentPack.cs +++ b/src/SMAPI/Framework/ContentPack.cs @@ -2,7 +2,7 @@ using System; using System.IO; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; -using StardewModdingAPI.Framework.Serialisation; +using StardewModdingAPI.Toolkit.Serialisation; using StardewModdingAPI.Toolkit.Utilities; using xTile; diff --git a/src/SMAPI/Framework/Exceptions/SParseException.cs b/src/SMAPI/Framework/Exceptions/SParseException.cs deleted file mode 100644 index f7133ee7..00000000 --- a/src/SMAPI/Framework/Exceptions/SParseException.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace StardewModdingAPI.Framework.Exceptions -{ - /// A format exception which provides a user-facing error message. - internal class SParseException : FormatException - { - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The error message. - /// The underlying exception, if any. - public SParseException(string message, Exception ex = null) - : base(message, ex) { } - } -} diff --git a/src/SMAPI/Framework/InternalExtensions.cs b/src/SMAPI/Framework/InternalExtensions.cs index bff4807c..ff3925fb 100644 --- a/src/SMAPI/Framework/InternalExtensions.cs +++ b/src/SMAPI/Framework/InternalExtensions.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Reflection; using Microsoft.Xna.Framework.Graphics; -using Newtonsoft.Json.Linq; using StardewModdingAPI.Framework.Reflection; using StardewValley; @@ -91,20 +90,5 @@ namespace StardewModdingAPI.Framework // get result return reflection.GetField(Game1.spriteBatch, fieldName).GetValue(); } - - /**** - ** Json.NET - ****/ - /// Get a JSON field value from a case-insensitive field name. This will check for an exact match first, then search without case sensitivity. - /// The value type. - /// The JSON object to search. - /// The field name. - public static T ValueIgnoreCase(this JObject obj, string fieldName) - { - JToken token = obj.GetValue(fieldName, StringComparison.InvariantCultureIgnoreCase); - return token != null - ? token.Value() - : default(T); - } } } diff --git a/src/SMAPI/Framework/LegacyManifestVersion.cs b/src/SMAPI/Framework/LegacyManifestVersion.cs deleted file mode 100644 index 454b9137..00000000 --- a/src/SMAPI/Framework/LegacyManifestVersion.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Newtonsoft.Json; - -namespace StardewModdingAPI.Framework -{ - /// An implementation of that hamdles the legacy version format. - internal class LegacyManifestVersion : SemanticVersion - { - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The major version incremented for major API changes. - /// The minor version incremented for backwards-compatible changes. - /// The patch version for backwards-compatible bug fixes. - /// An optional build tag. - [JsonConstructor] - public LegacyManifestVersion(int majorVersion, int minorVersion, int patchVersion, string build = null) - : base( - majorVersion, - minorVersion, - patchVersion, - build != "0" ? build : null // '0' from incorrect examples in old SMAPI documentation - ) - { } - } -} diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index e8726938..18904857 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -5,7 +5,7 @@ using System.Linq; using StardewModdingAPI.Events; using StardewModdingAPI.Framework.Input; using StardewModdingAPI.Framework.Models; -using StardewModdingAPI.Framework.Serialisation; +using StardewModdingAPI.Toolkit.Serialisation; using StardewModdingAPI.Toolkit.Utilities; namespace StardewModdingAPI.Framework.ModHelpers @@ -179,16 +179,14 @@ namespace StardewModdingAPI.Framework.ModHelpers throw new ArgumentException($"Can't create content pack for directory path '{directoryPath}' because no such directory exists."); // create manifest - IManifest manifest = new Manifest - { - Name = name, - Author = author, - Description = description, - Version = version, - UniqueID = id, - UpdateKeys = new string[0], - ContentPackFor = new ManifestContentPackFor { UniqueID = this.ModID } - }; + IManifest manifest = new Manifest( + uniqueID: id, + name: name, + author: author, + description: description, + version: version, + contentPackFor: new ManifestContentPackFor(this.ModID) + ); // create content pack return this.CreateContentPack(directoryPath, manifest); diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs index d46caa55..ddc8650c 100644 --- a/src/SMAPI/Framework/ModLoading/ModResolver.cs +++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs @@ -3,11 +3,11 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; -using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.ModData; using StardewModdingAPI.Framework.Models; -using StardewModdingAPI.Framework.Serialisation; +using StardewModdingAPI.Toolkit.Serialisation; using StardewModdingAPI.Toolkit.Utilities; +using ToolkitManifest = StardewModdingAPI.Toolkit.Serialisation.Models.Manifest; namespace StardewModdingAPI.Framework.ModLoading { @@ -28,25 +28,28 @@ namespace StardewModdingAPI.Framework.ModLoading { // read file Manifest manifest = null; - string path = Path.Combine(modDir.FullName, "manifest.json"); string error = null; - try { - manifest = jsonHelper.ReadJsonFile(path); - if (manifest == null) + string path = Path.Combine(modDir.FullName, "manifest.json"); + try { - error = File.Exists(path) - ? "its manifest is invalid." - : "it doesn't have a manifest."; + ToolkitManifest rawManifest = jsonHelper.ReadJsonFile(path); + if (rawManifest == null) + { + error = File.Exists(path) + ? "its manifest is invalid." + : "it doesn't have a manifest."; + } + manifest = new Manifest(rawManifest); + } + catch (SParseException ex) + { + error = $"parsing its manifest failed: {ex.Message}"; + } + catch (Exception ex) + { + error = $"parsing its manifest failed:\n{ex.GetLogSummary()}"; } - } - catch (SParseException ex) - { - error = $"parsing its manifest failed: {ex.Message}"; - } - catch (Exception ex) - { - error = $"parsing its manifest failed:\n{ex.GetLogSummary()}"; } // parse internal data record (if any) diff --git a/src/SMAPI/Framework/Models/Manifest.cs b/src/SMAPI/Framework/Models/Manifest.cs index f5867cf3..92ffe0dc 100644 --- a/src/SMAPI/Framework/Models/Manifest.cs +++ b/src/SMAPI/Framework/Models/Manifest.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; +using System.Linq; using Newtonsoft.Json; -using StardewModdingAPI.Framework.Serialisation.SmapiConverters; namespace StardewModdingAPI.Framework.Models { @@ -11,39 +11,87 @@ namespace StardewModdingAPI.Framework.Models ** Accessors *********/ /// The mod name. - public string Name { get; set; } + public string Name { get; } /// A brief description of the mod. - public string Description { get; set; } + public string Description { get; } /// The mod author's name. - public string Author { get; set; } + public string Author { get; } /// The mod version. - public ISemanticVersion Version { get; set; } + public ISemanticVersion Version { get; } /// The minimum SMAPI version required by this mod, if any. - public ISemanticVersion MinimumApiVersion { get; set; } + public ISemanticVersion MinimumApiVersion { get; } /// The name of the DLL in the directory that has the method. Mutually exclusive with . - public string EntryDll { get; set; } + public string EntryDll { get; } /// The mod which will read this as a content pack. Mutually exclusive with . - [JsonConverter(typeof(ManifestContentPackForConverter))] - public IManifestContentPackFor ContentPackFor { get; set; } + public IManifestContentPackFor ContentPackFor { get; } /// The other mods that must be loaded before this mod. - [JsonConverter(typeof(ManifestDependencyArrayConverter))] - public IManifestDependency[] Dependencies { get; set; } + public IManifestDependency[] Dependencies { get; } /// The namespaced mod IDs to query for updates (like Nexus:541). public string[] UpdateKeys { get; set; } /// The unique mod ID. - public string UniqueID { get; set; } + public string UniqueID { get; } /// Any manifest fields which didn't match a valid field. [JsonExtensionData] - public IDictionary ExtraFields { get; set; } + public IDictionary ExtraFields { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The toolkit manifest. + public Manifest(Toolkit.Serialisation.Models.Manifest manifest) + : this( + uniqueID: manifest.UniqueID, + name: manifest.Name, + author: manifest.Author, + description: manifest.Description, + version: manifest.Version != null ? new SemanticVersion(manifest.Version) : null, + entryDll: manifest.EntryDll, + minimumApiVersion: manifest.MinimumApiVersion != null ? new SemanticVersion(manifest.MinimumApiVersion) : null, + contentPackFor: manifest.ContentPackFor != null ? new ManifestContentPackFor(manifest.ContentPackFor) : null, + dependencies: manifest.Dependencies?.Select(p => p != null ? (IManifestDependency)new ManifestDependency(p) : null).ToArray(), + updateKeys: manifest.UpdateKeys, + extraFields: manifest.ExtraFields + ) + { } + + /// Construct an instance for a transitional content pack. + /// The unique mod ID. + /// The mod name. + /// The mod author's name. + /// A brief description of the mod. + /// The mod version. + /// The name of the DLL in the directory that has the method. Mutually exclusive with . + /// The minimum SMAPI version required by this mod, if any. + /// The modID which will read this as a content pack. Mutually exclusive with . + /// The other mods that must be loaded before this mod. + /// The namespaced mod IDs to query for updates (like Nexus:541). + /// Any manifest fields which didn't match a valid field. + public Manifest(string uniqueID, string name, string author, string description, ISemanticVersion version, string entryDll = null, ISemanticVersion minimumApiVersion = null, IManifestContentPackFor contentPackFor = null, IManifestDependency[] dependencies = null, string[] updateKeys = null, IDictionary extraFields = null) + { + this.Name = name; + this.Author = author; + this.Description = description; + this.Version = version; + this.UniqueID = uniqueID; + this.UpdateKeys = new string[0]; + this.EntryDll = entryDll; + this.ContentPackFor = contentPackFor; + this.MinimumApiVersion = minimumApiVersion; + this.Dependencies = dependencies ?? new IManifestDependency[0]; + this.UpdateKeys = updateKeys ?? new string[0]; + this.ExtraFields = extraFields; + } } } diff --git a/src/SMAPI/Framework/Models/ManifestContentPackFor.cs b/src/SMAPI/Framework/Models/ManifestContentPackFor.cs index 7836bbcc..cdad8893 100644 --- a/src/SMAPI/Framework/Models/ManifestContentPackFor.cs +++ b/src/SMAPI/Framework/Models/ManifestContentPackFor.cs @@ -7,9 +7,30 @@ namespace StardewModdingAPI.Framework.Models ** Accessors *********/ /// The unique ID of the mod which can read this content pack. - public string UniqueID { get; set; } + public string UniqueID { get; } /// The minimum required version (if any). - public ISemanticVersion MinimumVersion { get; set; } + public ISemanticVersion MinimumVersion { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The toolkit instance. + public ManifestContentPackFor(Toolkit.Serialisation.Models.ManifestContentPackFor contentPackFor) + { + this.UniqueID = contentPackFor.UniqueID; + this.MinimumVersion = new SemanticVersion(contentPackFor.MinimumVersion); + } + + /// Construct an instance. + /// The unique ID of the mod which can read this content pack. + /// The minimum required version (if any). + public ManifestContentPackFor(string uniqueID, ISemanticVersion minimumVersion = null) + { + this.UniqueID = uniqueID; + this.MinimumVersion = minimumVersion; + } } } diff --git a/src/SMAPI/Framework/Models/ManifestDependency.cs b/src/SMAPI/Framework/Models/ManifestDependency.cs index 97f0775a..e92597f3 100644 --- a/src/SMAPI/Framework/Models/ManifestDependency.cs +++ b/src/SMAPI/Framework/Models/ManifestDependency.cs @@ -7,18 +7,23 @@ namespace StardewModdingAPI.Framework.Models ** Accessors *********/ /// The unique mod ID to require. - public string UniqueID { get; set; } + public string UniqueID { get; } /// The minimum required version (if any). - public ISemanticVersion MinimumVersion { get; set; } + public ISemanticVersion MinimumVersion { get; } /// Whether the dependency must be installed to use the mod. - public bool IsRequired { get; set; } + public bool IsRequired { get; } /********* ** Public methods *********/ + /// Construct an instance. + /// The toolkit instance. + public ManifestDependency(Toolkit.Serialisation.Models.ManifestDependency dependency) + : this(dependency.UniqueID, dependency.MinimumVersion?.ToString(), dependency.IsRequired) { } + /// Construct an instance. /// The unique mod ID to require. /// The minimum required version (if any). diff --git a/src/SMAPI/Framework/Serialisation/ColorConverter.cs b/src/SMAPI/Framework/Serialisation/ColorConverter.cs new file mode 100644 index 00000000..c27065bf --- /dev/null +++ b/src/SMAPI/Framework/Serialisation/ColorConverter.cs @@ -0,0 +1,47 @@ +using System; +using Microsoft.Xna.Framework; +using Newtonsoft.Json.Linq; +using StardewModdingAPI.Toolkit.Serialisation; +using StardewModdingAPI.Toolkit.Serialisation.Converters; + +namespace StardewModdingAPI.Framework.Serialisation +{ + /// Handles deserialisation of for crossplatform compatibility. + /// + /// - Linux/Mac format: { "B": 76, "G": 51, "R": 25, "A": 102 } + /// - Windows format: "26, 51, 76, 102" + /// + internal class ColorConverter : SimpleReadOnlyConverter + { + /********* + ** Protected methods + *********/ + /// Read a JSON object. + /// The JSON object to read. + /// The path to the current JSON node. + protected override Color ReadObject(JObject obj, string path) + { + int r = obj.ValueIgnoreCase(nameof(Color.R)); + int g = obj.ValueIgnoreCase(nameof(Color.G)); + int b = obj.ValueIgnoreCase(nameof(Color.B)); + int a = obj.ValueIgnoreCase(nameof(Color.A)); + return new Color(r, g, b, a); + } + + /// Read a JSON string. + /// The JSON string value. + /// The path to the current JSON node. + protected override Color ReadString(string str, string path) + { + string[] parts = str.Split(','); + if (parts.Length != 4) + throw new SParseException($"Can't parse {typeof(Color).Name} from invalid value '{str}' (path: {path})."); + + int r = Convert.ToInt32(parts[0]); + int g = Convert.ToInt32(parts[1]); + int b = Convert.ToInt32(parts[2]); + int a = Convert.ToInt32(parts[3]); + return new Color(r, g, b, a); + } + } +} diff --git a/src/SMAPI/Framework/Serialisation/CrossplatformConverters/ColorConverter.cs b/src/SMAPI/Framework/Serialisation/CrossplatformConverters/ColorConverter.cs deleted file mode 100644 index f1b2f04f..00000000 --- a/src/SMAPI/Framework/Serialisation/CrossplatformConverters/ColorConverter.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using Microsoft.Xna.Framework; -using Newtonsoft.Json.Linq; -using StardewModdingAPI.Framework.Exceptions; - -namespace StardewModdingAPI.Framework.Serialisation.CrossplatformConverters -{ - /// Handles deserialisation of for crossplatform compatibility. - /// - /// - Linux/Mac format: { "B": 76, "G": 51, "R": 25, "A": 102 } - /// - Windows format: "26, 51, 76, 102" - /// - internal class ColorConverter : SimpleReadOnlyConverter - { - /********* - ** Protected methods - *********/ - /// Read a JSON object. - /// The JSON object to read. - /// The path to the current JSON node. - protected override Color ReadObject(JObject obj, string path) - { - int r = obj.ValueIgnoreCase(nameof(Color.R)); - int g = obj.ValueIgnoreCase(nameof(Color.G)); - int b = obj.ValueIgnoreCase(nameof(Color.B)); - int a = obj.ValueIgnoreCase(nameof(Color.A)); - return new Color(r, g, b, a); - } - - /// Read a JSON string. - /// The JSON string value. - /// The path to the current JSON node. - protected override Color ReadString(string str, string path) - { - string[] parts = str.Split(','); - if (parts.Length != 4) - throw new SParseException($"Can't parse {typeof(Color).Name} from invalid value '{str}' (path: {path})."); - - int r = Convert.ToInt32(parts[0]); - int g = Convert.ToInt32(parts[1]); - int b = Convert.ToInt32(parts[2]); - int a = Convert.ToInt32(parts[3]); - return new Color(r, g, b, a); - } - } -} diff --git a/src/SMAPI/Framework/Serialisation/CrossplatformConverters/PointConverter.cs b/src/SMAPI/Framework/Serialisation/CrossplatformConverters/PointConverter.cs deleted file mode 100644 index 434b7ea5..00000000 --- a/src/SMAPI/Framework/Serialisation/CrossplatformConverters/PointConverter.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using Microsoft.Xna.Framework; -using Newtonsoft.Json.Linq; -using StardewModdingAPI.Framework.Exceptions; - -namespace StardewModdingAPI.Framework.Serialisation.CrossplatformConverters -{ - /// Handles deserialisation of for crossplatform compatibility. - /// - /// - Linux/Mac format: { "X": 1, "Y": 2 } - /// - Windows format: "1, 2" - /// - internal class PointConverter : SimpleReadOnlyConverter - { - /********* - ** Protected methods - *********/ - /// Read a JSON object. - /// The JSON object to read. - /// The path to the current JSON node. - protected override Point ReadObject(JObject obj, string path) - { - int x = obj.ValueIgnoreCase(nameof(Point.X)); - int y = obj.ValueIgnoreCase(nameof(Point.Y)); - return new Point(x, y); - } - - /// Read a JSON string. - /// The JSON string value. - /// The path to the current JSON node. - protected override Point ReadString(string str, string path) - { - string[] parts = str.Split(','); - if (parts.Length != 2) - throw new SParseException($"Can't parse {typeof(Point).Name} from invalid value '{str}' (path: {path})."); - - int x = Convert.ToInt32(parts[0]); - int y = Convert.ToInt32(parts[1]); - return new Point(x, y); - } - } -} diff --git a/src/SMAPI/Framework/Serialisation/CrossplatformConverters/RectangleConverter.cs b/src/SMAPI/Framework/Serialisation/CrossplatformConverters/RectangleConverter.cs deleted file mode 100644 index 62bc8637..00000000 --- a/src/SMAPI/Framework/Serialisation/CrossplatformConverters/RectangleConverter.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Text.RegularExpressions; -using Microsoft.Xna.Framework; -using Newtonsoft.Json.Linq; -using StardewModdingAPI.Framework.Exceptions; - -namespace StardewModdingAPI.Framework.Serialisation.CrossplatformConverters -{ - /// Handles deserialisation of for crossplatform compatibility. - /// - /// - Linux/Mac format: { "X": 1, "Y": 2, "Width": 3, "Height": 4 } - /// - Windows format: "{X:1 Y:2 Width:3 Height:4}" - /// - internal class RectangleConverter : SimpleReadOnlyConverter - { - /********* - ** Protected methods - *********/ - /// Read a JSON object. - /// The JSON object to read. - /// The path to the current JSON node. - protected override Rectangle ReadObject(JObject obj, string path) - { - int x = obj.ValueIgnoreCase(nameof(Rectangle.X)); - int y = obj.ValueIgnoreCase(nameof(Rectangle.Y)); - int width = obj.ValueIgnoreCase(nameof(Rectangle.Width)); - int height = obj.ValueIgnoreCase(nameof(Rectangle.Height)); - return new Rectangle(x, y, width, height); - } - - /// Read a JSON string. - /// The JSON string value. - /// The path to the current JSON node. - protected override Rectangle ReadString(string str, string path) - { - if (string.IsNullOrWhiteSpace(str)) - return Rectangle.Empty; - - var match = Regex.Match(str, @"^\{X:(?\d+) Y:(?\d+) Width:(?\d+) Height:(?\d+)\}$", RegexOptions.IgnoreCase); - if (!match.Success) - throw new SParseException($"Can't parse {typeof(Rectangle).Name} from invalid value '{str}' (path: {path})."); - - int x = Convert.ToInt32(match.Groups["x"].Value); - int y = Convert.ToInt32(match.Groups["y"].Value); - int width = Convert.ToInt32(match.Groups["width"].Value); - int height = Convert.ToInt32(match.Groups["height"].Value); - - return new Rectangle(x, y, width, height); - } - } -} diff --git a/src/SMAPI/Framework/Serialisation/JsonHelper.cs b/src/SMAPI/Framework/Serialisation/JsonHelper.cs deleted file mode 100644 index 6cba343e..00000000 --- a/src/SMAPI/Framework/Serialisation/JsonHelper.cs +++ /dev/null @@ -1,139 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using Microsoft.Xna.Framework.Input; -using Newtonsoft.Json; -using StardewModdingAPI.Framework.Serialisation.CrossplatformConverters; -using StardewModdingAPI.Framework.Serialisation.SmapiConverters; - -namespace StardewModdingAPI.Framework.Serialisation -{ - /// Encapsulates SMAPI's JSON file parsing. - internal class JsonHelper - { - /********* - ** Accessors - *********/ - /// The JSON settings to use when serialising and deserialising files. - private readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings - { - Formatting = Formatting.Indented, - ObjectCreationHandling = ObjectCreationHandling.Replace, // avoid issue where default ICollection values are duplicated each time the config is loaded - Converters = new List - { - // SMAPI types - new SemanticVersionConverter(), - - // enums - new StringEnumConverter(), - new StringEnumConverter(), - new StringEnumConverter(), - - // crossplatform compatibility - new ColorConverter(), - new PointConverter(), - new RectangleConverter() - } - }; - - - /********* - ** Public methods - *********/ - /// Read a JSON file. - /// The model type. - /// The absolete file path. - /// Returns the deserialised model, or null if the file doesn't exist or is empty. - /// The given path is empty or invalid. - public TModel ReadJsonFile(string fullPath) - where TModel : class - { - // validate - if (string.IsNullOrWhiteSpace(fullPath)) - throw new ArgumentException("The file path is empty or invalid.", nameof(fullPath)); - - // read file - string json; - try - { - json = File.ReadAllText(fullPath); - } - catch (Exception ex) when (ex is DirectoryNotFoundException || ex is FileNotFoundException) - { - return null; - } - - // deserialise model - try - { - return this.Deserialise(json); - } - catch (Exception ex) - { - string error = $"Can't parse JSON file at {fullPath}."; - - if (ex is JsonReaderException) - { - error += " This doesn't seem to be valid JSON."; - if (json.Contains("“") || json.Contains("”")) - error += " Found curly quotes in the text; note that only straight quotes are allowed in JSON."; - } - error += $"\nTechnical details: {ex.Message}"; - throw new JsonReaderException(error); - } - } - - /// Save to a JSON file. - /// The model type. - /// The absolete file path. - /// The model to save. - /// The given path is empty or invalid. - public void WriteJsonFile(string fullPath, TModel model) - where TModel : class - { - // validate - if (string.IsNullOrWhiteSpace(fullPath)) - throw new ArgumentException("The file path is empty or invalid.", nameof(fullPath)); - - // create directory if needed - string dir = Path.GetDirectoryName(fullPath); - if (dir == null) - throw new ArgumentException("The file path is invalid.", nameof(fullPath)); - if (!Directory.Exists(dir)) - Directory.CreateDirectory(dir); - - // write file - string json = JsonConvert.SerializeObject(model, this.JsonSettings); - File.WriteAllText(fullPath, json); - } - - - /********* - ** Private methods - *********/ - /// Deserialize JSON text if possible. - /// The model type. - /// The raw JSON text. - private TModel Deserialise(string json) - { - try - { - return JsonConvert.DeserializeObject(json, this.JsonSettings); - } - catch (JsonReaderException) - { - // try replacing curly quotes - if (json.Contains("“") || json.Contains("”")) - { - try - { - return JsonConvert.DeserializeObject(json.Replace('“', '"').Replace('”', '"'), this.JsonSettings); - } - catch { /* rethrow original error */ } - } - - throw; - } - } - } -} diff --git a/src/SMAPI/Framework/Serialisation/PointConverter.cs b/src/SMAPI/Framework/Serialisation/PointConverter.cs new file mode 100644 index 00000000..fbc857d2 --- /dev/null +++ b/src/SMAPI/Framework/Serialisation/PointConverter.cs @@ -0,0 +1,43 @@ +using System; +using Microsoft.Xna.Framework; +using Newtonsoft.Json.Linq; +using StardewModdingAPI.Toolkit.Serialisation; +using StardewModdingAPI.Toolkit.Serialisation.Converters; + +namespace StardewModdingAPI.Framework.Serialisation +{ + /// Handles deserialisation of for crossplatform compatibility. + /// + /// - Linux/Mac format: { "X": 1, "Y": 2 } + /// - Windows format: "1, 2" + /// + internal class PointConverter : SimpleReadOnlyConverter + { + /********* + ** Protected methods + *********/ + /// Read a JSON object. + /// The JSON object to read. + /// The path to the current JSON node. + protected override Point ReadObject(JObject obj, string path) + { + int x = obj.ValueIgnoreCase(nameof(Point.X)); + int y = obj.ValueIgnoreCase(nameof(Point.Y)); + return new Point(x, y); + } + + /// Read a JSON string. + /// The JSON string value. + /// The path to the current JSON node. + protected override Point ReadString(string str, string path) + { + string[] parts = str.Split(','); + if (parts.Length != 2) + throw new SParseException($"Can't parse {typeof(Point).Name} from invalid value '{str}' (path: {path})."); + + int x = Convert.ToInt32(parts[0]); + int y = Convert.ToInt32(parts[1]); + return new Point(x, y); + } + } +} diff --git a/src/SMAPI/Framework/Serialisation/RectangleConverter.cs b/src/SMAPI/Framework/Serialisation/RectangleConverter.cs new file mode 100644 index 00000000..4f55cc32 --- /dev/null +++ b/src/SMAPI/Framework/Serialisation/RectangleConverter.cs @@ -0,0 +1,52 @@ +using System; +using System.Text.RegularExpressions; +using Microsoft.Xna.Framework; +using Newtonsoft.Json.Linq; +using StardewModdingAPI.Toolkit.Serialisation; +using StardewModdingAPI.Toolkit.Serialisation.Converters; + +namespace StardewModdingAPI.Framework.Serialisation +{ + /// Handles deserialisation of for crossplatform compatibility. + /// + /// - Linux/Mac format: { "X": 1, "Y": 2, "Width": 3, "Height": 4 } + /// - Windows format: "{X:1 Y:2 Width:3 Height:4}" + /// + internal class RectangleConverter : SimpleReadOnlyConverter + { + /********* + ** Protected methods + *********/ + /// Read a JSON object. + /// The JSON object to read. + /// The path to the current JSON node. + protected override Rectangle ReadObject(JObject obj, string path) + { + int x = obj.ValueIgnoreCase(nameof(Rectangle.X)); + int y = obj.ValueIgnoreCase(nameof(Rectangle.Y)); + int width = obj.ValueIgnoreCase(nameof(Rectangle.Width)); + int height = obj.ValueIgnoreCase(nameof(Rectangle.Height)); + return new Rectangle(x, y, width, height); + } + + /// Read a JSON string. + /// The JSON string value. + /// The path to the current JSON node. + protected override Rectangle ReadString(string str, string path) + { + if (string.IsNullOrWhiteSpace(str)) + return Rectangle.Empty; + + var match = Regex.Match(str, @"^\{X:(?\d+) Y:(?\d+) Width:(?\d+) Height:(?\d+)\}$", RegexOptions.IgnoreCase); + if (!match.Success) + throw new SParseException($"Can't parse {typeof(Rectangle).Name} from invalid value '{str}' (path: {path})."); + + int x = Convert.ToInt32(match.Groups["x"].Value); + int y = Convert.ToInt32(match.Groups["y"].Value); + int width = Convert.ToInt32(match.Groups["width"].Value); + int height = Convert.ToInt32(match.Groups["height"].Value); + + return new Rectangle(x, y, width, height); + } + } +} diff --git a/src/SMAPI/Framework/Serialisation/SimpleReadOnlyConverter.cs b/src/SMAPI/Framework/Serialisation/SimpleReadOnlyConverter.cs deleted file mode 100644 index 5765ad96..00000000 --- a/src/SMAPI/Framework/Serialisation/SimpleReadOnlyConverter.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using StardewModdingAPI.Framework.Exceptions; - -namespace StardewModdingAPI.Framework.Serialisation -{ - /// The base implementation for simplified converters which deserialise without overriding serialisation. - /// The type to deserialise. - internal abstract class SimpleReadOnlyConverter : JsonConverter - { - /********* - ** Accessors - *********/ - /// Whether this converter can write JSON. - public override bool CanWrite => false; - - - /********* - ** Public methods - *********/ - /// Get whether this instance can convert the specified object type. - /// The object type. - public override bool CanConvert(Type objectType) - { - return objectType == typeof(T); - } - - /// Writes the JSON representation of the object. - /// The JSON writer. - /// The value. - /// The calling serializer. - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - throw new InvalidOperationException("This converter does not write JSON."); - } - - /// Reads the JSON representation of the object. - /// The JSON reader. - /// The object type. - /// The object being read. - /// The calling serializer. - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - string path = reader.Path; - switch (reader.TokenType) - { - case JsonToken.StartObject: - return this.ReadObject(JObject.Load(reader), path); - case JsonToken.String: - return this.ReadString(JToken.Load(reader).Value(), path); - default: - throw new SParseException($"Can't parse {typeof(T).Name} from {reader.TokenType} node (path: {reader.Path})."); - } - } - - - /********* - ** Protected methods - *********/ - /// Read a JSON object. - /// The JSON object to read. - /// The path to the current JSON node. - protected virtual T ReadObject(JObject obj, string path) - { - throw new SParseException($"Can't parse {typeof(T).Name} from object node (path: {path})."); - } - - /// Read a JSON string. - /// The JSON string value. - /// The path to the current JSON node. - protected virtual T ReadString(string str, string path) - { - throw new SParseException($"Can't parse {typeof(T).Name} from string node (path: {path})."); - } - } -} diff --git a/src/SMAPI/Framework/Serialisation/SmapiConverters/ManifestContentPackForConverter.cs b/src/SMAPI/Framework/Serialisation/SmapiConverters/ManifestContentPackForConverter.cs deleted file mode 100644 index af7558f6..00000000 --- a/src/SMAPI/Framework/Serialisation/SmapiConverters/ManifestContentPackForConverter.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using Newtonsoft.Json; -using StardewModdingAPI.Framework.Models; - -namespace StardewModdingAPI.Framework.Serialisation.SmapiConverters -{ - /// Handles deserialisation of arrays. - internal class ManifestContentPackForConverter : JsonConverter - { - /********* - ** Accessors - *********/ - /// Whether this converter can write JSON. - public override bool CanWrite => false; - - - /********* - ** Public methods - *********/ - /// Get whether this instance can convert the specified object type. - /// The object type. - public override bool CanConvert(Type objectType) - { - return objectType == typeof(IManifestContentPackFor[]); - } - - - /********* - ** Protected methods - *********/ - /// Read the JSON representation of the object. - /// The JSON reader. - /// The object type. - /// The object being read. - /// The calling serializer. - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - return serializer.Deserialize(reader); - } - - /// Writes the JSON representation of the object. - /// The JSON writer. - /// The value. - /// The calling serializer. - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - throw new InvalidOperationException("This converter does not write JSON."); - } - } -} diff --git a/src/SMAPI/Framework/Serialisation/SmapiConverters/ManifestDependencyArrayConverter.cs b/src/SMAPI/Framework/Serialisation/SmapiConverters/ManifestDependencyArrayConverter.cs deleted file mode 100644 index 4150d5fb..00000000 --- a/src/SMAPI/Framework/Serialisation/SmapiConverters/ManifestDependencyArrayConverter.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Collections.Generic; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using StardewModdingAPI.Framework.Models; - -namespace StardewModdingAPI.Framework.Serialisation.SmapiConverters -{ - /// Handles deserialisation of arrays. - internal class ManifestDependencyArrayConverter : JsonConverter - { - /********* - ** Accessors - *********/ - /// Whether this converter can write JSON. - public override bool CanWrite => false; - - - /********* - ** Public methods - *********/ - /// Get whether this instance can convert the specified object type. - /// The object type. - public override bool CanConvert(Type objectType) - { - return objectType == typeof(IManifestDependency[]); - } - - - /********* - ** Protected methods - *********/ - /// Read the JSON representation of the object. - /// The JSON reader. - /// The object type. - /// The object being read. - /// The calling serializer. - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - List result = new List(); - foreach (JObject obj in JArray.Load(reader).Children()) - { - string uniqueID = obj.ValueIgnoreCase(nameof(IManifestDependency.UniqueID)); - string minVersion = obj.ValueIgnoreCase(nameof(IManifestDependency.MinimumVersion)); - bool required = obj.ValueIgnoreCase(nameof(IManifestDependency.IsRequired)) ?? true; - result.Add(new ManifestDependency(uniqueID, minVersion, required)); - } - return result.ToArray(); - } - - /// Writes the JSON representation of the object. - /// The JSON writer. - /// The value. - /// The calling serializer. - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - throw new InvalidOperationException("This converter does not write JSON."); - } - } -} diff --git a/src/SMAPI/Framework/Serialisation/SmapiConverters/SemanticVersionConverter.cs b/src/SMAPI/Framework/Serialisation/SmapiConverters/SemanticVersionConverter.cs deleted file mode 100644 index 7ee7e29b..00000000 --- a/src/SMAPI/Framework/Serialisation/SmapiConverters/SemanticVersionConverter.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Newtonsoft.Json.Linq; -using StardewModdingAPI.Framework.Exceptions; - -namespace StardewModdingAPI.Framework.Serialisation.SmapiConverters -{ - /// Handles deserialisation of . - internal class SemanticVersionConverter : SimpleReadOnlyConverter - { - /********* - ** Protected methods - *********/ - /// Read a JSON object. - /// The JSON object to read. - /// The path to the current JSON node. - protected override ISemanticVersion ReadObject(JObject obj, string path) - { - int major = obj.ValueIgnoreCase(nameof(ISemanticVersion.MajorVersion)); - int minor = obj.ValueIgnoreCase(nameof(ISemanticVersion.MinorVersion)); - int patch = obj.ValueIgnoreCase(nameof(ISemanticVersion.PatchVersion)); - string build = obj.ValueIgnoreCase(nameof(ISemanticVersion.Build)); - return new LegacyManifestVersion(major, minor, patch, build); - } - - /// Read a JSON string. - /// The JSON string value. - /// The path to the current JSON node. - protected override ISemanticVersion ReadString(string str, string path) - { - if (string.IsNullOrWhiteSpace(str)) - return null; - if (!SemanticVersion.TryParse(str, out ISemanticVersion version)) - throw new SParseException($"Can't parse semantic version from invalid value '{str}', should be formatted like 1.2, 1.2.30, or 1.2.30-beta (path: {path})."); - return version; - } - } -} diff --git a/src/SMAPI/Framework/Serialisation/SmapiConverters/StringEnumConverter.cs b/src/SMAPI/Framework/Serialisation/SmapiConverters/StringEnumConverter.cs deleted file mode 100644 index c88ac834..00000000 --- a/src/SMAPI/Framework/Serialisation/SmapiConverters/StringEnumConverter.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using Newtonsoft.Json.Converters; - -namespace StardewModdingAPI.Framework.Serialisation.SmapiConverters -{ - /// A variant of which only converts a specified enum. - /// The enum type. - internal class StringEnumConverter : StringEnumConverter - { - /********* - ** Public methods - *********/ - /// Get whether this instance can convert the specified object type. - /// The object type. - public override bool CanConvert(Type type) - { - return - base.CanConvert(type) - && (Nullable.GetUnderlyingType(type) ?? type) == typeof(T); - } - } -} diff --git a/src/SMAPI/IManifest.cs b/src/SMAPI/IManifest.cs index 183ac105..6c07d374 100644 --- a/src/SMAPI/IManifest.cs +++ b/src/SMAPI/IManifest.cs @@ -36,7 +36,7 @@ namespace StardewModdingAPI IManifestDependency[] Dependencies { get; } /// The namespaced mod IDs to query for updates (like Nexus:541). - string[] UpdateKeys { get; set; } + string[] UpdateKeys { get; } /// Any manifest fields which didn't match a valid field. IDictionary ExtraFields { get; } diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index 5c5137ef..e55a96b2 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -10,6 +10,7 @@ using System.Runtime.ExceptionServices; using System.Security; using System.Text.RegularExpressions; using System.Threading; +using Microsoft.Xna.Framework.Input; #if SMAPI_FOR_WINDOWS using System.Windows.Forms; #endif @@ -27,8 +28,11 @@ using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Framework.Serialisation; using StardewModdingAPI.Internal; using StardewModdingAPI.Toolkit.Framework.Clients.WebApi; +using StardewModdingAPI.Toolkit.Serialisation; +using StardewModdingAPI.Toolkit.Serialisation.Converters; using StardewModdingAPI.Toolkit.Utilities; using StardewValley; +using Keys = System.Windows.Forms.Keys; using Monitor = StardewModdingAPI.Framework.Monitor; using SObject = StardewValley.Object; using ThreadState = System.Threading.ThreadState; @@ -148,6 +152,18 @@ namespace StardewModdingAPI }; this.EventManager = new EventManager(this.Monitor, this.ModRegistry); + // init JSON parser + JsonConverter[] converters = { + new StringEnumConverter(), + new StringEnumConverter(), + new StringEnumConverter(), + new ColorConverter(), + new PointConverter(), + new RectangleConverter() + }; + foreach (JsonConverter converter in converters) + this.JsonHelper.JsonSettings.Converters.Add(converter); + // hook up events ContentEvents.Init(this.EventManager); ControlEvents.Init(this.EventManager); @@ -1093,7 +1109,7 @@ namespace StardewModdingAPI /// The mods for which to reload translations. private void ReloadTranslations(IEnumerable mods) { - JsonHelper jsonHelper = new JsonHelper(); + JsonHelper jsonHelper = this.JsonHelper; foreach (IModMetadata metadata in mods) { if (metadata.IsContentPack) diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj index a5ccc62d..3c953ec5 100644 --- a/src/SMAPI/StardewModdingAPI.csproj +++ b/src/SMAPI/StardewModdingAPI.csproj @@ -103,6 +103,9 @@ + + + @@ -114,16 +117,17 @@ + + + - - @@ -151,13 +155,6 @@ - - - - - - - @@ -235,16 +232,12 @@ - - - - @@ -283,7 +276,6 @@ - diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/Converters/ManifestContentPackForConverter.cs b/src/StardewModdingAPI.Toolkit/Serialisation/Converters/ManifestContentPackForConverter.cs new file mode 100644 index 00000000..232c22a7 --- /dev/null +++ b/src/StardewModdingAPI.Toolkit/Serialisation/Converters/ManifestContentPackForConverter.cs @@ -0,0 +1,50 @@ +using System; +using Newtonsoft.Json; +using StardewModdingAPI.Toolkit.Serialisation.Models; + +namespace StardewModdingAPI.Toolkit.Serialisation.Converters +{ + /// Handles deserialisation of arrays. + public class ManifestContentPackForConverter : JsonConverter + { + /********* + ** Accessors + *********/ + /// Whether this converter can write JSON. + public override bool CanWrite => false; + + + /********* + ** Public methods + *********/ + /// Get whether this instance can convert the specified object type. + /// The object type. + public override bool CanConvert(Type objectType) + { + return objectType == typeof(ManifestContentPackFor[]); + } + + + /********* + ** Protected methods + *********/ + /// Read the JSON representation of the object. + /// The JSON reader. + /// The object type. + /// The object being read. + /// The calling serializer. + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + return serializer.Deserialize(reader); + } + + /// Writes the JSON representation of the object. + /// The JSON writer. + /// The value. + /// The calling serializer. + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new InvalidOperationException("This converter does not write JSON."); + } + } +} diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/Converters/ManifestDependencyArrayConverter.cs b/src/StardewModdingAPI.Toolkit/Serialisation/Converters/ManifestDependencyArrayConverter.cs new file mode 100644 index 00000000..0a304ee3 --- /dev/null +++ b/src/StardewModdingAPI.Toolkit/Serialisation/Converters/ManifestDependencyArrayConverter.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using StardewModdingAPI.Toolkit.Serialisation.Models; + +namespace StardewModdingAPI.Toolkit.Serialisation.Converters +{ + /// Handles deserialisation of arrays. + internal class ManifestDependencyArrayConverter : JsonConverter + { + /********* + ** Accessors + *********/ + /// Whether this converter can write JSON. + public override bool CanWrite => false; + + + /********* + ** Public methods + *********/ + /// Get whether this instance can convert the specified object type. + /// The object type. + public override bool CanConvert(Type objectType) + { + return objectType == typeof(ManifestDependency[]); + } + + + /********* + ** Protected methods + *********/ + /// Read the JSON representation of the object. + /// The JSON reader. + /// The object type. + /// The object being read. + /// The calling serializer. + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + List result = new List(); + foreach (JObject obj in JArray.Load(reader).Children()) + { + string uniqueID = obj.ValueIgnoreCase(nameof(ManifestDependency.UniqueID)); + string minVersion = obj.ValueIgnoreCase(nameof(ManifestDependency.MinimumVersion)); + bool required = obj.ValueIgnoreCase(nameof(ManifestDependency.IsRequired)) ?? true; + result.Add(new ManifestDependency(uniqueID, minVersion, required)); + } + return result.ToArray(); + } + + /// Writes the JSON representation of the object. + /// The JSON writer. + /// The value. + /// The calling serializer. + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new InvalidOperationException("This converter does not write JSON."); + } + } +} diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/Converters/SemanticVersionConverter.cs b/src/StardewModdingAPI.Toolkit/Serialisation/Converters/SemanticVersionConverter.cs new file mode 100644 index 00000000..2075d27e --- /dev/null +++ b/src/StardewModdingAPI.Toolkit/Serialisation/Converters/SemanticVersionConverter.cs @@ -0,0 +1,36 @@ +using Newtonsoft.Json.Linq; +using StardewModdingAPI.Toolkit.Serialisation.Models; + +namespace StardewModdingAPI.Toolkit.Serialisation.Converters +{ + /// Handles deserialisation of . + internal class SemanticVersionConverter : SimpleReadOnlyConverter + { + /********* + ** Protected methods + *********/ + /// Read a JSON object. + /// The JSON object to read. + /// The path to the current JSON node. + protected override SemanticVersion ReadObject(JObject obj, string path) + { + int major = obj.ValueIgnoreCase("MajorVersion"); + int minor = obj.ValueIgnoreCase("MinorVersion"); + int patch = obj.ValueIgnoreCase("PatchVersion"); + string build = obj.ValueIgnoreCase("Build"); + return new LegacyManifestVersion(major, minor, patch, build); + } + + /// Read a JSON string. + /// The JSON string value. + /// The path to the current JSON node. + protected override SemanticVersion ReadString(string str, string path) + { + if (string.IsNullOrWhiteSpace(str)) + return null; + if (!SemanticVersion.TryParse(str, out ISemanticVersion version)) + throw new SParseException($"Can't parse semantic version from invalid value '{str}', should be formatted like 1.2, 1.2.30, or 1.2.30-beta (path: {path})."); + return (SemanticVersion)version; + } + } +} diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/Converters/SimpleReadOnlyConverter.cs b/src/StardewModdingAPI.Toolkit/Serialisation/Converters/SimpleReadOnlyConverter.cs new file mode 100644 index 00000000..5e0b0f4a --- /dev/null +++ b/src/StardewModdingAPI.Toolkit/Serialisation/Converters/SimpleReadOnlyConverter.cs @@ -0,0 +1,76 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace StardewModdingAPI.Toolkit.Serialisation.Converters +{ + /// The base implementation for simplified converters which deserialise without overriding serialisation. + /// The type to deserialise. + internal abstract class SimpleReadOnlyConverter : JsonConverter + { + /********* + ** Accessors + *********/ + /// Whether this converter can write JSON. + public override bool CanWrite => false; + + + /********* + ** Public methods + *********/ + /// Get whether this instance can convert the specified object type. + /// The object type. + public override bool CanConvert(Type objectType) + { + return objectType == typeof(T); + } + + /// Writes the JSON representation of the object. + /// The JSON writer. + /// The value. + /// The calling serializer. + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new InvalidOperationException("This converter does not write JSON."); + } + + /// Reads the JSON representation of the object. + /// The JSON reader. + /// The object type. + /// The object being read. + /// The calling serializer. + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + string path = reader.Path; + switch (reader.TokenType) + { + case JsonToken.StartObject: + return this.ReadObject(JObject.Load(reader), path); + case JsonToken.String: + return this.ReadString(JToken.Load(reader).Value(), path); + default: + throw new SParseException($"Can't parse {typeof(T).Name} from {reader.TokenType} node (path: {reader.Path})."); + } + } + + + /********* + ** Protected methods + *********/ + /// Read a JSON object. + /// The JSON object to read. + /// The path to the current JSON node. + protected virtual T ReadObject(JObject obj, string path) + { + throw new SParseException($"Can't parse {typeof(T).Name} from object node (path: {path})."); + } + + /// Read a JSON string. + /// The JSON string value. + /// The path to the current JSON node. + protected virtual T ReadString(string str, string path) + { + throw new SParseException($"Can't parse {typeof(T).Name} from string node (path: {path})."); + } + } +} diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/Converters/StringEnumConverter.cs b/src/StardewModdingAPI.Toolkit/Serialisation/Converters/StringEnumConverter.cs new file mode 100644 index 00000000..13e6e3a1 --- /dev/null +++ b/src/StardewModdingAPI.Toolkit/Serialisation/Converters/StringEnumConverter.cs @@ -0,0 +1,22 @@ +using System; +using Newtonsoft.Json.Converters; + +namespace StardewModdingAPI.Toolkit.Serialisation.Converters +{ + /// A variant of which only converts a specified enum. + /// The enum type. + internal class StringEnumConverter : StringEnumConverter + { + /********* + ** Public methods + *********/ + /// Get whether this instance can convert the specified object type. + /// The object type. + public override bool CanConvert(Type type) + { + return + base.CanConvert(type) + && (Nullable.GetUnderlyingType(type) ?? type) == typeof(T); + } + } +} diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/InternalExtensions.cs b/src/StardewModdingAPI.Toolkit/Serialisation/InternalExtensions.cs new file mode 100644 index 00000000..12b2c933 --- /dev/null +++ b/src/StardewModdingAPI.Toolkit/Serialisation/InternalExtensions.cs @@ -0,0 +1,21 @@ +using System; +using Newtonsoft.Json.Linq; + +namespace StardewModdingAPI.Toolkit.Serialisation +{ + /// Provides extension methods for parsing JSON. + public static class JsonExtensions + { + /// Get a JSON field value from a case-insensitive field name. This will check for an exact match first, then search without case sensitivity. + /// The value type. + /// The JSON object to search. + /// The field name. + public static T ValueIgnoreCase(this JObject obj, string fieldName) + { + JToken token = obj.GetValue(fieldName, StringComparison.InvariantCultureIgnoreCase); + return token != null + ? token.Value() + : default(T); + } + } +} diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/JsonHelper.cs b/src/StardewModdingAPI.Toolkit/Serialisation/JsonHelper.cs new file mode 100644 index 00000000..00f334ad --- /dev/null +++ b/src/StardewModdingAPI.Toolkit/Serialisation/JsonHelper.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Newtonsoft.Json; +using StardewModdingAPI.Toolkit.Serialisation.Converters; + +namespace StardewModdingAPI.Toolkit.Serialisation +{ + /// Encapsulates SMAPI's JSON file parsing. + public class JsonHelper + { + /********* + ** Accessors + *********/ + /// The JSON settings to use when serialising and deserialising files. + public JsonSerializerSettings JsonSettings { get; } = new JsonSerializerSettings + { + Formatting = Formatting.Indented, + ObjectCreationHandling = ObjectCreationHandling.Replace, // avoid issue where default ICollection values are duplicated each time the config is loaded + Converters = new List { new SemanticVersionConverter() } + }; + + + /********* + ** Public methods + *********/ + /// Read a JSON file. + /// The model type. + /// The absolete file path. + /// Returns the deserialised model, or null if the file doesn't exist or is empty. + /// The given path is empty or invalid. + public TModel ReadJsonFile(string fullPath) + where TModel : class + { + // validate + if (string.IsNullOrWhiteSpace(fullPath)) + throw new ArgumentException("The file path is empty or invalid.", nameof(fullPath)); + + // read file + string json; + try + { + json = File.ReadAllText(fullPath); + } + catch (Exception ex) when (ex is DirectoryNotFoundException || ex is FileNotFoundException) + { + return null; + } + + // deserialise model + try + { + return this.Deserialise(json); + } + catch (Exception ex) + { + string error = $"Can't parse JSON file at {fullPath}."; + + if (ex is JsonReaderException) + { + error += " This doesn't seem to be valid JSON."; + if (json.Contains("“") || json.Contains("”")) + error += " Found curly quotes in the text; note that only straight quotes are allowed in JSON."; + } + error += $"\nTechnical details: {ex.Message}"; + throw new JsonReaderException(error); + } + } + + /// Save to a JSON file. + /// The model type. + /// The absolete file path. + /// The model to save. + /// The given path is empty or invalid. + public void WriteJsonFile(string fullPath, TModel model) + where TModel : class + { + // validate + if (string.IsNullOrWhiteSpace(fullPath)) + throw new ArgumentException("The file path is empty or invalid.", nameof(fullPath)); + + // create directory if needed + string dir = Path.GetDirectoryName(fullPath); + if (dir == null) + throw new ArgumentException("The file path is invalid.", nameof(fullPath)); + if (!Directory.Exists(dir)) + Directory.CreateDirectory(dir); + + // write file + string json = JsonConvert.SerializeObject(model, this.JsonSettings); + File.WriteAllText(fullPath, json); + } + + + /********* + ** Private methods + *********/ + /// Deserialize JSON text if possible. + /// The model type. + /// The raw JSON text. + private TModel Deserialise(string json) + { + try + { + return JsonConvert.DeserializeObject(json, this.JsonSettings); + } + catch (JsonReaderException) + { + // try replacing curly quotes + if (json.Contains("“") || json.Contains("”")) + { + try + { + return JsonConvert.DeserializeObject(json.Replace('“', '"').Replace('”', '"'), this.JsonSettings); + } + catch { /* rethrow original error */ } + } + + throw; + } + } + } +} diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/Models/LegacyManifestVersion.cs b/src/StardewModdingAPI.Toolkit/Serialisation/Models/LegacyManifestVersion.cs new file mode 100644 index 00000000..12f6755b --- /dev/null +++ b/src/StardewModdingAPI.Toolkit/Serialisation/Models/LegacyManifestVersion.cs @@ -0,0 +1,26 @@ +using Newtonsoft.Json; + +namespace StardewModdingAPI.Toolkit.Serialisation.Models +{ + /// An implementation of that hamdles the legacy version format. + public class LegacyManifestVersion : SemanticVersion + { + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The major version incremented for major API changes. + /// The minor version incremented for backwards-compatible changes. + /// The patch version for backwards-compatible bug fixes. + /// An optional build tag. + [JsonConstructor] + public LegacyManifestVersion(int majorVersion, int minorVersion, int patchVersion, string build = null) + : base( + majorVersion, + minorVersion, + patchVersion, + build != "0" ? build : null // '0' from incorrect examples in old SMAPI documentation + ) + { } + } +} diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/Models/Manifest.cs b/src/StardewModdingAPI.Toolkit/Serialisation/Models/Manifest.cs new file mode 100644 index 00000000..68987dd1 --- /dev/null +++ b/src/StardewModdingAPI.Toolkit/Serialisation/Models/Manifest.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using StardewModdingAPI.Toolkit.Serialisation.Converters; + +namespace StardewModdingAPI.Toolkit.Serialisation.Models +{ + /// A manifest which describes a mod for SMAPI. + public class Manifest + { + /********* + ** Accessors + *********/ + /// The mod name. + public string Name { get; set; } + + /// A brief description of the mod. + public string Description { get; set; } + + /// The mod author's name. + public string Author { get; set; } + + /// The mod version. + public SemanticVersion Version { get; set; } + + /// The minimum SMAPI version required by this mod, if any. + public SemanticVersion MinimumApiVersion { get; set; } + + /// The name of the DLL in the directory that has the Entry method. Mutually exclusive with . + public string EntryDll { get; set; } + + /// The mod which will read this as a content pack. Mutually exclusive with . + [JsonConverter(typeof(ManifestContentPackForConverter))] + public ManifestContentPackFor ContentPackFor { get; set; } + + /// The other mods that must be loaded before this mod. + [JsonConverter(typeof(ManifestDependencyArrayConverter))] + public ManifestDependency[] Dependencies { get; set; } + + /// The namespaced mod IDs to query for updates (like Nexus:541). + public string[] UpdateKeys { get; set; } + + /// The unique mod ID. + public string UniqueID { get; set; } + + /// Any manifest fields which didn't match a valid field. + [JsonExtensionData] + public IDictionary ExtraFields { get; set; } + } +} diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/Models/ManifestContentPackFor.cs b/src/StardewModdingAPI.Toolkit/Serialisation/Models/ManifestContentPackFor.cs new file mode 100644 index 00000000..00546533 --- /dev/null +++ b/src/StardewModdingAPI.Toolkit/Serialisation/Models/ManifestContentPackFor.cs @@ -0,0 +1,15 @@ +namespace StardewModdingAPI.Toolkit.Serialisation.Models +{ + /// Indicates which mod can read the content pack represented by the containing manifest. + public class ManifestContentPackFor + { + /********* + ** Accessors + *********/ + /// The unique ID of the mod which can read this content pack. + public string UniqueID { get; set; } + + /// The minimum required version (if any). + public SemanticVersion MinimumVersion { get; set; } + } +} diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/Models/ManifestDependency.cs b/src/StardewModdingAPI.Toolkit/Serialisation/Models/ManifestDependency.cs new file mode 100644 index 00000000..d902f9ac --- /dev/null +++ b/src/StardewModdingAPI.Toolkit/Serialisation/Models/ManifestDependency.cs @@ -0,0 +1,35 @@ +namespace StardewModdingAPI.Toolkit.Serialisation.Models +{ + /// A mod dependency listed in a mod manifest. + public class ManifestDependency + { + /********* + ** Accessors + *********/ + /// The unique mod ID to require. + public string UniqueID { get; set; } + + /// The minimum required version (if any). + public SemanticVersion MinimumVersion { get; set; } + + /// Whether the dependency must be installed to use the mod. + public bool IsRequired { get; set; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The unique mod ID to require. + /// The minimum required version (if any). + /// Whether the dependency must be installed to use the mod. + public ManifestDependency(string uniqueID, string minimumVersion, bool required = true) + { + this.UniqueID = uniqueID; + this.MinimumVersion = !string.IsNullOrWhiteSpace(minimumVersion) + ? new SemanticVersion(minimumVersion) + : null; + this.IsRequired = required; + } + } +} diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/SParseException.cs b/src/StardewModdingAPI.Toolkit/Serialisation/SParseException.cs new file mode 100644 index 00000000..61a7b305 --- /dev/null +++ b/src/StardewModdingAPI.Toolkit/Serialisation/SParseException.cs @@ -0,0 +1,17 @@ +using System; + +namespace StardewModdingAPI.Toolkit.Serialisation +{ + /// A format exception which provides a user-facing error message. + internal class SParseException : FormatException + { + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The error message. + /// The underlying exception, if any. + public SParseException(string message, Exception ex = null) + : base(message, ex) { } + } +} -- cgit