diff options
Diffstat (limited to 'src/StardewModdingAPI/Framework/Models')
7 files changed, 329 insertions, 0 deletions
diff --git a/src/StardewModdingAPI/Framework/Models/Manifest.cs b/src/StardewModdingAPI/Framework/Models/Manifest.cs new file mode 100644 index 00000000..b85787e5 --- /dev/null +++ b/src/StardewModdingAPI/Framework/Models/Manifest.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using StardewModdingAPI.Framework.Serialisation; + +namespace StardewModdingAPI.Framework.Models +{ + /// <summary>A manifest which describes a mod for SMAPI.</summary> + internal class Manifest : IManifest + { + /********* + ** 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> + [JsonConverter(typeof(SFieldConverter))] + public ISemanticVersion Version { get; set; } + + /// <summary>The minimum SMAPI version required by this mod, if any.</summary> + [JsonConverter(typeof(SFieldConverter))] + public ISemanticVersion MinimumApiVersion { get; set; } + + /// <summary>The name of the DLL in the directory that has the <see cref="IMod.Entry"/> method.</summary> + public string EntryDll { get; set; } + + /// <summary>The other mods that must be loaded before this mod.</summary> + [JsonConverter(typeof(SFieldConverter))] + public IManifestDependency[] 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; } + } +} diff --git a/src/StardewModdingAPI/Framework/Models/ManifestDependency.cs b/src/StardewModdingAPI/Framework/Models/ManifestDependency.cs new file mode 100644 index 00000000..5646b335 --- /dev/null +++ b/src/StardewModdingAPI/Framework/Models/ManifestDependency.cs @@ -0,0 +1,34 @@ +namespace StardewModdingAPI.Framework.Models +{ + /// <summary>A mod dependency listed in a mod manifest.</summary> + internal class ManifestDependency : IManifestDependency + { + /********* + ** Accessors + *********/ + /// <summary>The unique mod ID to require.</summary> + public string UniqueID { get; set; } + + /// <summary>The minimum required version (if any).</summary> + public ISemanticVersion 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; + } + } +} diff --git a/src/StardewModdingAPI/Framework/Models/ModCompatibility.cs b/src/StardewModdingAPI/Framework/Models/ModCompatibility.cs new file mode 100644 index 00000000..54737e6c --- /dev/null +++ b/src/StardewModdingAPI/Framework/Models/ModCompatibility.cs @@ -0,0 +1,55 @@ +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/StardewModdingAPI/Framework/Models/ModDataID.cs b/src/StardewModdingAPI/Framework/Models/ModDataID.cs new file mode 100644 index 00000000..d19434fa --- /dev/null +++ b/src/StardewModdingAPI/Framework/Models/ModDataID.cs @@ -0,0 +1,85 @@ +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/StardewModdingAPI/Framework/Models/ModDataRecord.cs b/src/StardewModdingAPI/Framework/Models/ModDataRecord.cs new file mode 100644 index 00000000..c6a12188 --- /dev/null +++ b/src/StardewModdingAPI/Framework/Models/ModDataRecord.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using StardewModdingAPI.Framework.Serialisation; + +namespace StardewModdingAPI.Framework.Models +{ + /// <summary>Metadata about a mod from SMAPI's internal data.</summary> + internal class ModDataRecord + { + /********* + ** Accessors + *********/ + /// <summary>The unique mod identifier.</summary> + [JsonConverter(typeof(SFieldConverter))] + public ModDataID ID { get; set; } + + /// <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; } + + /// <summary>The compatibility of given mod versions (if any).</summary> + [JsonConverter(typeof(SFieldConverter))] + public ModCompatibility[] Compatibility { get; set; } = new ModCompatibility[0]; + + /// <summary>Map 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> + public IDictionary<string, string> MapRemoteVersions { 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) + { + return this.Compatibility.FirstOrDefault(p => p.MatchesVersion(version)); + } + + /// <summary>Get a semantic local version for update checks.</summary> + /// <param name="version">The local version to normalise.</param> + public string GetLocalVersionForUpdateChecks(string version) + { + return this.MapLocalVersions != null && this.MapLocalVersions.TryGetValue(version, out string newVersion) + ? newVersion + : 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.MapRemoteVersions != null && this.MapRemoteVersions.TryGetValue(version, out string newVersion) + ? newVersion + : version; + } + } +} diff --git a/src/StardewModdingAPI/Framework/Models/ModStatus.cs b/src/StardewModdingAPI/Framework/Models/ModStatus.cs new file mode 100644 index 00000000..343ccb7e --- /dev/null +++ b/src/StardewModdingAPI/Framework/Models/ModStatus.cs @@ -0,0 +1,18 @@ +namespace StardewModdingAPI.Framework.Models +{ + /// <summary>Indicates how SMAPI should treat a mod.</summary> + internal enum ModStatus + { + /// <summary>Don't override the status.</summary> + None, + + /// <summary>The mod is obsolete and shouldn't be used, regardless of version.</summary> + Obsolete, + + /// <summary>Assume the mod is not compatible, even if SMAPI doesn't detect any incompatible code.</summary> + AssumeBroken, + + /// <summary>Assume the mod is compatible, even if SMAPI detects incompatible code.</summary> + AssumeCompatible + } +} diff --git a/src/StardewModdingAPI/Framework/Models/SConfig.cs b/src/StardewModdingAPI/Framework/Models/SConfig.cs new file mode 100644 index 00000000..401e1a3a --- /dev/null +++ b/src/StardewModdingAPI/Framework/Models/SConfig.cs @@ -0,0 +1,27 @@ +namespace StardewModdingAPI.Framework.Models +{ + /// <summary>The SMAPI configuration settings.</summary> + internal class SConfig + { + /******** + ** Accessors + ********/ + /// <summary>Whether to enable development features.</summary> + public bool DeveloperMode { get; set; } + + /// <summary>Whether to check for newer versions of SMAPI and mods on startup.</summary> + public bool CheckForUpdates { get; set; } + + /// <summary>SMAPI's GitHub project name, used to perform update checks.</summary> + public string GitHubProjectName { get; set; } + + /// <summary>The base URL for SMAPI's web API, used to perform update checks.</summary> + public string WebApiBaseUrl { get; set; } + + /// <summary>Whether SMAPI should log more information about the game context.</summary> + public bool VerboseLogging { get; set; } + + /// <summary>Extra metadata about mods.</summary> + public ModDataRecord[] ModData { get; set; } + } +} |