summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2019-10-01 21:41:15 -0400
committerJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2019-10-01 21:41:15 -0400
commit65997c1243a60ae15cc0b832ebcd41d96c3ea06a (patch)
tree4b55e07011ed3144d6a996821ddc1886f35de83f
parent845deb43d60147603ec31fe4ae5fd7d747556d8c (diff)
downloadSMAPI-65997c1243a60ae15cc0b832ebcd41d96c3ea06a.tar.gz
SMAPI-65997c1243a60ae15cc0b832ebcd41d96c3ea06a.tar.bz2
SMAPI-65997c1243a60ae15cc0b832ebcd41d96c3ea06a.zip
auto-fix save data when a custom location mod is removed
-rw-r--r--docs/README.md6
-rw-r--r--docs/release-notes.md4
-rw-r--r--src/SMAPI/Framework/SCore.cs2
-rw-r--r--src/SMAPI/Framework/SGame.cs19
-rw-r--r--src/SMAPI/Patches/LoadErrorPatch.cs28
-rw-r--r--src/SMAPI/i18n/default.json2
6 files changed, 53 insertions, 8 deletions
diff --git a/docs/README.md b/docs/README.md
index fdb60693..54e9f26f 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -23,10 +23,10 @@ doesn't change any of your game files. It serves eight main purposes:
_SMAPI intercepts errors, shows the error info in the SMAPI console, and in most cases
automatically recovers the game. That prevents mods from crashing the game, and makes it
possible to troubleshoot errors in the game itself that would otherwise show a generic 'program
- has stopped working' type of message._
+ has stopped working' type of message._
- _That also includes automatically fixing save data when a load would crash, e.g. due to a custom
- NPC mod the player removed._
+ _SMAPI also automatically fixes save data in some cases when a load would crash, e.g. due to a
+ custom location or NPC mod that was removed._
6. **Provide update checks.**
_SMAPI automatically checks for new versions of your installed mods, and notifies you when any
diff --git a/docs/release-notes.md b/docs/release-notes.md
index 5d8253b4..41a98dbe 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -13,7 +13,7 @@ For players:
SMAPI should have less impact on game performance and startup time for some players.
* **Added more error recovery.**
- SMAPI now detects and prevents more crashes due to game or mod bugs, or due to removing some mods which add custom content.
+ SMAPI now detects and prevents more crashes due to game/mod bugs, or due to removing mods which add custom locations or NPCs.
* **Improved mod scanning.**
SMAPI now supports some non-standard mod structures automatically, improves compatibility with the Vortex mod manager, and improves various error/skip messages related to mod loading.
@@ -46,7 +46,7 @@ For modders:
* Improved mod scanning:
* Now ignores metadata files and folders (like `__MACOSX` and `__folder_managed_by_vortex`) and content files (like `.txt` or `.png`), which avoids missing-manifest errors in some common cases.
* Now detects XNB mods more accurately, and consolidates multi-folder XNB mods in logged messages.
- * SMAPI now automatically fixes your save if you remove a custom NPC mod. (Invalid NPCs are now removed on load, with a warning in the console.)
+ * SMAPI now automatically removes invalid content when loading a save to prevent crashes. A warning is shown in-game when this happens. This applies for locations and NPCs.
* Added support for configuring console colors via `smapi-internal/config.json` (intended for players with unusual consoles).
* Improved launch script compatibility on Linux (thanks to kurumushi and toastal!).
* Save Backup now works in the background, to avoid affecting startup time for players with a large number of saves.
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index bd131762..bfdf1c51 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -245,7 +245,7 @@ namespace StardewModdingAPI.Framework
new DialogueErrorPatch(this.MonitorForGame, this.Reflection),
new ObjectErrorPatch(),
new LoadContextPatch(this.Reflection, this.GameInstance.OnLoadStageChanged),
- new LoadErrorPatch(this.Monitor)
+ new LoadErrorPatch(this.Monitor, this.GameInstance.OnSaveContentRemoved)
);
// add exit handler
diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs
index 89705352..13858fc5 100644
--- a/src/SMAPI/Framework/SGame.cs
+++ b/src/SMAPI/Framework/SGame.cs
@@ -65,6 +65,9 @@ namespace StardewModdingAPI.Framework
/// <remarks>Skipping a few frames ensures the game finishes initializing the world before mods try to change it.</remarks>
private readonly Countdown AfterLoadTimer = new Countdown(5);
+ /// <summary>Whether custom content was removed from the save data to avoid a crash.</summary>
+ private bool IsSaveContentRemoved;
+
/// <summary>Whether the game is saving and SMAPI has already raised <see cref="IGameLoopEvents.Saving"/>.</summary>
private bool IsBetweenSaveEvents;
@@ -216,6 +219,12 @@ namespace StardewModdingAPI.Framework
this.Events.ModMessageReceived.RaiseForMods(new ModMessageReceivedEventArgs(message), mod => mod != null && modIDs.Contains(mod.Manifest.UniqueID));
}
+ /// <summary>A callback invoked when custom content is removed from the save data to avoid a crash.</summary>
+ internal void OnSaveContentRemoved()
+ {
+ this.IsSaveContentRemoved = true;
+ }
+
/// <summary>A callback invoked when the game's low-level load stage changes.</summary>
/// <param name="newStage">The new load stage.</param>
internal void OnLoadStageChanged(LoadStage newStage)
@@ -458,6 +467,16 @@ namespace StardewModdingAPI.Framework
WatcherSnapshot state = this.WatcherSnapshot;
/*********
+ ** Display in-game warnings
+ *********/
+ // save content removed
+ if (this.IsSaveContentRemoved && Context.IsWorldReady)
+ {
+ this.IsSaveContentRemoved = false;
+ Game1.addHUDMessage(new HUDMessage(this.Translator.Get("warn.invalid-content-removed"), HUDMessage.error_type));
+ }
+
+ /*********
** Pre-update events
*********/
{
diff --git a/src/SMAPI/Patches/LoadErrorPatch.cs b/src/SMAPI/Patches/LoadErrorPatch.cs
index 87e8ee14..eedb4164 100644
--- a/src/SMAPI/Patches/LoadErrorPatch.cs
+++ b/src/SMAPI/Patches/LoadErrorPatch.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@@ -20,6 +21,9 @@ namespace StardewModdingAPI.Patches
/// <summary>Writes messages to the console and log file.</summary>
private static IMonitor Monitor;
+ /// <summary>A callback invoked when custom content is removed from the save data to avoid a crash.</summary>
+ private static Action OnContentRemoved;
+
/*********
** Accessors
@@ -33,9 +37,11 @@ namespace StardewModdingAPI.Patches
*********/
/// <summary>Construct an instance.</summary>
/// <param name="monitor">Writes messages to the console and log file.</param>
- public LoadErrorPatch(IMonitor monitor)
+ /// <param name="onContentRemoved">A callback invoked when custom content is removed from the save data to avoid a crash.</param>
+ public LoadErrorPatch(IMonitor monitor, Action onContentRemoved)
{
LoadErrorPatch.Monitor = monitor;
+ LoadErrorPatch.OnContentRemoved = onContentRemoved;
}
@@ -58,6 +64,22 @@ namespace StardewModdingAPI.Patches
/// <returns>Returns whether to execute the original method.</returns>
private static bool Before_SaveGame_LoadDataToLocations(List<GameLocation> gamelocations)
{
+ bool removedAny = false;
+
+ // remove invalid locations
+ foreach (GameLocation location in gamelocations.ToArray())
+ {
+ if (location is Cellar)
+ continue; // missing cellars will be added by the game code
+
+ if (Game1.getLocationFromName(location.name) == null)
+ {
+ LoadErrorPatch.Monitor.Log($"Removed invalid location '{location.Name}' to avoid a crash when loading save '{Constants.SaveFolderName}'. (Did you remove a custom location mod?)", LogLevel.Warn);
+ gamelocations.Remove(location);
+ removedAny = true;
+ }
+ }
+
// get building interiors
var interiors =
(
@@ -83,11 +105,15 @@ namespace StardewModdingAPI.Patches
{
LoadErrorPatch.Monitor.Log($"Removed invalid villager '{npc.Name}' to avoid a crash when loading save '{Constants.SaveFolderName}'. (Did you remove a custom NPC mod?)", LogLevel.Warn);
location.characters.Remove(npc);
+ removedAny = true;
}
}
}
}
+ if (removedAny)
+ LoadErrorPatch.OnContentRemoved();
+
return true;
}
}
diff --git a/src/SMAPI/i18n/default.json b/src/SMAPI/i18n/default.json
index 0db3279e..5a3e4a6e 100644
--- a/src/SMAPI/i18n/default.json
+++ b/src/SMAPI/i18n/default.json
@@ -1,3 +1,3 @@
{
-
+ "warn.invalid-content-removed": "Invalid content was removed to prevent a crash (see the SMAPI console for info)."
}