summaryrefslogtreecommitdiff
path: root/src/StardewModdingAPI.Toolkit
diff options
context:
space:
mode:
Diffstat (limited to 'src/StardewModdingAPI.Toolkit')
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModEntryModel.cs33
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModEntryVersionModel.cs31
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs123
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModSeachModel.cs36
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModSearchEntryModel.cs34
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/WebApiClient.cs73
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs237
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityInfo.cs24
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityStatus.cs27
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs54
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModList.cs18
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/ModData/MetadataModel.cs14
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataField.cs82
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataFieldKey.cs18
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataModel.cs127
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataRecord.cs147
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataRecordVersionedFields.cs54
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/ModData/ModDatabase.cs65
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/ModData/ModStatus.cs18
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/ModData/ModWarning.cs36
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModFolder.cs64
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs183
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/UpdateData/ModRepositoryKey.cs21
-rw-r--r--src/StardewModdingAPI.Toolkit/Framework/UpdateData/UpdateKey.cs73
-rw-r--r--src/StardewModdingAPI.Toolkit/ModToolkit.cs89
-rw-r--r--src/StardewModdingAPI.Toolkit/Properties/AssemblyInfo.cs7
-rw-r--r--src/StardewModdingAPI.Toolkit/SemanticVersion.cs312
-rw-r--r--src/StardewModdingAPI.Toolkit/Serialisation/Converters/ManifestContentPackForConverter.cs50
-rw-r--r--src/StardewModdingAPI.Toolkit/Serialisation/Converters/ManifestDependencyArrayConverter.cs60
-rw-r--r--src/StardewModdingAPI.Toolkit/Serialisation/Converters/SemanticVersionConverter.cs98
-rw-r--r--src/StardewModdingAPI.Toolkit/Serialisation/Converters/SimpleReadOnlyConverter.cs76
-rw-r--r--src/StardewModdingAPI.Toolkit/Serialisation/InternalExtensions.cs21
-rw-r--r--src/StardewModdingAPI.Toolkit/Serialisation/JsonHelper.cs136
-rw-r--r--src/StardewModdingAPI.Toolkit/Serialisation/Models/Manifest.cs74
-rw-r--r--src/StardewModdingAPI.Toolkit/Serialisation/Models/ManifestContentPackFor.cs15
-rw-r--r--src/StardewModdingAPI.Toolkit/Serialisation/Models/ManifestDependency.cs35
-rw-r--r--src/StardewModdingAPI.Toolkit/Serialisation/SParseException.cs17
-rw-r--r--src/StardewModdingAPI.Toolkit/StardewModdingAPI.Toolkit.csproj27
-rw-r--r--src/StardewModdingAPI.Toolkit/Utilities/FileUtilities.cs46
-rw-r--r--src/StardewModdingAPI.Toolkit/Utilities/PathUtilities.cs85
40 files changed, 0 insertions, 2740 deletions
diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModEntryModel.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModEntryModel.cs
deleted file mode 100644
index 8a9c0a25..00000000
--- a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModEntryModel.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
-{
- /// <summary>Metadata about a mod.</summary>
- public class ModEntryModel
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The mod's unique ID (if known).</summary>
- public string ID { get; set; }
-
- /// <summary>The main version.</summary>
- public ModEntryVersionModel Main { get; set; }
-
- /// <summary>The latest optional version, if newer than <see cref="Main"/>.</summary>
- public ModEntryVersionModel Optional { get; set; }
-
- /// <summary>The latest unofficial version, if newer than <see cref="Main"/> and <see cref="Optional"/>.</summary>
- public ModEntryVersionModel Unofficial { get; set; }
-
- /// <summary>The latest unofficial version for the current Stardew Valley or SMAPI beta, if any (see <see cref="HasBetaInfo"/>).</summary>
- public ModEntryVersionModel UnofficialForBeta { get; set; }
-
- /// <summary>Optional extended data which isn't needed for update checks.</summary>
- public ModExtendedMetadataModel Metadata { get; set; }
-
- /// <summary>Whether a Stardew Valley or SMAPI beta which affects mod compatibility is in progress. If this is true, <see cref="UnofficialForBeta"/> should be used for beta versions of SMAPI instead of <see cref="Unofficial"/>.</summary>
- public bool HasBetaInfo { get; set; }
-
- /// <summary>The errors that occurred while fetching update data.</summary>
- public string[] Errors { get; set; } = new string[0];
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModEntryVersionModel.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModEntryVersionModel.cs
deleted file mode 100644
index dadb8c10..00000000
--- a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModEntryVersionModel.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
-{
- /// <summary>Metadata about a version.</summary>
- public class ModEntryVersionModel
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The version number.</summary>
- public ISemanticVersion Version { get; set; }
-
- /// <summary>The mod page URL.</summary>
- public string Url { get; set; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- public ModEntryVersionModel() { }
-
- /// <summary>Construct an instance.</summary>
- /// <param name="version">The version number.</param>
- /// <param name="url">The mod page URL.</param>
- public ModEntryVersionModel(ISemanticVersion version, string url)
- {
- this.Version = version;
- this.Url = url;
- }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs
deleted file mode 100644
index 989c18b0..00000000
--- a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs
+++ /dev/null
@@ -1,123 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Converters;
-using StardewModdingAPI.Toolkit.Framework.Clients.Wiki;
-using StardewModdingAPI.Toolkit.Framework.ModData;
-
-namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
-{
- /// <summary>Extended metadata about a mod.</summary>
- public class ModExtendedMetadataModel
- {
- /*********
- ** Accessors
- *********/
- /****
- ** Mod info
- ****/
- /// <summary>The mod's unique ID. A mod may have multiple current IDs in rare cases (e.g. due to parallel releases or unofficial updates).</summary>
- public string[] ID { get; set; } = new string[0];
-
- /// <summary>The mod's display name.</summary>
- public string Name { get; set; }
-
- /// <summary>The mod ID on Nexus.</summary>
- public int? NexusID { get; set; }
-
- /// <summary>The mod ID in the Chucklefish mod repo.</summary>
- public int? ChucklefishID { get; set; }
-
- /// <summary>The mod ID in the ModDrop mod repo.</summary>
- public int? ModDropID { get; set; }
-
- /// <summary>The GitHub repository in the form 'owner/repo'.</summary>
- public string GitHubRepo { get; set; }
-
- /// <summary>The URL to a non-GitHub source repo.</summary>
- public string CustomSourceUrl { get; set; }
-
- /// <summary>The custom mod page URL (if applicable).</summary>
- public string CustomUrl { get; set; }
-
-
- /****
- ** Stable compatibility
- ****/
- /// <summary>The compatibility status.</summary>
- [JsonConverter(typeof(StringEnumConverter))]
- public WikiCompatibilityStatus? CompatibilityStatus { get; set; }
-
- /// <summary>The human-readable summary of the compatibility status or workaround, without HTML formatitng.</summary>
- public string CompatibilitySummary { get; set; }
-
- /// <summary>The game or SMAPI version which broke this mod, if applicable.</summary>
- public string BrokeIn { get; set; }
-
-
- /****
- ** Beta compatibility
- ****/
- /// <summary>The compatibility status for the Stardew Valley beta (if any).</summary>
- [JsonConverter(typeof(StringEnumConverter))]
- public WikiCompatibilityStatus? BetaCompatibilityStatus { get; set; }
-
- /// <summary>The human-readable summary of the compatibility status or workaround for the Stardew Valley beta (if any), without HTML formatitng.</summary>
- public string BetaCompatibilitySummary { get; set; }
-
- /// <summary>The beta game or SMAPI version which broke this mod, if applicable.</summary>
- public string BetaBrokeIn { get; set; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- public ModExtendedMetadataModel() { }
-
- /// <summary>Construct an instance.</summary>
- /// <param name="wiki">The mod metadata from the wiki (if available).</param>
- /// <param name="db">The mod metadata from SMAPI's internal DB (if available).</param>
- public ModExtendedMetadataModel(WikiModEntry wiki, ModDataRecord db)
- {
- // wiki data
- if (wiki != null)
- {
- this.ID = wiki.ID;
- this.Name = wiki.Name.FirstOrDefault();
- this.NexusID = wiki.NexusID;
- this.ChucklefishID = wiki.ChucklefishID;
- this.ModDropID = wiki.ModDropID;
- this.GitHubRepo = wiki.GitHubRepo;
- this.CustomSourceUrl = wiki.CustomSourceUrl;
- this.CustomUrl = wiki.CustomUrl;
-
- this.CompatibilityStatus = wiki.Compatibility.Status;
- this.CompatibilitySummary = wiki.Compatibility.Summary;
- this.BrokeIn = wiki.Compatibility.BrokeIn;
-
- this.BetaCompatibilityStatus = wiki.BetaCompatibility?.Status;
- this.BetaCompatibilitySummary = wiki.BetaCompatibility?.Summary;
- this.BetaBrokeIn = wiki.BetaCompatibility?.BrokeIn;
- }
-
- // internal DB data
- if (db != null)
- {
- this.ID = this.ID.Union(db.FormerIDs).ToArray();
- this.Name = this.Name ?? db.DisplayName;
- }
- }
-
- /// <summary>Get update keys based on the metadata.</summary>
- public IEnumerable<string> GetUpdateKeys()
- {
- if (this.NexusID.HasValue)
- yield return $"Nexus:{this.NexusID}";
- if (this.ChucklefishID.HasValue)
- yield return $"Chucklefish:{this.ChucklefishID}";
- if (this.GitHubRepo != null)
- yield return $"GitHub:{this.GitHubRepo}";
- }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModSeachModel.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModSeachModel.cs
deleted file mode 100644
index e352e1cc..00000000
--- a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModSeachModel.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.Linq;
-
-namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
-{
- /// <summary>Specifies mods whose update-check info to fetch.</summary>
- public class ModSearchModel
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The mods for which to find data.</summary>
- public ModSearchEntryModel[] Mods { get; set; }
-
- /// <summary>Whether to include extended metadata for each mod.</summary>
- public bool IncludeExtendedMetadata { get; set; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an empty instance.</summary>
- public ModSearchModel()
- {
- // needed for JSON deserialising
- }
-
- /// <summary>Construct an instance.</summary>
- /// <param name="mods">The mods to search.</param>
- /// <param name="includeExtendedMetadata">Whether to include extended metadata for each mod.</param>
- public ModSearchModel(ModSearchEntryModel[] mods, bool includeExtendedMetadata)
- {
- this.Mods = mods.ToArray();
- this.IncludeExtendedMetadata = includeExtendedMetadata;
- }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModSearchEntryModel.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModSearchEntryModel.cs
deleted file mode 100644
index bca47647..00000000
--- a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModSearchEntryModel.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
-{
- /// <summary>Specifies the identifiers for a mod to match.</summary>
- public class ModSearchEntryModel
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The unique mod ID.</summary>
- public string ID { get; set; }
-
- /// <summary>The namespaced mod update keys (if available).</summary>
- public string[] UpdateKeys { get; set; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an empty instance.</summary>
- public ModSearchEntryModel()
- {
- // needed for JSON deserialising
- }
-
- /// <summary>Construct an instance.</summary>
- /// <param name="id">The unique mod ID.</param>
- /// <param name="updateKeys">The namespaced mod update keys (if available).</param>
- public ModSearchEntryModel(string id, string[] updateKeys)
- {
- this.ID = id;
- this.UpdateKeys = updateKeys ?? new string[0];
- }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/WebApiClient.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/WebApiClient.cs
deleted file mode 100644
index 7c3df384..00000000
--- a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/WebApiClient.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using Newtonsoft.Json;
-using StardewModdingAPI.Toolkit.Serialisation;
-
-namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
-{
- /// <summary>Provides methods for interacting with the SMAPI web API.</summary>
- public class WebApiClient
- {
- /*********
- ** Fields
- *********/
- /// <summary>The base URL for the web API.</summary>
- private readonly Uri BaseUrl;
-
- /// <summary>The API version number.</summary>
- private readonly ISemanticVersion Version;
-
- /// <summary>The JSON serializer settings to use.</summary>
- private readonly JsonSerializerSettings JsonSettings = new JsonHelper().JsonSettings;
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="baseUrl">The base URL for the web API.</param>
- /// <param name="version">The web API version.</param>
- public WebApiClient(string baseUrl, ISemanticVersion version)
- {
- this.BaseUrl = new Uri(baseUrl);
- this.Version = version;
- }
-
- /// <summary>Get metadata about a set of mods from the web API.</summary>
- /// <param name="mods">The mod keys for which to fetch the latest version.</param>
- /// <param name="includeExtendedMetadata">Whether to include extended metadata for each mod.</param>
- public IDictionary<string, ModEntryModel> GetModInfo(ModSearchEntryModel[] mods, bool includeExtendedMetadata = false)
- {
- return this.Post<ModSearchModel, ModEntryModel[]>(
- $"v{this.Version}/mods",
- new ModSearchModel(mods, includeExtendedMetadata)
- ).ToDictionary(p => p.ID);
- }
-
-
- /*********
- ** Private methods
- *********/
- /// <summary>Fetch the response from the backend API.</summary>
- /// <typeparam name="TBody">The body content type.</typeparam>
- /// <typeparam name="TResult">The expected response type.</typeparam>
- /// <param name="url">The request URL, optionally excluding the base URL.</param>
- /// <param name="content">The body content to post.</param>
- private TResult Post<TBody, TResult>(string url, TBody content)
- {
- // note: avoid HttpClient for Mac compatibility
- using (WebClient client = new WebClient())
- {
- Uri fullUrl = new Uri(this.BaseUrl, url);
- string data = JsonConvert.SerializeObject(content);
-
- client.Headers["Content-Type"] = "application/json";
- client.Headers["User-Agent"] = $"SMAPI/{this.Version}";
- string response = client.UploadString(fullUrl, data);
- return JsonConvert.DeserializeObject<TResult>(response, this.JsonSettings);
- }
- }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs
deleted file mode 100644
index 3e9b8ea6..00000000
--- a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs
+++ /dev/null
@@ -1,237 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using System.Net;
-using System.Threading.Tasks;
-using HtmlAgilityPack;
-using Pathoschild.Http.Client;
-
-namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
-{
- /// <summary>An HTTP client for fetching mod metadata from the wiki.</summary>
- public class WikiClient : IDisposable
- {
- /*********
- ** Fields
- *********/
- /// <summary>The underlying HTTP client.</summary>
- private readonly IClient Client;
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="userAgent">The user agent for the wiki API.</param>
- /// <param name="baseUrl">The base URL for the wiki API.</param>
- public WikiClient(string userAgent, string baseUrl = "https://stardewvalleywiki.com/mediawiki/api.php")
- {
- this.Client = new FluentClient(baseUrl).SetUserAgent(userAgent);
- }
-
- /// <summary>Fetch mods from the compatibility list.</summary>
- public async Task<WikiModList> FetchModsAsync()
- {
- // fetch HTML
- ResponseModel response = await this.Client
- .GetAsync("")
- .WithArguments(new
- {
- action = "parse",
- page = "Modding:Mod_compatibility",
- format = "json"
- })
- .As<ResponseModel>();
- string html = response.Parse.Text["*"];
-
- // parse HTML
- var doc = new HtmlDocument();
- doc.LoadHtml(html);
-
- // fetch game versions
- string stableVersion = doc.DocumentNode.SelectSingleNode("div[@class='game-stable-version']")?.InnerText;
- string betaVersion = doc.DocumentNode.SelectSingleNode("div[@class='game-beta-version']")?.InnerText;
- if (betaVersion == stableVersion)
- betaVersion = null;
-
- // find mod entries
- HtmlNodeCollection modNodes = doc.DocumentNode.SelectNodes("table[@id='mod-list']//tr[@class='mod']");
- if (modNodes == null)
- throw new InvalidOperationException("Can't parse wiki compatibility list, no mods found.");
-
- // parse
- WikiModEntry[] mods = this.ParseEntries(modNodes).ToArray();
- return new WikiModList
- {
- StableVersion = stableVersion,
- BetaVersion = betaVersion,
- Mods = mods
- };
- }
-
- /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
- public void Dispose()
- {
- this.Client?.Dispose();
- }
-
-
- /*********
- ** Private methods
- *********/
- /// <summary>Parse valid mod compatibility entries.</summary>
- /// <param name="nodes">The HTML compatibility entries.</param>
- private IEnumerable<WikiModEntry> ParseEntries(IEnumerable<HtmlNode> nodes)
- {
- foreach (HtmlNode node in nodes)
- {
- // extract fields
- string[] names = this.GetAttributeAsCsv(node, "data-name");
- string[] authors = this.GetAttributeAsCsv(node, "data-author");
- string[] ids = this.GetAttributeAsCsv(node, "data-id");
- string[] warnings = this.GetAttributeAsCsv(node, "data-warnings");
- int? nexusID = this.GetAttributeAsNullableInt(node, "data-nexus-id");
- int? chucklefishID = this.GetAttributeAsNullableInt(node, "data-cf-id");
- int? modDropID = this.GetAttributeAsNullableInt(node, "data-moddrop-id");
- string githubRepo = this.GetAttribute(node, "data-github");
- string customSourceUrl = this.GetAttribute(node, "data-custom-source");
- string customUrl = this.GetAttribute(node, "data-url");
- string anchor = this.GetAttribute(node, "id");
- string contentPackFor = this.GetAttribute(node, "data-content-pack-for");
-
- // parse stable compatibility
- WikiCompatibilityInfo compatibility = new WikiCompatibilityInfo
- {
- Status = this.GetAttributeAsEnum<WikiCompatibilityStatus>(node, "data-status") ?? WikiCompatibilityStatus.Ok,
- BrokeIn = this.GetAttribute(node, "data-broke-in"),
- UnofficialVersion = this.GetAttributeAsSemanticVersion(node, "data-unofficial-version"),
- UnofficialUrl = this.GetAttribute(node, "data-unofficial-url"),
- Summary = this.GetInnerHtml(node, "mod-summary")?.Trim()
- };
-
- // parse beta compatibility
- WikiCompatibilityInfo betaCompatibility = null;
- {
- WikiCompatibilityStatus? betaStatus = this.GetAttributeAsEnum<WikiCompatibilityStatus>(node, "data-beta-status");
- if (betaStatus.HasValue)
- {
- betaCompatibility = new WikiCompatibilityInfo
- {
- Status = betaStatus.Value,
- BrokeIn = this.GetAttribute(node, "data-beta-broke-in"),
- UnofficialVersion = this.GetAttributeAsSemanticVersion(node, "data-beta-unofficial-version"),
- UnofficialUrl = this.GetAttribute(node, "data-beta-unofficial-url"),
- Summary = this.GetInnerHtml(node, "mod-beta-summary")
- };
- }
- }
-
- // yield model
- yield return new WikiModEntry
- {
- ID = ids,
- Name = names,
- Author = authors,
- NexusID = nexusID,
- ChucklefishID = chucklefishID,
- ModDropID = modDropID,
- GitHubRepo = githubRepo,
- CustomSourceUrl = customSourceUrl,
- CustomUrl = customUrl,
- ContentPackFor = contentPackFor,
- Compatibility = compatibility,
- BetaCompatibility = betaCompatibility,
- Warnings = warnings,
- Anchor = anchor
- };
- }
- }
-
- /// <summary>Get an attribute value.</summary>
- /// <param name="element">The element whose attributes to read.</param>
- /// <param name="name">The attribute name.</param>
- private string GetAttribute(HtmlNode element, string name)
- {
- string value = element.GetAttributeValue(name, null);
- if (string.IsNullOrWhiteSpace(value))
- return null;
-
- return WebUtility.HtmlDecode(value);
- }
-
- /// <summary>Get an attribute value and parse it as a comma-delimited list of strings.</summary>
- /// <param name="element">The element whose attributes to read.</param>
- /// <param name="name">The attribute name.</param>
- private string[] GetAttributeAsCsv(HtmlNode element, string name)
- {
- string raw = this.GetAttribute(element, name);
- return !string.IsNullOrWhiteSpace(raw)
- ? raw.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(p => p.Trim()).ToArray()
- : new string[0];
- }
-
- /// <summary>Get an attribute value and parse it as an enum value.</summary>
- /// <typeparam name="TEnum">The enum type.</typeparam>
- /// <param name="element">The element whose attributes to read.</param>
- /// <param name="name">The attribute name.</param>
- private TEnum? GetAttributeAsEnum<TEnum>(HtmlNode element, string name) where TEnum : struct
- {
- string raw = this.GetAttribute(element, name);
- if (raw == null)
- return null;
- if (!Enum.TryParse(raw, true, out TEnum value) && Enum.IsDefined(typeof(TEnum), value))
- throw new InvalidOperationException($"Unknown {typeof(TEnum).Name} value '{raw}' when parsing compatibility list.");
- return value;
- }
-
- /// <summary>Get an attribute value and parse it as a semantic version.</summary>
- /// <param name="element">The element whose attributes to read.</param>
- /// <param name="name">The attribute name.</param>
- private ISemanticVersion GetAttributeAsSemanticVersion(HtmlNode element, string name)
- {
- string raw = this.GetAttribute(element, name);
- return SemanticVersion.TryParse(raw, out ISemanticVersion version)
- ? version
- : null;
- }
-
- /// <summary>Get an attribute value and parse it as a nullable int.</summary>
- /// <param name="element">The element whose attributes to read.</param>
- /// <param name="name">The attribute name.</param>
- private int? GetAttributeAsNullableInt(HtmlNode element, string name)
- {
- string raw = this.GetAttribute(element, name);
- if (raw != null && int.TryParse(raw, out int value))
- return value;
- return null;
- }
-
- /// <summary>Get the text of an element with the given class name.</summary>
- /// <param name="container">The metadata container.</param>
- /// <param name="className">The field name.</param>
- private string GetInnerHtml(HtmlNode container, string className)
- {
- return container.Descendants().FirstOrDefault(p => p.HasClass(className))?.InnerHtml;
- }
-
- /// <summary>The response model for the MediaWiki parse API.</summary>
- [SuppressMessage("ReSharper", "ClassNeverInstantiated.Local")]
- [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
- private class ResponseModel
- {
- /// <summary>The parse API results.</summary>
- public ResponseParseModel Parse { get; set; }
- }
-
- /// <summary>The inner response model for the MediaWiki parse API.</summary>
- [SuppressMessage("ReSharper", "ClassNeverInstantiated.Local")]
- [SuppressMessage("ReSharper", "CollectionNeverUpdated.Local")]
- [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
- private class ResponseParseModel
- {
- /// <summary>The parsed text.</summary>
- public IDictionary<string, string> Text { get; set; }
- }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityInfo.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityInfo.cs
deleted file mode 100644
index 204acd2b..00000000
--- a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityInfo.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
-{
- /// <summary>Compatibility info for a mod.</summary>
- public class WikiCompatibilityInfo
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The compatibility status.</summary>
- public WikiCompatibilityStatus Status { get; set; }
-
- /// <summary>The human-readable summary of the compatibility status or workaround, without HTML formatting.</summary>
- public string Summary { get; set; }
-
- /// <summary>The game or SMAPI version which broke this mod (if applicable).</summary>
- public string BrokeIn { get; set; }
-
- /// <summary>The version of the latest unofficial update, if applicable.</summary>
- public ISemanticVersion UnofficialVersion { get; set; }
-
- /// <summary>The URL to the latest unofficial update, if applicable.</summary>
- public string UnofficialUrl { get; set; }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityStatus.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityStatus.cs
deleted file mode 100644
index a1d2dfae..00000000
--- a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityStatus.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
-{
- /// <summary>The compatibility status for a mod.</summary>
- public enum WikiCompatibilityStatus
- {
- /// <summary>The mod is compatible.</summary>
- Ok = 0,
-
- /// <summary>The mod is compatible if you use an optional official download.</summary>
- Optional = 1,
-
- /// <summary>The mod is compatible if you use an unofficial update.</summary>
- Unofficial = 2,
-
- /// <summary>The mod isn't compatible, but the player can fix it or there's a good alternative.</summary>
- Workaround = 3,
-
- /// <summary>The mod isn't compatible.</summary>
- Broken = 4,
-
- /// <summary>The mod is no longer maintained by the author, and an unofficial update or continuation is unlikely.</summary>
- Abandoned = 5,
-
- /// <summary>The mod is no longer needed and should be removed.</summary>
- Obsolete = 6
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs
deleted file mode 100644
index cf416cc6..00000000
--- a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
-{
- /// <summary>A mod entry in the wiki list.</summary>
- public class WikiModEntry
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The mod's unique ID. If the mod has alternate/old IDs, they're listed in latest to newest order.</summary>
- public string[] ID { get; set; }
-
- /// <summary>The mod's display name. If the mod has multiple names, the first one is the most canonical name.</summary>
- public string[] Name { get; set; }
-
- /// <summary>The mod's author name. If the author has multiple names, the first one is the most canonical name.</summary>
- public string[] Author { get; set; }
-
- /// <summary>The mod ID on Nexus.</summary>
- public int? NexusID { get; set; }
-
- /// <summary>The mod ID in the Chucklefish mod repo.</summary>
- public int? ChucklefishID { get; set; }
-
- /// <summary>The mod ID in the ModDrop mod repo.</summary>
- public int? ModDropID { get; set; }
-
- /// <summary>The GitHub repository in the form 'owner/repo'.</summary>
- public string GitHubRepo { get; set; }
-
- /// <summary>The URL to a non-GitHub source repo.</summary>
- public string CustomSourceUrl { get; set; }
-
- /// <summary>The custom mod page URL (if applicable).</summary>
- public string CustomUrl { get; set; }
-
- /// <summary>The name of the mod which loads this content pack, if applicable.</summary>
- public string ContentPackFor { get; set; }
-
- /// <summary>The mod's compatibility with the latest stable version of the game.</summary>
- public WikiCompatibilityInfo Compatibility { get; set; }
-
- /// <summary>The mod's compatibility with the latest beta version of the game (if any).</summary>
- public WikiCompatibilityInfo BetaCompatibility { get; set; }
-
- /// <summary>Whether a Stardew Valley or SMAPI beta which affects mod compatibility is in progress. If this is true, <see cref="BetaCompatibility"/> should be used for beta versions of SMAPI instead of <see cref="Compatibility"/>.</summary>
- public bool HasBetaInfo => this.BetaCompatibility != null;
-
- /// <summary>The human-readable warnings for players about this mod.</summary>
- public string[] Warnings { get; set; }
-
- /// <summary>The link anchor for the mod entry in the wiki compatibility list.</summary>
- public string Anchor { get; set; }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModList.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModList.cs
deleted file mode 100644
index 0d614f28..00000000
--- a/src/StardewModdingAPI.Toolkit/Framework/Clients/Wiki/WikiModList.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
-{
- /// <summary>Metadata from the wiki's mod compatibility list.</summary>
- public class WikiModList
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The stable game version.</summary>
- public string StableVersion { get; set; }
-
- /// <summary>The beta game version (if any).</summary>
- public string BetaVersion { get; set; }
-
- /// <summary>The mods on the wiki.</summary>
- public WikiModEntry[] Mods { get; set; }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModData/MetadataModel.cs b/src/StardewModdingAPI.Toolkit/Framework/ModData/MetadataModel.cs
deleted file mode 100644
index ef6d4dd9..00000000
--- a/src/StardewModdingAPI.Toolkit/Framework/ModData/MetadataModel.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System.Collections.Generic;
-
-namespace StardewModdingAPI.Toolkit.Framework.ModData
-{
- /// <summary>The SMAPI predefined metadata.</summary>
- internal class MetadataModel
- {
- /********
- ** Accessors
- ********/
- /// <summary>Extra metadata about mods.</summary>
- public IDictionary<string, ModDataModel> ModData { get; set; }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataField.cs b/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataField.cs
deleted file mode 100644
index b3954693..00000000
--- a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataField.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-using System.Linq;
-
-namespace StardewModdingAPI.Toolkit.Framework.ModData
-{
- /// <summary>A versioned mod metadata field.</summary>
- public 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(p => !string.IsNullOrWhiteSpace(p));
-
- // non-manifest fields
- case ModDataFieldKey.AlternativeUrl:
- case ModDataFieldKey.StatusReasonPhrase:
- case ModDataFieldKey.Status:
- return false;
-
- default:
- return false;
- }
- }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataFieldKey.cs b/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataFieldKey.cs
deleted file mode 100644
index 09dd0cc5..00000000
--- a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataFieldKey.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-namespace StardewModdingAPI.Toolkit.Framework.ModData
-{
- /// <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/StardewModdingAPI.Toolkit/Framework/ModData/ModDataModel.cs b/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataModel.cs
deleted file mode 100644
index 18039762..00000000
--- a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataModel.cs
+++ /dev/null
@@ -1,127 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Runtime.Serialization;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-
-namespace StardewModdingAPI.Toolkit.Framework.ModData
-{
- /// <summary>The raw mod metadata from SMAPI's internal mod list.</summary>
- internal class ModDataModel
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The mod's current unique ID.</summary>
- public string ID { get; set; }
-
- /// <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. If the mod's ID changed over time, multiple variants can be separated by the
- /// <c>|</c> character.
- /// </remarks>
- public string FormerIDs { get; set; }
-
- /// <summary>Maps local versions to a semantic version for update checks.</summary>
- public IDictionary<string, string> MapLocalVersions { get; set; } = new Dictionary<string, string>();
-
- /// <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 mod warnings to suppress, even if they'd normally be shown.</summary>
- public ModWarning SuppressWarnings { get; set; }
-
- /// <summary>This field stores properties that aren't mapped to another field before they're parsed into <see cref="Fields"/>.</summary>
- [JsonExtensionData]
- public IDictionary<string, JToken> ExtensionData { get; set; }
-
- /// <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 a parsed representation of the <see cref="Fields"/>.</summary>
- public IEnumerable<ModDataField> GetFields()
- {
- 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 the former mod IDs.</summary>
- public IEnumerable<string> GetFormerIDs()
- {
- if (this.FormerIDs != null)
- {
- foreach (string id in this.FormerIDs.Split('|'))
- yield return id.Trim();
- }
- }
-
-
- /*********
- ** 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;
- }
- }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataRecord.cs b/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataRecord.cs
deleted file mode 100644
index 794ad2e4..00000000
--- a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataRecord.cs
+++ /dev/null
@@ -1,147 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace StardewModdingAPI.Toolkit.Framework.ModData
-{
- /// <summary>The parsed mod metadata from SMAPI's internal mod list.</summary>
- public class ModDataRecord
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The mod's default display name.</summary>
- public string DisplayName { get; }
-
- /// <summary>The mod's current unique ID.</summary>
- public string ID { get; }
-
- /// <summary>The former mod IDs (if any).</summary>
- public string[] FormerIDs { get; }
-
- /// <summary>The mod warnings to suppress, even if they'd normally be shown.</summary>
- public ModWarning SuppressWarnings { get; set; }
-
- /// <summary>Maps local versions to a semantic version for update checks.</summary>
- public IDictionary<string, string> MapLocalVersions { get; }
-
- /// <summary>Maps remote versions to a semantic version for update checks.</summary>
- public IDictionary<string, string> MapRemoteVersions { get; }
-
- /// <summary>The versioned field data.</summary>
- public ModDataField[] Fields { get; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="displayName">The mod's default display name.</param>
- /// <param name="model">The raw data model.</param>
- internal ModDataRecord(string displayName, ModDataModel model)
- {
- this.DisplayName = displayName;
- this.ID = model.ID;
- this.FormerIDs = model.GetFormerIDs().ToArray();
- this.SuppressWarnings = model.SuppressWarnings;
- this.MapLocalVersions = new Dictionary<string, string>(model.MapLocalVersions, StringComparer.InvariantCultureIgnoreCase);
- this.MapRemoteVersions = new Dictionary<string, string>(model.MapRemoteVersions, StringComparer.InvariantCultureIgnoreCase);
- this.Fields = model.GetFields().ToArray();
- }
-
- /// <summary>Get whether the mod has (or previously had) the given ID.</summary>
- /// <param name="id">The mod ID.</param>
- public bool HasID(string id)
- {
- // try main ID
- if (this.ID.Equals(id, StringComparison.InvariantCultureIgnoreCase))
- return true;
-
- // try former IDs
- foreach (string formerID in this.FormerIDs)
- {
- if (formerID.Equals(id, StringComparison.InvariantCultureIgnoreCase))
- return true;
- }
-
- return false;
- }
-
- /// <summary>Get a semantic local version for update checks.</summary>
- /// <param name="version">The remote version to normalise.</param>
- public ISemanticVersion GetLocalVersionForUpdateChecks(ISemanticVersion version)
- {
- return this.MapLocalVersions != null && this.MapLocalVersions.TryGetValue(version.ToString(), out string newVersion)
- ? new SemanticVersion(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)
- {
- // normalise version if possible
- if (SemanticVersion.TryParse(version, out ISemanticVersion parsed))
- version = parsed.ToString();
-
- // fetch remote version
- return this.MapRemoteVersions != null && this.MapRemoteVersions.TryGetValue(version, out string newVersion)
- ? newVersion
- : version;
- }
-
- /// <summary>Get the possible mod IDs.</summary>
- public IEnumerable<string> GetIDs()
- {
- return this.FormerIDs
- .Concat(new[] { this.ID })
- .Where(p => !string.IsNullOrWhiteSpace(p))
- .Select(p => p.Trim())
- .Distinct();
- }
-
- /// <summary>Get the default update key for this mod, if any.</summary>
- public string GetDefaultUpdateKey()
- {
- string updateKey = this.Fields.FirstOrDefault(p => p.Key == ModDataFieldKey.UpdateKey && p.IsDefault)?.Value;
- return !string.IsNullOrWhiteSpace(updateKey)
- ? updateKey
- : null;
- }
-
- /// <summary>Get a parsed representation of the <see cref="ModDataRecord.Fields"/> which match a given manifest.</summary>
- /// <param name="manifest">The manifest to match.</param>
- public ModDataRecordVersionedFields GetVersionedFields(IManifest manifest)
- {
- ModDataRecordVersionedFields parsed = new ModDataRecordVersionedFields { DisplayName = this.DisplayName, DataRecord = this };
- foreach (ModDataField field in this.Fields.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;
- }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataRecordVersionedFields.cs b/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataRecordVersionedFields.cs
deleted file mode 100644
index 237f2c66..00000000
--- a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDataRecordVersionedFields.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-namespace StardewModdingAPI.Toolkit.Framework.ModData
-{
- /// <summary>The versioned fields from a <see cref="ModDataRecord"/> for a specific manifest.</summary>
- public class ModDataRecordVersionedFields
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The underlying data record.</summary>
- public ModDataRecord DataRecord { get; set; }
-
- /// <summary>The default mod name to display when the name isn't available (e.g. during dependency checks).</summary>
- public string DisplayName { get; set; }
-
- /// <summary>The update key to apply.</summary>
- public string UpdateKey { 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 ISemanticVersion GetLocalVersionForUpdateChecks(ISemanticVersion 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 ISemanticVersion GetRemoteVersionForUpdateChecks(ISemanticVersion version)
- {
- if (version == null)
- return null;
-
- string rawVersion = this.DataRecord.GetRemoteVersionForUpdateChecks(version.ToString());
- return rawVersion != null
- ? new SemanticVersion(rawVersion)
- : version;
- }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDatabase.cs b/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDatabase.cs
deleted file mode 100644
index a9da884a..00000000
--- a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModDatabase.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace StardewModdingAPI.Toolkit.Framework.ModData
-{
- /// <summary>Handles access to SMAPI's internal mod metadata list.</summary>
- public class ModDatabase
- {
- /*********
- ** Fields
- *********/
- /// <summary>The underlying mod data records indexed by default display name.</summary>
- private readonly ModDataRecord[] Records;
-
- /// <summary>Get an update URL for an update key (if valid).</summary>
- private readonly Func<string, string> GetUpdateUrl;
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an empty instance.</summary>
- public ModDatabase()
- : this(new ModDataRecord[0], key => null) { }
-
- /// <summary>Construct an instance.</summary>
- /// <param name="records">The underlying mod data records indexed by default display name.</param>
- /// <param name="getUpdateUrl">Get an update URL for an update key (if valid).</param>
- public ModDatabase(IEnumerable<ModDataRecord> records, Func<string, string> getUpdateUrl)
- {
- this.Records = records.ToArray();
- this.GetUpdateUrl = getUpdateUrl;
- }
-
- /// <summary>Get all mod data records.</summary>
- public IEnumerable<ModDataRecord> GetAll()
- {
- return this.Records;
- }
-
- /// <summary>Get a mod data record.</summary>
- /// <param name="modID">The unique mod ID.</param>
- public ModDataRecord Get(string modID)
- {
- return !string.IsNullOrWhiteSpace(modID)
- ? this.Records.FirstOrDefault(p => p.HasID(modID))
- : null;
- }
-
- /// <summary>Get the mod page URL for a mod (if available).</summary>
- /// <param name="id">The unique mod ID.</param>
- public string GetModPageUrlFor(string id)
- {
- // get update key
- ModDataRecord record = this.Get(id);
- ModDataField updateKeyField = record?.Fields.FirstOrDefault(p => p.Key == ModDataFieldKey.UpdateKey);
- if (updateKeyField == null)
- return null;
-
- // get update URL
- return this.GetUpdateUrl(updateKeyField.Value);
- }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModStatus.cs b/src/StardewModdingAPI.Toolkit/Framework/ModData/ModStatus.cs
deleted file mode 100644
index 09da74bf..00000000
--- a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModStatus.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-namespace StardewModdingAPI.Toolkit.Framework.ModData
-{
- /// <summary>Indicates how SMAPI should treat a mod.</summary>
- public 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.Toolkit/Framework/ModData/ModWarning.cs b/src/StardewModdingAPI.Toolkit/Framework/ModData/ModWarning.cs
deleted file mode 100644
index d61c427f..00000000
--- a/src/StardewModdingAPI.Toolkit/Framework/ModData/ModWarning.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System;
-
-namespace StardewModdingAPI.Toolkit.Framework.ModData
-{
- /// <summary>Indicates a detected non-error mod issue.</summary>
- [Flags]
- public enum ModWarning
- {
- /// <summary>No issues detected.</summary>
- None = 0,
-
- /// <summary>SMAPI detected incompatible code in the mod, but was configured to load it anyway.</summary>
- BrokenCodeLoaded = 1,
-
- /// <summary>The mod affects the save serializer in a way that may make saves unloadable without the mod.</summary>
- ChangesSaveSerialiser = 2,
-
- /// <summary>The mod patches the game in a way that may impact stability.</summary>
- PatchesGame = 4,
-
- /// <summary>The mod uses the <c>dynamic</c> keyword which won't work on Linux/Mac.</summary>
- UsesDynamic = 8,
-
- /// <summary>The mod references specialised 'unvalided update tick' events which may impact stability.</summary>
- UsesUnvalidatedUpdateTick = 16,
-
- /// <summary>The mod has no update keys set.</summary>
- NoUpdateKeys = 32,
-
- /// <summary>Uses .NET APIs for filesystem access.</summary>
- AccessesFilesystem = 64,
-
- /// <summary>Uses .NET APIs for shell or process access.</summary>
- AccessesShell = 128
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModFolder.cs b/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModFolder.cs
deleted file mode 100644
index bb467b36..00000000
--- a/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModFolder.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using StardewModdingAPI.Toolkit.Serialisation.Models;
-using StardewModdingAPI.Toolkit.Utilities;
-
-namespace StardewModdingAPI.Toolkit.Framework.ModScanning
-{
- /// <summary>The info about a mod read from its folder.</summary>
- public class ModFolder
- {
- /*********
- ** Accessors
- *********/
- /// <summary>A suggested display name for the mod folder.</summary>
- public string DisplayName { get; }
-
- /// <summary>The folder containing the mod's manifest.json.</summary>
- public DirectoryInfo Directory { get; }
-
- /// <summary>The mod manifest.</summary>
- public Manifest Manifest { get; }
-
- /// <summary>The error which occurred parsing the manifest, if any.</summary>
- public string ManifestParseError { get; }
-
- /// <summary>Whether the mod should be loaded by default. This is <c>false</c> if it was found within a folder whose name starts with a dot.</summary>
- public bool ShouldBeLoaded { get; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="root">The root folder containing mods.</param>
- /// <param name="directory">The folder containing the mod's manifest.json.</param>
- /// <param name="manifest">The mod manifest.</param>
- /// <param name="manifestParseError">The error which occurred parsing the manifest, if any.</param>
- /// <param name="shouldBeLoaded">Whether the mod should be loaded by default. This should be <c>false</c> if it was found within a folder whose name starts with a dot.</param>
- public ModFolder(DirectoryInfo root, DirectoryInfo directory, Manifest manifest, string manifestParseError = null, bool shouldBeLoaded = true)
- {
- // save info
- this.Directory = directory;
- this.Manifest = manifest;
- this.ManifestParseError = manifestParseError;
- this.ShouldBeLoaded = shouldBeLoaded;
-
- // set display name
- this.DisplayName = manifest?.Name;
- if (string.IsNullOrWhiteSpace(this.DisplayName))
- this.DisplayName = PathUtilities.GetRelativePath(root.FullName, directory.FullName);
- }
-
- /// <summary>Get the update keys for a mod.</summary>
- /// <param name="manifest">The mod manifest.</param>
- public IEnumerable<string> GetUpdateKeys(Manifest manifest)
- {
- return
- (manifest.UpdateKeys ?? new string[0])
- .Where(p => !string.IsNullOrWhiteSpace(p))
- .ToArray();
- }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs b/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs
deleted file mode 100644
index 0ab73d56..00000000
--- a/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs
+++ /dev/null
@@ -1,183 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using StardewModdingAPI.Toolkit.Serialisation;
-using StardewModdingAPI.Toolkit.Serialisation.Models;
-
-namespace StardewModdingAPI.Toolkit.Framework.ModScanning
-{
- /// <summary>Scans folders for mod data.</summary>
- public class ModScanner
- {
- /*********
- ** Fields
- *********/
- /// <summary>The JSON helper with which to read manifests.</summary>
- private readonly JsonHelper JsonHelper;
-
- /// <summary>A list of filesystem entry names to ignore when checking whether a folder should be treated as a mod.</summary>
- private readonly HashSet<string> IgnoreFilesystemEntries = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase)
- {
- ".DS_Store",
- "mcs",
- "Thumbs.db"
- };
-
- /// <summary>The extensions for files which an XNB mod may contain. If a mod contains *only* these file extensions, it should be considered an XNB mod.</summary>
- private readonly HashSet<string> PotentialXnbModExtensions = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase)
- {
- ".md",
- ".png",
- ".txt",
- ".xnb"
- };
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="jsonHelper">The JSON helper with which to read manifests.</param>
- public ModScanner(JsonHelper jsonHelper)
- {
- this.JsonHelper = jsonHelper;
- }
-
- /// <summary>Extract information about all mods in the given folder.</summary>
- /// <param name="rootPath">The root folder containing mods.</param>
- public IEnumerable<ModFolder> GetModFolders(string rootPath)
- {
- DirectoryInfo root = new DirectoryInfo(rootPath);
- return this.GetModFolders(root, root);
- }
-
- /// <summary>Extract information from a mod folder.</summary>
- /// <param name="root">The root folder containing mods.</param>
- /// <param name="searchFolder">The folder to search for a mod.</param>
- public ModFolder ReadFolder(DirectoryInfo root, DirectoryInfo searchFolder)
- {
- // find manifest.json
- FileInfo manifestFile = this.FindManifest(searchFolder);
-
- // set appropriate invalid-mod error
- if (manifestFile == null)
- {
- FileInfo[] files = searchFolder.GetFiles("*", SearchOption.AllDirectories).Where(this.IsRelevant).ToArray();
- if (!files.Any())
- return new ModFolder(root, searchFolder, null, "it's an empty folder.");
- if (files.All(file => this.PotentialXnbModExtensions.Contains(file.Extension)))
- return new ModFolder(root, searchFolder, null, "it's not a SMAPI mod (see https://smapi.io/xnb for info).");
- return new ModFolder(root, searchFolder, null, "it contains files, but none of them are manifest.json.");
- }
-
- // read mod info
- Manifest manifest = null;
- string manifestError = null;
- {
- try
- {
- if (!this.JsonHelper.ReadJsonFileIfExists<Manifest>(manifestFile.FullName, out manifest) || manifest == null)
- manifestError = "its manifest is invalid.";
- }
- catch (SParseException ex)
- {
- manifestError = $"parsing its manifest failed: {ex.Message}";
- }
- catch (Exception ex)
- {
- manifestError = $"parsing its manifest failed:\n{ex}";
- }
- }
-
- // normalise display fields
- if (manifest != null)
- {
- manifest.Name = this.StripNewlines(manifest.Name);
- manifest.Description = this.StripNewlines(manifest.Description);
- manifest.Author = this.StripNewlines(manifest.Author);
- }
-
- return new ModFolder(root, manifestFile.Directory, manifest, manifestError);
- }
-
-
- /*********
- ** Private methods
- *********/
- /// <summary>Recursively extract information about all mods in the given folder.</summary>
- /// <param name="root">The root mod folder.</param>
- /// <param name="folder">The folder to search for mods.</param>
- public IEnumerable<ModFolder> GetModFolders(DirectoryInfo root, DirectoryInfo folder)
- {
- // skip
- if (folder.FullName != root.FullName && folder.Name.StartsWith("."))
- yield return new ModFolder(root, folder, null, "ignored folder because its name starts with a dot.", shouldBeLoaded: false);
-
- // recurse into subfolders
- else if (this.IsModSearchFolder(root, folder))
- {
- foreach (DirectoryInfo subfolder in folder.EnumerateDirectories())
- {
- foreach (ModFolder match in this.GetModFolders(root, subfolder))
- yield return match;
- }
- }
-
- // treat as mod folder
- else
- yield return this.ReadFolder(root, folder);
- }
-
- /// <summary>Find the manifest for a mod folder.</summary>
- /// <param name="folder">The folder to search.</param>
- private FileInfo FindManifest(DirectoryInfo folder)
- {
- while (true)
- {
- // check for manifest in current folder
- FileInfo file = new FileInfo(Path.Combine(folder.FullName, "manifest.json"));
- if (file.Exists)
- return file;
-
- // check for single subfolder
- FileSystemInfo[] entries = folder.EnumerateFileSystemInfos().Take(2).ToArray();
- if (entries.Length == 1 && entries[0] is DirectoryInfo subfolder)
- {
- folder = subfolder;
- continue;
- }
-
- // not found
- return null;
- }
- }
-
- /// <summary>Get whether a given folder should be treated as a search folder (i.e. look for subfolders containing mods).</summary>
- /// <param name="root">The root mod folder.</param>
- /// <param name="folder">The folder to search for mods.</param>
- private bool IsModSearchFolder(DirectoryInfo root, DirectoryInfo folder)
- {
- if (root.FullName == folder.FullName)
- return true;
-
- DirectoryInfo[] subfolders = folder.GetDirectories().Where(this.IsRelevant).ToArray();
- FileInfo[] files = folder.GetFiles().Where(this.IsRelevant).ToArray();
- return subfolders.Any() && !files.Any();
- }
-
- /// <summary>Get whether a file or folder is relevant when deciding how to process a mod folder.</summary>
- /// <param name="entry">The file or folder.</param>
- private bool IsRelevant(FileSystemInfo entry)
- {
- return !this.IgnoreFilesystemEntries.Contains(entry.Name);
- }
-
- /// <summary>Strip newlines from a string.</summary>
- /// <param name="input">The input to strip.</param>
- private string StripNewlines(string input)
- {
- return input?.Replace("\r", "").Replace("\n", "");
- }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Framework/UpdateData/ModRepositoryKey.cs b/src/StardewModdingAPI.Toolkit/Framework/UpdateData/ModRepositoryKey.cs
deleted file mode 100644
index f6c402d5..00000000
--- a/src/StardewModdingAPI.Toolkit/Framework/UpdateData/ModRepositoryKey.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-namespace StardewModdingAPI.Toolkit.Framework.UpdateData
-{
- /// <summary>A mod repository which SMAPI can check for updates.</summary>
- public enum ModRepositoryKey
- {
- /// <summary>An unknown or invalid mod repository.</summary>
- Unknown,
-
- /// <summary>The Chucklefish mod repository.</summary>
- Chucklefish,
-
- /// <summary>A GitHub project containing releases.</summary>
- GitHub,
-
- /// <summary>The ModDrop mod repository.</summary>
- ModDrop,
-
- /// <summary>The Nexus Mods mod repository.</summary>
- Nexus
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Framework/UpdateData/UpdateKey.cs b/src/StardewModdingAPI.Toolkit/Framework/UpdateData/UpdateKey.cs
deleted file mode 100644
index 865ebcf7..00000000
--- a/src/StardewModdingAPI.Toolkit/Framework/UpdateData/UpdateKey.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-using System;
-
-namespace StardewModdingAPI.Toolkit.Framework.UpdateData
-{
- /// <summary>A namespaced mod ID which uniquely identifies a mod within a mod repository.</summary>
- public class UpdateKey
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The raw update key text.</summary>
- public string RawText { get; }
-
- /// <summary>The mod repository containing the mod.</summary>
- public ModRepositoryKey Repository { get; }
-
- /// <summary>The mod ID within the repository.</summary>
- public string ID { get; }
-
- /// <summary>Whether the update key seems to be valid.</summary>
- public bool LooksValid { get; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="rawText">The raw update key text.</param>
- /// <param name="repository">The mod repository containing the mod.</param>
- /// <param name="id">The mod ID within the repository.</param>
- public UpdateKey(string rawText, ModRepositoryKey repository, string id)
- {
- this.RawText = rawText;
- this.Repository = repository;
- this.ID = id;
- this.LooksValid =
- repository != ModRepositoryKey.Unknown
- && !string.IsNullOrWhiteSpace(id);
- }
-
- /// <summary>Parse a raw update key.</summary>
- /// <param name="raw">The raw update key to parse.</param>
- public static UpdateKey Parse(string raw)
- {
- // split parts
- string[] parts = raw?.Split(':');
- if (parts == null || parts.Length != 2)
- return new UpdateKey(raw, ModRepositoryKey.Unknown, null);
-
- // extract parts
- string repositoryKey = parts[0].Trim();
- string id = parts[1].Trim();
- if (string.IsNullOrWhiteSpace(id))
- id = null;
-
- // parse
- if (!Enum.TryParse(repositoryKey, true, out ModRepositoryKey repository))
- return new UpdateKey(raw, ModRepositoryKey.Unknown, id);
- if (id == null)
- return new UpdateKey(raw, repository, null);
-
- return new UpdateKey(raw, repository, id);
- }
-
- /// <summary>Get a string that represents the current object.</summary>
- public override string ToString()
- {
- return this.LooksValid
- ? $"{this.Repository}:{this.ID}"
- : this.RawText;
- }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/ModToolkit.cs b/src/StardewModdingAPI.Toolkit/ModToolkit.cs
deleted file mode 100644
index 1b53e59e..00000000
--- a/src/StardewModdingAPI.Toolkit/ModToolkit.cs
+++ /dev/null
@@ -1,89 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Threading.Tasks;
-using Newtonsoft.Json;
-using StardewModdingAPI.Toolkit.Framework.Clients.Wiki;
-using StardewModdingAPI.Toolkit.Framework.ModData;
-using StardewModdingAPI.Toolkit.Framework.ModScanning;
-using StardewModdingAPI.Toolkit.Serialisation;
-
-namespace StardewModdingAPI.Toolkit
-{
- /// <summary>A convenience wrapper for the various tools.</summary>
- public class ModToolkit
- {
- /*********
- ** Fields
- *********/
- /// <summary>The default HTTP user agent for the toolkit.</summary>
- private readonly string UserAgent;
-
- /// <summary>Maps vendor keys (like <c>Nexus</c>) to their mod URL template (where <c>{0}</c> is the mod ID). This doesn't affect update checks, which defer to the remote web API.</summary>
- private readonly IDictionary<string, string> VendorModUrls = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase)
- {
- ["Chucklefish"] = "https://community.playstarbound.com/resources/{0}",
- ["GitHub"] = "https://github.com/{0}/releases",
- ["Nexus"] = "https://www.nexusmods.com/stardewvalley/mods/{0}"
- };
-
-
- /*********
- ** Accessors
- *********/
- /// <summary>Encapsulates SMAPI's JSON parsing.</summary>
- public JsonHelper JsonHelper { get; } = new JsonHelper();
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- public ModToolkit()
- {
- ISemanticVersion version = new SemanticVersion(this.GetType().Assembly.GetName().Version);
- this.UserAgent = $"SMAPI Mod Handler Toolkit/{version}";
- }
-
- /// <summary>Extract mod metadata from the wiki compatibility list.</summary>
- public async Task<WikiModList> GetWikiCompatibilityListAsync()
- {
- var client = new WikiClient(this.UserAgent);
- return await client.FetchModsAsync();
- }
-
- /// <summary>Get SMAPI's internal mod database.</summary>
- /// <param name="metadataPath">The file path for the SMAPI metadata file.</param>
- public ModDatabase GetModDatabase(string metadataPath)
- {
- MetadataModel metadata = JsonConvert.DeserializeObject<MetadataModel>(File.ReadAllText(metadataPath));
- ModDataRecord[] records = metadata.ModData.Select(pair => new ModDataRecord(pair.Key, pair.Value)).ToArray();
- return new ModDatabase(records, this.GetUpdateUrl);
- }
-
- /// <summary>Extract information about all mods in the given folder.</summary>
- /// <param name="rootPath">The root folder containing mods.</param>
- public IEnumerable<ModFolder> GetModFolders(string rootPath)
- {
- return new ModScanner(this.JsonHelper).GetModFolders(rootPath);
- }
-
- /// <summary>Get an update URL for an update key (if valid).</summary>
- /// <param name="updateKey">The update key.</param>
- public string GetUpdateUrl(string updateKey)
- {
- string[] parts = updateKey.Split(new[] { ':' }, 2);
- if (parts.Length != 2)
- return null;
-
- string vendorKey = parts[0].Trim();
- string modID = parts[1].Trim();
-
- if (this.VendorModUrls.TryGetValue(vendorKey, out string urlTemplate))
- return string.Format(urlTemplate, modID);
-
- return null;
- }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Properties/AssemblyInfo.cs b/src/StardewModdingAPI.Toolkit/Properties/AssemblyInfo.cs
deleted file mode 100644
index 1bb19e8c..00000000
--- a/src/StardewModdingAPI.Toolkit/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-
-[assembly: AssemblyTitle("SMAPI.Toolkit")]
-[assembly: AssemblyDescription("A library which encapsulates mod-handling logic for mod managers and tools. Not intended for use by mods.")]
-[assembly: InternalsVisibleTo("StardewModdingAPI")]
-[assembly: InternalsVisibleTo("StardewModdingAPI.Web")]
diff --git a/src/StardewModdingAPI.Toolkit/SemanticVersion.cs b/src/StardewModdingAPI.Toolkit/SemanticVersion.cs
deleted file mode 100644
index ba9ca6c6..00000000
--- a/src/StardewModdingAPI.Toolkit/SemanticVersion.cs
+++ /dev/null
@@ -1,312 +0,0 @@
-using System;
-using System.Text.RegularExpressions;
-
-namespace StardewModdingAPI.Toolkit
-{
- /// <summary>A semantic version with an optional release tag.</summary>
- /// <remarks>
- /// The implementation is defined by Semantic Version 2.0 (https://semver.org/), with a few deviations:
- /// - short-form "x.y" versions are supported (equivalent to "x.y.0");
- /// - hyphens are synonymous with dots in prerelease tags (like "-unofficial.3-pathoschild");
- /// - +build suffixes are not supported;
- /// - and "-unofficial" in prerelease tags is always lower-precedence (e.g. "1.0-beta" is newer than "1.0-unofficial").
- /// </remarks>
- public class SemanticVersion : ISemanticVersion
- {
- /*********
- ** Fields
- *********/
- /// <summary>A regex pattern matching a valid prerelease tag.</summary>
- internal const string TagPattern = @"(?>[a-z0-9]+[\-\.]?)+";
-
- /// <summary>A regex pattern matching a version within a larger string.</summary>
- internal const string UnboundedVersionPattern = @"(?>(?<major>0|[1-9]\d*))\.(?>(?<minor>0|[1-9]\d*))(?>(?:\.(?<patch>0|[1-9]\d*))?)(?:-(?<prerelease>" + SemanticVersion.TagPattern + "))?";
-
- /// <summary>A regular expression matching a semantic version string.</summary>
- /// <remarks>This pattern is derived from the BNF documentation in the <a href="https://github.com/mojombo/semver">semver repo</a>, with deviations to support the Stardew Valley mod conventions (see remarks on <see cref="SemanticVersion"/>).</remarks>
- internal static readonly Regex Regex = new Regex($@"^{SemanticVersion.UnboundedVersionPattern}$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ExplicitCapture);
-
-
- /*********
- ** Accessors
- *********/
- /// <summary>The major version incremented for major API changes.</summary>
- public int MajorVersion { get; }
-
- /// <summary>The minor version incremented for backwards-compatible changes.</summary>
- public int MinorVersion { get; }
-
- /// <summary>The patch version for backwards-compatible bug fixes.</summary>
- public int PatchVersion { get; }
-
- /// <summary>An optional prerelease tag.</summary>
- public string PrereleaseTag { get; }
-
-#if !SMAPI_3_0_STRICT
- /// <summary>An optional prerelease tag.</summary>
- [Obsolete("Use " + nameof(ISemanticVersion.PrereleaseTag) + " instead")]
- public string Build => this.PrereleaseTag;
-
- /// <summary>Whether the version was parsed from the legacy object format.</summary>
- public bool IsLegacyFormat { get; }
-#endif
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="major">The major version incremented for major API changes.</param>
- /// <param name="minor">The minor version incremented for backwards-compatible changes.</param>
- /// <param name="patch">The patch version for backwards-compatible fixes.</param>
- /// <param name="prereleaseTag">An optional prerelease tag.</param>
- /// <param name="isLegacyFormat">Whether the version was parsed from the legacy object format.</param>
- public SemanticVersion(int major, int minor, int patch, string prereleaseTag = null
-#if !SMAPI_3_0_STRICT
- , bool isLegacyFormat = false
-#endif
- )
- {
- this.MajorVersion = major;
- this.MinorVersion = minor;
- this.PatchVersion = patch;
- this.PrereleaseTag = this.GetNormalisedTag(prereleaseTag);
-#if !SMAPI_3_0_STRICT
- this.IsLegacyFormat = isLegacyFormat;
-#endif
-
- this.AssertValid();
- }
-
- /// <summary>Construct an instance.</summary>
- /// <param name="version">The assembly version.</param>
- /// <exception cref="ArgumentNullException">The <paramref name="version"/> is null.</exception>
- public SemanticVersion(Version version)
- {
- if (version == null)
- throw new ArgumentNullException(nameof(version), "The input version can't be null.");
-
- this.MajorVersion = version.Major;
- this.MinorVersion = version.Minor;
- this.PatchVersion = version.Build;
-
- this.AssertValid();
- }
-
- /// <summary>Construct an instance.</summary>
- /// <param name="version">The semantic version string.</param>
- /// <exception cref="ArgumentNullException">The <paramref name="version"/> is null.</exception>
- /// <exception cref="FormatException">The <paramref name="version"/> is not a valid semantic version.</exception>
- public SemanticVersion(string version)
- {
- // parse
- if (version == null)
- throw new ArgumentNullException(nameof(version), "The input version string can't be null.");
- var match = SemanticVersion.Regex.Match(version.Trim());
- if (!match.Success)
- throw new FormatException($"The input '{version}' isn't a valid semantic version.");
-
- // initialise
- this.MajorVersion = int.Parse(match.Groups["major"].Value);
- this.MinorVersion = match.Groups["minor"].Success ? int.Parse(match.Groups["minor"].Value) : 0;
- this.PatchVersion = match.Groups["patch"].Success ? int.Parse(match.Groups["patch"].Value) : 0;
- this.PrereleaseTag = match.Groups["prerelease"].Success ? this.GetNormalisedTag(match.Groups["prerelease"].Value) : null;
-
- this.AssertValid();
- }
-
- /// <summary>Get an integer indicating whether this version precedes (less than 0), supercedes (more than 0), or is equivalent to (0) the specified version.</summary>
- /// <param name="other">The version to compare with this instance.</param>
- /// <exception cref="ArgumentNullException">The <paramref name="other"/> value is null.</exception>
- public int CompareTo(ISemanticVersion other)
- {
- if (other == null)
- throw new ArgumentNullException(nameof(other));
- return this.CompareTo(other.MajorVersion, other.MinorVersion, other.PatchVersion, other.PrereleaseTag);
- }
-
- /// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
- /// <returns>true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false.</returns>
- /// <param name="other">An object to compare with this object.</param>
- public bool Equals(ISemanticVersion other)
- {
- return other != null && this.CompareTo(other) == 0;
- }
-
- /// <summary>Whether this is a pre-release version.</summary>
- public bool IsPrerelease()
- {
- return !string.IsNullOrWhiteSpace(this.PrereleaseTag);
- }
-
- /// <summary>Get whether this version is older than the specified version.</summary>
- /// <param name="other">The version to compare with this instance.</param>
- public bool IsOlderThan(ISemanticVersion other)
- {
- return this.CompareTo(other) < 0;
- }
-
- /// <summary>Get whether this version is older than the specified version.</summary>
- /// <param name="other">The version to compare with this instance.</param>
- /// <exception cref="FormatException">The specified version is not a valid semantic version.</exception>
- public bool IsOlderThan(string other)
- {
- return this.IsOlderThan(new SemanticVersion(other));
- }
-
- /// <summary>Get whether this version is newer than the specified version.</summary>
- /// <param name="other">The version to compare with this instance.</param>
- public bool IsNewerThan(ISemanticVersion other)
- {
- return this.CompareTo(other) > 0;
- }
-
- /// <summary>Get whether this version is newer than the specified version.</summary>
- /// <param name="other">The version to compare with this instance.</param>
- /// <exception cref="FormatException">The specified version is not a valid semantic version.</exception>
- public bool IsNewerThan(string other)
- {
- return this.IsNewerThan(new SemanticVersion(other));
- }
-
- /// <summary>Get whether this version is between two specified versions (inclusively).</summary>
- /// <param name="min">The minimum version.</param>
- /// <param name="max">The maximum version.</param>
- public bool IsBetween(ISemanticVersion min, ISemanticVersion max)
- {
- return this.CompareTo(min) >= 0 && this.CompareTo(max) <= 0;
- }
-
- /// <summary>Get whether this version is between two specified versions (inclusively).</summary>
- /// <param name="min">The minimum version.</param>
- /// <param name="max">The maximum version.</param>
- /// <exception cref="FormatException">One of the specified versions is not a valid semantic version.</exception>
- public bool IsBetween(string min, string max)
- {
- return this.IsBetween(new SemanticVersion(min), new SemanticVersion(max));
- }
-
- /// <summary>Get a string representation of the version.</summary>
- public override string ToString()
- {
- // version
- string result = this.PatchVersion != 0
- ? $"{this.MajorVersion}.{this.MinorVersion}.{this.PatchVersion}"
- : $"{this.MajorVersion}.{this.MinorVersion}";
-
- // tag
- string tag = this.PrereleaseTag;
- if (tag != null)
- result += $"-{tag}";
- return result;
- }
-
- /// <summary>Parse a version string without throwing an exception if it fails.</summary>
- /// <param name="version">The version string.</param>
- /// <param name="parsed">The parsed representation.</param>
- /// <returns>Returns whether parsing the version succeeded.</returns>
- public static bool TryParse(string version, out ISemanticVersion parsed)
- {
- try
- {
- parsed = new SemanticVersion(version);
- return true;
- }
- catch
- {
- parsed = null;
- return false;
- }
- }
-
-
- /*********
- ** Private methods
- *********/
- /// <summary>Get a normalised build tag.</summary>
- /// <param name="tag">The tag to normalise.</param>
- private string GetNormalisedTag(string tag)
- {
- tag = tag?.Trim();
- return !string.IsNullOrWhiteSpace(tag) ? tag : null;
- }
-
- /// <summary>Get an integer indicating whether this version precedes (less than 0), supercedes (more than 0), or is equivalent to (0) the specified version.</summary>
- /// <param name="otherMajor">The major version to compare with this instance.</param>
- /// <param name="otherMinor">The minor version to compare with this instance.</param>
- /// <param name="otherPatch">The patch version to compare with this instance.</param>
- /// <param name="otherTag">The prerelease tag to compare with this instance.</param>
- private int CompareTo(int otherMajor, int otherMinor, int otherPatch, string otherTag)
- {
- const int same = 0;
- const int curNewer = 1;
- const int curOlder = -1;
-
- // compare stable versions
- if (this.MajorVersion != otherMajor)
- return this.MajorVersion.CompareTo(otherMajor);
- if (this.MinorVersion != otherMinor)
- return this.MinorVersion.CompareTo(otherMinor);
- if (this.PatchVersion != otherPatch)
- return this.PatchVersion.CompareTo(otherPatch);
- if (this.PrereleaseTag == otherTag)
- return same;
-
- // stable supercedes pre-release
- bool curIsStable = string.IsNullOrWhiteSpace(this.PrereleaseTag);
- bool otherIsStable = string.IsNullOrWhiteSpace(otherTag);
- if (curIsStable)
- return curNewer;
- if (otherIsStable)
- return curOlder;
-
- // compare two pre-release tag values
- string[] curParts = this.PrereleaseTag.Split('.', '-');
- string[] otherParts = otherTag.Split('.', '-');
- for (int i = 0; i < curParts.Length; i++)
- {
- // longer prerelease tag supercedes if otherwise equal
- if (otherParts.Length <= i)
- return curNewer;
-
- // compare if different
- if (curParts[i] != otherParts[i])
- {
- // unofficial is always lower-precedence
- if (otherParts[i].Equals("unofficial", StringComparison.InvariantCultureIgnoreCase))
- return curNewer;
- if (curParts[i].Equals("unofficial", StringComparison.InvariantCultureIgnoreCase))
- return curOlder;
-
- // compare numerically if possible
- {
- if (int.TryParse(curParts[i], out int curNum) && int.TryParse(otherParts[i], out int otherNum))
- return curNum.CompareTo(otherNum);
- }
-
- // else compare lexically
- return string.Compare(curParts[i], otherParts[i], StringComparison.OrdinalIgnoreCase);
- }
- }
-
- // fallback (this should never happen)
- return string.Compare(this.ToString(), new SemanticVersion(otherMajor, otherMinor, otherPatch, otherTag).ToString(), StringComparison.InvariantCultureIgnoreCase);
- }
-
- /// <summary>Assert that the current version is valid.</summary>
- private void AssertValid()
- {
- if (this.MajorVersion < 0 || this.MinorVersion < 0 || this.PatchVersion < 0)
- throw new FormatException($"{this} isn't a valid semantic version. The major, minor, and patch numbers can't be negative.");
- if (this.MajorVersion == 0 && this.MinorVersion == 0 && this.PatchVersion == 0)
- throw new FormatException($"{this} isn't a valid semantic version. At least one of the major, minor, and patch numbers must be more than zero.");
- if (this.PrereleaseTag != null)
- {
- if (this.PrereleaseTag.Trim() == "")
- throw new FormatException($"{this} isn't a valid semantic version. The tag cannot be a blank string (but may be omitted).");
- if (!Regex.IsMatch(this.PrereleaseTag, $"^{SemanticVersion.TagPattern}$", RegexOptions.IgnoreCase))
- throw new FormatException($"{this} isn't a valid semantic version. The tag is invalid.");
- }
- }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/Converters/ManifestContentPackForConverter.cs b/src/StardewModdingAPI.Toolkit/Serialisation/Converters/ManifestContentPackForConverter.cs
deleted file mode 100644
index 232c22a7..00000000
--- a/src/StardewModdingAPI.Toolkit/Serialisation/Converters/ManifestContentPackForConverter.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-using System;
-using Newtonsoft.Json;
-using StardewModdingAPI.Toolkit.Serialisation.Models;
-
-namespace StardewModdingAPI.Toolkit.Serialisation.Converters
-{
- /// <summary>Handles deserialisation of <see cref="ManifestContentPackFor"/> arrays.</summary>
- public class ManifestContentPackForConverter : 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(ManifestContentPackFor[]);
- }
-
-
- /*********
- ** 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)
- {
- return serializer.Deserialize<ManifestContentPackFor>(reader);
- }
-
- /// <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/StardewModdingAPI.Toolkit/Serialisation/Converters/ManifestDependencyArrayConverter.cs b/src/StardewModdingAPI.Toolkit/Serialisation/Converters/ManifestDependencyArrayConverter.cs
deleted file mode 100644
index 0a304ee3..00000000
--- a/src/StardewModdingAPI.Toolkit/Serialisation/Converters/ManifestDependencyArrayConverter.cs
+++ /dev/null
@@ -1,60 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using StardewModdingAPI.Toolkit.Serialisation.Models;
-
-namespace StardewModdingAPI.Toolkit.Serialisation.Converters
-{
- /// <summary>Handles deserialisation of <see cref="ManifestDependency"/> arrays.</summary>
- internal class ManifestDependencyArrayConverter : 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(ManifestDependency[]);
- }
-
-
- /*********
- ** 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<ManifestDependency> result = new List<ManifestDependency>();
- foreach (JObject obj in JArray.Load(reader).Children<JObject>())
- {
- string uniqueID = obj.ValueIgnoreCase<string>(nameof(ManifestDependency.UniqueID));
- string minVersion = obj.ValueIgnoreCase<string>(nameof(ManifestDependency.MinimumVersion));
- bool required = obj.ValueIgnoreCase<bool?>(nameof(ManifestDependency.IsRequired)) ?? true;
- result.Add(new ManifestDependency(uniqueID, minVersion, required));
- }
- 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/StardewModdingAPI.Toolkit/Serialisation/Converters/SemanticVersionConverter.cs b/src/StardewModdingAPI.Toolkit/Serialisation/Converters/SemanticVersionConverter.cs
deleted file mode 100644
index aca06849..00000000
--- a/src/StardewModdingAPI.Toolkit/Serialisation/Converters/SemanticVersionConverter.cs
+++ /dev/null
@@ -1,98 +0,0 @@
-using System;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-
-namespace StardewModdingAPI.Toolkit.Serialisation.Converters
-{
- /// <summary>Handles deserialisation of <see cref="ISemanticVersion"/>.</summary>
- internal class SemanticVersionConverter : JsonConverter
- {
- /*********
- ** Accessors
- *********/
- /// <summary>Get whether this converter can read JSON.</summary>
- public override bool CanRead => true;
-
- /// <summary>Get whether this converter can write JSON.</summary>
- public override bool CanWrite => true;
-
-
- /*********
- ** 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 typeof(ISemanticVersion).IsAssignableFrom(objectType);
- }
-
- /// <summary>Reads 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)
- {
- string path = reader.Path;
- switch (reader.TokenType)
- {
- case JsonToken.StartObject:
- return this.ReadObject(JObject.Load(reader));
- case JsonToken.String:
- return this.ReadString(JToken.Load(reader).Value<string>(), path);
- default:
- throw new SParseException($"Can't parse {nameof(ISemanticVersion)} from {reader.TokenType} node (path: {reader.Path}).");
- }
- }
-
- /// <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)
- {
- writer.WriteValue(value?.ToString());
- }
-
-
- /*********
- ** Private methods
- *********/
- /// <summary>Read a JSON object.</summary>
- /// <param name="obj">The JSON object to read.</param>
- private ISemanticVersion ReadObject(JObject obj)
- {
- int major = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.MajorVersion));
- int minor = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.MinorVersion));
- int patch = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.PatchVersion));
- string prereleaseTag = obj.ValueIgnoreCase<string>(nameof(ISemanticVersion.PrereleaseTag));
-#if !SMAPI_3_0_STRICT
- if (string.IsNullOrWhiteSpace(prereleaseTag))
- {
- prereleaseTag = obj.ValueIgnoreCase<string>("Build");
- if (prereleaseTag == "0")
- prereleaseTag = null; // '0' from incorrect examples in old SMAPI documentation
- }
-#endif
-
- return new SemanticVersion(major, minor, patch, prereleaseTag
-#if !SMAPI_3_0_STRICT
- , isLegacyFormat: true
-#endif
- );
- }
-
- /// <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>
- private ISemanticVersion ReadString(string str, string path)
- {
- if (string.IsNullOrWhiteSpace(str))
- return null;
- if (!SemanticVersion.TryParse(str, out ISemanticVersion version))
- throw new SParseException($"Can't parse semantic version from invalid value '{str}', should be formatted like 1.2, 1.2.30, or 1.2.30-beta (path: {path}).");
- return version;
- }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/Converters/SimpleReadOnlyConverter.cs b/src/StardewModdingAPI.Toolkit/Serialisation/Converters/SimpleReadOnlyConverter.cs
deleted file mode 100644
index 5e0b0f4a..00000000
--- a/src/StardewModdingAPI.Toolkit/Serialisation/Converters/SimpleReadOnlyConverter.cs
+++ /dev/null
@@ -1,76 +0,0 @@
-using System;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-
-namespace StardewModdingAPI.Toolkit.Serialisation.Converters
-{
- /// <summary>The base implementation for simplified converters which deserialise <typeparamref name="T"/> without overriding serialisation.</summary>
- /// <typeparam name="T">The type to deserialise.</typeparam>
- internal abstract class SimpleReadOnlyConverter<T> : 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(T);
- }
-
- /// <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.");
- }
-
- /// <summary>Reads 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)
- {
- string path = reader.Path;
- switch (reader.TokenType)
- {
- case JsonToken.StartObject:
- return this.ReadObject(JObject.Load(reader), path);
- case JsonToken.String:
- return this.ReadString(JToken.Load(reader).Value<string>(), path);
- default:
- throw new SParseException($"Can't parse {typeof(T).Name} from {reader.TokenType} node (path: {reader.Path}).");
- }
- }
-
-
- /*********
- ** Protected methods
- *********/
- /// <summary>Read a JSON object.</summary>
- /// <param name="obj">The JSON object to read.</param>
- /// <param name="path">The path to the current JSON node.</param>
- protected virtual T ReadObject(JObject obj, string path)
- {
- throw new SParseException($"Can't parse {typeof(T).Name} from object node (path: {path}).");
- }
-
- /// <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 virtual T ReadString(string str, string path)
- {
- throw new SParseException($"Can't parse {typeof(T).Name} from string node (path: {path}).");
- }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/InternalExtensions.cs b/src/StardewModdingAPI.Toolkit/Serialisation/InternalExtensions.cs
deleted file mode 100644
index 12b2c933..00000000
--- a/src/StardewModdingAPI.Toolkit/Serialisation/InternalExtensions.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-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);
- }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/JsonHelper.cs b/src/StardewModdingAPI.Toolkit/Serialisation/JsonHelper.cs
deleted file mode 100644
index cf2ce0d1..00000000
--- a/src/StardewModdingAPI.Toolkit/Serialisation/JsonHelper.cs
+++ /dev/null
@@ -1,136 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Converters;
-using StardewModdingAPI.Toolkit.Serialisation.Converters;
-
-namespace StardewModdingAPI.Toolkit.Serialisation
-{
- /// <summary>Encapsulates SMAPI's JSON file parsing.</summary>
- public class JsonHelper
- {
- /*********
- ** Accessors
- *********/
- /// <summary>The JSON settings to use when serialising and deserialising files.</summary>
- public JsonSerializerSettings JsonSettings { get; } = new JsonSerializerSettings
- {
- Formatting = Formatting.Indented,
- ObjectCreationHandling = ObjectCreationHandling.Replace, // avoid issue where default ICollection<T> values are duplicated each time the config is loaded
- Converters = new List<JsonConverter>
- {
- new SemanticVersionConverter(),
- new StringEnumConverter()
- }
- };
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Read a JSON file.</summary>
- /// <typeparam name="TModel">The model type.</typeparam>
- /// <param name="fullPath">The absolete file path.</param>
- /// <param name="result">The parsed content model.</param>
- /// <returns>Returns false if the file doesn't exist, else true.</returns>
- /// <exception cref="ArgumentException">The given <paramref name="fullPath"/> is empty or invalid.</exception>
- /// <exception cref="JsonReaderException">The file contains invalid JSON.</exception>
- public bool ReadJsonFileIfExists<TModel>(string fullPath, out TModel result)
- {
- // validate
- if (string.IsNullOrWhiteSpace(fullPath))
- throw new ArgumentException("The file path is empty or invalid.", nameof(fullPath));
-
- // read file
- string json;
- try
- {
- json = File.ReadAllText(fullPath);
- }
- catch (Exception ex) when (ex is DirectoryNotFoundException || ex is FileNotFoundException)
- {
- result = default(TModel);
- return false;
- }
-
- // deserialise model
- try
- {
- result = this.Deserialise<TModel>(json);
- return true;
- }
- catch (Exception ex)
- {
- string error = $"Can't parse JSON file at {fullPath}.";
-
- if (ex is JsonReaderException)
- {
- error += " This doesn't seem to be valid JSON.";
- if (json.Contains("“") || json.Contains("”"))
- error += " Found curly quotes in the text; note that only straight quotes are allowed in JSON.";
- }
- error += $"\nTechnical details: {ex.Message}";
- throw new JsonReaderException(error);
- }
- }
-
- /// <summary>Save to a JSON file.</summary>
- /// <typeparam name="TModel">The model type.</typeparam>
- /// <param name="fullPath">The absolete file path.</param>
- /// <param name="model">The model to save.</param>
- /// <exception cref="InvalidOperationException">The given path is empty or invalid.</exception>
- public void WriteJsonFile<TModel>(string fullPath, TModel model)
- where TModel : class
- {
- // validate
- if (string.IsNullOrWhiteSpace(fullPath))
- throw new ArgumentException("The file path is empty or invalid.", nameof(fullPath));
-
- // create directory if needed
- string dir = Path.GetDirectoryName(fullPath);
- if (dir == null)
- throw new ArgumentException("The file path is invalid.", nameof(fullPath));
- if (!Directory.Exists(dir))
- Directory.CreateDirectory(dir);
-
- // write file
- string json = this.Serialise(model);
- File.WriteAllText(fullPath, json);
- }
-
- /// <summary>Deserialize JSON text if possible.</summary>
- /// <typeparam name="TModel">The model type.</typeparam>
- /// <param name="json">The raw JSON text.</param>
- public TModel Deserialise<TModel>(string json)
- {
- try
- {
- return JsonConvert.DeserializeObject<TModel>(json, this.JsonSettings);
- }
- catch (JsonReaderException)
- {
- // try replacing curly quotes
- if (json.Contains("“") || json.Contains("”"))
- {
- try
- {
- return JsonConvert.DeserializeObject<TModel>(json.Replace('“', '"').Replace('”', '"'), this.JsonSettings);
- }
- catch { /* rethrow original error */ }
- }
-
- throw;
- }
- }
-
- /// <summary>Serialize a model to JSON text.</summary>
- /// <typeparam name="TModel">The model type.</typeparam>
- /// <param name="model">The model to serialise.</param>
- /// <param name="formatting">The formatting to apply.</param>
- public string Serialise<TModel>(TModel model, Formatting formatting = Formatting.Indented)
- {
- return JsonConvert.SerializeObject(model, formatting, this.JsonSettings);
- }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/Models/Manifest.cs b/src/StardewModdingAPI.Toolkit/Serialisation/Models/Manifest.cs
deleted file mode 100644
index 6cb9496b..00000000
--- a/src/StardewModdingAPI.Toolkit/Serialisation/Models/Manifest.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-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 : 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>
- public ISemanticVersion Version { get; set; }
-
- /// <summary>The minimum SMAPI version required by this mod, if any.</summary>
- public ISemanticVersion 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 IManifestContentPackFor ContentPackFor { get; set; }
-
- /// <summary>The other mods that must be loaded before this mod.</summary>
- [JsonConverter(typeof(ManifestDependencyArrayConverter))]
- 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; }
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- public Manifest() { }
-
- /// <summary>Construct an instance for a transitional content pack.</summary>
- /// <param name="uniqueID">The unique mod ID.</param>
- /// <param name="name">The mod name.</param>
- /// <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="contentPackFor">The modID which will read this as a content pack.</param>
- public Manifest(string uniqueID, string name, string author, string description, ISemanticVersion version, string contentPackFor = null)
- {
- this.Name = name;
- this.Author = author;
- this.Description = description;
- this.Version = version;
- this.UniqueID = uniqueID;
- this.UpdateKeys = new string[0];
- this.ContentPackFor = new ManifestContentPackFor { UniqueID = contentPackFor };
- }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/Models/ManifestContentPackFor.cs b/src/StardewModdingAPI.Toolkit/Serialisation/Models/ManifestContentPackFor.cs
deleted file mode 100644
index d0e42216..00000000
--- a/src/StardewModdingAPI.Toolkit/Serialisation/Models/ManifestContentPackFor.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-namespace StardewModdingAPI.Toolkit.Serialisation.Models
-{
- /// <summary>Indicates which mod can read the content pack represented by the containing manifest.</summary>
- public class ManifestContentPackFor : IManifestContentPackFor
- {
- /*********
- ** 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 ISemanticVersion MinimumVersion { get; set; }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/Models/ManifestDependency.cs b/src/StardewModdingAPI.Toolkit/Serialisation/Models/ManifestDependency.cs
deleted file mode 100644
index 8db58d5d..00000000
--- a/src/StardewModdingAPI.Toolkit/Serialisation/Models/ManifestDependency.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-namespace StardewModdingAPI.Toolkit.Serialisation.Models
-{
- /// <summary>A mod dependency listed in a mod manifest.</summary>
- public 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.Toolkit/Serialisation/SParseException.cs b/src/StardewModdingAPI.Toolkit/Serialisation/SParseException.cs
deleted file mode 100644
index 61a7b305..00000000
--- a/src/StardewModdingAPI.Toolkit/Serialisation/SParseException.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-
-namespace StardewModdingAPI.Toolkit.Serialisation
-{
- /// <summary>A format exception which provides a user-facing error message.</summary>
- internal class SParseException : FormatException
- {
- /*********
- ** Public methods
- *********/
- /// <summary>Construct an instance.</summary>
- /// <param name="message">The error message.</param>
- /// <param name="ex">The underlying exception, if any.</param>
- public SParseException(string message, Exception ex = null)
- : base(message, ex) { }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/StardewModdingAPI.Toolkit.csproj b/src/StardewModdingAPI.Toolkit/StardewModdingAPI.Toolkit.csproj
deleted file mode 100644
index 351b36b6..00000000
--- a/src/StardewModdingAPI.Toolkit/StardewModdingAPI.Toolkit.csproj
+++ /dev/null
@@ -1,27 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
- <PropertyGroup>
- <TargetFrameworks>net4.5;netstandard2.0</TargetFrameworks>
- <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
- <OutputPath>..\..\bin\$(Configuration)\SMAPI.Toolkit</OutputPath>
- <DocumentationFile>..\..\bin\$(Configuration)\SMAPI.Toolkit\$(TargetFramework)\StardewModdingAPI.Toolkit.xml</DocumentationFile>
- <LangVersion>latest</LangVersion>
- </PropertyGroup>
-
- <ItemGroup>
- <Compile Include="..\..\build\GlobalAssemblyInfo.cs" Link="Properties\GlobalAssemblyInfo.cs" />
- </ItemGroup>
-
- <ItemGroup>
- <PackageReference Include="HtmlAgilityPack" Version="1.8.9" />
- <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
- <PackageReference Include="Pathoschild.Http.FluentClient" Version="3.2.0" />
- </ItemGroup>
-
- <ItemGroup>
- <ProjectReference Include="..\StardewModdingAPI.Toolkit.CoreInterfaces\StardewModdingAPI.Toolkit.CoreInterfaces.csproj" />
- </ItemGroup>
-
- <Import Project="..\..\build\common.targets" />
-
-</Project>
diff --git a/src/StardewModdingAPI.Toolkit/Utilities/FileUtilities.cs b/src/StardewModdingAPI.Toolkit/Utilities/FileUtilities.cs
deleted file mode 100644
index 7856fdb1..00000000
--- a/src/StardewModdingAPI.Toolkit/Utilities/FileUtilities.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using System.IO;
-using System.Threading;
-
-namespace StardewModdingAPI.Toolkit.Utilities
-{
- /// <summary>Provides utilities for dealing with files.</summary>
- public static class FileUtilities
- {
- /*********
- ** Public methods
- *********/
- /// <summary>Delete a file or folder regardless of file permissions, and block until deletion completes.</summary>
- /// <param name="entry">The file or folder to reset.</param>
- public static void ForceDelete(FileSystemInfo entry)
- {
- // ignore if already deleted
- entry.Refresh();
- if (!entry.Exists)
- return;
-
- // delete children
- if (entry is DirectoryInfo folder)
- {
- foreach (FileSystemInfo child in folder.GetFileSystemInfos())
- FileUtilities.ForceDelete(child);
- }
-
- // reset permissions & delete
- entry.Attributes = FileAttributes.Normal;
- entry.Delete();
-
- // wait for deletion to finish
- for (int i = 0; i < 10; i++)
- {
- entry.Refresh();
- if (entry.Exists)
- Thread.Sleep(500);
- }
-
- // throw exception if deletion didn't happen before timeout
- entry.Refresh();
- if (entry.Exists)
- throw new IOException($"Timed out trying to delete {entry.FullName}");
- }
- }
-}
diff --git a/src/StardewModdingAPI.Toolkit/Utilities/PathUtilities.cs b/src/StardewModdingAPI.Toolkit/Utilities/PathUtilities.cs
deleted file mode 100644
index 8a3c2b03..00000000
--- a/src/StardewModdingAPI.Toolkit/Utilities/PathUtilities.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-using System;
-using System.Diagnostics.Contracts;
-using System.IO;
-using System.Linq;
-using System.Text.RegularExpressions;
-
-namespace StardewModdingAPI.Toolkit.Utilities
-{
- /// <summary>Provides utilities for normalising file paths.</summary>
- public static class PathUtilities
- {
- /*********
- ** Fields
- *********/
- /// <summary>The possible directory separator characters in a file path.</summary>
- private static readonly char[] PossiblePathSeparators = new[] { '/', '\\', Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }.Distinct().ToArray();
-
- /// <summary>The preferred directory separator chaeacter in an asset key.</summary>
- private static readonly string PreferredPathSeparator = Path.DirectorySeparatorChar.ToString();
-
-
- /*********
- ** Public methods
- *********/
- /// <summary>Get the segments from a path (e.g. <c>/usr/bin/boop</c> => <c>usr</c>, <c>bin</c>, and <c>boop</c>).</summary>
- /// <param name="path">The path to split.</param>
- /// <param name="limit">The number of segments to match. Any additional segments will be merged into the last returned part.</param>
- public static string[] GetSegments(string path, int? limit = null)
- {
- return limit.HasValue
- ? path.Split(PathUtilities.PossiblePathSeparators, limit.Value, StringSplitOptions.RemoveEmptyEntries)
- : path.Split(PathUtilities.PossiblePathSeparators, StringSplitOptions.RemoveEmptyEntries);
- }
-
- /// <summary>Normalise path separators in a file path.</summary>
- /// <param name="path">The file path to normalise.</param>
- [Pure]
- public static string NormalisePathSeparators(string path)
- {
- string[] parts = PathUtilities.GetSegments(path);
- string normalised = string.Join(PathUtilities.PreferredPathSeparator, parts);
- if (path.StartsWith(PathUtilities.PreferredPathSeparator))
- normalised = PathUtilities.PreferredPathSeparator + normalised; // keep root slash
- return normalised;
- }
-
- /// <summary>Get a directory or file path relative to a given source path.</summary>
- /// <param name="sourceDir">The source folder path.</param>
- /// <param name="targetPath">The target folder or file path.</param>
- [Pure]
- public static string GetRelativePath(string sourceDir, string targetPath)
- {
- // convert to URIs
- Uri from = new Uri(sourceDir.TrimEnd(PathUtilities.PossiblePathSeparators) + "/");
- Uri to = new Uri(targetPath.TrimEnd(PathUtilities.PossiblePathSeparators) + "/");
- if (from.Scheme != to.Scheme)
- throw new InvalidOperationException($"Can't get path for '{targetPath}' relative to '{sourceDir}'.");
-
- // get relative path
- string relative = PathUtilities.NormalisePathSeparators(Uri.UnescapeDataString(from.MakeRelativeUri(to).ToString()));
- if (relative == "")
- relative = "./";
- return relative;
- }
-
- /// <summary>Get whether a path is relative and doesn't try to climb out of its containing folder (e.g. doesn't contain <c>../</c>).</summary>
- /// <param name="path">The path to check.</param>
- public static bool IsSafeRelativePath(string path)
- {
- if (string.IsNullOrWhiteSpace(path))
- return true;
-
- return
- !Path.IsPathRooted(path)
- && PathUtilities.GetSegments(path).All(segment => segment.Trim() != "..");
- }
-
- /// <summary>Get whether a string is a valid 'slug', containing only basic characters that are safe in all contexts (e.g. filenames, URLs, etc).</summary>
- /// <param name="str">The string to check.</param>
- public static bool IsSlug(string str)
- {
- return !Regex.IsMatch(str, "[^a-z0-9_.-]", RegexOptions.IgnoreCase);
- }
- }
-}