diff --git a/build/GlobalAssemblyInfo.cs b/build/GlobalAssemblyInfo.cs
index da80f48b2..3faed842b 100644
--- a/build/GlobalAssemblyInfo.cs
+++ b/build/GlobalAssemblyInfo.cs
@@ -1,5 +1,5 @@
using System.Reflection;
[assembly: AssemblyProduct("SMAPI")]
-[assembly: AssemblyVersion("2.10.1")]
-[assembly: AssemblyFileVersion("2.10.1")]
+[assembly: AssemblyVersion("2.10.2")]
+[assembly: AssemblyFileVersion("2.10.2")]
diff --git a/docs/mod-build-config.md b/docs/mod-build-config.md
index 0c1cc10a5..a97c3171b 100644
--- a/docs/mod-build-config.md
+++ b/docs/mod-build-config.md
@@ -132,8 +132,6 @@ If you don't want to include a file in the mod folder or release zip:
relative path in your mod folder, that file won't be included.
### Non-mod projects
-**(upcoming in 2.1)**
-
You can use the package in non-mod projects too (e.g. unit tests or framework DLLs). You'll need to
disable deploying the mod and creating a release zip:
@@ -218,10 +216,18 @@ That error means the package couldn't find your game. You can specify the game p
_[Game path](#game-path)_ above.
## Release notes
-### 2.1 alpha
+### 2.2
+* Added support for SMAPI 2.8+ (still compatible with earlier versions).
+* Added default game paths for 32-bit Windows.
+* Fixed valid manifests marked invalid in some cases.
+
+### 2.1
* Added support for Stardew Valley 1.3.
-* Added support for unit test projects.
+* Added support for non-mod projects.
* Added C# analyzers to warn about implicit conversions of Netcode fields in Stardew Valley 1.3.
+* Added option to ignore files by regex pattern.
+* Added reference to new SMAPI DLL.
+* Fixed some game paths not detected by NuGet package.
### 2.0.2
* Fixed compatibility issue on Linux.
diff --git a/docs/release-notes.md b/docs/release-notes.md
index e08e7af40..4527b12de 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -1,12 +1,41 @@
# Release notes
+## 2.10.2
+Released 08 January 2019 for Stardew Valley 1.3.32–33.
+
+* For players:
+ * SMAPI now keeps the first save backup created for the day, instead of the last one.
+ * Fixed save backup for some Linux/Mac players. (When compression isn't available, SMAPI will now create uncompressed backups instead.)
+ * Fixed some common dependencies not linking to the mod page in 'missing mod' errors.
+ * Fixed 'unknown mod' deprecation warnings showing a stack trace when developers mode not enabled.
+ * Fixed 'unknown mod' deprecation warnings when they occur in the Mod constructor.
+ * Fixed confusing error message when using SMAPI 2.10._x_ with Stardew Valley 1.3.35+.
+ * Tweaked XNB mod message for clarity.
+ * Updated compatibility list.
+
+* For the web UI:
+ * Added beta status filter to compatibility list.
+ * Fixed broken ModDrop links in the compatibility list.
+
+* For modders:
+ * Asset changes are now propagated into the parsed save being loaded if applicable.
+ * Added locale to context trace logs.
+ * Fixed error loading custom map tilesheets in some cases.
+ * Fixed error when swapping maps mid-session for a location with interior doors.
+ * Fixed `Constants.SaveFolderName` and `CurrentSavePath` not available during early load stages when using `Specialised.LoadStageChanged` event.
+ * Fixed `LoadStage.SaveParsed` raised before the parsed save data is available.
+ * Fixed 'unknown mod' deprecation warnings showing the wrong stack trace.
+ * Fixed `e.Cursor` in input events showing wrong grab tile when player using a controller moves without moving the viewpoint.
+ * Fixed incorrect 'bypassed safety checks' warning for mods using the new `Specialised.LoadStageChanged` event in 2.10.
+ * Deprecated `EntryDll` values whose capitalisation don't match the actual file. (This works on Windows, but causes errors for Linux/Mac players.)
+
## 2.10.1
-Released 30 December 2018 for Stardew Valley 1.3.32.
+Released 30 December 2018 for Stardew Valley 1.3.32–33.
* For players:
* Fixed some mod integrations not working correctly in SMAPI 2.10.
## 2.10
-Released 29 December 2018 for Stardew Valley 1.3.32.
+Released 29 December 2018 for Stardew Valley 1.3.32–33.
* For players:
* Added `world_clear` console command to remove spawned or placed entities.
diff --git a/docs/technical-docs.md b/docs/technical-docs.md
index 1d69f8688..98dd35409 100644
--- a/docs/technical-docs.md
+++ b/docs/technical-docs.md
@@ -70,8 +70,8 @@ on the wiki for the first-time setup.
## Customisation
### Configuration file
-You can customise the SMAPI behaviour by editing the `StardewModdingAPI.config.json` file in your
-game folder.
+You can customise the SMAPI behaviour by editing the `smapi-internal/StardewModdingAPI.config.json`
+file in your game folder.
Basic fields:
diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json
index 0341c390d..b5fd0424d 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": "2.10.1",
+ "Version": "2.10.2",
"Description": "Adds SMAPI console commands that let you manipulate the game.",
"UniqueID": "SMAPI.ConsoleCommands",
"EntryDll": "ConsoleCommands.dll",
- "MinimumApiVersion": "2.10.1"
+ "MinimumApiVersion": "2.10.2"
}
diff --git a/src/SMAPI.Mods.SaveBackup/ModEntry.cs b/src/SMAPI.Mods.SaveBackup/ModEntry.cs
index 56a86cd9f..d10131b38 100644
--- a/src/SMAPI.Mods.SaveBackup/ModEntry.cs
+++ b/src/SMAPI.Mods.SaveBackup/ModEntry.cs
@@ -20,8 +20,11 @@ public class ModEntry : Mod
/// The absolute path to the folder in which to store save backups.
private readonly string BackupFolder = Path.Combine(Constants.ExecutionPath, "save-backups");
+ /// A unique label for the save backup to create.
+ private readonly string BackupLabel = $"{DateTime.UtcNow:yyyy-MM-dd} - SMAPI {Constants.ApiVersion} with Stardew Valley {Game1.version}";
+
/// The name of the save archive to create.
- private readonly string FileName = $"{DateTime.UtcNow:yyyy-MM-dd} - SMAPI {Constants.ApiVersion} with Stardew Valley {Game1.version}.zip";
+ private string FileName => $"{this.BackupLabel}.zip";
/*********
@@ -59,8 +62,9 @@ private void CreateBackup(DirectoryInfo backupFolder)
{
// get target path
FileInfo targetFile = new FileInfo(Path.Combine(backupFolder.FullName, this.FileName));
- if (targetFile.Exists)
- targetFile.Delete(); //return;
+ DirectoryInfo fallbackDir = new DirectoryInfo(Path.Combine(backupFolder.FullName, this.BackupLabel));
+ if (targetFile.Exists || fallbackDir.Exists)
+ return;
// create zip
// due to limitations with the bundled Mono on Mac, we can't reference System.IO.Compression.
@@ -70,12 +74,23 @@ private void CreateBackup(DirectoryInfo backupFolder)
case GamePlatform.Linux:
case GamePlatform.Windows:
{
- Assembly coreAssembly = Assembly.Load("System.IO.Compression, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089") ?? throw new InvalidOperationException("Can't load System.IO.Compression assembly.");
- Assembly fsAssembly = Assembly.Load("System.IO.Compression.FileSystem, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089") ?? throw new InvalidOperationException("Can't load System.IO.Compression assembly.");
- Type compressionLevelType = coreAssembly.GetType("System.IO.Compression.CompressionLevel") ?? throw new InvalidOperationException("Can't load CompressionLevel type.");
- Type zipFileType = fsAssembly.GetType("System.IO.Compression.ZipFile") ?? throw new InvalidOperationException("Can't load ZipFile type.");
- MethodInfo createMethod = zipFileType.GetMethod("CreateFromDirectory", new[] { typeof(string), typeof(string), compressionLevelType, typeof(bool) }) ?? throw new InvalidOperationException("Can't load ZipFile.CreateFromDirectory method.");
- createMethod.Invoke(null, new object[] { Constants.SavesPath, targetFile.FullName, CompressionLevel.Fastest, false });
+ try
+ {
+ // create compressed backup
+ Assembly coreAssembly = Assembly.Load("System.IO.Compression, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089") ?? throw new InvalidOperationException("Can't load System.IO.Compression assembly.");
+ Assembly fsAssembly = Assembly.Load("System.IO.Compression.FileSystem, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089") ?? throw new InvalidOperationException("Can't load System.IO.Compression assembly.");
+ Type compressionLevelType = coreAssembly.GetType("System.IO.Compression.CompressionLevel") ?? throw new InvalidOperationException("Can't load CompressionLevel type.");
+ Type zipFileType = fsAssembly.GetType("System.IO.Compression.ZipFile") ?? throw new InvalidOperationException("Can't load ZipFile type.");
+ MethodInfo createMethod = zipFileType.GetMethod("CreateFromDirectory", new[] { typeof(string), typeof(string), compressionLevelType, typeof(bool) }) ?? throw new InvalidOperationException("Can't load ZipFile.CreateFromDirectory method.");
+ createMethod.Invoke(null, new object[] { Constants.SavesPath, targetFile.FullName, CompressionLevel.Fastest, false });
+ }
+ catch (Exception ex) when (ex is TypeLoadException || ex.InnerException is TypeLoadException)
+ {
+ // create uncompressed backup if compression fails
+ this.Monitor.Log("Couldn't zip the save backup, creating uncompressed backup instead.");
+ this.Monitor.Log(ex.ToString(), LogLevel.Trace);
+ this.RecursiveCopy(new DirectoryInfo(Constants.SavesPath), fallbackDir, copyRoot: false);
+ }
}
break;
@@ -132,5 +147,32 @@ private void PruneBackups(DirectoryInfo backupFolder, int backupsToKeep)
this.Monitor.Log(ex.ToString(), LogLevel.Trace);
}
}
+
+ /// Recursively copy a directory or file.
+ /// The file or folder to copy.
+ /// The folder to copy into.
+ /// Whether to copy the root folder itself, or false to only copy its contents.
+ /// Derived from the SMAPI installer code.
+ private void RecursiveCopy(FileSystemInfo source, DirectoryInfo targetFolder, bool copyRoot = true)
+ {
+ if (!targetFolder.Exists)
+ targetFolder.Create();
+
+ switch (source)
+ {
+ case FileInfo sourceFile:
+ sourceFile.CopyTo(Path.Combine(targetFolder.FullName, sourceFile.Name));
+ 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);
+ break;
+
+ default:
+ throw new NotSupportedException($"Unknown filesystem info type '{source.GetType().FullName}'.");
+ }
+ }
}
}
diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json
index b2b9ad4b0..7ac537ca8 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": "2.10.1",
+ "Version": "2.10.2",
"Description": "Automatically backs up all your saves once per day into its folder.",
"UniqueID": "SMAPI.SaveBackup",
"EntryDll": "SaveBackup.dll",
- "MinimumApiVersion": "2.10.1"
+ "MinimumApiVersion": "2.10.2"
}
diff --git a/src/SMAPI.Web/Startup.cs b/src/SMAPI.Web/Startup.cs
index 915535137..a2e47482e 100644
--- a/src/SMAPI.Web/Startup.cs
+++ b/src/SMAPI.Web/Startup.cs
@@ -161,12 +161,13 @@ private RewriteOptions GetRedirectRules()
));
// shortcut redirects
+ redirects.Add(new RedirectToUrlRule(@"^/3\.0\.?$", "https://stardewvalleywiki.com/Modding:Migrate_to_SMAPI_3.0"));
redirects.Add(new RedirectToUrlRule(@"^/buildmsg(?:/?(.*))$", "https://github.com/Pathoschild/SMAPI/blob/develop/docs/mod-build-config.md#$1"));
redirects.Add(new RedirectToUrlRule(@"^/compat\.?$", "https://mods.smapi.io"));
- redirects.Add(new RedirectToUrlRule(@"^/3\.0\.?$", "https://stardewvalleywiki.com/Modding:Migrate_to_SMAPI_3.0"));
redirects.Add(new RedirectToUrlRule(@"^/docs\.?$", "https://stardewvalleywiki.com/Modding:Index"));
redirects.Add(new RedirectToUrlRule(@"^/install\.?$", "https://stardewvalleywiki.com/Modding:Player_Guide/Getting_Started#Install_SMAPI"));
redirects.Add(new RedirectToUrlRule(@"^/troubleshoot(.*)$", "https://stardewvalleywiki.com/Modding:Player_Guide/Troubleshooting$1"));
+ redirects.Add(new RedirectToUrlRule(@"^/xnb\.?$", "https://stardewvalleywiki.com/Modding:Using_XNB_mods"));
// redirect legacy canimod.com URLs
var wikiRedirects = new Dictionary
diff --git a/src/SMAPI.Web/ViewModels/ModModel.cs b/src/SMAPI.Web/ViewModels/ModModel.cs
index f1a52f988..ae81acf5e 100644
--- a/src/SMAPI.Web/ViewModels/ModModel.cs
+++ b/src/SMAPI.Web/ViewModels/ModModel.cs
@@ -107,7 +107,7 @@ private IEnumerable GetModPageUrls(WikiModEntry entry)
if (entry.ModDropID.HasValue)
{
anyFound = true;
- yield return new ModLinkModel($"https://www.moddrop.com/sdv/mod/467243/{entry.ModDropID}", "ModDrop");
+ yield return new ModLinkModel($"https://www.moddrop.com/sdv/mod/{entry.ModDropID}", "ModDrop");
}
// fallback
diff --git a/src/SMAPI.Web/Views/Mods/Index.cshtml b/src/SMAPI.Web/Views/Mods/Index.cshtml
index a6c94cf17..a30a00481 100644
--- a/src/SMAPI.Web/Views/Mods/Index.cshtml
+++ b/src/SMAPI.Web/Views/Mods/Index.cshtml
@@ -4,15 +4,16 @@
ViewData["Title"] = "SMAPI mod compatibility";
}
@section Head {
-
+
-
+
}
@@ -39,7 +40,7 @@