summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build/common.targets6
-rw-r--r--build/prepare-install-package.targets2
-rw-r--r--docs/release-notes.md50
-rw-r--r--src/SMAPI.Installer/SMAPI.Installer.csproj2
-rw-r--r--src/SMAPI.Internal/ExceptionHelper.cs (renamed from src/SMAPI.Internal/ExceptionExtensions.cs)36
-rw-r--r--src/SMAPI.Internal/SMAPI.Internal.projitems2
-rw-r--r--src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj2
-rw-r--r--src/SMAPI.Mods.ConsoleCommands/SMAPI.Mods.ConsoleCommands.csproj2
-rw-r--r--src/SMAPI.Mods.ConsoleCommands/manifest.json4
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatcher.cs33
-rw-r--r--src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj2
-rw-r--r--src/SMAPI.Mods.ErrorHandler/manifest.json4
-rw-r--r--src/SMAPI.Mods.SaveBackup/SMAPI.Mods.SaveBackup.csproj2
-rw-r--r--src/SMAPI.Mods.SaveBackup/manifest.json4
-rw-r--r--src/SMAPI.Tests/SMAPI.Tests.csproj2
-rw-r--r--src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs139
-rw-r--r--src/SMAPI.Toolkit.CoreInterfaces/SMAPI.Toolkit.CoreInterfaces.csproj2
-rw-r--r--src/SMAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs16
-rw-r--r--src/SMAPI.Toolkit/Framework/Clients/Wiki/ChangeDescriptor.cs193
-rw-r--r--src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs105
-rw-r--r--src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiDataOverrideEntry.cs29
-rw-r--r--src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs13
-rw-r--r--src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs10
-rw-r--r--src/SMAPI.Toolkit/SMAPI.Toolkit.csproj2
-rw-r--r--src/SMAPI.Web/Controllers/ModsApiController.cs30
-rw-r--r--src/SMAPI.Web/Framework/ModSiteManager.cs39
-rw-r--r--src/SMAPI.Web/Startup.cs12
-rw-r--r--src/SMAPI.Web/Views/Mods/Index.cshtml12
-rw-r--r--src/SMAPI.Web/wwwroot/Content/js/mods.js30
-rw-r--r--src/SMAPI/Constants.cs2
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManager.cs44
-rw-r--r--src/SMAPI/Framework/Logging/LogManager.cs4
-rw-r--r--src/SMAPI/Framework/SCore.cs15
-rw-r--r--src/SMAPI/Framework/SGame.cs14
-rw-r--r--src/SMAPI/Framework/SGameRunner.cs10
-rw-r--r--src/SMAPI/Metadata/CoreAssetPropagator.cs60
-rw-r--r--src/SMAPI/SMAPI.csproj2
37 files changed, 737 insertions, 199 deletions
diff --git a/build/common.targets b/build/common.targets
index 5eb69901..11584d33 100644
--- a/build/common.targets
+++ b/build/common.targets
@@ -1,7 +1,7 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!--set general build properties -->
- <Version>3.12.6</Version>
+ <Version>3.12.7</Version>
<Product>SMAPI</Product>
<LangVersion>latest</LangVersion>
<AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths>
@@ -54,13 +54,13 @@
<Copy SourceFiles="@(TranslationFiles)" DestinationFolder="$(GamePath)\Mods\$(AssemblyName)\i18n" />
</Target>
- <Target Name="CopyToolkit" Condition="'$(MSBuildProjectName)' == 'SMAPI.Toolkit' AND $(TargetFramework) == 'net4.5'" AfterTargets="PostBuildEvent">
+ <Target Name="CopyToolkit" Condition="'$(MSBuildProjectName)' == 'SMAPI.Toolkit' AND $(TargetFramework) == 'net452'" AfterTargets="PostBuildEvent">
<Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).xml" DestinationFolder="$(GamePath)\smapi-internal" />
</Target>
- <Target Name="CopyToolkitCoreInterfaces" Condition="'$(MSBuildProjectName)' == 'SMAPI.Toolkit.CoreInterfaces' AND $(TargetFramework) == 'net4.5'" AfterTargets="PostBuildEvent">
+ <Target Name="CopyToolkitCoreInterfaces" Condition="'$(MSBuildProjectName)' == 'SMAPI.Toolkit.CoreInterfaces' AND $(TargetFramework) == 'net452'" AfterTargets="PostBuildEvent">
<Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).xml" DestinationFolder="$(GamePath)\smapi-internal" />
diff --git a/build/prepare-install-package.targets b/build/prepare-install-package.targets
index 88e565f9..601f6496 100644
--- a/build/prepare-install-package.targets
+++ b/build/prepare-install-package.targets
@@ -14,7 +14,7 @@
<OutRootPath>$(SolutionDir)\..\bin</OutRootPath>
<SmapiBin>$(BuildRootPath)\SMAPI\bin\$(Configuration)</SmapiBin>
- <ToolkitBin>$(BuildRootPath)\SMAPI.Toolkit\bin\$(Configuration)\net4.5</ToolkitBin>
+ <ToolkitBin>$(BuildRootPath)\SMAPI.Toolkit\bin\$(Configuration)\net452</ToolkitBin>
<ConsoleCommandsBin>$(BuildRootPath)\SMAPI.Mods.ConsoleCommands\bin\$(Configuration)</ConsoleCommandsBin>
<ErrorHandlerBin>$(BuildRootPath)\SMAPI.Mods.ErrorHandler\bin\$(Configuration)</ErrorHandlerBin>
<SaveBackupBin>$(BuildRootPath)\SMAPI.Mods.SaveBackup\bin\$(Configuration)</SaveBackupBin>
diff --git a/docs/release-notes.md b/docs/release-notes.md
index c51dec39..a7a5e6dd 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -3,6 +3,24 @@
# Release notes
## Upcoming release
* For players:
+ * Fixed mod edits to the farmhouse shifting the player down one tile in some cases.
+
+* For mod authors:
+ * SMAPI now intercepts dictionary duplicate-key errors and adds the key to the error message to simplify troubleshooting. (Due to Harmony limitations, this only works for the dictionary types used by the game.)
+ * Fixed map tile rotations/flips not working for farmhands in split-screen mode.
+ * Fixed barn/coop exit warps being reset when you edit their interior map.
+
+* For the web UI:
+ * Added support for unified [mod data overrides](https://stardewvalleywiki.com/Modding:Mod_compatibility#Mod_data_overrides) defined on the wiki.
+ * The mod compatibility list now shows separate beta stats when 'show advanced info' is enabled.
+
+## 3.12.7
+Released 18 September 2021 for Stardew Valley 1.5.4.
+
+* For players:
+ * Added more progress updates in the log during startup.
+ * Simplified asset load error message.
+ * Simplified exception logs.
* Fixed crash loading mods with corrupted translation files.
* For mod authors:
@@ -14,7 +32,7 @@
* Fixed JSON validator line numbers sometimes incorrect.
## 3.12.6
-Released 03 September 2021 for Stardew Valley 1.5.4 or later.
+Released 03 September 2021 for Stardew Valley 1.5.4.
* For players:
* Added friendly error when using SMAPI 3.2._x_ with Stardew Valley 1.5.5 or later.
@@ -38,18 +56,18 @@ to format asset names, you should switch to `PathUtilities.NormalizeAssetName` n
continue working in the next game update.
## 3.12.5
-Released 26 August 2021 for Stardew Valley 1.5.4 or later.
+Released 26 August 2021 for Stardew Valley 1.5.4.
* Fixed some mods in unofficial 64-bit mode no longer loading after SMAPI 3.12.3.
## 3.12.4
-Released 25 August 2021 for Stardew Valley 1.5.4 or later.
+Released 25 August 2021 for Stardew Valley 1.5.4.
* For players:
* Fixed error loading some mods in SMAPI 3.12.3.
## 3.12.3
-Released 25 August 2021 for Stardew Valley 1.5.4 or later.
+Released 25 August 2021 for Stardew Valley 1.5.4.
* For players:
* Added friendly error in 64-bit mode when a mod is 32-bit only.
@@ -68,7 +86,7 @@ Released 25 August 2021 for Stardew Valley 1.5.4 or later.
* Fixed update checks shown for prerelease mod versions if you have a working non-prerelease version.
## 3.12.2
-Released 05 August 2021 for Stardew Valley 1.5.4 or later.
+Released 05 August 2021 for Stardew Valley 1.5.4.
* For players:
* Fixed error creating a new save or joining a multiplayer world in 3.12.1.
@@ -79,7 +97,7 @@ Released 05 August 2021 for Stardew Valley 1.5.4 or later.
* Fixed `NullReferenceException` in SMAPI's error-handling when trying to handle an invalid `ReflectionTypeLoadException`.
## 3.12.1
-Released 03 August 2021 for Stardew Valley 1.5.4 or later.
+Released 03 August 2021 for Stardew Valley 1.5.4.
* For players:
* The software conflict message is now shown as a warning to simplify troubleshooting.
@@ -90,7 +108,7 @@ Released 03 August 2021 for Stardew Valley 1.5.4 or later.
* Fixed `Constants.Save*` fields incorrect if the save's folder name and ID don't match.
## 3.12.0
-Released 01 August 2021 for Stardew Valley 1.5.4 or later. See [release highlights](https://www.patreon.com/posts/54388616).
+Released 01 August 2021 for Stardew Valley 1.5.4. See [release highlights](https://www.patreon.com/posts/54388616).
* For players:
* Added save recovery when content mods leave null objects in the save (in _Error Handler_).
@@ -108,7 +126,7 @@ Released 01 August 2021 for Stardew Valley 1.5.4 or later. See [release highligh
* Fixed reloading a map not correctly reapplying interior doors.
## 3.11.0
-Released 09 July 2021 for Stardew Valley 1.5.4 or later. See [release highlights](https://www.patreon.com/posts/53514295).
+Released 09 July 2021 for Stardew Valley 1.5.4. See [release highlights](https://www.patreon.com/posts/53514295).
* For players:
* Updated for Stardew Valley 1.4.5 multiplayer hotfix on Linux/macOS.
@@ -132,13 +150,13 @@ Released 09 July 2021 for Stardew Valley 1.5.4 or later. See [release highlights
* Fixed JSON schema for `i18n` files requiring the wrong value for the `$schema` field.
## 3.10.1
-Released 03 May 2021 for Stardew Valley 1.5.4 or later.
+Released 03 May 2021 for Stardew Valley 1.5.4.
* For players:
* Fixed installer leaving an unneeded `StardewModdingAPI-x64.exe` file in 32-bit game folders.
## 3.10
-Released 03 May 2021 for Stardew Valley 1.5.4 or later. See [release highlights](https://www.patreon.com/posts/50764911).
+Released 03 May 2021 for Stardew Valley 1.5.4. See [release highlights](https://www.patreon.com/posts/50764911).
* For players:
* Added full support for the [unofficial 64-bit Stardew Valley patch](https://stardewvalleywiki.com/Modding:Migrate_to_64-bit_on_Windows), which removes memory limits. The installer detects which version of SMAPI you need, and SMAPI shows update alerts for Stardew64Installer if applicable.
@@ -163,7 +181,7 @@ Released 03 May 2021 for Stardew Valley 1.5.4 or later. See [release highlights]
* Fixed update subkeys not working in file descriptions for Nexus mods marked as adult content.
## 3.9.5
-Released 21 March 2021 for Stardew Valley 1.5.4 or later.
+Released 21 March 2021 for Stardew Valley 1.5.4.
* For players:
* Added console command to reset community center bundles _(in Console Commands)_.
@@ -184,13 +202,13 @@ Released 21 March 2021 for Stardew Valley 1.5.4 or later.
_Note: mods don't need to handle the difference in most cases, but some players may use MonoGame on Windows in upcoming versions. Mods which check `Constants.TargetPlatform` should review usages as needed._
## 3.9.4
-Released 07 March 2021 for Stardew Valley 1.5.4 or later.
+Released 07 March 2021 for Stardew Valley 1.5.4.
* For players:
* Fixed installer error if the `Mods` folder doesn't exist in 3.9.3.
## 3.9.3
-Released 07 March 2021 for Stardew Valley 1.5.4 or later.
+Released 07 March 2021 for Stardew Valley 1.5.4.
* For players:
* Added descriptive error if possible when a `PathTooLongException` crashes SMAPI or the installer.
@@ -206,7 +224,7 @@ Released 07 March 2021 for Stardew Valley 1.5.4 or later.
* Updated the JSON validator/schema for Content Patcher 1.21.
## 3.9.2
-Released 21 February 2021 for Stardew Valley 1.5.4 or later.
+Released 21 February 2021 for Stardew Valley 1.5.4.
* For players:
* Added more aggressive memory optimization to reduce `OutOfMemoryException` errors with some mods.
@@ -229,14 +247,14 @@ Released 21 February 2021 for Stardew Valley 1.5.4 or later.
* Fixed SMAPI toolkit defaulting the mod type incorrectly if a mod's `manifest.json` has neither `EntryDll` nor `ContentPackFor`. This only affects external tools, since SMAPI itself validates those fields separately.
## 3.9.1
-Released 25 January 2021 for Stardew Valley 1.5.4 or later.
+Released 25 January 2021 for Stardew Valley 1.5.4.
* For players:
* Fixed _tile contains an invalid TileSheet reference_ crash after mods change certain maps.
* Fixed _patched game code_ issue shown for the bundled Error Handler mod.
## 3.9
-Released 22 January 2021 for Stardew Valley 1.5.4 or later. See [release highlights](https://www.patreon.com/posts/46553874).
+Released 22 January 2021 for Stardew Valley 1.5.4. See [release highlights](https://www.patreon.com/posts/46553874).
* For players:
* Updated for Stardew Valley 1.5.4.
diff --git a/src/SMAPI.Installer/SMAPI.Installer.csproj b/src/SMAPI.Installer/SMAPI.Installer.csproj
index 1777be5f..c47f3e6e 100644
--- a/src/SMAPI.Installer/SMAPI.Installer.csproj
+++ b/src/SMAPI.Installer/SMAPI.Installer.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<RootNamespace>StardewModdingAPI.Installer</RootNamespace>
<Description>The SMAPI installer for players.</Description>
- <TargetFramework>net45</TargetFramework>
+ <TargetFramework>net452</TargetFramework>
<OutputType>Exe</OutputType>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
diff --git a/src/SMAPI.Internal/ExceptionExtensions.cs b/src/SMAPI.Internal/ExceptionHelper.cs
index d8189048..4bc55f95 100644
--- a/src/SMAPI.Internal/ExceptionExtensions.cs
+++ b/src/SMAPI.Internal/ExceptionHelper.cs
@@ -1,10 +1,11 @@
using System;
using System.Reflection;
+using System.Text.RegularExpressions;
namespace StardewModdingAPI.Internal
{
/// <summary>Provides extension methods for handling exceptions.</summary>
- internal static class ExceptionExtensions
+ internal static class ExceptionHelper
{
/*********
** Public methods
@@ -15,20 +16,26 @@ namespace StardewModdingAPI.Internal
{
try
{
+ string message;
switch (exception)
{
case TypeLoadException ex:
- return $"Failed loading type '{ex.TypeName}': {exception}";
+ message = $"Failed loading type '{ex.TypeName}': {exception}";
+ break;
case ReflectionTypeLoadException ex:
string summary = ex.ToString();
foreach (Exception childEx in ex.LoaderExceptions ?? new Exception[0])
summary += $"\n\n{childEx?.GetLogSummary()}";
- return summary;
+ message = summary;
+ break;
default:
- return exception?.ToString() ?? $"<null exception>\n{Environment.StackTrace}";
+ message = exception?.ToString() ?? $"<null exception>\n{Environment.StackTrace}";
+ break;
}
+
+ return ExceptionHelper.SimplifyExtensionMessage(message);
}
catch (Exception ex)
{
@@ -44,5 +51,26 @@ namespace StardewModdingAPI.Internal
exception = exception.InnerException;
return exception;
}
+
+ /// <summary>Simplify common patterns in exception log messages that don't convey useful info.</summary>
+ /// <param name="message">The log message to simplify.</param>
+ public static string SimplifyExtensionMessage(string message)
+ {
+ // remove namespace for core exception types
+ message = Regex.Replace(
+ message,
+ @"(?:StardewModdingAPI\.Framework\.Exceptions|Microsoft\.Xna\.Framework|System|System\.IO)\.([a-zA-Z]+Exception):",
+ "$1:"
+ );
+
+ // remove unneeded root build paths for SMAPI and Stardew Valley
+ message = message
+ .Replace(@"C:\source\_Stardew\SMAPI\src\", "")
+ .Replace(@"C:\GitlabRunner\builds\Gq5qA5P4\0\ConcernedApe\", "");
+
+ // remove placeholder info in Linux/macOS stack traces
+ return message
+ .Replace(@"<filename unknown>:0", "");
+ }
}
}
diff --git a/src/SMAPI.Internal/SMAPI.Internal.projitems b/src/SMAPI.Internal/SMAPI.Internal.projitems
index 0ee94a5b..41d356c0 100644
--- a/src/SMAPI.Internal/SMAPI.Internal.projitems
+++ b/src/SMAPI.Internal/SMAPI.Internal.projitems
@@ -14,6 +14,6 @@
<Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\ConsoleLogLevel.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\IConsoleWriter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\MonitorColorScheme.cs" />
- <Compile Include="$(MSBuildThisFileDirectory)ExceptionExtensions.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)ExceptionHelper.cs" />
</ItemGroup>
</Project> \ No newline at end of file
diff --git a/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj b/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj
index b18e79d5..93769650 100644
--- a/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj
+++ b/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<!--build-->
<RootNamespace>StardewModdingAPI.ModBuildConfig</RootNamespace>
- <TargetFramework>net45</TargetFramework>
+ <TargetFramework>net452</TargetFramework>
<LangVersion>latest</LangVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
diff --git a/src/SMAPI.Mods.ConsoleCommands/SMAPI.Mods.ConsoleCommands.csproj b/src/SMAPI.Mods.ConsoleCommands/SMAPI.Mods.ConsoleCommands.csproj
index a187c1ff..528348a0 100644
--- a/src/SMAPI.Mods.ConsoleCommands/SMAPI.Mods.ConsoleCommands.csproj
+++ b/src/SMAPI.Mods.ConsoleCommands/SMAPI.Mods.ConsoleCommands.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<AssemblyName>ConsoleCommands</AssemblyName>
<RootNamespace>StardewModdingAPI.Mods.ConsoleCommands</RootNamespace>
- <TargetFramework>net45</TargetFramework>
+ <TargetFramework>net452</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json
index de223c01..e53bf991 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.12.6",
+ "Version": "3.12.7",
"Description": "Adds SMAPI console commands that let you manipulate the game.",
"UniqueID": "SMAPI.ConsoleCommands",
"EntryDll": "ConsoleCommands.dll",
- "MinimumApiVersion": "3.12.6"
+ "MinimumApiVersion": "3.12.7"
}
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatcher.cs
index 6ad64e16..8ceafcc5 100644
--- a/src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatcher.cs
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatcher.cs
@@ -48,6 +48,11 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
original: AccessTools.Method(dictionaryType, "get_Item") ?? throw new InvalidOperationException($"Can't find method {PatchHelper.GetMethodString(dictionaryType, "get_Item")} to patch."),
finalizer: this.GetHarmonyMethod(nameof(DictionaryPatcher.Finalize_GetItem))
);
+
+ harmony.Patch(
+ original: AccessTools.Method(dictionaryType, "Add") ?? throw new InvalidOperationException($"Can't find method {PatchHelper.GetMethodString(dictionaryType, "Add")} to patch."),
+ finalizer: this.GetHarmonyMethod(nameof(DictionaryPatcher.Finalize_Add))
+ );
}
}
}
@@ -63,13 +68,31 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
private static Exception Finalize_GetItem(object key, Exception __exception)
{
if (__exception is KeyNotFoundException)
- {
- DictionaryPatcher.Reflection
- .GetField<string>(__exception, "_message")
- .SetValue($"{__exception.Message}\nkey: '{key}'");
- }
+ DictionaryPatcher.AddKey(__exception, key);
return __exception;
}
+
+ /// <summary>The method to call after a dictionary insert throws an exception.</summary>
+ /// <param name="key">The dictionary key being inserted.</param>
+ /// <param name="__exception">The exception thrown by the wrapped method, if any.</param>
+ /// <returns>Returns the exception to throw, if any.</returns>
+ private static Exception Finalize_Add(object key, Exception __exception)
+ {
+ if (__exception is ArgumentException)
+ DictionaryPatcher.AddKey(__exception, key);
+
+ return __exception;
+ }
+
+ /// <summary>Add the dictionary key to an exception message.</summary>
+ /// <param name="exception">The exception whose message to edit.</param>
+ /// <param name="key">The dictionary key.</param>
+ private static void AddKey(Exception exception, object key)
+ {
+ DictionaryPatcher.Reflection
+ .GetField<string>(exception, "_message")
+ .SetValue($"{exception.Message}\nkey: '{key}'");
+ }
}
}
diff --git a/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj b/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj
index eb878bc5..182a978e 100644
--- a/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj
+++ b/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<AssemblyName>ErrorHandler</AssemblyName>
<RootNamespace>StardewModdingAPI.Mods.ErrorHandler</RootNamespace>
- <TargetFramework>net45</TargetFramework>
+ <TargetFramework>net452</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
diff --git a/src/SMAPI.Mods.ErrorHandler/manifest.json b/src/SMAPI.Mods.ErrorHandler/manifest.json
index fcb6d7eb..37a7e9bf 100644
--- a/src/SMAPI.Mods.ErrorHandler/manifest.json
+++ b/src/SMAPI.Mods.ErrorHandler/manifest.json
@@ -1,9 +1,9 @@
{
"Name": "Error Handler",
"Author": "SMAPI",
- "Version": "3.12.6",
+ "Version": "3.12.7",
"Description": "Handles some common vanilla errors to log more useful info or avoid breaking the game.",
"UniqueID": "SMAPI.ErrorHandler",
"EntryDll": "ErrorHandler.dll",
- "MinimumApiVersion": "3.12.6"
+ "MinimumApiVersion": "3.12.7"
}
diff --git a/src/SMAPI.Mods.SaveBackup/SMAPI.Mods.SaveBackup.csproj b/src/SMAPI.Mods.SaveBackup/SMAPI.Mods.SaveBackup.csproj
index a6f76781..079beb08 100644
--- a/src/SMAPI.Mods.SaveBackup/SMAPI.Mods.SaveBackup.csproj
+++ b/src/SMAPI.Mods.SaveBackup/SMAPI.Mods.SaveBackup.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<AssemblyName>SaveBackup</AssemblyName>
<RootNamespace>StardewModdingAPI.Mods.SaveBackup</RootNamespace>
- <TargetFramework>net45</TargetFramework>
+ <TargetFramework>net452</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json
index 1c84b5c2..28c7ec14 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.12.6",
+ "Version": "3.12.7",
"Description": "Automatically backs up all your saves once per day into its folder.",
"UniqueID": "SMAPI.SaveBackup",
"EntryDll": "SaveBackup.dll",
- "MinimumApiVersion": "3.12.6"
+ "MinimumApiVersion": "3.12.7"
}
diff --git a/src/SMAPI.Tests/SMAPI.Tests.csproj b/src/SMAPI.Tests/SMAPI.Tests.csproj
index 27520baf..8f7bfab4 100644
--- a/src/SMAPI.Tests/SMAPI.Tests.csproj
+++ b/src/SMAPI.Tests/SMAPI.Tests.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<AssemblyName>SMAPI.Tests</AssemblyName>
<RootNamespace>SMAPI.Tests</RootNamespace>
- <TargetFramework>net45</TargetFramework>
+ <TargetFramework>net452</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<LangVersion>latest</LangVersion>
</PropertyGroup>
diff --git a/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs b/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs
new file mode 100644
index 00000000..b896b09c
--- /dev/null
+++ b/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs
@@ -0,0 +1,139 @@
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using StardewModdingAPI;
+using StardewModdingAPI.Toolkit.Framework.Clients.Wiki;
+
+namespace SMAPI.Tests.WikiClient
+{
+ /// <summary>Unit tests for <see cref="ChangeDescriptor"/>.</summary>
+ [TestFixture]
+ internal class ChangeDescriptorTests
+ {
+ /*********
+ ** Unit tests
+ *********/
+ /****
+ ** Constructor
+ ****/
+ [Test(Description = "Assert that Parse sets the expected values for valid and invalid descriptors.")]
+ public void Parse_SetsExpectedValues_Raw()
+ {
+ // arrange
+ string rawDescriptor = "-Nexus:2400, -B, XX → YY, Nexus:451,+A, XXX → YYY, invalidA →, → invalidB";
+ string[] expectedAdd = new[] { "Nexus:451", "A" };
+ string[] expectedRemove = new[] { "Nexus:2400", "B" };
+ IDictionary<string, string> expectedReplace = new Dictionary<string, string>
+ {
+ ["XX"] = "YY",
+ ["XXX"] = "YYY"
+ };
+ string[] expectedErrors = new[]
+ {
+ "Failed parsing ' invalidA →': can't map to a blank value. Use the '-value' format to remove a value.",
+ "Failed parsing ' → invalidB': can't map from a blank old value. Use the '+value' format to add a value."
+ };
+
+ // act
+ ChangeDescriptor parsed = ChangeDescriptor.Parse(rawDescriptor, out string[] errors);
+
+ // assert
+ Assert.That(parsed.Add, Is.EquivalentTo(expectedAdd), $"{nameof(parsed.Add)} doesn't match the expected value.");
+ Assert.That(parsed.Remove, Is.EquivalentTo(expectedRemove), $"{nameof(parsed.Replace)} doesn't match the expected value.");
+ Assert.That(parsed.Replace, Is.EquivalentTo(expectedReplace), $"{nameof(parsed.Replace)} doesn't match the expected value.");
+ Assert.That(errors, Is.EquivalentTo(expectedErrors), $"{nameof(errors)} doesn't match the expected value.");
+ }
+
+ [Test(Description = "Assert that Parse sets the expected values for descriptors when a format callback is specified.")]
+ public void Parse_SetsExpectedValues_Formatted()
+ {
+ // arrange
+ string rawDescriptor = "-1.0.1, -2.0-beta, 1.00 → 1.0, 1.0.0,+2.0-beta.15, 2.0 → 2.0-beta, invalidA →, → invalidB";
+ string[] expectedAdd = new[] { "1.0.0", "2.0.0-beta.15" };
+ string[] expectedRemove = new[] { "1.0.1", "2.0.0-beta" };
+ IDictionary<string, string> expectedReplace = new Dictionary<string, string>
+ {
+ ["1.00"] = "1.0.0",
+ ["2.0.0"] = "2.0.0-beta"
+ };
+ string[] expectedErrors = new[]
+ {
+ "Failed parsing ' invalidA →': can't map to a blank value. Use the '-value' format to remove a value.",
+ "Failed parsing ' → invalidB': can't map from a blank old value. Use the '+value' format to add a value."
+ };
+
+ // act
+ ChangeDescriptor parsed = ChangeDescriptor.Parse(
+ rawDescriptor,
+ out string[] errors,
+ formatValue: raw => SemanticVersion.TryParse(raw, out ISemanticVersion version)
+ ? version.ToString()
+ : raw
+ );
+
+ // assert
+