summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r--src/SMAPI/Framework/IModMetadata.cs2
-rw-r--r--src/SMAPI/Framework/ModLoading/ModMetadata.cs4
-rw-r--r--src/SMAPI/Framework/ModLoading/ModResolver.cs24
-rw-r--r--src/SMAPI/Framework/Models/ModCompatibility.cs55
-rw-r--r--src/SMAPI/Framework/Models/ModDataField.cs82
-rw-r--r--src/SMAPI/Framework/Models/ModDataFieldKey.cs18
-rw-r--r--src/SMAPI/Framework/Models/ModDataID.cs85
-rw-r--r--src/SMAPI/Framework/Models/ModDataRecord.cs220
-rw-r--r--src/SMAPI/Framework/Models/ParsedModDataRecord.cs48
-rw-r--r--src/SMAPI/Framework/Serialisation/SmapiConverters/ModCompatibilityArrayConverter.cs61
-rw-r--r--src/SMAPI/Framework/Serialisation/SmapiConverters/ModDataIdConverter.cs19
11 files changed, 361 insertions, 257 deletions
diff --git a/src/SMAPI/Framework/IModMetadata.cs b/src/SMAPI/Framework/IModMetadata.cs
index a36994fd..41484567 100644
--- a/src/SMAPI/Framework/IModMetadata.cs
+++ b/src/SMAPI/Framework/IModMetadata.cs
@@ -19,7 +19,7 @@ namespace StardewModdingAPI.Framework
IManifest Manifest { get; }
/// <summary>>Metadata about the mod from SMAPI's internal data (if any).</summary>
- ModDataRecord DataRecord { get; }
+ ParsedModDataRecord DataRecord { get; }
/// <summary>The metadata resolution status.</summary>
ModMetadataStatus Status { get; }
diff --git a/src/SMAPI/Framework/ModLoading/ModMetadata.cs b/src/SMAPI/Framework/ModLoading/ModMetadata.cs
index 30fe211b..1a71920e 100644
--- a/src/SMAPI/Framework/ModLoading/ModMetadata.cs
+++ b/src/SMAPI/Framework/ModLoading/ModMetadata.cs
@@ -18,7 +18,7 @@ namespace StardewModdingAPI.Framework.ModLoading
public IManifest Manifest { get; }
/// <summary>Metadata about the mod from SMAPI's internal data (if any).</summary>
- public ModDataRecord DataRecord { get; }
+ public ParsedModDataRecord DataRecord { get; }
/// <summary>The metadata resolution status.</summary>
public ModMetadataStatus Status { get; private set; }
@@ -41,7 +41,7 @@ namespace StardewModdingAPI.Framework.ModLoading
/// <param name="directoryPath">The mod's full directory path.</param>
/// <param name="manifest">The mod manifest.</param>
/// <param name="dataRecord">Metadata about the mod from SMAPI's internal data (if any).</param>
- public ModMetadata(string displayName, string directoryPath, IManifest manifest, ModDataRecord dataRecord)
+ public ModMetadata(string displayName, string directoryPath, IManifest manifest, ParsedModDataRecord dataRecord)
{
this.DisplayName = displayName;
this.DirectoryPath = directoryPath;
diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs
index 9802d9e9..6671e880 100644
--- a/src/SMAPI/Framework/ModLoading/ModResolver.cs
+++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs
@@ -53,18 +53,15 @@ namespace StardewModdingAPI.Framework.ModLoading
error = $"parsing its manifest failed:\n{ex.GetLogSummary()}";
}
- // get internal data record (if any)
- ModDataRecord dataRecord = null;
+ // parse internal data record (if any)
+ ParsedModDataRecord dataRecord = null;
if (manifest != null)
{
- string key = !string.IsNullOrWhiteSpace(manifest.UniqueID) ? manifest.UniqueID : manifest.EntryDll;
- dataRecord = dataRecords.FirstOrDefault(record => record.ID.Matches(key, manifest));
+ ModDataRecord rawDataRecord = dataRecords.FirstOrDefault(p => p.Matches(manifest));
+ if (rawDataRecord != null)
+ dataRecord = rawDataRecord.ParseFieldsFor(manifest);
}
- // add default update keys
- if (manifest != null && manifest.UpdateKeys == null && dataRecord?.UpdateKeys != null)
- manifest.UpdateKeys = dataRecord.UpdateKeys;
-
// build metadata
string displayName = !string.IsNullOrWhiteSpace(manifest?.Name)
? manifest.Name
@@ -93,17 +90,16 @@ namespace StardewModdingAPI.Framework.ModLoading
continue;
// validate compatibility
- ModCompatibility compatibility = mod.DataRecord?.GetCompatibility(mod.Manifest.Version);
- switch (compatibility?.Status)
+ switch (mod.DataRecord?.Status)
{
case ModStatus.Obsolete:
- mod.SetStatus(ModMetadataStatus.Failed, $"it's obsolete: {compatibility.ReasonPhrase}");
+ mod.SetStatus(ModMetadataStatus.Failed, $"it's obsolete: {mod.DataRecord.StatusReasonPhrase}");
continue;
case ModStatus.AssumeBroken:
{
// get reason
- string reasonPhrase = compatibility.ReasonPhrase ?? "it's no longer compatible";
+ string reasonPhrase = mod.DataRecord.StatusReasonPhrase ?? "it's no longer compatible";
// get update URLs
List<string> updateUrls = new List<string>();
@@ -124,10 +120,10 @@ namespace StardewModdingAPI.Framework.ModLoading
// build error
string error = $"{reasonPhrase}. Please check for a ";
- if (mod.Manifest.Version.Equals(compatibility.UpperVersion))
+ if (mod.DataRecord.StatusUpperVersion == null || mod.Manifest.Version.Equals(mod.DataRecord.StatusUpperVersion))
error += "newer version";
else
- error += $"version newer than {compatibility.UpperVersion}";
+ error += $"version newer than {mod.DataRecord.StatusUpperVersion}";
error += " at " + string.Join(" or ", updateUrls);
mod.SetStatus(ModMetadataStatus.Failed, error);
diff --git a/src/SMAPI/Framework/Models/ModCompatibility.cs b/src/SMAPI/Framework/Models/ModCompatibility.cs
deleted file mode 100644
index 54737e6c..00000000
--- a/src/SMAPI/Framework/Models/ModCompatibility.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using System;
-
-namespace StardewModdingAPI.Framework.Models
-{
- /// <summary>Specifies the compatibility of a given mod version range.</summary>
- internal class ModCompatibility
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The lowest version in the range, or <c>null</c> for all past versions.</summary>
- public ISemanticVersion LowerVersion { get; }
-
- /// <summary>The highest version in the range, or <c>null</c> for all future versions.</summary>
- public ISemanticVersion UpperVersion { get; }
-
- /// <summary>The mod compatibility.</summary>
- public ModStatus Status { get; }
-
- /// <summary>The reason phrase to show in log output, or <c>null</c> to use the default value.</summary>
- /// <example>For example, "this version is incompatible with the latest version of the game".</example>
- public string ReasonPhrase { get; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="versionRange">A version range, which consists of two version strings separated by a '~' character. Either side can be left blank for an unbounded range.</param>
- /// <param name="status">The mod compatibility.</param>
- /// <param name="reasonPhrase">The reason phrase to show in log output, or <c>null</c> to use the default value.</param>
- public ModCompatibility(string versionRange, ModStatus status, string reasonPhrase)
- {
- // extract version strings
- string[] versions = versionRange.Split('~');
- if (versions.Length != 2)
- throw new FormatException($"Could not parse '{versionRange}' as a version range. It must have two version strings separated by a '~' character (either side can be left blank for an unbounded range).");
-
- // initialise
- this.LowerVersion = !string.IsNullOrWhiteSpace(versions[0]) ? new SemanticVersion(versions[0]) : null;
- this.UpperVersion = !string.IsNullOrWhiteSpace(versions[1]) ? new SemanticVersion(versions[1]) : null;
- this.Status = status;
- this.ReasonPhrase = reasonPhrase;
- }
-
- /// <summary>Get whether a given version is contained within this compatibility range.</summary>
- /// <param name="version">The version to check.</param>
- public bool MatchesVersion(ISemanticVersion version)
- {
- return
- (this.LowerVersion == null || !version.IsOlderThan(this.LowerVersion))
- && (this.UpperVersion == null || !version.IsNewerThan(this.UpperVersion));
- }
- }
-}
diff --git a/src/SMAPI/Framework/Models/ModDataField.cs b/src/SMAPI/Framework/Models/ModDataField.cs
new file mode 100644
index 00000000..0812b39b
--- /dev/null
+++ b/src/SMAPI/Framework/Models/ModDataField.cs
@@ -0,0 +1,82 @@
+using System.Linq;
+
+namespace StardewModdingAPI.Framework.Models
+{
+ /// <summary>A versioned mod metadata field.</summary>
+ internal class ModDataField
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The field key.</summary>
+ public ModDataFieldKey Key { get; }
+
+ /// <summary>The field value.</summary>
+ public string Value { get; }
+
+ /// <summary>Whether this field should only be applied if it's not already set.</summary>
+ public bool IsDefault { get; }
+
+ /// <summary>The lowest version in the range, or <c>null</c> for all past versions.</summary>
+ public ISemanticVersion LowerVersion { get; }
+
+ /// <summary>The highest version in the range, or <c>null</c> for all future versions.</summary>
+ public ISemanticVersion UpperVersion { get; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="key">The field key.</param>
+ /// <param name="value">The field value.</param>
+ /// <param name="isDefault">Whether this field should only be applied if it's not already set.</param>
+ /// <param name="lowerVersion">The lowest version in the range, or <c>null</c> for all past versions.</param>
+ /// <param name="upperVersion">The highest version in the range, or <c>null</c> for all future versions.</param>
+ public ModDataField(ModDataFieldKey key, string value, bool isDefault, ISemanticVersion lowerVersion, ISemanticVersion upperVersion)
+ {
+ this.Key = key;
+ this.Value = value;
+ this.IsDefault = isDefault;
+ this.LowerVersion = lowerVersion;
+ this.UpperVersion = upperVersion;
+ }
+
+ /// <summary>Get whether this data field applies for the given manifest.</summary>
+ /// <param name="manifest">The mod manifest.</param>
+ public bool IsMatch(IManifest manifest)
+ {
+ return
+ manifest?.Version != null // ignore invalid manifest
+ && (!this.IsDefault || !this.HasFieldValue(manifest, this.Key))
+ && (this.LowerVersion == null || !manifest.Version.IsOlderThan(this.LowerVersion))
+ && (this.UpperVersion == null || !manifest.Version.IsNewerThan(this.UpperVersion));
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Get whether a manifest field has a meaningful value for the purposes of enforcing <see cref="IsDefault"/>.</summary>
+ /// <param name="manifest">The mod manifest.</param>
+ /// <param name="key">The field key matching <see cref="ModDataFieldKey"/>.</param>
+ private bool HasFieldValue(IManifest manifest, ModDataFieldKey key)
+ {
+ switch (key)
+ {
+ // update key
+ case ModDataFieldKey.UpdateKey:
+ return manifest.UpdateKeys != null && manifest.UpdateKeys.Any();
+
+ // non-manifest fields
+ case ModDataFieldKey.AlternativeUrl:
+ case ModDataFieldKey.StatusReasonPhrase:
+ case ModDataFieldKey.Status:
+ return false;
+
+ default:
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/SMAPI/Framework/Models/ModDataFieldKey.cs b/src/SMAPI/Framework/Models/ModDataFieldKey.cs
new file mode 100644
index 00000000..5767afc9
--- /dev/null
+++ b/src/SMAPI/Framework/Models/ModDataFieldKey.cs
@@ -0,0 +1,18 @@
+namespace StardewModdingAPI.Framework.Models
+{
+ /// <summary>The valid field keys.</summary>
+ public enum ModDataFieldKey
+ {
+ /// <summary>A manifest update key.</summary>
+ UpdateKey,
+
+ /// <summary>An alternative URL the player can check for an updated version.</summary>
+ AlternativeUrl,
+
+ /// <summary>The mod's predefined compatibility status.</summary>
+ Status,
+
+ /// <summary>A reason phrase for the <see cref="Status"/>, or <c>null</c> to use the default reason.</summary>
+ StatusReasonPhrase
+ }
+}
diff --git a/src/SMAPI/Framework/Models/ModDataID.cs b/src/SMAPI/Framework/Models/ModDataID.cs
deleted file mode 100644
index d19434fa..00000000
--- a/src/SMAPI/Framework/Models/ModDataID.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-using System;
-using System.Linq;
-using Newtonsoft.Json;
-
-namespace StardewModdingAPI.Framework.Models
-{
- /// <summary>Uniquely identifies a mod in SMAPI's internal data.</summary>
- /// <remarks>
- /// This represents a custom format which uniquely identifies a mod across all versions, even
- /// if its field values change or it doesn't specify a unique ID. This is mapped to a string
- /// with the following format:
- ///
- /// 1. If the mod's identifier changed over time, multiple variants can be separated by the <c>|</c>
- /// character.
- /// 2. Each variant can take one of two forms:
- /// - A simple string matching the mod's UniqueID value.
- /// - A JSON structure containing any of three manifest fields (ID, Name, and Author) to match.
- /// </remarks>
- internal class ModDataID
- {
- /*********
- ** Properties
- *********/
- /// <summary>The unique sets of field values which identify this mod.</summary>
- private readonly FieldSnapshot[] Snapshots;
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- public ModDataID() { }
-
- /// <summary>Construct an instance.</summary>
- /// <param name="data">The mod identifier string (see remarks on <see cref="ModDataID"/>).</param>
- public ModDataID(string data)
- {
- this.Snapshots =
- (
- from string part in data.Split('|')
- let str = part.Trim()
- select str.StartsWith("{")
- ? JsonConvert.DeserializeObject<FieldSnapshot>(str)
- : new FieldSnapshot { ID = str }
- )
- .ToArray();
- }
-
- /// <summary>Get whether this ID matches a given mod manifest.</summary>
- /// <param name="id">The mod's unique ID, or a substitute ID if it isn't set in the manifest.</param>
- /// <param name="manifest">The manifest to check.</param>
- public bool Matches(string id, IManifest manifest)
- {
- return this.Snapshots.Any(snapshot =>
- snapshot.ID.Equals(id, StringComparison.InvariantCultureIgnoreCase)
- && (
- snapshot.Author == null
- || snapshot.Author.Equals(manifest.Author, StringComparison.InvariantCultureIgnoreCase)
- || (manifest.ExtraFields.ContainsKey("Authour") && snapshot.Author.Equals(manifest.ExtraFields["Authour"].ToString(), StringComparison.InvariantCultureIgnoreCase))
- )
- && (snapshot.Name == null || snapshot.Name.Equals(manifest.Name, StringComparison.InvariantCultureIgnoreCase))
- );
- }
-
-
- /*********
- ** Private models
- *********/
- /// <summary>A unique set of fields which identifies the mod.</summary>
- private class FieldSnapshot
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The unique mod ID.</summary>
- public string ID { get; set; }
-
- /// <summary>The mod name, or <c>null</c> to ignore the mod name.</summary>
- public string Name { get; set; }
-
- /// <summary>The author name, or <c>null</c> to ignore the author.</summary>
- public string Author { get; set; }
- }
- }
-}
diff --git a/src/SMAPI/Framework/Models/ModDataRecord.cs b/src/SMAPI/Framework/Models/ModDataRecord.cs
index 580acb70..2c26741c 100644
--- a/src/SMAPI/Framework/Models/ModDataRecord.cs
+++ b/src/SMAPI/Framework/Models/ModDataRecord.cs
@@ -1,49 +1,188 @@
+using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
+using System.Runtime.Serialization;
using Newtonsoft.Json;
-using StardewModdingAPI.Framework.Serialisation.SmapiConverters;
+using Newtonsoft.Json.Linq;
namespace StardewModdingAPI.Framework.Models
{
- /// <summary>Metadata about a mod from SMAPI's internal data.</summary>
+ /// <summary>Raw mod metadata from SMAPI's internal mod list.</summary>
internal class ModDataRecord
{
/*********
- ** Accessors
+ ** Properties
*********/
- /// <summary>The unique mod identifier.</summary>
- [JsonConverter(typeof(ModDataIdConverter))]
- public ModDataID ID { get; set; }
+ /// <summary>This field stores properties that aren't mapped to another field before they're parsed into <see cref="Fields"/>.</summary>
+ [JsonExtensionData]
+ private IDictionary<string, JToken> ExtensionData;
- /// <summary>A value to inject into <see cref="IManifest.UpdateKeys"/> field if it's not already set.</summary>
- public string[] UpdateKeys { get; set; }
- /// <summary>The URL where the player can get an unofficial or alternative version of the mod if the official version isn't compatible.</summary>
- public string AlternativeUrl { get; set; }
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The mod's current unique ID.</summary>
+ public string ID { get; set; }
- /// <summary>The compatibility of given mod versions (if any).</summary>
- [JsonConverter(typeof(ModCompatibilityArrayConverter))]
- public ModCompatibility[] Compatibility { get; set; } = new ModCompatibility[0];
+ /// <summary>The former mod IDs (if any).</summary>
+ /// <remarks>
+ /// This uses a custom format which uniquely identifies a mod across multiple versions and
+ /// supports matching other fields if no ID was specified. This doesn't include the latest
+ /// ID, if any. Format rules:
+ /// 1. If the mod's ID changed over time, multiple variants can be separated by the
+ /// <c>|</c> character.
+ /// 2. Each variant can take one of two forms:
+ /// - A simple string matching the mod's UniqueID value.
+ /// - A JSON structure containing any of four manifest fields (ID, Name, Author, and
+ /// EntryDll) to match.
+ /// </remarks>
+ public string FormerIDs { get; set; }
- /// <summary>Map local versions to a semantic version for update checks.</summary>
+ /// <summary>Maps local versions to a semantic version for update checks.</summary>
public IDictionary<string, string> MapLocalVersions { get; set; } = new Dictionary<string, string>();
- /// <summary>Map remote versions to a semantic version for update checks.</summary>
+ /// <summary>Maps remote versions to a semantic version for update checks.</summary>
public IDictionary<string, string> MapRemoteVersions { get; set; } = new Dictionary<string, string>();
+ /// <summary>The versioned field data.</summary>
+ /// <remarks>
+ /// This maps field names to values. This should be accessed via <see cref="GetFields"/>.
+ /// Format notes:
+ /// - Each key consists of a field name prefixed with any combination of version range
+ /// and <c>Default</c>, separated by pipes (whitespace trimmed). For example, <c>Name</c>
+ /// will always override the name, <c>Default | Name</c> will only override a blank
+ /// name, and <c>~1.1 | Default | Name</c> will override blank names up to version 1.1.
+ /// - The version format is <c>min~max</c> (where either side can be blank for unbounded), or
+ /// a single version number.
+ /// - The field name itself corresponds to a <see cref="ModDataFieldKey"/> value.
+ /// </remarks>
+ public IDictionary<string, string> Fields { get; set; } = new Dictionary<string, string>();
+
/*********
** Public methods
*********/
- /// <summary>Get the compatibility record for a given version, if any.</summary>
- /// <param name="version">The mod version to check.</param>
- public ModCompatibility GetCompatibility(ISemanticVersion version)
+ /// <summary>Get whether the manifest matches the <see cref="FormerIDs"/> field.</summary>
+ /// <param name="manifest">The mod manifest to check.</param>
+ public bool Matches(IManifest manifest)
+ {
+ // try main ID
+ if (this.ID != null && this.ID.Equals(manifest.UniqueID, StringComparison.InvariantCultureIgnoreCase))
+ return true;
+
+ // try former IDs
+ if (this.FormerIDs != null)
+ {
+ foreach (string part in this.FormerIDs.Split('|'))
+ {
+ // packed field snapshot
+ if (part.StartsWith("{"))
+ {
+ FieldSnapshot snapshot = JsonConvert.DeserializeObject<FieldSnapshot>(part);
+ bool isMatch =
+ (snapshot.ID == null || snapshot.ID.Equals(manifest.UniqueID, StringComparison.InvariantCultureIgnoreCase))
+ && (snapshot.EntryDll == null || snapshot.EntryDll.Equals(manifest.EntryDll, StringComparison.InvariantCultureIgnoreCase))
+ && (
+ snapshot.Author == null
+ || snapshot.Author.Equals(manifest.Author, StringComparison.InvariantCultureIgnoreCase)
+ || (manifest.ExtraFields.ContainsKey("Authour") && snapshot.Author.Equals(manifest.ExtraFields["Authour"].ToString(), StringComparison.InvariantCultureIgnoreCase))
+ )
+ && (snapshot.Name == null || snapshot.Name.Equals(manifest.Name, StringComparison.InvariantCultureIgnoreCase));
+
+ if (isMatch)
+ return true;
+ }
+
+ // plain ID
+ else if (part.Equals(manifest.UniqueID, StringComparison.InvariantCultureIgnoreCase))
+ return true;
+ }
+ }
+
+ // no match
+ return false;
+ }
+
+ /// <summary>Get a parsed representation of the <see cref="Fields"/>.</summary>
+ public IEnumerable<ModDataField> GetFields()
{
- return this.Compatibility.FirstOrDefault(p => p.MatchesVersion(version));
+ foreach (KeyValuePair<string, string> pair in this.Fields)
+ {
+ // init fields
+ string packedKey = pair.Key;
+ string value = pair.Value;
+ bool isDefault = false;
+ ISemanticVersion lowerVersion = null;
+ ISemanticVersion upperVersion = null;
+
+ // parse
+ string[] parts = packedKey.Split('|').Select(p => p.Trim()).ToArray();
+ ModDataFieldKey fieldKey = (ModDataFieldKey)Enum.Parse(typeof(ModDataFieldKey), parts.Last(), ignoreCase: true);
+ foreach (string part in parts.Take(parts.Length - 1))
+ {
+ // 'default'
+ if (part.Equals("Default", StringComparison.InvariantCultureIgnoreCase))
+ {
+ isDefault = true;
+ continue;
+ }
+
+ // version range
+ if (part.Contains("~"))
+ {
+ string[] versionParts = part.Split(new[] { '~' }, 2);
+ lowerVersion = versionParts[0] != "" ? new SemanticVersion(versionParts[0]) : null;
+ upperVersion = versionParts[1] != "" ? new SemanticVersion(versionParts[1]) : null;
+ continue;
+ }
+
+ // single version
+ lowerVersion = new SemanticVersion(part);
+ upperVersion = new SemanticVersion(part);
+ }
+
+ yield return new ModDataField(fieldKey, value, isDefault, lowerVersion, upperVersion);
+ }
+ }
+
+ /// <summary>Get a parsed representation of the <see cref="Fields"/> which match a given manifest.</summary>
+ /// <param name="manifest">The manifest to match.</param>
+ public ParsedModDataRecord ParseFieldsFor(IManifest manifest)
+ {
+ ParsedModDataRecord parsed = new ParsedModDataRecord { DataRecord = this };
+ foreach (ModDataField field in this.GetFields().Where(field => field.IsMatch(manifest)))
+ {
+ switch (field.Key)
+ {
+ // update key
+ case ModDataFieldKey.UpdateKey:
+ parsed.UpdateKey = field.Value;
+ break;
+
+ // alternative URL
+ case ModDataFieldKey.AlternativeUrl:
+ parsed.AlternativeUrl = field.Value;
+ break;
+
+ // status
+ case ModDataFieldKey.Status:
+ parsed.Status = (ModStatus)Enum.Parse(typeof(ModStatus), field.Value, ignoreCase: true);
+ parsed.StatusUpperVersion = field.UpperVersion;
+ break;
+
+ // status reason phrase
+ case ModDataFieldKey.StatusReasonPhrase:
+ parsed.StatusReasonPhrase = field.Value;
+ break;
+ }
+ }
+
+ return parsed;
}
/// <summary>Get a semantic local version for update checks.</summary>
- /// <param name="version">The local version to normalise.</param>
+ /// <param name="version">The remote version to normalise.</param>
public string GetLocalVersionForUpdateChecks(string version)
{
return this.MapLocalVersions != null && this.MapLocalVersions.TryGetValue(version, out string newVersion)
@@ -59,5 +198,46 @@ namespace StardewModdingAPI.Framework.Models
? newVersion
: version;
}
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>The method invoked after JSON deserialisation.</summary>
+ /// <param name="context">The deserialisation context.</param>
+ [OnDeserialized]
+ private void OnDeserialized(StreamingContext context)
+ {
+ if (this.ExtensionData != null)
+ {
+ this.Fields = this.ExtensionData.ToDictionary(p => p.Key, p => p.Value.ToString());
+ this.ExtensionData = null;
+ }
+ }
+
+
+ /*********
+ ** Private models
+ *********/
+ /// <summary>A unique set of fields which identifies the mod.</summary>
+ [SuppressMessage("ReSharper", "ClassNeverInstantiated.Local", Justification = "Used via JSON deserialisation.")]
+ [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local", Justification = "Used via JSON deserialisation.")]
+ private class FieldSnapshot
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The unique mod ID (or <c>null</c> to ignore it).</summary>
+ public string ID { get; set; }
+
+ /// <summary>The entry DLL (or <c>null</c> to ignore it).</summary>
+ public string EntryDll { get; set; }
+
+ /// <summary>The mod name (or <c>null</c> to ignore it).</summary>
+ public string Name { get; set; }
+
+ /// <summary>The author name (or <c>null</c> to ignore it).</summary>
+ public string Author { get; set; }
+ }
}
}
diff --git a/src/SMAPI/Framework/Models/ParsedModDataRecord.cs b/src/SMAPI/Framework/Models/ParsedModDataRecord.cs
new file mode 100644
index 00000000..0abc7b89
--- /dev/null
+++ b/src/SMAPI/Framework/Models/ParsedModDataRecord.cs
@@ -0,0 +1,48 @@
+namespace StardewModdingAPI.Framework.Models
+{
+ /// <summary>A parsed representation of the fields from a <see cref="ModDataRecord"/> for a specific manifest.</summary>
+ internal class ParsedModDataRecord
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>The underlying data record.</summary>
+ public ModDataRecord DataRecord { get; set; }
+
+ /// <summary>The update key to apply.</summary>
+ public string UpdateKey { get; set; }
+
+ /// <summary>The mod version to apply.</summary>
+ public ISemanticVersion Version { get; set; }
+
+ /// <summary>The alternative URL the player can check for an updated version.</summary>
+ public string AlternativeUrl { get; set; }
+
+ /// <summary>The predefined compatibility status.</summary>
+ public ModStatus Status { get; set; } = ModStatus.None;
+
+ /// <summary>A reason phrase for the <see cref="Status"/>, or <c>null</c> to use the default reason.</summary>
+ public string StatusReasonPhrase { get; set; }
+
+ /// <summary>The upper version for which the <see cref="Status"/> applies (if any).</summary>
+ public ISemanticVersion StatusUpperVersion { get; set; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Get a semantic local version for update checks.</summary>
+ /// <param name="version">The remote version to normalise.</param>
+ public string GetLocalVersionForUpdateChecks(string version)
+ {
+ return this.DataRecord.GetLocalVersionForUpdateChecks(version);
+ }
+
+ /// <summary>Get a semantic remote version for update checks.</summary>
+ /// <param name="version">The remote version to normalise.</param>
+ public string GetRemoteVersionForUpdateChecks(string version)
+ {
+ return this.DataRecord.GetRemoteVersionForUpdateChecks(version);
+ }
+ }
+}
diff --git a/src/SMAPI/Framework/Serialisation/SmapiConverters/ModCompatibilityArrayConverter.cs b/src/SMAPI/Framework/Serialisation/SmapiConverters/ModCompatibilityArrayConverter.cs
deleted file mode 100644
index 3232dde4..00000000
--- a/src/SMAPI/Framework/Serialisation/SmapiConverters/ModCompatibilityArrayConverter.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using StardewModdingAPI.Framework.Models;
-
-namespace StardewModdingAPI.Framework.Serialisation.SmapiConverters
-{
- /// <summary>Handles deserialisation of <see cref="ModCompatibility"/> arrays.</summary>
- internal class ModCompatibilityArrayConverter : JsonConverter
- {
- /*********
- ** Accessors
- *********/
- /// <summary>Whether this converter can write JSON.</summary>
- public override bool CanWrite => false;
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Get whether this instance can convert the specified object type.</summary>
- /// <param name="objectType">The object type.</param>
- public override bool CanConvert(Type objectType)
- {
- return objectType == typeof(ModCompatibility[]);
- }
-
-
- /*********
- ** Protected methods
- *********/
- /// <summary>Read the JSON representation of the object.</summary>
- /// <param name="reader">The JSON reader.</param>
- /// <param name="objectType">The object type.</param>
- /// <param name="existingValue">The object being read.</param>
- /// <param name="serializer">The calling serializer.</param>
- public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
- {
- List<ModCompatibility> result = new List<ModCompatibility>();
- foreach (JProperty property in JObject.Load(reader).Properties())
- {
- string range = property.Name;
- ModStatus status = (ModStatus)Enum.Parse(typeof(ModStatus), property.Value.Value<string>(nameof(ModCompatibility.Status)));
- string reasonPhrase = property.Value.Value<string>(nameof(ModCompatibility.ReasonPhrase));
-
- result.Add(new ModCompatibility(range, status, reasonPhrase));
- }
- return result.ToArray();
- }
-
- /// <summary>Writes the JSON representation of the object.</summary>
- /// <param name="writer">The JSON writer.</param>
- /// <param name="value">The value.</param>
- /// <param name="serializer">The calling serializer.</param>
- 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/ModDataIdConverter.cs b/src/SMAPI/Framework/Serialisation/SmapiConverters/ModDataIdConverter.cs
deleted file mode 100644
index 8a10db47..00000000
--- a/src/SMAPI/Framework/Serialisation/SmapiConverters/ModDataIdConverter.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using StardewModdingAPI.Framework.Models;
-
-namespace StardewModdingAPI.Framework.Serialisation.SmapiConverters
-{
- /// <summary>Handles deserialisation of <see cref="ModDataID"/>.</summary>
- internal class ModDataIdConverter : SimpleReadOnlyConverter<ModDataID>
- {
- /*********
- ** Protected methods
- *********/
- /// <summary>Read a JSON string.</summary>
- /// <param name="str">The JSON string value.</param>
- /// <param name="path">The path to the current JSON node.</param>
- protected override ModDataID ReadString(string str, string path)
- {
- return new ModDataID(str);
- }
- }
-}