summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2021-03-07 20:15:10 -0500
committerJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2021-03-07 20:15:10 -0500
commit5399239c2be01396958ed454d6ae56f006d20ca5 (patch)
tree87683eda0873a0c24d9bcee5d47f1cec896f21bb
parentdb011ee751bdfb8bbd9abbeb706966db4c4e2461 (diff)
parenta571f459f59a6ecfdd53e3158ba8d29157598920 (diff)
downloadSMAPI-5399239c2be01396958ed454d6ae56f006d20ca5.tar.gz
SMAPI-5399239c2be01396958ed454d6ae56f006d20ca5.tar.bz2
SMAPI-5399239c2be01396958ed454d6ae56f006d20ca5.zip
Merge branch 'develop' into stable
-rw-r--r--build/common.targets2
-rw-r--r--docs/release-notes.md13
-rw-r--r--src/SMAPI.Installer/InteractiveInstaller.cs12
-rw-r--r--src/SMAPI.Installer/Program.cs25
-rw-r--r--src/SMAPI.Mods.ConsoleCommands/manifest.json4
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/DialogueErrorPatch.cs7
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/EventPatches.cs7
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatches.cs7
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/LoadErrorPatch.cs7
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/ObjectErrorPatch.cs7
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/ScheduleErrorPatch.cs7
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchValidationPatches.cs7
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/UtilityErrorPatches.cs7
-rw-r--r--src/SMAPI.Mods.ErrorHandler/manifest.json4
-rw-r--r--src/SMAPI.Mods.SaveBackup/manifest.json4
-rw-r--r--src/SMAPI.Toolkit/Properties/AssemblyInfo.cs1
-rw-r--r--src/SMAPI.Toolkit/Utilities/PathUtilities.cs25
-rw-r--r--src/SMAPI/Constants.cs2
-rw-r--r--src/SMAPI/Enums/LoadStage.cs31
-rw-r--r--src/SMAPI/Framework/IModMetadata.cs6
-rw-r--r--src/SMAPI/Framework/Logging/LogManager.cs23
-rw-r--r--src/SMAPI/Framework/ModLoading/AssemblyLoader.cs51
-rw-r--r--src/SMAPI/Framework/ModLoading/ModMetadata.cs19
-rw-r--r--src/SMAPI/Framework/Patching/GamePatcher.cs2
-rw-r--r--src/SMAPI/Framework/Patching/IHarmonyPatch.cs6
-rw-r--r--src/SMAPI/Framework/SCore.cs30
-rw-r--r--src/SMAPI/Patches/LoadContextPatch.cs73
27 files changed, 246 insertions, 143 deletions
diff --git a/build/common.targets b/build/common.targets
index 29acbb56..50c839e1 100644
--- a/build/common.targets
+++ b/build/common.targets
@@ -4,7 +4,7 @@
<!--set properties -->
<PropertyGroup>
- <Version>3.9.2</Version>
+ <Version>3.9.3</Version>
<Product>SMAPI</Product>
<LangVersion>latest</LangVersion>
diff --git a/docs/release-notes.md b/docs/release-notes.md
index fb67d8dc..36351f34 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -7,6 +7,19 @@
* Migrated to Harmony 2.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info).
-->
+## 3.9.3
+Released 07 March 2021 for Stardew Valley 1.5.4 or later.
+
+* For players:
+ * Added descriptive error if possible when a `PathTooLongException` crashes SMAPI or the installer.
+ * The installer window now tries to stay open if it crashed, so you can read the error and ask for help.
+ * Fixed console showing _found 1 mod with warnings_ with no mods listed in some cases.
+
+* For mod authors:
+ * Added three stages to the specialised [`LoadStageChanged` event](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events#Specialised): `CreatedInitialLocations`, `SaveAddedLocations`, and `ReturningToTitle`.
+ * Fixed `RewriteMods` option ignored when rewriting for OS compatibility.
+ * Fixed edge case when playing as a farmhand in non-English where translatable assets loaded via `IAssetLoader` weren't reapplied immediately when the server disconnects.
+
## 3.9.2
Released 21 February 2021 for Stardew Valley 1.5.4 or later.
diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs
index ff74a659..3d673719 100644
--- a/src/SMAPI.Installer/InteractiveInstaller.cs
+++ b/src/SMAPI.Installer/InteractiveInstaller.cs
@@ -279,6 +279,7 @@ namespace StardewModdingApi.Installer
/*********
** Step 4: validate assumptions
*********/
+ // executable exists
if (!File.Exists(paths.ExecutablePath))
{
this.PrintError("The detected game install path doesn't contain a Stardew Valley executable.");
@@ -286,6 +287,17 @@ namespace StardewModdingApi.Installer
return;
}
+ // game folder doesn't contain paths beyond the max limit
+ {
+ string[] tooLongPaths = PathUtilities.GetTooLongPaths(Path.Combine(paths.GamePath, "Mods")).ToArray();
+ if (tooLongPaths.Any())
+ {
+ this.PrintError($"SMAPI can't install to the detected game folder, because some of its files exceed the maximum {context.Platform} path length.\nIf you need help fixing this error, see https://smapi.io/help\n\nAffected paths:\n {string.Join("\n ", tooLongPaths)}");
+ Console.ReadLine();
+ return;
+ }
+ }
+
/*********
** Step 5: ask what to do
diff --git a/src/SMAPI.Installer/Program.cs b/src/SMAPI.Installer/Program.cs
index 6c479621..d9c31dd6 100644
--- a/src/SMAPI.Installer/Program.cs
+++ b/src/SMAPI.Installer/Program.cs
@@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.IO.Compression;
using System.Reflection;
+using System.Threading;
namespace StardewModdingApi.Installer
{
@@ -49,7 +50,15 @@ namespace StardewModdingApi.Installer
// launch installer
var installer = new InteractiveInstaller(bundleDir.FullName);
- installer.Run(args);
+
+ try
+ {
+ installer.Run(args);
+ }
+ catch (Exception ex)
+ {
+ Program.PrintErrorAndExit($"The installer failed with an unexpected exception.\nIf you need help fixing this error, see https://smapi.io/help\n\n{ex}");
+ }
}
/*********
@@ -76,5 +85,19 @@ namespace StardewModdingApi.Installer
return null;
}
}
+
+ /// <summary>Write an error directly to the console and exit.</summary>
+ /// <param name="message">The error message to display.</param>
+ private static void PrintErrorAndExit(string message)
+ {
+ Console.ForegroundColor = ConsoleColor.Red;
+ Console.WriteLine(message);
+ Console.ResetColor();
+
+ Console.WriteLine("Game has ended. Press any key to exit.");
+ Thread.Sleep(100);
+ Console.ReadKey();
+ Environment.Exit(0);
+ }
}
}
diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json
index aa3d6ceb..58692de1 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.9.2",
+ "Version": "3.9.3",
"Description": "Adds SMAPI console commands that let you manipulate the game.",
"UniqueID": "SMAPI.ConsoleCommands",
"EntryDll": "ConsoleCommands.dll",
- "MinimumApiVersion": "3.9.2"
+ "MinimumApiVersion": "3.9.3"
}
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/DialogueErrorPatch.cs b/src/SMAPI.Mods.ErrorHandler/Patches/DialogueErrorPatch.cs
index ba0ca582..cce13064 100644
--- a/src/SMAPI.Mods.ErrorHandler/Patches/DialogueErrorPatch.cs
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/DialogueErrorPatch.cs
@@ -30,13 +30,6 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
/*********
- ** Accessors
- *********/
- /// <inheritdoc />
- public string Name => nameof(DialogueErrorPatch);
-
-
- /*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/EventPatches.cs b/src/SMAPI.Mods.ErrorHandler/Patches/EventPatches.cs
index a15c1d32..72863d17 100644
--- a/src/SMAPI.Mods.ErrorHandler/Patches/EventPatches.cs
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/EventPatches.cs
@@ -24,13 +24,6 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
/*********
- ** Accessors
- *********/
- /// <inheritdoc />
- public string Name => nameof(EventPatches);
-
-
- /*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatches.cs b/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatches.cs
index c10f2de7..1edf2d6a 100644
--- a/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatches.cs
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatches.cs
@@ -25,13 +25,6 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
/*********
- ** Accessors
- *********/
- /// <inheritdoc />
- public string Name => nameof(GameLocationPatches);
-
-
- /*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/LoadErrorPatch.cs b/src/SMAPI.Mods.ErrorHandler/Patches/LoadErrorPatch.cs
index 2227ea07..52d5f5a1 100644
--- a/src/SMAPI.Mods.ErrorHandler/Patches/LoadErrorPatch.cs
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/LoadErrorPatch.cs
@@ -32,13 +32,6 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
/*********
- ** Accessors
- *********/
- /// <inheritdoc />
- public string Name => nameof(LoadErrorPatch);
-
-
- /*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/ObjectErrorPatch.cs b/src/SMAPI.Mods.ErrorHandler/Patches/ObjectErrorPatch.cs
index 70f054cd..9f8a98cd 100644
--- a/src/SMAPI.Mods.ErrorHandler/Patches/ObjectErrorPatch.cs
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/ObjectErrorPatch.cs
@@ -21,13 +21,6 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
internal class ObjectErrorPatch : IHarmonyPatch
{
/*********
- ** Accessors
- *********/
- /// <inheritdoc />
- public string Name => nameof(ObjectErrorPatch);
-
-
- /*********
** Public methods
*********/
/// <inheritdoc />
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/ScheduleErrorPatch.cs b/src/SMAPI.Mods.ErrorHandler/Patches/ScheduleErrorPatch.cs
index abbd1a8f..d2a5e988 100644
--- a/src/SMAPI.Mods.ErrorHandler/Patches/ScheduleErrorPatch.cs
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/ScheduleErrorPatch.cs
@@ -27,13 +27,6 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
/*********
- ** Accessors
- *********/
- /// <inheritdoc />
- public string Name => nameof(ScheduleErrorPatch);
-
-
- /*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchValidationPatches.cs b/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchValidationPatches.cs
index 0211cfb1..95e4f5ef 100644
--- a/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchValidationPatches.cs
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchValidationPatches.cs
@@ -17,13 +17,6 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
internal class SpriteBatchValidationPatches : IHarmonyPatch
{
/*********
- ** Accessors
- *********/
- /// <inheritdoc />
- public string Name => nameof(SpriteBatchValidationPatches);
-
-
- /*********
** Public methods
*********/
/// <inheritdoc />
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/UtilityErrorPatches.cs b/src/SMAPI.Mods.ErrorHandler/Patches/UtilityErrorPatches.cs
index 481c881e..1ddd407c 100644
--- a/src/SMAPI.Mods.ErrorHandler/Patches/UtilityErrorPatches.cs
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/UtilityErrorPatches.cs
@@ -19,13 +19,6 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
internal class UtilityErrorPatches : IHarmonyPatch
{
/*********
- ** Accessors
- *********/
- /// <inheritdoc />
- public string Name => nameof(UtilityErrorPatches);
-
-
- /*********
** Public methods
*********/
/// <inheritdoc />
diff --git a/src/SMAPI.Mods.ErrorHandler/manifest.json b/src/SMAPI.Mods.ErrorHandler/manifest.json
index b6df0f49..962b27f8 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.9.2",
+ "Version": "3.9.3",
"Description": "Handles some common vanilla errors to log more useful info or avoid breaking the game.",
"UniqueID": "SMAPI.ErrorHandler",
"EntryDll": "ErrorHandler.dll",
- "MinimumApiVersion": "3.9.2"
+ "MinimumApiVersion": "3.9.3"
}
diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json
index 4d2003e2..d51bc5d9 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.9.2",
+ "Version": "3.9.3",
"Description": "Automatically backs up all your saves once per day into its folder.",
"UniqueID": "SMAPI.SaveBackup",
"EntryDll": "SaveBackup.dll",
- "MinimumApiVersion": "3.9.2"
+ "MinimumApiVersion": "3.9.3"
}
diff --git a/src/SMAPI.Toolkit/Properties/AssemblyInfo.cs b/src/SMAPI.Toolkit/Properties/AssemblyInfo.cs
index 233e680b..eede4562 100644
--- a/src/SMAPI.Toolkit/Properties/AssemblyInfo.cs
+++ b/src/SMAPI.Toolkit/Properties/AssemblyInfo.cs
@@ -1,4 +1,5 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("StardewModdingAPI")]
+[assembly: InternalsVisibleTo("SMAPI.Installer")]
[assembly: InternalsVisibleTo("SMAPI.Web")]
diff --git a/src/SMAPI.Toolkit/Utilities/PathUtilities.cs b/src/SMAPI.Toolkit/Utilities/PathUtilities.cs
index c9fb6213..a394edba 100644
--- a/src/SMAPI.Toolkit/Utilities/PathUtilities.cs
+++ b/src/SMAPI.Toolkit/Utilities/PathUtilities.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
@@ -133,5 +134,29 @@ namespace StardewModdingAPI.Toolkit.Utilities
{
return !Regex.IsMatch(str, "[^a-z0-9_.-]", RegexOptions.IgnoreCase);
}
+
+ /// <summary>Get the paths which exceed the OS length limit.</summary>
+ /// <param name="rootPath">The root path to search.</param>
+ internal static IEnumerable<string> GetTooLongPaths(string rootPath)
+ {
+ return Directory
+ .EnumerateFileSystemEntries(rootPath, "*.*", SearchOption.AllDirectories)
+ .Where(PathUtilities.IsPathTooLong);
+ }
+
+ /// <summary>Get whether a file or directory path exceeds the OS path length limit.</summary>
+ /// <param name="path">The path to test.</param>
+ internal static bool IsPathTooLong(string path)
+ {
+ try
+ {
+ _ = Path.GetFullPath(path);
+ return false;
+ }
+ catch (PathTooLongException)
+ {
+ return true;
+ }
+ }
}
}
diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs
index 54fb54ab..a81a6bc9 100644
--- a/src/SMAPI/Constants.cs
+++ b/src/SMAPI/Constants.cs
@@ -54,7 +54,7 @@ namespace StardewModdingAPI
** Public
****/
/// <summary>SMAPI's current semantic version.</summary>
- public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("3.9.2");
+ public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("3.9.3");
/// <summary>The minimum supported version of Stardew Valley.</summary>
public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.5.4");
diff --git a/src/SMAPI/Enums/LoadStage.cs b/src/SMAPI/Enums/LoadStage.cs
index 5c2b0412..302c263b 100644
--- a/src/SMAPI/Enums/LoadStage.cs
+++ b/src/SMAPI/Enums/LoadStage.cs
@@ -4,33 +4,42 @@ namespace StardewModdingAPI.Enums
public enum LoadStage
{
/// <summary>A save is not loaded or loading.</summary>
- None,
+ None = 0,
/// <summary>The game is creating a new save slot, and has initialized the basic save info.</summary>
- CreatedBasicInfo,
+ CreatedBasicInfo = 1,
+
+ /// <summary>The game is creating a new save slot, and has added the location instances but hasn't fully initialized them yet.</summary>
+ CreatedInitialLocations = 10,
/// <summary>The game is creating a new save slot, and has initialized the in-game locations.</summary>
- CreatedLocations,
+ CreatedLocations = 2,
/// <summary>The game is creating a new save slot, and has created the physical save files.</summary>
- CreatedSaveFile,
+ CreatedSaveFile = 3,
/// <summary>The game is loading a save slot, and has read the raw save data into <see cref="StardewValley.SaveGame.loaded"/>. Not applicable when connecting to a multiplayer host. This is equivalent to <see cref="StardewValley.SaveGame.getLoadEnumerator"/> value 20.</summary>
- SaveParsed,
+ SaveParsed = 4,
/// <summary>The game is loading a save slot, and has applied the basic save info (including player data). Not applicable when connecting to a multiplayer host. Note that some basic info (like daily luck) is not initialized at this point. This is equivalent to <see cref="StardewValley.SaveGame.getLoadEnumerator"/> value 36.</summary>
- SaveLoadedBasicInfo,
+ SaveLoadedBasicInfo = 5,
+
+ /// <summary>The game is loading a save slot and has added the location instances, but hasn't restored their save data yet. Not applicable when connecting to a multiplayer host.</summary>
+ SaveAddedLocations = 11,
- /// <summary>The game is loading a save slot, and has applied the in-game location data. Not applicable when connecting to a multiplayer host. This is equivalent to <see cref="StardewValley.SaveGame.getLoadEnumerator"/> value 50.</summary>
- SaveLoadedLocations,
+ /// <summary>The game is loading a save slot, and has restored the in-game location data. Not applicable when connecting to a multiplayer host. This is equivalent to <see cref="StardewValley.SaveGame.getLoadEnumerator"/> value 50.</summary>
+ SaveLoadedLocations = 6,
/// <summary>The final metadata has been loaded from the save file. This happens before the game applies problem fixes, checks for achievements, starts music, etc. Not applicable when connecting to a multiplayer host.</summary>
- Preloaded,
+ Preloaded = 7,
/// <summary>The save is fully loaded, but the world may not be fully initialized yet.</summary>
- Loaded,
+ Loaded = 8,
/// <summary>The save is fully loaded, the world has been initialized, and <see cref="Context.IsWorldReady"/> is now true.</summary>
- Ready
+ Ready = 9,
+
+ /// <summary>The game is exiting the loaded save and returning to the title screen. This happens before it returns to title; see <see cref="None"/> after it returns.</summary>
+ ReturningToTitle = 12
}
}
diff --git a/src/SMAPI/Framework/IModMetadata.cs b/src/SMAPI/Framework/IModMetadata.cs
index 70cf0036..5d2f352d 100644
--- a/src/SMAPI/Framework/IModMetadata.cs
+++ b/src/SMAPI/Framework/IModMetadata.cs
@@ -34,7 +34,7 @@ namespace StardewModdingAPI.Framework
/// <summary>The reason the mod failed to load, if applicable.</summary>
ModFailReason? FailReason { get; }
- /// <summary>Indicates non-error issues with the mod.</summary>
+ /// <summary>The non-error issues with the mod, ignoring those suppressed via <see cref="DataRecord"/>.</summary>
ModWarning Warnings { get; }
/// <summary>The reason the metadata is invalid, if any.</summary>
@@ -124,9 +124,9 @@ namespace StardewModdingAPI.Framework
/// <summary>Whether the mod has at least one valid update key set.</summary>
bool HasValidUpdateKeys();
- /// <summary>Get whether the mod has any of the given warnings which haven't been suppressed in the <see cref="DataRecord"/>.</summary>
+ /// <summary>Get whether the mod has any of the given warnings, ignoring those suppressed via <see cref="DataRecord"/>.</summary>
/// <param name="warnings">The warnings to check.</param>
- bool HasUnsuppressedWarnings(params ModWarning[] warnings);
+ bool HasWarnings(params ModWarning[] warnings);
/// <summary>Get a relative path which includes the root folder name.</summary>
string GetRelativePathWithRoot();
diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs
index 2c7be399..0dd45355 100644
--- a/src/SMAPI/Framework/Logging/LogManager.cs
+++ b/src/SMAPI/Framework/Logging/LogManager.cs
@@ -252,11 +252,22 @@ namespace StardewModdingAPI.Framework.Logging
break;
// missing content folder exception
- case FileNotFoundException ex when ex.Message == "Could not find file 'C:\\Program Files (x86)\\Steam\\SteamApps\\common\\Stardew Valley\\Content\\XACT\\FarmerSounds.xgs'.": // path in error is hardcoded regardless of install path
+ case FileNotFoundException ex when ex.Message == "Couldn't find file 'C:\\Program Files (x86)\\Steam\\SteamApps\\common\\Stardew Valley\\Content\\XACT\\FarmerSounds.xgs'.": // path in error is hardcoded regardless of install path
this.Monitor.Log("The game can't find its Content\\XACT\\FarmerSounds.xgs file. You can usually fix this by resetting your content files (see https://smapi.io/troubleshoot#reset-content ), or by uninstalling and reinstalling the game.", LogLevel.Error);
this.Monitor.Log($"Technical details: {ex.GetLogSummary()}");
break;
+ // path too long exception
+ case PathTooLongException:
+ {
+ string[] affectedPaths = PathUtilities.GetTooLongPaths(Constants.ModsPath).ToArray();
+ string message = affectedPaths.Any()
+ ? $"SMAPI can't launch because some of your mod files exceed the maximum path length on {Constants.Platform}.\nIf you need help fixing this error, see https://smapi.io/help\n\nAffected paths:\n {string.Join("\n ", affectedPaths)}"
+ : $"The game failed to launch: {exception.GetLogSummary()}";
+ this.MonitorForGame.Log(message, LogLevel.Error);
+ }
+ break;
+
// generic exception
default:
this.MonitorForGame.Log($"The game failed to launch: {exception.GetLogSummary()}", LogLevel.Error);
@@ -505,7 +516,7 @@ namespace StardewModdingAPI.Framework.Logging
{
this.LogModWarningGroup(
modsWithWarnings,
- match: mod => mod.HasUnsuppressedWarnings(ModWarning.AccessesConsole, ModWarning.AccessesFilesystem, ModWarning.AccessesShell),
+ match: mod => mod.HasWarnings(ModWarning.AccessesConsole, ModWarning.AccessesFilesystem, ModWarning.AccessesShell),
level: LogLevel.Debug,
heading: "Direct system access",
blurb: new[]
@@ -517,11 +528,11 @@ namespace StardewModdingAPI.Framework.Logging
modLabel: mod =>
{
List<string> labels = new List<string>();
- if (mod.HasUnsuppressedWarnings(ModWarning.AccessesConsole))
+ if (mod.HasWarnings(ModWarning.AccessesConsole))
labels.Add("console");
- if (mod.HasUnsuppressedWarnings(ModWarning.AccessesFilesystem))
+ if (mod.HasWarnings(ModWarning.AccessesFilesystem))
labels.Add("files");
- if (mod.HasUnsuppressedWarnings(ModWarning.AccessesShell))
+ if (mod.HasWarnings(ModWarning.AccessesShell))
labels.Add("shells/processes");
return $"{mod.DisplayName} ({string.Join(", ", labels)})";
@@ -582,7 +593,7 @@ namespace StardewModdingAPI.Framework.Logging
/// <param name="blurb">A detailed explanation of the warning, split into lines.</param>
private void LogModWarningGroup(IModMetadata[] mods, ModWarning warning, LogLevel level, string heading, params string[] blurb)
{
- this.LogModWarningGroup(mods, mod => mod.HasUnsuppressedWarnings(warning), level, heading, blurb);
+ this.LogModWarningGroup(mods, mod => mod.HasWarnings(warning), level, heading, blurb);
}
diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
index 4fae0f44..69535aa5 100644
--- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
+++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
@@ -276,37 +276,40 @@ namespace StardewModdingAPI.Framework.ModLoading
// swap assembly references if needed (e.g. XNA => MonoGame)
bool platformChanged = false;
- for (int i = 0; i < module.AssemblyReferences.Count; i++)
+ if (this.RewriteMods)
{
- // remove old assembly reference
- if (this.AssemblyMap.RemoveNames.Any(name => module.AssemblyReferences[i].Name == name))
+ for (int i = 0; i < module.AssemblyReferences.Count; i++)
{
- this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Rewriting {filename} for OS...");
- platformChanged = true;
- module.AssemblyReferences.RemoveAt(i);
- i--;
+ // remove old assembly reference
+ if (this.AssemblyMap.RemoveNames.Any(name => module.AssemblyReferences[i].Name == name))
+ {
+ this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Rewriting {filename} for OS...");
+ platformChanged = true;
+ module.AssemblyReferences.RemoveAt(i);
+ i--;
+ }
}
- }
- if (platformChanged)
- {
- // add target assembly references
- foreach (AssemblyNameReference target in this.AssemblyMap.TargetReferences.Values)
- module.AssemblyReferences.Add(target);
+ if (platformChanged)
+ {
+ // add target assembly references
+ foreach (AssemblyNameReference target in this.AssemblyMap.TargetReferences.Values)
+ module.AssemblyReferences.Add(target);
- // rewrite type scopes to use target assemblies
- IEnumerable<TypeReference> typeReferences = module.GetTypeReferences().OrderBy(p => p.FullName);
- foreach (TypeReference type in typeReferences)
- this.ChangeTypeScope(type);
+ // rewrite type scopes to use target assemblies
+ IEnumerable<TypeReference> typeReferences = module.GetTypeReferences().OrderBy(p => p.FullName);
+ foreach (TypeReference type in typeReferences)
+ this.ChangeTypeScope(type);
- // rewrite types using custom attributes
- foreach (TypeDefinition type in module.GetTypes())
- {
- foreach (var attr in type.CustomAttributes)
+ // rewrite types using custom attributes
+ foreach (TypeDefinition type in module.GetTypes())
{
- foreach (var conField in attr.ConstructorArguments)
+ foreach (var attr in type.CustomAttributes)
{
- if (conField.Value is TypeReference typeRef)
- this.ChangeTypeScope(typeRef);
+ foreach (var conField in attr.ConstructorArguments)
+ {
+ if (conField.Value is TypeReference typeRef)
+ this.ChangeTypeScope(typeRef);
+ }
}
}
}
diff --git a/src/SMAPI/Framework/ModLoading/ModMetadata.cs b/src/SMAPI/Framework/ModLoading/ModMetadata.cs
index 18d2b112..b4de3d6c 100644
--- a/src/SMAPI/Framework/ModLoading/ModMetadata.cs
+++ b/src/SMAPI/Framework/ModLoading/ModMetadata.cs
@@ -14,6 +14,13 @@ namespace StardewModdingAPI.Framework.ModLoading
internal class ModMetadata : IModMetadata
{
/*********
+ ** Fields
+ *********/
+ /// <summary>The non-error issues with the mod, including warnings suppressed by the data record.</summary>
+ private ModWarning ActualWarnings = ModWarning.None;
+
+
+ /*********
** Accessors
*********/
/// <inheritdoc />
@@ -41,7 +48,7 @@ namespace StardewModdingAPI.Framework.ModLoading
public ModFailReason? FailReason { get; private set; }
/// <inheritdoc />
- public ModWarning Warnings { get; private set; }
+ public ModWarning Warnings => this.ActualWarnings & ~(this.DataRecord?.DataRecord.SuppressWarnings ?? ModWarning.None);
/// <inheritdoc />
public string Error { get; private set; }
@@ -116,7 +123,7 @@ namespace StardewModdingAPI.Framework.ModLoading
/// <inheritdoc />
public IModMetadata SetWarning(ModWarning warning)
{
- this.Warnings |= warning;
+ this.ActualWarnings |= warning;
return this;
}
@@ -218,12 +225,10 @@ namespace StardewModdingAPI.Framework.ModLoading
}
/// <inheritdoc />
- public bool HasUnsuppressedWarnings(params ModWarning[] warnings)
+ public bool HasWarnings(params ModWarning[] warnings)
{
- return warnings.Any(warning =>
- this.Warnings.HasFlag(warning)
- && (this.DataRecord?.DataRecord == null || !this.DataRecord.DataRecord.SuppressWarnings.HasFlag(warning))
- );
+ ModWarning curWarnings = this.Warnings;
+ return warnings.Any(warning => curWarnings.HasFlag(warning));
}
/// <inheritdoc />
diff --git a/src/SMAPI/Framework/Patching/GamePatcher.cs b/src/SMAPI/Framework/Patching/GamePatcher.cs
index 82d7b9c8..ddecda08 100644
--- a/src/SMAPI/Framework/Patching/GamePatcher.cs
+++ b/src/SMAPI/Framework/Patching/GamePatcher.cs
@@ -44,7 +44,7 @@ namespace StardewModdingAPI.Framework.Patching
}
catch (Exception ex)
{
- this.Monitor.Log($"Couldn't apply runtime patch '{patch.Name}' to the game. Some SMAPI features may not work correctly. See log file for details.", LogLevel.Error);
+ this.Monitor.Log($"Couldn't apply runtime patch '{patch.GetType().Name}' to the game. Some SMAPI features may not work correctly. See log file for details.", LogLevel.Error);
this.Monitor.Log(ex.GetLogSummary(), LogLevel.Trace);
}
}
diff --git a/src/SMAPI/Framework/Patching/IHarmonyPatch.cs b/src/SMAPI/Framework/Patching/IHarmonyPatch.cs
index 922243fa..38d30ab2 100644
--- a/src/SMAPI/Framework/Patching/IHarmonyPatch.cs
+++ b/src/SMAPI/Framework/Patching/IHarmonyPatch.cs
@@ -9,9 +9,9 @@ namespace StardewModdingAPI.Framework.Patching
/// <summary>A Harmony patch to apply.</summary>
internal interface IHarmonyPatch
{
- /// <summary>A unique name for this patch.</summary>
- string Name { get; }
-
+ /*********
+ ** Methods
+ *********/
/// <summary>Apply the Harmony patch.</summary>
/// <param name="harmony">The Harmony instance.</param>
#if HARMONY_2
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index 2d783eb2..5df4b61b 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -1054,17 +1054,24 @@ namespace StardewModdingAPI.Framework
LoadStage oldStage = Context.LoadStage;
Context.LoadStage = newStage;
this.Monitor.VerboseLog($"Context: load stage changed to {newStage}");
- if (newStage == LoadStage.None)
- {
- this.Monitor.Log("Context: returned to title");
- this.OnReturnedToTitle();
- }
- // override chatbox
- if (newStage == LoadStage.Loaded)
+ // handle stages
+ switch (newStage)
{
- Game1.onScreenMenus.Remove(Game1.chatBox);
- Game1.onScreenMenus.Add(Game1.chatBox = new SChatBox(this.LogManager.MonitorForGame));
+ case LoadStage.ReturningToTitle:
+ this.Monitor.Log("Context: returning to title");
+ this.OnReturningToTitle();
+ break;
+
+ case LoadStage.None:
+ this.JustReturnedToTitle = true;
+ break;
+
+ case LoadStage.Loaded:
+ // override chatbox
+ Game1.onScreenMenus.Remove(Game1.chatBox);
+ Game1.onScreenMenus.Add(Game1.chatBox = new SChatBox(this.LogManager.MonitorForGame));
+ break;
}
// raise events
@@ -1113,13 +1120,12 @@ namespace StardewModdingAPI.Framework
this.EventManager.DayEnding.RaiseEmpty();
}
- /// <summary>Raised after the player returns to the title screen.</summary>
- private void OnReturnedToTitle()
+ /// <summary>Raised immediately before the player returns to the title screen.</summary>
+ private void OnReturningToTitle()
{
// perform cleanup
this.Multiplayer.CleanupOnMultiplayerExit();
this.ContentCore.OnReturningToTitleScreen();
- this.JustReturnedToTitle = true;
}
/// <summary>Raised before the game exits.</summary>
diff --git a/src/SMAPI/Patches/LoadContextPatch.cs b/src/SMAPI/Patches/LoadContextPatch.cs
index ceda061b..c43d7071 100644
--- a/src/SMAPI/Patches/LoadContextPatch.cs
+++ b/src/SMAPI/Patches/LoadContextPatch.cs
@@ -29,12 +29,8 @@ namespace StardewModdingAPI.Patches
/// <summary>A callback to invoke when the load stage changes.</summary>
private static Action<LoadStage> OnStageChanged;
-
- /*********
- ** Accessors
- *********/
- /// <inheritdoc />
- public string Name => nameof(LoadContextPatch);
+ /// <summary>Whether the game is running running the code in <see cref="Game1.loadForNewGame"/>.</summary>
+ private static bool IsInLoadForNewGame;
/*********
@@ -62,11 +58,24 @@ namespace StardewModdingAPI.Patches
prefix: new HarmonyMethod(this.GetType(), nameof(LoadContextPatch.Before_TitleMenu_CreatedNewCharacter))
);
- // detect CreatedLocations
+ // detect CreatedInitialLocations and SaveAddedLocations
+ harmony.Patch(
+ original: AccessTools.Method(typeof(Game1), nameof(Game1.AddModNPCs)),
+ prefix: new HarmonyMethod(this.GetType(), nameof(LoadContextPatch.Before_Game1_AddModNPCs))
+ );
+
+ // detect CreatedLocations, and track IsInLoadForNewGame
harmony.Patch(
original: AccessTools.Method(typeof(Game1), nameof(Game1.loadForNewGame)),
+ prefix: new HarmonyMethod(this.GetType(), nameof(LoadContextPatch.Before_Game1_LoadForNewGame)),
postfix: new HarmonyMethod(this.GetType(), nameof(LoadContextPatch.After_Game1_LoadForNewGame))
);
+
+ // detect ReturningToTitle
+ harmony.Patch(
+ original: AccessTools.Method(typeof(Game1), nameof(Game1.CleanupReturningToTitle)),
+ prefix: new HarmonyMethod(this.GetType(), nameof(LoadContextPatch.Before_Game1_CleanupReturningToTitle))
+ );
}
@@ -82,16 +91,58 @@ namespace StardewModdingAPI.Patches
return true;
}
+ /// <summary>Called before <see cref="Game1.AddModNPCs"/>.</summary>
+ /// <returns>Returns whether to execute the original method.</returns>
+ /// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks>
+ private static bool Before_Game1_AddModNPCs()
+ {
+ // When this method is called from Game1.loadForNewGame, it happens right after adding the vanilla
+ // locations but before initializing them.
+ if (LoadContextPatch.IsInLoadForNewGame)
+ {
+ LoadContextPatch.OnStageChanged(LoadContextPatch.IsCreating()
+ ? LoadStage.CreatedInitialLocations
+ : LoadStage.SaveAddedLocations
+ );
+ }
+
+ return true;
+ }
+
+ /// <summary>Called before <see cref="Game1.CleanupReturningToTitle"/>.</summary>
+ /// <returns>Returns whether to execute the original method.</returns>
+ /// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks>
+ private static bool Before_Game1_CleanupReturningToTitle()
+ {
+ LoadContextPatch.OnStageChanged(LoadStage.ReturningToTitle);
+ return true;
+ }
+
+ /// <summary>Called before <see cref="Game1.loadForNewGame"/>.</summary>
+ /// <returns>Returns whether to execute the original method.</returns>
+ /// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks>
+ private static bool Before_Game1_LoadForNewGame()
+ {
+ LoadContextPatch.IsInLoadForNewGame = true;
+ return true;
+ }
+
/// <summary>Called after <see cref="Game1.loadForNewGame"/>.</summary>
/// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks>
private static void After_Game1_LoadForNewGame()
{
- bool creating =
- (Game1.currentMinigame is Intro) // creating save with intro
- || (Game1.activeClickableMenu is TitleMenu menu && LoadContextPatch.Reflection.GetField<bool>(menu, "transitioningCharacterCreationMenu").GetValue()); // creating save, skipped intro
+ LoadContextPatch.IsInLoadForNewGame = false;
- if (creating)
+ if (LoadContextPatch.IsCreating())
LoadContextPatch.OnStageChanged(LoadStage.CreatedLocations);
}
+
+ /// <summary>Get whether the save file is currently being created.</summary>
+ private static bool IsCreating()
+ {
+ return
+ (Game1.currentMinigame is Intro) // creating save with intro
+ || (Game1.activeClickableMenu is TitleMenu menu && LoadContextPatch.Reflection.GetField<bool>(menu, "transitioningCharacterCreationMenu").GetValue()); // creating save, skipped intro
+ }
}
}