move manifest parsing into toolkit (#532)

This commit is contained in:
Jesse Plamondon-Willard 2018-06-05 20:22:46 -04:00
parent 265ce35fd1
commit 625c538f24
27 changed files with 343 additions and 189 deletions

View File

@ -9,7 +9,7 @@ using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.ModData; using StardewModdingAPI.Framework.ModData;
using StardewModdingAPI.Framework.Models; using StardewModdingAPI.Framework.Models;
using StardewModdingAPI.Framework.ModLoading; using StardewModdingAPI.Framework.ModLoading;
using StardewModdingAPI.Framework.Serialisation; using StardewModdingAPI.Toolkit.Serialisation;
namespace StardewModdingAPI.Tests.Core namespace StardewModdingAPI.Tests.Core
{ {
@ -93,8 +93,8 @@ namespace StardewModdingAPI.Tests.Core
Assert.IsNotNull(mod, "The loaded manifest shouldn't be null."); 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(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(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.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."); 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 // arrange
Mock<IModMetadata> mock = this.GetMetadata("Mod A", new string[0], allowStatusChange: true); Mock<IModMetadata> 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); this.SetupMetadataForValidation(mock);
// act // act
@ -174,7 +174,7 @@ namespace StardewModdingAPI.Tests.Core
public void ValidateManifests_MissingEntryDLL_Fails() public void ValidateManifests_MissingEntryDLL_Fails()
{ {
// arrange // arrange
Mock<IModMetadata> mock = this.GetMetadata(this.GetManifest("Mod A", "1.0", manifest => manifest.EntryDll = "Missing.dll"), allowStatusChange: true); Mock<IModMetadata> mock = this.GetMetadata(this.GetManifest(id: "Mod A", version: "1.0", entryDll: "Missing.dll"), allowStatusChange: true);
this.SetupMetadataForValidation(mock); this.SetupMetadataForValidation(mock);
// act // act
@ -189,7 +189,7 @@ namespace StardewModdingAPI.Tests.Core
{ {
// arrange // arrange
Mock<IModMetadata> modA = this.GetMetadata("Mod A", new string[0], allowStatusChange: true); Mock<IModMetadata> modA = this.GetMetadata("Mod A", new string[0], allowStatusChange: true);
Mock<IModMetadata> modB = this.GetMetadata(this.GetManifest("Mod A", "1.0", manifest => manifest.Name = "Mod B"), allowStatusChange: true); Mock<IModMetadata> modB = this.GetMetadata(this.GetManifest(id: "Mod A", name: "Mod B", version: "1.0"), allowStatusChange: true);
Mock<IModMetadata> modC = this.GetMetadata("Mod C", new string[0], allowStatusChange: false); Mock<IModMetadata> modC = this.GetMetadata("Mod C", new string[0], allowStatusChange: false);
foreach (Mock<IModMetadata> mod in new[] { modA, modB, modC }) foreach (Mock<IModMetadata> mod in new[] { modA, modB, modC })
this.SetupMetadataForValidation(mod); this.SetupMetadataForValidation(mod);
@ -398,8 +398,8 @@ namespace StardewModdingAPI.Tests.Core
{ {
// arrange // arrange
// A 1.0 ◀── B (need A 1.1) // A 1.0 ◀── B (need A 1.1)
Mock<IModMetadata> modA = this.GetMetadata(this.GetManifest("Mod A", "1.0")); Mock<IModMetadata> modA = this.GetMetadata(this.GetManifest(id: "Mod A", version: "1.0"));
Mock<IModMetadata> modB = this.GetMetadata(this.GetManifest("Mod B", "1.0", new ManifestDependency("Mod A", "1.1")), allowStatusChange: true); Mock<IModMetadata> modB = this.GetMetadata(this.GetManifest(id: "Mod B", version: "1.0", dependencies: new IManifestDependency[] { new ManifestDependency("Mod A", "1.1") }), allowStatusChange: true);
// act // act
IModMetadata[] mods = new ModResolver().ProcessDependencies(new[] { modA.Object, modB.Object }, new ModDatabase()).ToArray(); IModMetadata[] mods = new ModResolver().ProcessDependencies(new[] { modA.Object, modB.Object }, new ModDatabase()).ToArray();
@ -414,8 +414,8 @@ namespace StardewModdingAPI.Tests.Core
{ {
// arrange // arrange
// A 1.0 ◀── B (need A 1.0-beta) // A 1.0 ◀── B (need A 1.0-beta)
Mock<IModMetadata> modA = this.GetMetadata(this.GetManifest("Mod A", "1.0")); Mock<IModMetadata> modA = this.GetMetadata(this.GetManifest(id: "Mod A", version: "1.0"));
Mock<IModMetadata> modB = this.GetMetadata(this.GetManifest("Mod B", "1.0", new ManifestDependency("Mod A", "1.0-beta")), allowStatusChange: false); Mock<IModMetadata> modB = this.GetMetadata(this.GetManifest(id: "Mod B", version: "1.0", dependencies: new IManifestDependency[] { new ManifestDependency("Mod A", "1.0-beta") }), allowStatusChange: false);
// act // act
IModMetadata[] mods = new ModResolver().ProcessDependencies(new[] { modA.Object, modB.Object }, new ModDatabase()).ToArray(); IModMetadata[] mods = new ModResolver().ProcessDependencies(new[] { modA.Object, modB.Object }, new ModDatabase()).ToArray();
@ -431,8 +431,8 @@ namespace StardewModdingAPI.Tests.Core
{ {
// arrange // arrange
// A ◀── B // A ◀── B
Mock<IModMetadata> modA = this.GetMetadata(this.GetManifest("Mod A", "1.0")); Mock<IModMetadata> modA = this.GetMetadata(this.GetManifest(id: "Mod A", version: "1.0"));
Mock<IModMetadata> modB = this.GetMetadata(this.GetManifest("Mod B", "1.0", new ManifestDependency("Mod A", "1.0", required: false)), allowStatusChange: false); Mock<IModMetadata> 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 // act
IModMetadata[] mods = new ModResolver().ProcessDependencies(new[] { modB.Object, modA.Object }, new ModDatabase()).ToArray(); IModMetadata[] mods = new ModResolver().ProcessDependencies(new[] { modB.Object, modA.Object }, new ModDatabase()).ToArray();
@ -448,7 +448,7 @@ namespace StardewModdingAPI.Tests.Core
{ {
// arrange // arrange
// A ◀── B where A doesn't exist // A ◀── B where A doesn't exist
Mock<IModMetadata> modB = this.GetMetadata(this.GetManifest("Mod B", "1.0", new ManifestDependency("Mod A", "1.0", required: false)), allowStatusChange: false); Mock<IModMetadata> 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 // act
IModMetadata[] mods = new ModResolver().ProcessDependencies(new[] { modB.Object }, new ModDatabase()).ToArray(); IModMetadata[] mods = new ModResolver().ProcessDependencies(new[] { modB.Object }, new ModDatabase()).ToArray();
@ -463,46 +463,26 @@ namespace StardewModdingAPI.Tests.Core
** Private methods ** Private methods
*********/ *********/
/// <summary>Get a randomised basic manifest.</summary> /// <summary>Get a randomised basic manifest.</summary>
/// <param name="adjust">Adjust the generated manifest.</param> /// <param name="id">The <see cref="IManifest.UniqueID"/> value, or <c>null</c> for a generated value.</param>
private Manifest GetManifest(Action<Manifest> adjust = null) /// <param name="name">The <see cref="IManifest.Name"/> value, or <c>null</c> for a generated value.</param>
/// <param name="version">The <see cref="IManifest.Version"/> value, or <c>null</c> for a generated value.</param>
/// <param name="entryDll">The <see cref="IManifest.EntryDll"/> value, or <c>null</c> for a generated value.</param>
/// <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)
{ {
Manifest manifest = new Manifest return new Manifest(
{ uniqueID: id ?? $"{Sample.String()}.{Sample.String()}",
Name = Sample.String(), name: name ?? id ?? Sample.String(),
Author = Sample.String(), author: Sample.String(),
Version = new SemanticVersion(Sample.Int(), Sample.Int(), Sample.Int(), Sample.String()), description: Sample.String(),
Description = Sample.String(), version: version != null ? new SemanticVersion(version) : new SemanticVersion(Sample.Int(), Sample.Int(), Sample.Int(), Sample.String()),
UniqueID = $"{Sample.String()}.{Sample.String()}", entryDll: entryDll ?? $"{Sample.String()}.dll",
EntryDll = $"{Sample.String()}.dll" contentPackFor: contentPackForID != null ? new ManifestContentPackFor(contentPackForID) : null,
}; minimumApiVersion: minimumApiVersion != null ? new SemanticVersion(minimumApiVersion) : null,
adjust?.Invoke(manifest); dependencies: dependencies
return manifest; );
}
/// <summary>Get a randomised basic manifest.</summary>
/// <param name="uniqueID">The mod's name and unique ID.</param>
/// <param name="version">The mod version.</param>
/// <param name="adjust">Adjust the generated manifest.</param>
/// <param name="dependencies">The dependencies this mod requires.</param>
private IManifest GetManifest(string uniqueID, string version, Action<Manifest> 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);
});
}
/// <summary>Get a randomised basic manifest.</summary>
/// <param name="uniqueID">The mod's name and unique ID.</param>
/// <param name="version">The mod version.</param>
/// <param name="dependencies">The dependencies this mod requires.</param>
private IManifest GetManifest(string uniqueID, string version, params IManifestDependency[] dependencies)
{
return this.GetManifest(uniqueID, version, null, dependencies);
} }
/// <summary>Get a randomised basic manifest.</summary> /// <summary>Get a randomised basic manifest.</summary>
@ -518,7 +498,7 @@ namespace StardewModdingAPI.Tests.Core
/// <param name="allowStatusChange">Whether the code being tested is allowed to change the mod status.</param> /// <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) private Mock<IModMetadata> 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); return this.GetMetadata(manifest, allowStatusChange);
} }

View File

@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json; using Newtonsoft.Json;
using NUnit.Framework; using NUnit.Framework;
using StardewModdingAPI.Framework; using StardewModdingAPI.Framework;
using StardewModdingAPI.Toolkit.Serialisation.Models;
namespace StardewModdingAPI.Tests.Utilities namespace StardewModdingAPI.Tests.Utilities
{ {

View File

@ -2,7 +2,7 @@ using System;
using System.IO; using System.IO;
using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Framework.Serialisation; using StardewModdingAPI.Toolkit.Serialisation;
using StardewModdingAPI.Toolkit.Utilities; using StardewModdingAPI.Toolkit.Utilities;
using xTile; using xTile;

View File

@ -2,7 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using Newtonsoft.Json.Linq;
using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Framework.Reflection;
using StardewValley; using StardewValley;
@ -91,20 +90,5 @@ namespace StardewModdingAPI.Framework
// get result // get result
return reflection.GetField<bool>(Game1.spriteBatch, fieldName).GetValue(); return reflection.GetField<bool>(Game1.spriteBatch, fieldName).GetValue();
} }
/****
** Json.NET
****/
/// <summary>Get a JSON field value from a case-insensitive field name. This will check for an exact match first, then search without case sensitivity.</summary>
/// <typeparam name="T">The value type.</typeparam>
/// <param name="obj">The JSON object to search.</param>
/// <param name="fieldName">The field name.</param>
public static T ValueIgnoreCase<T>(this JObject obj, string fieldName)
{
JToken token = obj.GetValue(fieldName, StringComparison.InvariantCultureIgnoreCase);
return token != null
? token.Value<T>()
: default(T);
}
} }
} }

View File

@ -5,7 +5,7 @@ using System.Linq;
using StardewModdingAPI.Events; using StardewModdingAPI.Events;
using StardewModdingAPI.Framework.Input; using StardewModdingAPI.Framework.Input;
using StardewModdingAPI.Framework.Models; using StardewModdingAPI.Framework.Models;
using StardewModdingAPI.Framework.Serialisation; using StardewModdingAPI.Toolkit.Serialisation;
using StardewModdingAPI.Toolkit.Utilities; using StardewModdingAPI.Toolkit.Utilities;
namespace StardewModdingAPI.Framework.ModHelpers 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."); throw new ArgumentException($"Can't create content pack for directory path '{directoryPath}' because no such directory exists.");
// create manifest // create manifest
IManifest manifest = new Manifest IManifest manifest = new Manifest(
{ uniqueID: id,
Name = name, name: name,
Author = author, author: author,
Description = description, description: description,
Version = version, version: version,
UniqueID = id, contentPackFor: new ManifestContentPackFor(this.ModID)
UpdateKeys = new string[0], );
ContentPackFor = new ManifestContentPackFor { UniqueID = this.ModID }
};
// create content pack // create content pack
return this.CreateContentPack(directoryPath, manifest); return this.CreateContentPack(directoryPath, manifest);

View File

@ -3,11 +3,11 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using StardewModdingAPI.Framework.Exceptions;
using StardewModdingAPI.Framework.ModData; using StardewModdingAPI.Framework.ModData;
using StardewModdingAPI.Framework.Models; using StardewModdingAPI.Framework.Models;
using StardewModdingAPI.Framework.Serialisation; using StardewModdingAPI.Toolkit.Serialisation;
using StardewModdingAPI.Toolkit.Utilities; using StardewModdingAPI.Toolkit.Utilities;
using ToolkitManifest = StardewModdingAPI.Toolkit.Serialisation.Models.Manifest;
namespace StardewModdingAPI.Framework.ModLoading namespace StardewModdingAPI.Framework.ModLoading
{ {
@ -28,17 +28,19 @@ namespace StardewModdingAPI.Framework.ModLoading
{ {
// read file // read file
Manifest manifest = null; Manifest manifest = null;
string path = Path.Combine(modDir.FullName, "manifest.json");
string error = null; string error = null;
{
string path = Path.Combine(modDir.FullName, "manifest.json");
try try
{ {
manifest = jsonHelper.ReadJsonFile<Manifest>(path); ToolkitManifest rawManifest = jsonHelper.ReadJsonFile<ToolkitManifest>(path);
if (manifest == null) if (rawManifest == null)
{ {
error = File.Exists(path) error = File.Exists(path)
? "its manifest is invalid." ? "its manifest is invalid."
: "it doesn't have a manifest."; : "it doesn't have a manifest.";
} }
manifest = new Manifest(rawManifest);
} }
catch (SParseException ex) catch (SParseException ex)
{ {
@ -48,6 +50,7 @@ namespace StardewModdingAPI.Framework.ModLoading
{ {
error = $"parsing its manifest failed:\n{ex.GetLogSummary()}"; error = $"parsing its manifest failed:\n{ex.GetLogSummary()}";
} }
}
// parse internal data record (if any) // parse internal data record (if any)
ParsedModDataRecord dataRecord = modDatabase.GetParsed(manifest); ParsedModDataRecord dataRecord = modDatabase.GetParsed(manifest);

View File

@ -1,6 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using StardewModdingAPI.Framework.Serialisation.SmapiConverters;
namespace StardewModdingAPI.Framework.Models namespace StardewModdingAPI.Framework.Models
{ {
@ -11,39 +11,87 @@ namespace StardewModdingAPI.Framework.Models
** Accessors ** Accessors
*********/ *********/
/// <summary>The mod name.</summary> /// <summary>The mod name.</summary>
public string Name { get; set; } public string Name { get; }
/// <summary>A brief description of the mod.</summary> /// <summary>A brief description of the mod.</summary>
public string Description { get; set; } public string Description { get; }
/// <summary>The mod author's name.</summary> /// <summary>The mod author's name.</summary>
public string Author { get; set; } public string Author { get; }
/// <summary>The mod version.</summary> /// <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> /// <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 <see cref="IMod.Entry"/> method. Mutually exclusive with <see cref="ContentPackFor"/>.</summary> /// <summary>The name of the DLL in the directory that has the <see cref="IMod.Entry"/> 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="IManifest.EntryDll"/>.</summary> /// <summary>The mod which will read this as a content pack. Mutually exclusive with <see cref="IManifest.EntryDll"/>.</summary>
[JsonConverter(typeof(ManifestContentPackForConverter))] public IManifestContentPackFor ContentPackFor { get; }
public IManifestContentPackFor ContentPackFor { get; set; }
/// <summary>The other mods that must be loaded before this mod.</summary> /// <summary>The other mods that must be loaded before this mod.</summary>
[JsonConverter(typeof(ManifestDependencyArrayConverter))] public IManifestDependency[] Dependencies { get; }
public IManifestDependency[] Dependencies { get; set; }
/// <summary>The namespaced mod IDs to query for updates (like <c>Nexus:541</c>).</summary> /// <summary>The namespaced mod IDs to query for updates (like <c>Nexus:541</c>).</summary>
public string[] UpdateKeys { get; set; } public string[] UpdateKeys { get; set; }
/// <summary>The unique mod ID.</summary> /// <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> /// <summary>Any manifest fields which didn't match a valid field.</summary>
[JsonExtensionData] [JsonExtensionData]
public IDictionary<string, object> ExtraFields { get; set; } public IDictionary<string, object> ExtraFields { get; }
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="manifest">The toolkit manifest.</param>
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
)
{ }
/// <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="entryDll">The name of the DLL in the directory that has the <see cref="IMod.Entry"/> method. Mutually exclusive with <paramref name="contentPackFor"/>.</param>
/// <param name="minimumApiVersion">The minimum SMAPI version required by this mod, if any.</param>
/// <param name="contentPackFor">The modID which will read this as a content pack. Mutually exclusive with <paramref name="entryDll"/>.</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>
/// <param name="extraFields">Any manifest fields which didn't match a valid field.</param>
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<string, object> 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;
}
} }
} }

View File

@ -7,9 +7,30 @@ namespace StardewModdingAPI.Framework.Models
** Accessors ** Accessors
*********/ *********/
/// <summary>The unique ID of the mod which can read this content pack.</summary> /// <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> /// <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="contentPackFor">The toolkit instance.</param>
public ManifestContentPackFor(Toolkit.Serialisation.Models.ManifestContentPackFor contentPackFor)
{
this.UniqueID = contentPackFor.UniqueID;
this.MinimumVersion = new SemanticVersion(contentPackFor.MinimumVersion);
}
/// <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 = null)
{
this.UniqueID = uniqueID;
this.MinimumVersion = minimumVersion;
}
} }
} }

View File

@ -7,18 +7,23 @@ namespace StardewModdingAPI.Framework.Models
** Accessors ** Accessors
*********/ *********/
/// <summary>The unique mod ID to require.</summary> /// <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> /// <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> /// <summary>Whether the dependency must be installed to use the mod.</summary>
public bool IsRequired { get; set; } public bool IsRequired { get; }
/********* /*********
** Public methods ** Public methods
*********/ *********/
/// <summary>Construct an instance.</summary>
/// <param name="dependency">The toolkit instance.</param>
public ManifestDependency(Toolkit.Serialisation.Models.ManifestDependency dependency)
: this(dependency.UniqueID, dependency.MinimumVersion?.ToString(), dependency.IsRequired) { }
/// <summary>Construct an instance.</summary> /// <summary>Construct an instance.</summary>
/// <param name="uniqueID">The unique mod ID to require.</param> /// <param name="uniqueID">The unique mod ID to require.</param>
/// <param name="minimumVersion">The minimum required version (if any).</param> /// <param name="minimumVersion">The minimum required version (if any).</param>

View File

@ -1,9 +1,10 @@
using System; using System;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Toolkit.Serialisation;
using StardewModdingAPI.Toolkit.Serialisation.Converters;
namespace StardewModdingAPI.Framework.Serialisation.CrossplatformConverters namespace StardewModdingAPI.Framework.Serialisation
{ {
/// <summary>Handles deserialisation of <see cref="Color"/> for crossplatform compatibility.</summary> /// <summary>Handles deserialisation of <see cref="Color"/> for crossplatform compatibility.</summary>
/// <remarks> /// <remarks>

View File

@ -1,9 +1,10 @@
using System; using System;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Toolkit.Serialisation;
using StardewModdingAPI.Toolkit.Serialisation.Converters;
namespace StardewModdingAPI.Framework.Serialisation.CrossplatformConverters namespace StardewModdingAPI.Framework.Serialisation
{ {
/// <summary>Handles deserialisation of <see cref="PointConverter"/> for crossplatform compatibility.</summary> /// <summary>Handles deserialisation of <see cref="PointConverter"/> for crossplatform compatibility.</summary>
/// <remarks> /// <remarks>

View File

@ -2,9 +2,10 @@ using System;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Toolkit.Serialisation;
using StardewModdingAPI.Toolkit.Serialisation.Converters;
namespace StardewModdingAPI.Framework.Serialisation.CrossplatformConverters namespace StardewModdingAPI.Framework.Serialisation
{ {
/// <summary>Handles deserialisation of <see cref="Rectangle"/> for crossplatform compatibility.</summary> /// <summary>Handles deserialisation of <see cref="Rectangle"/> for crossplatform compatibility.</summary>
/// <remarks> /// <remarks>

View File

@ -36,7 +36,7 @@ namespace StardewModdingAPI
IManifestDependency[] Dependencies { get; } IManifestDependency[] Dependencies { get; }
/// <summary>The namespaced mod IDs to query for updates (like <c>Nexus:541</c>).</summary> /// <summary>The namespaced mod IDs to query for updates (like <c>Nexus:541</c>).</summary>
string[] UpdateKeys { get; set; } string[] UpdateKeys { get; }
/// <summary>Any manifest fields which didn't match a valid field.</summary> /// <summary>Any manifest fields which didn't match a valid field.</summary>
IDictionary<string, object> ExtraFields { get; } IDictionary<string, object> ExtraFields { get; }

View File

@ -10,6 +10,7 @@ using System.Runtime.ExceptionServices;
using System.Security; using System.Security;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using Microsoft.Xna.Framework.Input;
#if SMAPI_FOR_WINDOWS #if SMAPI_FOR_WINDOWS
using System.Windows.Forms; using System.Windows.Forms;
#endif #endif
@ -27,8 +28,11 @@ using StardewModdingAPI.Framework.Reflection;
using StardewModdingAPI.Framework.Serialisation; using StardewModdingAPI.Framework.Serialisation;
using StardewModdingAPI.Internal; using StardewModdingAPI.Internal;
using StardewModdingAPI.Toolkit.Framework.Clients.WebApi; using StardewModdingAPI.Toolkit.Framework.Clients.WebApi;
using StardewModdingAPI.Toolkit.Serialisation;
using StardewModdingAPI.Toolkit.Serialisation.Converters;
using StardewModdingAPI.Toolkit.Utilities; using StardewModdingAPI.Toolkit.Utilities;
using StardewValley; using StardewValley;
using Keys = System.Windows.Forms.Keys;
using Monitor = StardewModdingAPI.Framework.Monitor; using Monitor = StardewModdingAPI.Framework.Monitor;
using SObject = StardewValley.Object; using SObject = StardewValley.Object;
using ThreadState = System.Threading.ThreadState; using ThreadState = System.Threading.ThreadState;
@ -148,6 +152,18 @@ namespace StardewModdingAPI
}; };
this.EventManager = new EventManager(this.Monitor, this.ModRegistry); this.EventManager = new EventManager(this.Monitor, this.ModRegistry);
// init JSON parser
JsonConverter[] converters = {
new StringEnumConverter<Buttons>(),
new StringEnumConverter<Keys>(),
new StringEnumConverter<SButton>(),
new ColorConverter(),
new PointConverter(),
new RectangleConverter()
};
foreach (JsonConverter converter in converters)
this.JsonHelper.JsonSettings.Converters.Add(converter);
// hook up events // hook up events
ContentEvents.Init(this.EventManager); ContentEvents.Init(this.EventManager);
ControlEvents.Init(this.EventManager); ControlEvents.Init(this.EventManager);
@ -1093,7 +1109,7 @@ namespace StardewModdingAPI
/// <param name="mods">The mods for which to reload translations.</param> /// <param name="mods">The mods for which to reload translations.</param>
private void ReloadTranslations(IEnumerable<IModMetadata> mods) private void ReloadTranslations(IEnumerable<IModMetadata> mods)
{ {
JsonHelper jsonHelper = new JsonHelper(); JsonHelper jsonHelper = this.JsonHelper;
foreach (IModMetadata metadata in mods) foreach (IModMetadata metadata in mods)
{ {
if (metadata.IsContentPack) if (metadata.IsContentPack)

View File

@ -103,6 +103,9 @@
<Compile Include="Framework\ContentManagers\GameContentManager.cs" /> <Compile Include="Framework\ContentManagers\GameContentManager.cs" />
<Compile Include="Framework\ContentManagers\IContentManager.cs" /> <Compile Include="Framework\ContentManagers\IContentManager.cs" />
<Compile Include="Framework\ContentManagers\ModContentManager.cs" /> <Compile Include="Framework\ContentManagers\ModContentManager.cs" />
<Compile Include="Framework\Serialisation\ColorConverter.cs" />
<Compile Include="Framework\Serialisation\PointConverter.cs" />
<Compile Include="Framework\Serialisation\RectangleConverter.cs" />
<Compile Include="Framework\Events\ModEventsBase.cs" /> <Compile Include="Framework\Events\ModEventsBase.cs" />
<Compile Include="Framework\Events\EventManager.cs" /> <Compile Include="Framework\Events\EventManager.cs" />
<Compile Include="Events\IModEvents.cs" /> <Compile Include="Events\IModEvents.cs" />
@ -114,16 +117,17 @@
<Compile Include="Framework\Events\ModEvents.cs" /> <Compile Include="Framework\Events\ModEvents.cs" />
<Compile Include="Framework\Events\ModInputEvents.cs" /> <Compile Include="Framework\Events\ModInputEvents.cs" />
<Compile Include="Framework\Input\GamePadStateBuilder.cs" /> <Compile Include="Framework\Input\GamePadStateBuilder.cs" />
<Compile Include="Framework\Models\Manifest.cs" />
<Compile Include="Framework\Models\ManifestContentPackFor.cs" />
<Compile Include="Framework\Models\ManifestDependency.cs" />
<Compile Include="Framework\ModHelpers\InputHelper.cs" /> <Compile Include="Framework\ModHelpers\InputHelper.cs" />
<Compile Include="IInputHelper.cs" /> <Compile Include="IInputHelper.cs" />
<Compile Include="Framework\Input\SInputState.cs" /> <Compile Include="Framework\Input\SInputState.cs" />
<Compile Include="Framework\Input\InputStatus.cs" /> <Compile Include="Framework\Input\InputStatus.cs" />
<Compile Include="Framework\LegacyManifestVersion.cs" />
<Compile Include="Framework\ModData\ModDatabase.cs" /> <Compile Include="Framework\ModData\ModDatabase.cs" />
<Compile Include="Framework\ModData\ModDataField.cs" /> <Compile Include="Framework\ModData\ModDataField.cs" />
<Compile Include="Framework\ModData\ModDataFieldKey.cs" /> <Compile Include="Framework\ModData\ModDataFieldKey.cs" />
<Compile Include="Framework\ModData\ParsedModDataRecord.cs" /> <Compile Include="Framework\ModData\ParsedModDataRecord.cs" />
<Compile Include="Framework\Models\ManifestContentPackFor.cs" />
<Compile Include="Framework\Models\SMetadata.cs" /> <Compile Include="Framework\Models\SMetadata.cs" />
<Compile Include="Framework\ModHelpers\MultiplayerHelper.cs" /> <Compile Include="Framework\ModHelpers\MultiplayerHelper.cs" />
<Compile Include="Framework\ModLoading\Finders\EventFinder.cs" /> <Compile Include="Framework\ModLoading\Finders\EventFinder.cs" />
@ -151,13 +155,6 @@
<Compile Include="Framework\Reflection\InterfaceProxyBuilder.cs" /> <Compile Include="Framework\Reflection\InterfaceProxyBuilder.cs" />
<Compile Include="Framework\Reflection\InterfaceProxyFactory.cs" /> <Compile Include="Framework\Reflection\InterfaceProxyFactory.cs" />
<Compile Include="Framework\RewriteFacades\SpriteBatchMethods.cs" /> <Compile Include="Framework\RewriteFacades\SpriteBatchMethods.cs" />
<Compile Include="Framework\Serialisation\SmapiConverters\ManifestContentPackForConverter.cs" />
<Compile Include="Framework\Serialisation\SmapiConverters\ManifestDependencyArrayConverter.cs" />
<Compile Include="Framework\Serialisation\SmapiConverters\SemanticVersionConverter.cs" />
<Compile Include="Framework\Serialisation\SimpleReadOnlyConverter.cs" />
<Compile Include="Framework\Serialisation\CrossplatformConverters\RectangleConverter.cs" />
<Compile Include="Framework\Serialisation\CrossplatformConverters\ColorConverter.cs" />
<Compile Include="Framework\Serialisation\CrossplatformConverters\PointConverter.cs" />
<Compile Include="Framework\SMultiplayer.cs" /> <Compile Include="Framework\SMultiplayer.cs" />
<Compile Include="Framework\StateTracking\Comparers\EquatableComparer.cs" /> <Compile Include="Framework\StateTracking\Comparers\EquatableComparer.cs" />
<Compile Include="Framework\StateTracking\Comparers\ObjectReferenceComparer.cs" /> <Compile Include="Framework\StateTracking\Comparers\ObjectReferenceComparer.cs" />
@ -235,16 +232,12 @@
<Compile Include="Context.cs" /> <Compile Include="Context.cs" />
<Compile Include="Framework\Logging\ConsoleInterceptionManager.cs" /> <Compile Include="Framework\Logging\ConsoleInterceptionManager.cs" />
<Compile Include="Framework\Logging\InterceptingTextWriter.cs" /> <Compile Include="Framework\Logging\InterceptingTextWriter.cs" />
<Compile Include="Framework\Models\ManifestDependency.cs" />
<Compile Include="Framework\ModData\ModStatus.cs" /> <Compile Include="Framework\ModData\ModStatus.cs" />
<Compile Include="Framework\Models\SConfig.cs" /> <Compile Include="Framework\Models\SConfig.cs" />
<Compile Include="Framework\ModLoading\ModMetadata.cs" /> <Compile Include="Framework\ModLoading\ModMetadata.cs" />
<Compile Include="Framework\Reflection\ReflectedProperty.cs" /> <Compile Include="Framework\Reflection\ReflectedProperty.cs" />
<Compile Include="Framework\RequestExitDelegate.cs" /> <Compile Include="Framework\RequestExitDelegate.cs" />
<Compile Include="Framework\ContentCoordinator.cs" /> <Compile Include="Framework\ContentCoordinator.cs" />
<Compile Include="Framework\Exceptions\SParseException.cs" />
<Compile Include="Framework\Serialisation\JsonHelper.cs" />
<Compile Include="Framework\Serialisation\SmapiConverters\StringEnumConverter.cs" />
<Compile Include="IAssetEditor.cs" /> <Compile Include="IAssetEditor.cs" />
<Compile Include="IAssetInfo.cs" /> <Compile Include="IAssetInfo.cs" />
<Compile Include="IAssetLoader.cs" /> <Compile Include="IAssetLoader.cs" />
@ -283,7 +276,6 @@
<Compile Include="Events\ChangeType.cs" /> <Compile Include="Events\ChangeType.cs" />
<Compile Include="Events\ItemStackChange.cs" /> <Compile Include="Events\ItemStackChange.cs" />
<Compile Include="Framework\Monitor.cs" /> <Compile Include="Framework\Monitor.cs" />
<Compile Include="Framework\Models\Manifest.cs" />
<Compile Include="Metadata\InstructionMetadata.cs" /> <Compile Include="Metadata\InstructionMetadata.cs" />
<Compile Include="Mod.cs" /> <Compile Include="Mod.cs" />
<Compile Include="PatchMode.cs" /> <Compile Include="PatchMode.cs" />

View File

@ -1,11 +1,11 @@
using System; using System;
using Newtonsoft.Json; using Newtonsoft.Json;
using StardewModdingAPI.Framework.Models; using StardewModdingAPI.Toolkit.Serialisation.Models;
namespace StardewModdingAPI.Framework.Serialisation.SmapiConverters namespace StardewModdingAPI.Toolkit.Serialisation.Converters
{ {
/// <summary>Handles deserialisation of <see cref="IManifestContentPackFor"/> arrays.</summary> /// <summary>Handles deserialisation of <see cref="ManifestContentPackFor"/> arrays.</summary>
internal class ManifestContentPackForConverter : JsonConverter public class ManifestContentPackForConverter : JsonConverter
{ {
/********* /*********
** Accessors ** Accessors
@ -21,7 +21,7 @@ namespace StardewModdingAPI.Framework.Serialisation.SmapiConverters
/// <param name="objectType">The object type.</param> /// <param name="objectType">The object type.</param>
public override bool CanConvert(Type objectType) public override bool CanConvert(Type objectType)
{ {
return objectType == typeof(IManifestContentPackFor[]); return objectType == typeof(ManifestContentPackFor[]);
} }

View File

@ -2,11 +2,11 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using StardewModdingAPI.Framework.Models; using StardewModdingAPI.Toolkit.Serialisation.Models;
namespace StardewModdingAPI.Framework.Serialisation.SmapiConverters namespace StardewModdingAPI.Toolkit.Serialisation.Converters
{ {
/// <summary>Handles deserialisation of <see cref="IManifestDependency"/> arrays.</summary> /// <summary>Handles deserialisation of <see cref="ManifestDependency"/> arrays.</summary>
internal class ManifestDependencyArrayConverter : JsonConverter internal class ManifestDependencyArrayConverter : JsonConverter
{ {
/********* /*********
@ -23,7 +23,7 @@ namespace StardewModdingAPI.Framework.Serialisation.SmapiConverters
/// <param name="objectType">The object type.</param> /// <param name="objectType">The object type.</param>
public override bool CanConvert(Type objectType) public override bool CanConvert(Type objectType)
{ {
return objectType == typeof(IManifestDependency[]); return objectType == typeof(ManifestDependency[]);
} }
@ -37,12 +37,12 @@ namespace StardewModdingAPI.Framework.Serialisation.SmapiConverters
/// <param name="serializer">The calling serializer.</param> /// <param name="serializer">The calling serializer.</param>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{ {
List<IManifestDependency> result = new List<IManifestDependency>(); List<ManifestDependency> result = new List<ManifestDependency>();
foreach (JObject obj in JArray.Load(reader).Children<JObject>()) foreach (JObject obj in JArray.Load(reader).Children<JObject>())
{ {
string uniqueID = obj.ValueIgnoreCase<string>(nameof(IManifestDependency.UniqueID)); string uniqueID = obj.ValueIgnoreCase<string>(nameof(ManifestDependency.UniqueID));
string minVersion = obj.ValueIgnoreCase<string>(nameof(IManifestDependency.MinimumVersion)); string minVersion = obj.ValueIgnoreCase<string>(nameof(ManifestDependency.MinimumVersion));
bool required = obj.ValueIgnoreCase<bool?>(nameof(IManifestDependency.IsRequired)) ?? true; bool required = obj.ValueIgnoreCase<bool?>(nameof(ManifestDependency.IsRequired)) ?? true;
result.Add(new ManifestDependency(uniqueID, minVersion, required)); result.Add(new ManifestDependency(uniqueID, minVersion, required));
} }
return result.ToArray(); return result.ToArray();

View File

@ -1,10 +1,10 @@
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Toolkit.Serialisation.Models;
namespace StardewModdingAPI.Framework.Serialisation.SmapiConverters namespace StardewModdingAPI.Toolkit.Serialisation.Converters
{ {
/// <summary>Handles deserialisation of <see cref="SemanticVersion"/>.</summary> /// <summary>Handles deserialisation of <see cref="SemanticVersion"/>.</summary>
internal class SemanticVersionConverter : SimpleReadOnlyConverter<ISemanticVersion> internal class SemanticVersionConverter : SimpleReadOnlyConverter<SemanticVersion>
{ {
/********* /*********
** Protected methods ** Protected methods
@ -12,25 +12,25 @@ namespace StardewModdingAPI.Framework.Serialisation.SmapiConverters
/// <summary>Read a JSON object.</summary> /// <summary>Read a JSON object.</summary>
/// <param name="obj">The JSON object to read.</param> /// <param name="obj">The JSON object to read.</param>
/// <param name="path">The path to the current JSON node.</param> /// <param name="path">The path to the current JSON node.</param>
protected override ISemanticVersion ReadObject(JObject obj, string path) protected override SemanticVersion ReadObject(JObject obj, string path)
{ {
int major = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.MajorVersion)); int major = obj.ValueIgnoreCase<int>("MajorVersion");
int minor = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.MinorVersion)); int minor = obj.ValueIgnoreCase<int>("MinorVersion");
int patch = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.PatchVersion)); int patch = obj.ValueIgnoreCase<int>("PatchVersion");
string build = obj.ValueIgnoreCase<string>(nameof(ISemanticVersion.Build)); string build = obj.ValueIgnoreCase<string>("Build");
return new LegacyManifestVersion(major, minor, patch, build); return new LegacyManifestVersion(major, minor, patch, build);
} }
/// <summary>Read a JSON string.</summary> /// <summary>Read a JSON string.</summary>
/// <param name="str">The JSON string value.</param> /// <param name="str">The JSON string value.</param>
/// <param name="path">The path to the current JSON node.</param> /// <param name="path">The path to the current JSON node.</param>
protected override ISemanticVersion ReadString(string str, string path) protected override SemanticVersion ReadString(string str, string path)
{ {
if (string.IsNullOrWhiteSpace(str)) if (string.IsNullOrWhiteSpace(str))
return null; return null;
if (!SemanticVersion.TryParse(str, out ISemanticVersion version)) 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})."); 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; return (SemanticVersion)version;
} }
} }
} }

View File

@ -1,9 +1,8 @@
using System; using System;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using StardewModdingAPI.Framework.Exceptions;
namespace StardewModdingAPI.Framework.Serialisation namespace StardewModdingAPI.Toolkit.Serialisation.Converters
{ {
/// <summary>The base implementation for simplified converters which deserialise <typeparamref name="T"/> without overriding serialisation.</summary> /// <summary>The base implementation for simplified converters which deserialise <typeparamref name="T"/> without overriding serialisation.</summary>
/// <typeparam name="T">The type to deserialise.</typeparam> /// <typeparam name="T">The type to deserialise.</typeparam>

View File

@ -1,7 +1,7 @@
using System; using System;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
namespace StardewModdingAPI.Framework.Serialisation.SmapiConverters namespace StardewModdingAPI.Toolkit.Serialisation.Converters
{ {
/// <summary>A variant of <see cref="StringEnumConverter"/> which only converts a specified enum.</summary> /// <summary>A variant of <see cref="StringEnumConverter"/> which only converts a specified enum.</summary>
/// <typeparam name="T">The enum type.</typeparam> /// <typeparam name="T">The enum type.</typeparam>

View File

@ -0,0 +1,21 @@
using System;
using Newtonsoft.Json.Linq;
namespace StardewModdingAPI.Toolkit.Serialisation
{
/// <summary>Provides extension methods for parsing JSON.</summary>
public static class JsonExtensions
{
/// <summary>Get a JSON field value from a case-insensitive field name. This will check for an exact match first, then search without case sensitivity.</summary>
/// <typeparam name="T">The value type.</typeparam>
/// <param name="obj">The JSON object to search.</param>
/// <param name="fieldName">The field name.</param>
public static T ValueIgnoreCase<T>(this JObject obj, string fieldName)
{
JToken token = obj.GetValue(fieldName, StringComparison.InvariantCultureIgnoreCase);
return token != null
? token.Value<T>()
: default(T);
}
}
}

View File

@ -1,39 +1,23 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using Microsoft.Xna.Framework.Input;
using Newtonsoft.Json; using Newtonsoft.Json;
using StardewModdingAPI.Framework.Serialisation.CrossplatformConverters; using StardewModdingAPI.Toolkit.Serialisation.Converters;
using StardewModdingAPI.Framework.Serialisation.SmapiConverters;
namespace StardewModdingAPI.Framework.Serialisation namespace StardewModdingAPI.Toolkit.Serialisation
{ {
/// <summary>Encapsulates SMAPI's JSON file parsing.</summary> /// <summary>Encapsulates SMAPI's JSON file parsing.</summary>
internal class JsonHelper public class JsonHelper
{ {
/********* /*********
** Accessors ** Accessors
*********/ *********/
/// <summary>The JSON settings to use when serialising and deserialising files.</summary> /// <summary>The JSON settings to use when serialising and deserialising files.</summary>
private readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings public JsonSerializerSettings JsonSettings { get; } = new JsonSerializerSettings
{ {
Formatting = Formatting.Indented, Formatting = Formatting.Indented,
ObjectCreationHandling = ObjectCreationHandling.Replace, // avoid issue where default ICollection<T> values are duplicated each time the config is loaded ObjectCreationHandling = ObjectCreationHandling.Replace, // avoid issue where default ICollection<T> values are duplicated each time the config is loaded
Converters = new List<JsonConverter> Converters = new List<JsonConverter> { new SemanticVersionConverter() }
{
// SMAPI types
new SemanticVersionConverter(),
// enums
new StringEnumConverter<Buttons>(),
new StringEnumConverter<Keys>(),
new StringEnumConverter<SButton>(),
// crossplatform compatibility
new ColorConverter(),
new PointConverter(),
new RectangleConverter()
}
}; };

View File

@ -1,9 +1,9 @@
using Newtonsoft.Json; using Newtonsoft.Json;
namespace StardewModdingAPI.Framework namespace StardewModdingAPI.Toolkit.Serialisation.Models
{ {
/// <summary>An implementation of <see cref="ISemanticVersion"/> that hamdles the legacy <see cref="IManifest"/> version format.</summary> /// <summary>An implementation of <see cref="ISemanticVersion"/> that hamdles the legacy <see cref="Manifest"/> version format.</summary>
internal class LegacyManifestVersion : SemanticVersion public class LegacyManifestVersion : SemanticVersion
{ {
/********* /*********
** Public methods ** Public methods

View File

@ -0,0 +1,49 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using StardewModdingAPI.Toolkit.Serialisation.Converters;
namespace StardewModdingAPI.Toolkit.Serialisation.Models
{
/// <summary>A manifest which describes a mod for SMAPI.</summary>
public class Manifest
{
/*********
** Accessors
*********/
/// <summary>The mod name.</summary>
public string Name { get; set; }
/// <summary>A brief description of the mod.</summary>
public string Description { get; set; }
/// <summary>The mod author's name.</summary>
public string Author { get; set; }
/// <summary>The mod version.</summary>
public SemanticVersion Version { get; set; }
/// <summary>The minimum SMAPI version required by this mod, if any.</summary>
public SemanticVersion MinimumApiVersion { get; set; }
/// <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; }
/// <summary>The mod which will read this as a content pack. Mutually exclusive with <see cref="Manifest.EntryDll"/>.</summary>
[JsonConverter(typeof(ManifestContentPackForConverter))]
public ManifestContentPackFor ContentPackFor { get; set; }
/// <summary>The other mods that must be loaded before this mod.</summary>
[JsonConverter(typeof(ManifestDependencyArrayConverter))]
public ManifestDependency[] Dependencies { get; set; }
/// <summary>The namespaced mod IDs to query for updates (like <c>Nexus:541</c>).</summary>
public string[] UpdateKeys { get; set; }
/// <summary>The unique mod ID.</summary>
public string UniqueID { get; set; }
/// <summary>Any manifest fields which didn't match a valid field.</summary>
[JsonExtensionData]
public IDictionary<string, object> ExtraFields { get; set; }
}
}

View File

@ -0,0 +1,15 @@
namespace StardewModdingAPI.Toolkit.Serialisation.Models
{
/// <summary>Indicates which mod can read the content pack represented by the containing manifest.</summary>
public class ManifestContentPackFor
{
/*********
** Accessors
*********/
/// <summary>The unique ID of the mod which can read this content pack.</summary>
public string UniqueID { get; set; }
/// <summary>The minimum required version (if any).</summary>
public SemanticVersion MinimumVersion { get; set; }
}
}

View File

@ -0,0 +1,35 @@
namespace StardewModdingAPI.Toolkit.Serialisation.Models
{
/// <summary>A mod dependency listed in a mod manifest.</summary>
public class ManifestDependency
{
/*********
** Accessors
*********/
/// <summary>The unique mod ID to require.</summary>
public string UniqueID { get; set; }
/// <summary>The minimum required version (if any).</summary>
public SemanticVersion MinimumVersion { get; set; }
/// <summary>Whether the dependency must be installed to use the mod.</summary>
public bool IsRequired { get; set; }
/*********
** Public methods
*********/
/// <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>
public ManifestDependency(string uniqueID, string minimumVersion, bool required = true)
{
this.UniqueID = uniqueID;
this.MinimumVersion = !string.IsNullOrWhiteSpace(minimumVersion)
? new SemanticVersion(minimumVersion)
: null;
this.IsRequired = required;
}
}
}

View File

@ -1,6 +1,6 @@
using System; using System;
namespace StardewModdingAPI.Framework.Exceptions namespace StardewModdingAPI.Toolkit.Serialisation
{ {
/// <summary>A format exception which provides a user-facing error message.</summary> /// <summary>A format exception which provides a user-facing error message.</summary>
internal class SParseException : FormatException internal class SParseException : FormatException