diff options
author | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2021-03-07 20:15:10 -0500 |
---|---|---|
committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2021-03-07 20:15:10 -0500 |
commit | 5399239c2be01396958ed454d6ae56f006d20ca5 (patch) | |
tree | 87683eda0873a0c24d9bcee5d47f1cec896f21bb | |
parent | db011ee751bdfb8bbd9abbeb706966db4c4e2461 (diff) | |
parent | a571f459f59a6ecfdd53e3158ba8d29157598920 (diff) | |
download | SMAPI-5399239c2be01396958ed454d6ae56f006d20ca5.tar.gz SMAPI-5399239c2be01396958ed454d6ae56f006d20ca5.tar.bz2 SMAPI-5399239c2be01396958ed454d6ae56f006d20ca5.zip |
Merge branch 'develop' into stable
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 + } } } |