diff options
Diffstat (limited to 'src')
38 files changed, 563 insertions, 283 deletions
diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj b/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj index c1d5626f..e2be66d9 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj @@ -7,7 +7,7 @@ <ItemGroup> <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="2.10.0" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" /> <PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.16.1"> <PrivateAssets>all</PrivateAssets> diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json index 0d0e4901..971c591a 100644 --- a/src/SMAPI.Mods.ConsoleCommands/manifest.json +++ b/src/SMAPI.Mods.ConsoleCommands/manifest.json @@ -1,9 +1,9 @@ { "Name": "Console Commands", "Author": "SMAPI", - "Version": "3.2.0", + "Version": "3.3.0", "Description": "Adds SMAPI console commands that let you manipulate the game.", "UniqueID": "SMAPI.ConsoleCommands", "EntryDll": "ConsoleCommands.dll", - "MinimumApiVersion": "3.2.0" + "MinimumApiVersion": "3.3.0" } diff --git a/src/SMAPI.Mods.SaveBackup/ModEntry.cs b/src/SMAPI.Mods.SaveBackup/ModEntry.cs index 8b139d8f..b8d3be1c 100644 --- a/src/SMAPI.Mods.SaveBackup/ModEntry.cs +++ b/src/SMAPI.Mods.SaveBackup/ModEntry.cs @@ -66,29 +66,37 @@ namespace StardewModdingAPI.Mods.SaveBackup FileInfo targetFile = new FileInfo(Path.Combine(backupFolder.FullName, this.FileName)); DirectoryInfo fallbackDir = new DirectoryInfo(Path.Combine(backupFolder.FullName, this.BackupLabel)); if (targetFile.Exists || fallbackDir.Exists) + { + this.Monitor.Log("Already backed up today."); return; + } // copy saves to fallback directory (ignore non-save files/folders) - this.Monitor.Log($"Backing up saves to {fallbackDir.FullName}...", LogLevel.Trace); DirectoryInfo savesDir = new DirectoryInfo(Constants.SavesPath); - this.RecursiveCopy(savesDir, fallbackDir, entry => this.MatchSaveFolders(savesDir, entry), copyRoot: false); + if (!this.RecursiveCopy(savesDir, fallbackDir, entry => this.MatchSaveFolders(savesDir, entry), copyRoot: false)) + { + this.Monitor.Log("No saves found."); + return; + } // compress backup if possible - this.Monitor.Log("Compressing backup if possible...", LogLevel.Trace); if (!this.TryCompress(fallbackDir.FullName, targetFile, out Exception compressError)) { - if (Constants.TargetPlatform != GamePlatform.Android) // expected to fail on Android - this.Monitor.Log($"Couldn't compress backup, leaving it uncompressed.\n{compressError}", LogLevel.Trace); + this.Monitor.Log(Constants.TargetPlatform != GamePlatform.Android + ? $"Backed up to {fallbackDir.FullName}." // expected to fail on Android + : $"Backed up to {fallbackDir.FullName}. Couldn't compress backup:\n{compressError}" + ); } else + { + this.Monitor.Log($"Backed up to {targetFile.FullName}."); fallbackDir.Delete(recursive: true); - - this.Monitor.Log("Backup done!", LogLevel.Trace); + } } catch (Exception ex) { - this.Monitor.Log("Couldn't back up save files (see log file for details).", LogLevel.Warn); - this.Monitor.Log(ex.ToString(), LogLevel.Trace); + this.Monitor.Log("Couldn't back up saves (see log file for details).", LogLevel.Warn); + this.Monitor.Log(ex.ToString()); } } @@ -108,7 +116,7 @@ namespace StardewModdingAPI.Mods.SaveBackup { try { - this.Monitor.Log($"Deleting {entry.Name}...", LogLevel.Trace); + this.Monitor.Log($"Deleting {entry.Name}..."); if (entry is DirectoryInfo folder) folder.Delete(recursive: true); else @@ -123,7 +131,7 @@ namespace StardewModdingAPI.Mods.SaveBackup catch (Exception ex) { this.Monitor.Log("Couldn't remove old backups (see log file for details).", LogLevel.Warn); - this.Monitor.Log(ex.ToString(), LogLevel.Trace); + this.Monitor.Log(ex.ToString()); } } @@ -199,29 +207,33 @@ namespace StardewModdingAPI.Mods.SaveBackup /// <param name="copyRoot">Whether to copy the root folder itself, or <c>false</c> to only copy its contents.</param> /// <param name="filter">A filter which matches the files or directories to copy, or <c>null</c> to copy everything.</param> /// <remarks>Derived from the SMAPI installer code.</remarks> - private void RecursiveCopy(FileSystemInfo source, DirectoryInfo targetFolder, Func<FileSystemInfo, bool> filter, bool copyRoot = true) + /// <returns>Returns whether any files were copied.</returns> + private bool RecursiveCopy(FileSystemInfo source, DirectoryInfo targetFolder, Func<FileSystemInfo, bool> filter, bool copyRoot = true) { - if (!targetFolder.Exists) - targetFolder.Create(); + if (!source.Exists || filter?.Invoke(source) == false) + return false; - if (filter?.Invoke(source) == false) - return; + bool anyCopied = false; switch (source) { case FileInfo sourceFile: + targetFolder.Create(); sourceFile.CopyTo(Path.Combine(targetFolder.FullName, sourceFile.Name)); + anyCopied = true; break; case DirectoryInfo sourceDir: DirectoryInfo targetSubfolder = copyRoot ? new DirectoryInfo(Path.Combine(targetFolder.FullName, sourceDir.Name)) : targetFolder; foreach (var entry in sourceDir.EnumerateFileSystemInfos()) - this.RecursiveCopy(entry, targetSubfolder, filter); + anyCopied = this.RecursiveCopy(entry, targetSubfolder, filter) || anyCopied; break; default: throw new NotSupportedException($"Unknown filesystem info type '{source.GetType().FullName}'."); } + + return anyCopied; } /// <summary>A copy filter which matches save folders.</summary> diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json index 74256013..4559d1b0 100644 --- a/src/SMAPI.Mods.SaveBackup/manifest.json +++ b/src/SMAPI.Mods.SaveBackup/manifest.json @@ -1,9 +1,9 @@ { "Name": "Save Backup", "Author": "SMAPI", - "Version": "3.2.0", + "Version": "3.3.0", "Description": "Automatically backs up all your saves once per day into its folder.", "UniqueID": "SMAPI.SaveBackup", "EntryDll": "SaveBackup.dll", - "MinimumApiVersion": "3.2.0" + "MinimumApiVersion": "3.3.0" } diff --git a/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModEntryVersionModel.cs b/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModEntryVersionModel.cs index dadb8c10..188db31d 100644 --- a/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModEntryVersionModel.cs +++ b/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModEntryVersionModel.cs @@ -1,3 +1,6 @@ +using Newtonsoft.Json; +using StardewModdingAPI.Toolkit.Serialization.Converters; + namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi { /// <summary>Metadata about a version.</summary> @@ -7,6 +10,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi ** Accessors *********/ /// <summary>The version number.</summary> + [JsonConverter(typeof(NonStandardSemanticVersionConverter))] public ISemanticVersion Version { get; set; } /// <summary>The mod page URL.</summary> diff --git a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj index 16a97dbf..a7de7166 100644 --- a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj +++ b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj @@ -12,7 +12,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="HtmlAgilityPack" Version="1.11.18" /> + <PackageReference Include="HtmlAgilityPack" Version="1.11.20" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Pathoschild.Http.FluentClient" Version="3.3.1" /> <PackageReference Include="System.Management" Version="4.5.0" Condition="'$(OS)' == 'Windows_NT'" /> diff --git a/src/SMAPI.Toolkit/SemanticVersion.cs b/src/SMAPI.Toolkit/SemanticVersion.cs index 5ead6dc8..86db2820 100644 --- a/src/SMAPI.Toolkit/SemanticVersion.cs +++ b/src/SMAPI.Toolkit/SemanticVersion.cs @@ -199,18 +199,19 @@ namespace StardewModdingAPI.Toolkit /// <returns>Returns whether parsing the version succeeded.</returns> public static bool TryParse(string version, out ISemanticVersion parsed) { - return SemanticVersion.TryParseNonStandard(version, out parsed) && !parsed.IsNonStandard(); + return SemanticVersion.TryParse(version, allowNonStandard: false, out parsed); } - /// <summary>Parse a version string without throwing an exception if it fails, including support for non-standard extensions like <see cref="IPlatformSpecificVersion"/>.</summary> + /// <summary>Parse a version string without throwing an exception if it fails.</summary> /// <param name="version">The version string.</param> + /// <param name="allowNonStandard">Whether to allow non-standard extensions to semantic versioning.</param> /// <param name="parsed">The parsed representation.</param> /// <returns>Returns whether parsing the version succeeded.</returns> - public static bool TryParseNonStandard(string version, out ISemanticVersion parsed) + public static bool TryParse(string version, bool allowNonStandard, out ISemanticVersion parsed) { try { - parsed = new SemanticVersion(version, true); + parsed = new SemanticVersion(version, allowNonStandard); return true; } catch diff --git a/src/SMAPI.Toolkit/Serialization/Converters/NonStandardSemanticVersionConverter.cs b/src/SMAPI.Toolkit/Serialization/Converters/NonStandardSemanticVersionConverter.cs new file mode 100644 index 00000000..6f870bcf --- /dev/null +++ b/src/SMAPI.Toolkit/Serialization/Converters/NonStandardSemanticVersionConverter.cs @@ -0,0 +1,15 @@ +namespace StardewModdingAPI.Toolkit.Serialization.Converters +{ + /// <summary>Handles deserialization of <see cref="ISemanticVersion"/>, allowing for non-standard extensions.</summary> + internal class NonStandardSemanticVersionConverter : SemanticVersionConverter + { + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + public NonStandardSemanticVersionConverter() + { + this.AllowNonStandard = true; + } + } +} diff --git a/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs b/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs index e1b9db1d..3604956b 100644 --- a/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs +++ b/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs @@ -8,6 +8,13 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters internal class SemanticVersionConverter : JsonConverter { /********* + ** Fields + *********/ + /// <summary>Whether to allow non-standard extensions to semantic versioning.</summary> + protected bool AllowNonStandard { get; set; } + + + /********* ** Accessors *********/ /// <summary>Get whether this converter can read JSON.</summary> @@ -78,7 +85,7 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters { if (string.IsNullOrWhiteSpace(str)) return null; - if (!SemanticVersion.TryParse(str, out ISemanticVersion version)) + if (!SemanticVersion.TryParse(str, allowNonStandard: this.AllowNonStandard, out ISemanticVersion version)) throw new SParseException($"Can't parse semantic version from invalid value '{str}', should be formatted like 1.2, 1.2.30, or 1.2.30-beta (path: {path})."); return version; } diff --git a/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs b/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs index 2a01fe4b..c45448f3 100644 --- a/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs +++ b/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs @@ -53,7 +53,19 @@ namespace StardewModdingAPI.Toolkit.Utilities } catch { } #endif - return (platform == Platform.Mac ? "MacOS " : "") + Environment.OSVersion; + + string name = Environment.OSVersion.ToString(); + switch (platform) + { + case Platform.Android: + name = $"Android {name}"; + break; + + case Platform.Mac: + name = $"MacOS {name}"; + break; + } + return name; } /// <summary>Get the name of the Stardew Valley executable.</summary> diff --git a/src/SMAPI.Web/Controllers/ModsApiController.cs b/src/SMAPI.Web/Controllers/ModsApiController.cs index f194b4d0..06768f03 100644 --- a/src/SMAPI.Web/Controllers/ModsApiController.cs +++ b/src/SMAPI.Web/Controllers/ModsApiController.cs @@ -41,11 +41,8 @@ namespace StardewModdingAPI.Web.Controllers /// <summary>The cache in which to store mod data.</summary> private readonly IModCacheRepository ModCache; - /// <summary>The number of minutes successful update checks should be cached before refetching them.</summary> - private readonly int SuccessCacheMinutes; - - /// <summary>The number of minutes failed update checks should be cached before refetching them.</summary> - private readonly int ErrorCacheMinutes; + /// <summary>The config settings for mod update checks.</summary> + private readonly IOptions<ModUpdateCheckConfig> Config; /// <summary>The internal mod metadata list.</summary> private readonly ModDatabase ModDatabase; @@ -58,21 +55,19 @@ namespace StardewModdingAPI.Web.Controllers /// <param name="environment">The web hosting environment.</param> /// <param name="wikiCache">The cache in which to store wiki data.</param> /// <param name="modCache">The cache in which to store mod metadata.</param> - /// <param name="configProvider">The config settings for mod update checks.</param> + /// <param name="config">The config settings for mod update checks.</param> /// <param name="chucklefish">The Chucklefish API client.</param> /// <param name="curseForge">The CurseForge API client.</param> /// <param name="github">The GitHub API client.</param> /// <param name="modDrop">The ModDrop API client.</param> /// <param name="nexus">The Nexus API client.</param> - public ModsApiController(IHostingEnvironment environment, IWikiCacheRepository wikiCache, IModCacheRepository modCache, IOptions<ModUpdateCheckConfig> configProvider, IChucklefishClient chucklefish, ICurseForgeClient curseForge, IGitHubClient github, IModDropClient modDrop, INexusClient nexus) + public ModsApiController(IHostingEnvironment environment, IWikiCacheRepository wikiCache, IModCacheRepository modCache, IOptions<ModUpdateCheckConfig> config, IChucklefishClient chucklefish, ICurseForgeClient curseForge, IGitHubClient github, IModDropClient modDrop, INexusClient nexus) { this.ModDatabase = new ModToolkit().GetModDatabase(Path.Combine(environment.WebRootPath, "SMAPI.metadata.json")); - ModUpdateCheckConfig config = configProvider.Value; this.WikiCache = wikiCache; this.ModCache = modCache; - this.SuccessCacheMinutes = config.SuccessCacheMinutes; - this.ErrorCacheMinutes = config.ErrorCacheMinutes; + this.Config = config; this.Repositories = new IModRepository[] { @@ -133,6 +128,8 @@ namespace StardewModdingAPI.Web.Controllers ModDataRecord record = this.ModDatabase.Get(search.ID); WikiModEntry wikiEntry = wikiData.FirstOrDefault(entry => entry.ID.Contains(search.ID.Trim(), StringComparer.InvariantCultureIgnoreCase)); UpdateKey[] updateKeys = this.GetUpdateKeys(search.UpdateKeys, record, wikiEntry).ToArray(); + ModOverrideConfig overrides = this.Config.Value.ModOverrides.FirstOrDefault(p => p.ID.Equals(search.ID?.Trim(), StringComparison.InvariantCultureIgnoreCase)); + bool allowNonStandardVersions = overrides?.AllowNonStandardVersions ?? false; // get latest versions ModEntryModel result = new ModEntryModel { ID = search.ID }; @@ -151,7 +148,7 @@ namespace StardewModdingAPI.Web.Controllers } // fetch data - ModInfoModel data = await this.GetInfoForUpdateKeyAsync(updateKey); + ModInfoModel data = await this.GetInfoForUpdateKeyAsync(updateKey, allowNonStandardVersions); if (data.Error != null) { errors.Add(data.Error); @@ -161,7 +158,7 @@ namespace StardewModdingAPI.Web.Controllers // handle main version if (data.Version != null) { - ISemanticVersion version = this.GetMappedVersion(data.Version, wikiEntry?.MapRemoteVersions); + ISemanticVersion version = this.GetMappedVersion(data.Version, wikiEntry?.MapRemoteVersions, allowNonStandardVersions); if (version == null) { errors.Add($"The update key '{updateKey}' matches a mod with invalid semantic version '{data.Version}'."); @@ -175,7 +172,7 @@ namespace StardewModdingAPI.Web.Controllers // handle optional version if (data.PreviewVersion != null) { - ISemanticVersion version = this.GetMappedVersion(data.PreviewVersion, wikiEntry?.MapRemoteVersions); + ISemanticVersion version = this.GetMappedVersion(data.PreviewVersion, wikiEntry?.MapRemoteVersions, allowNonStandardVersions); if (version == null) { errors.Add($"The update key '{updateKey}' matches a mod with invalid optional semantic version '{data.PreviewVersion}'."); @@ -215,16 +212,16 @@ namespace StardewModdingAPI.Web.Controllers } // special cases - if (result.ID == "Pathoschild.SMAPI") + if (overrides?.SetUrl != null) { if (main != null) - main.Url = "https://smapi.io/"; + main.Url = overrides.SetUrl; if (optional != null) - optional.Url = "https://smapi.io/"; + optional.Url = overrides.SetUrl; } // get recommended update (if any) - ISemanticVersion installedVersion = this.GetMappedVersion(search.InstalledVersion?.ToString(), wikiEntry?.MapLocalVersions); + ISemanticVersion installedVersion = this.GetMappedVersion(search.InstalledVersion?.ToString(), wikiEntry?.MapLocalVersions, allowNonStandard: allowNonStandardVersions); if (apiVersion != null && installedVersion != null) { // get newer versions @@ -283,10 +280,11 @@ namespace StardewModdingAPI.Web.Controllers /// <summary>Get the mod info for an update key.</summary> /// <param name="updateKey">The namespaced update key.</param> - private async Task<ModInfoModel> GetInfoForUpdateKeyAsync(UpdateKey updateKey) + /// <param name="allowNonStandardVersions">Whether to allow non-standard versions.</param> + private async Task<ModInfoModel> GetInfoForUpdateKeyAsync(UpdateKey updateKey, bool allowNonStandardVersions) { // get mod - if (!this.ModCache.TryGetMod(updateKey.Repository, updateKey.ID, out CachedMod mod) || this.ModCache.IsStale(mod.LastUpdated, mod.FetchStatus == RemoteModStatus.TemporaryError ? this.ErrorCacheMinutes : this.SuccessCacheMinutes)) + if (!this.ModCache.TryGetMod(updateKey.Repository, updateKey.ID, out CachedMod mod) || this.ModCache.IsStale(mod.LastUpdated, mod.FetchStatus == RemoteModStatus.TemporaryError ? this.Config.Value.ErrorCacheMinutes : this.Config.Value.SuccessCacheMinutes)) { // get site if (!this.Repositories.TryGetValue(updateKey.Repository, out IModRepository repository)) @@ -298,7 +296,7 @@ namespace StardewModdingAPI.Web.Controllers { if (result.Version == null) result.SetError(RemoteModStatus.InvalidData, $"The update key '{updateKey}' matches a mod with no version number."); - else if (!SemanticVersion.TryParse(result.Version, out _)) + else if (!SemanticVersion.TryParse(result.Version, allowNonStandardVersions, out _)) result.SetError(RemoteModStatus.InvalidData, $"The update key '{updateKey}' matches a mod with invalid semantic version '{result.Version}'."); } @@ -357,15 +355,16 @@ namespace StardewModdingAPI.Web.Controllers /// <summary>Get a semantic local version for update checks.</summary> /// <param name="version">The version to parse.</param> /// <param name="map">A map of version replacements.</param> - private ISemanticVersion GetMappedVersion(string version, IDictionary<string, string> map) + /// <param name="allowNonStandard">Whether to allow non-standard versions.</param> + private ISemanticVersion GetMappedVersion(string version, IDictionary<string, string> map, bool allowNonStandard) { // try mapped version - string rawNewVersion = this.GetRawMappedVersion(version, map); - if (SemanticVersion.TryParse(rawNewVersion, out ISemanticVersion parsedNew)) + string rawNewVersion = this.GetRawMappedVersion(version, map, allowNonStandard); + if (SemanticVersion.TryParse(rawNewVersion, allowNonStandard, out ISemanticVersion parsedNew)) return parsedNew; // return original version - return SemanticVersion.TryParse(version, out ISemanticVersion parsedOld) + return SemanticVersion.TryParse(version, allowNonStandard, out ISemanticVersion parsedOld) ? parsedOld : null; } @@ -373,7 +372,8 @@ namespace StardewModdingAPI.Web.Controllers /// <summary>Get a semantic local version for update checks.</summary> /// <param name="version">The version to map.</param> /// <param name="map">A map of version replacements.</param> - private string GetRawMappedVersion(string version, IDictionary<string, string> map) + /// <param name="allowNonStandard">Whether to allow non-standard versions.</param> + private string GetRawMappedVersion(string version, IDictionary<string, string> map, bool allowNonStandard) { if (version == null || map == null || !map.Any()) return version; @@ -383,14 +383,14 @@ namespace StardewModdingAPI.Web.Controllers return map[version]; // match parsed version - if (SemanticVersion.TryParse(version, out ISemanticVersion parsed)) + if (SemanticVersion.TryParse(version, allowNonStandard, out ISemanticVersion parsed)) { if (map.ContainsKey(parsed.ToString())) return map[parsed.ToString()]; foreach (var pair in map) { - if (SemanticVersion.TryParse(pair.Key, out ISemanticVersion target) && parsed.Equals(target) && SemanticVersion.TryParse(pair.Value, out ISemanticVersion newVersion)) + if (SemanticVersion.TryParse(pair.Key, allowNonStandard, out ISemanticVersion target) && parsed.Equals(target) && SemanticVersion.TryParse(pair.Value, allowNonStandard, out ISemanticVersion newVersion)) return newVersion.ToString(); } } diff --git a/src/SMAPI.Web/Framework/ConfigModels/ModOverrideConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/ModOverrideConfig.cs new file mode 100644 index 00000000..f382d7b5 --- /dev/null +++ b/src/SMAPI.Web/Framework/ConfigModels/ModOverrideConfig.cs @@ -0,0 +1,15 @@ +namespace StardewModdingAPI.Web.Framework.ConfigModels +{ + /// <summary>Override update-check metadata for a mod.</summary> + internal class ModOverrideConfig + { + /// <summary>The unique ID from the mod's manifest.</summary> + public string ID { get; set; } + + /// <summary>Whether to allow non-standard versions.</summary> + public bool AllowNonStandardVersions { get; set; } + + /// <summary>The mod page URL to use regardless of which site has the update, or <c>null</c> to use the site URL.</summary> + public string SetUrl { get; set; } + } +} diff --git a/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs index 46073eb8..bd58dba0 100644 --- a/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs +++ b/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs @@ -11,5 +11,8 @@ namespace StardewModdingAPI.Web.Framework.ConfigModels /// <summary>The number of minutes failed update checks should be cached before refetching them.</summary> public int ErrorCacheMinutes { get; set; } + + /// <summary>Update-check metadata to override.</summary> + public ModOverrideConfig[] ModOverrides { get; set; } } } diff --git a/src/SMAPI.Web/Framework/VersionConstraint.cs b/src/SMAPI.Web/Framework/VersionConstraint.cs index 72f5ef84..f0c57c41 100644 --- a/src/SMAPI.Web/Framework/VersionConstraint.cs +++ b/src/SMAPI.Web/Framework/VersionConstraint.cs @@ -28,7 +28,7 @@ namespace StardewModdingAPI.Web.Framework return values.TryGetValue(routeKey, out object routeValue) && routeValue is string routeStr - && SemanticVersion.TryParseNonStandard(routeStr, out _); + && SemanticVersion.TryParse(routeStr, allowNonStandard: true, out _); } } } diff --git a/src/SMAPI.Web/SMAPI.Web.csproj b/src/SMAPI.Web/SMAPI.Web.csproj index 148631a9..97bea0fb 100644 --- a/src/SMAPI.Web/SMAPI.Web.csproj +++ b/src/SMAPI.Web/SMAPI.Web.csproj @@ -12,11 +12,11 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="Azure.Storage.Blobs" Version="12.2.0" /> + <PackageReference Include="Azure.Storage.Blobs" Version="12.3.0" /> <PackageReference Include="Hangfire.AspNetCore" Version="1.7.9" /> <PackageReference Include="Hangfire.MemoryStorage" Version="1.6.3" /> <PackageReference Include="Hangfire.Mongo" Version="0.6.6" /> - <PackageReference Include="HtmlAgilityPack" Version="1.11.18" /> + <Pac |
