From 0c0f7898f4ff3b2e4afdbdc438674b30be7dacc7 Mon Sep 17 00:00:00 2001 From: TehPers Date: Tue, 28 Jun 2022 16:37:58 -0700 Subject: Search assembly directory for dependencies --- src/SMAPI/Framework/ModLoading/AssemblyLoader.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs index eb940c41..bd29a159 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs @@ -264,8 +264,18 @@ namespace StardewModdingAPI.Framework.ModLoading if (!file.Exists) yield break; // not a local assembly + // add the assembly's directory temporarily if needed + // this is needed by F# mods which bundle FSharp.Core.dll, for example + string? temporarySearchDir = null; + if (file.DirectoryName is not null && !this.AssemblyDefinitionResolver.GetSearchDirectories().Contains(file.DirectoryName)) + { + this.AssemblyDefinitionResolver.AddSearchDirectory(file.DirectoryName); + temporarySearchDir = file.DirectoryName; + } + // read assembly AssemblyDefinition assembly; + try { byte[] assemblyBytes = File.ReadAllBytes(file.FullName); Stream readStream = this.TrackForDisposal(new MemoryStream(assemblyBytes)); @@ -286,6 +296,12 @@ namespace StardewModdingAPI.Framework.ModLoading assembly = this.TrackForDisposal(AssemblyDefinition.ReadAssembly(readStream, new ReaderParameters(ReadingMode.Immediate) { AssemblyResolver = assemblyResolver, InMemory = true })); } } + finally + { + // clean up temporary search directory + if (temporarySearchDir is not null) + this.AssemblyDefinitionResolver.RemoveSearchDirectory(temporarySearchDir); + } // skip if already visited if (visitedAssemblyNames.Contains(assembly.Name.Name)) -- cgit From 1b3a1a48d0d0d7e2423db54f3266cabd80a5a9b3 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 8 Jul 2022 19:02:33 -0400 Subject: refactor assembly resolver to avoid repeatedly copying search directory list --- src/SMAPI/Constants.cs | 4 +- .../ModLoading/AssemblyDefinitionResolver.cs | 70 ++++++++++++++++++++-- src/SMAPI/Framework/ModLoading/AssemblyLoader.cs | 5 +- 3 files changed, 67 insertions(+), 12 deletions(-) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 33468717..442f2ec8 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -244,8 +244,8 @@ namespace StardewModdingAPI internal static void ConfigureAssemblyResolver(AssemblyDefinitionResolver resolver) { // add search paths - resolver.AddSearchDirectory(Constants.GamePath); - resolver.AddSearchDirectory(Constants.InternalFilesPath); + resolver.TryAddSearchDirectory(Constants.GamePath); + resolver.TryAddSearchDirectory(Constants.InternalFilesPath); // add SMAPI explicitly // Normally this would be handled automatically by the search paths, but for some reason there's a specific diff --git a/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs b/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs index b3378ad1..5a850255 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs @@ -4,18 +4,31 @@ using Mono.Cecil; namespace StardewModdingAPI.Framework.ModLoading { /// A minimal assembly definition resolver which resolves references to known assemblies. - internal class AssemblyDefinitionResolver : DefaultAssemblyResolver + internal class AssemblyDefinitionResolver : IAssemblyResolver { /********* ** Fields *********/ + /// The underlying assembly resolver. + private readonly DefaultAssemblyResolverWrapper Resolver = new(); + /// The known assemblies. private readonly IDictionary Lookup = new Dictionary(); + /// The directory paths to search for assemblies. + private readonly HashSet SearchPaths = new(); + /********* ** Public methods *********/ + /// Construct an instance. + public AssemblyDefinitionResolver() + { + foreach (string path in this.Resolver.GetSearchDirectories()) + this.SearchPaths.Add(path); + } + /// Add known assemblies to the resolver. /// The known assemblies. public void Add(params AssemblyDefinition[] assemblies) @@ -29,7 +42,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// The assembly names for which it should be returned. public void AddWithExplicitNames(AssemblyDefinition assembly, params string[] names) { - this.RegisterAssembly(assembly); + this.Resolver.AddAssembly(assembly); foreach (string name in names) this.Lookup[name] = assembly; } @@ -37,18 +50,52 @@ namespace StardewModdingAPI.Framework.ModLoading /// Resolve an assembly reference. /// The assembly name. /// The assembly can't be resolved. - public override AssemblyDefinition Resolve(AssemblyNameReference name) + public AssemblyDefinition Resolve(AssemblyNameReference name) { - return this.ResolveName(name.Name) ?? base.Resolve(name); + return this.ResolveName(name.Name) ?? this.Resolver.Resolve(name); } /// Resolve an assembly reference. /// The assembly name. /// The assembly reader parameters. /// The assembly can't be resolved. - public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) + public AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) { - return this.ResolveName(name.Name) ?? base.Resolve(name, parameters); + return this.ResolveName(name.Name) ?? this.Resolver.Resolve(name, parameters); + } + + /// Add a directory path to search for assemblies, if it's non-null and not already added. + /// The path to search. + /// Returns whether the path was successfully added. + public bool TryAddSearchDirectory(string? path) + { + if (path is not null && this.SearchPaths.Add(path)) + { + this.Resolver.AddSearchDirectory(path); + return true; + } + + return false; + } + + /// Remove a directory path to search for assemblies, if it's non-null. + /// The path to remove. + /// Returns whether the path was in the list and removed. + public bool RemoveSearchDirectory(string? path) + { + if (path is not null && this.SearchPaths.Remove(path)) + { + this.Resolver.RemoveSearchDirectory(path); + return true; + } + + return false; + } + + /// + public void Dispose() + { + this.Resolver.Dispose(); } @@ -63,5 +110,16 @@ namespace StardewModdingAPI.Framework.ModLoading ? match : null; } + + /// An internal wrapper around to allow access to its protected methods. + private class DefaultAssemblyResolverWrapper : DefaultAssemblyResolver + { + /// Add an assembly to the resolver. + /// The assembly to add. + public void AddAssembly(AssemblyDefinition assembly) + { + this.RegisterAssembly(assembly); + } + } } } diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs index bd29a159..01037870 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs @@ -267,11 +267,8 @@ namespace StardewModdingAPI.Framework.ModLoading // add the assembly's directory temporarily if needed // this is needed by F# mods which bundle FSharp.Core.dll, for example string? temporarySearchDir = null; - if (file.DirectoryName is not null && !this.AssemblyDefinitionResolver.GetSearchDirectories().Contains(file.DirectoryName)) - { - this.AssemblyDefinitionResolver.AddSearchDirectory(file.DirectoryName); + if (this.AssemblyDefinitionResolver.TryAddSearchDirectory(file.DirectoryName)) temporarySearchDir = file.DirectoryName; - } // read assembly AssemblyDefinition assembly; -- cgit From 521129ad213da58fda5851f181a81bf661dde9b5 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 9 Jul 2022 00:53:11 -0400 Subject: raise deprecation levels --- docs/release-notes.md | 2 ++ src/SMAPI/Constants.cs | 2 +- src/SMAPI/Framework/Content/AssetInfo.cs | 4 ++-- src/SMAPI/Framework/ModHelpers/CommandHelper.cs | 2 +- src/SMAPI/Framework/ModHelpers/ContentHelper.cs | 8 ++++---- src/SMAPI/Framework/ModHelpers/ModHelper.cs | 2 +- src/SMAPI/Framework/SCore.cs | 6 +++--- src/SMAPI/Utilities/PerScreen.cs | 2 +- 8 files changed, 15 insertions(+), 13 deletions(-) (limited to 'src/SMAPI/Framework') diff --git a/docs/release-notes.md b/docs/release-notes.md index df3949be..13c0dce8 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -14,6 +14,8 @@ * For mod authors: * While loading your mod, SMAPI now searches for indirect dependencies in your mod's folder to support edge cases like F# mods (thanks to TehPers)! + * **Raised deprecation message levels.** + _Deprecation warnings are now player-visible in the SMAPI console as grayed-out `DEBUG` messages. They'll be raised to `WARN` level in SMAPI 3.17 next month, which will be the last major release before SMAPI 4.0._ * For the web UI: * Added log parser warning about performance of PyTK 1.23.0 or earlier. diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index c59af612..7aaa33bb 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -90,7 +90,7 @@ namespace StardewModdingAPI source: null, nounPhrase: $"{nameof(Constants)}.{nameof(Constants.ExecutionPath)}", version: "3.14.0", - severity: DeprecationLevel.Notice + severity: DeprecationLevel.Info ); return Constants.GamePath; diff --git a/src/SMAPI/Framework/Content/AssetInfo.cs b/src/SMAPI/Framework/Content/AssetInfo.cs index 43feed27..af000300 100644 --- a/src/SMAPI/Framework/Content/AssetInfo.cs +++ b/src/SMAPI/Framework/Content/AssetInfo.cs @@ -45,7 +45,7 @@ namespace StardewModdingAPI.Framework.Content source: null, nounPhrase: $"{nameof(IAssetInfo)}.{nameof(IAssetInfo.AssetName)}", version: "3.14.0", - severity: DeprecationLevel.Notice, + severity: DeprecationLevel.Info, unlessStackIncludes: new[] { $"{typeof(AssetInterceptorChange).FullName}.{nameof(AssetInterceptorChange.CanIntercept)}", @@ -84,7 +84,7 @@ namespace StardewModdingAPI.Framework.Content source: null, nounPhrase: $"{nameof(IAssetInfo)}.{nameof(IAssetInfo.AssetNameEquals)}", version: "3.14.0", - severity: DeprecationLevel.Notice, + severity: DeprecationLevel.Info, unlessStackIncludes: new[] { $"{typeof(AssetInterceptorChange).FullName}.{nameof(AssetInterceptorChange.CanIntercept)}", diff --git a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs index 21435f62..b7d4861f 100644 --- a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs @@ -43,7 +43,7 @@ namespace StardewModdingAPI.Framework.ModHelpers source: this.Mod, nounPhrase: $"{nameof(IModHelper)}.{nameof(IModHelper.ConsoleCommands)}.{nameof(ICommandHelper.Trigger)}", version: "3.8.1", - severity: DeprecationLevel.Notice + severity: DeprecationLevel.Info ); return this.CommandManager.Trigger(name, arguments); diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index 9992cb52..0a1633bf 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -62,7 +62,7 @@ namespace StardewModdingAPI.Framework.ModHelpers source: this.Mod, nounPhrase: $"{nameof(IContentHelper)}.{nameof(IContentHelper.AssetLoaders)}", version: "3.14.0", - severity: DeprecationLevel.Notice + severity: DeprecationLevel.Info ); return this.ObservableAssetLoaders; @@ -78,7 +78,7 @@ namespace StardewModdingAPI.Framework.ModHelpers source: this.Mod, nounPhrase: $"{nameof(IContentHelper)}.{nameof(IContentHelper.AssetEditors)}", version: "3.14.0", - severity: DeprecationLevel.Notice + severity: DeprecationLevel.Info ); return this.ObservableAssetEditors; @@ -126,7 +126,7 @@ namespace StardewModdingAPI.Framework.ModHelpers this.Mod, "loading assets from the Content folder with a .xnb file extension", "3.14.0", - DeprecationLevel.Notice + DeprecationLevel.Info ); } @@ -150,7 +150,7 @@ namespace StardewModdingAPI.Framework.ModHelpers this.Mod, "loading XNB files from the mod folder without the .xnb file extension", "3.14.0", - DeprecationLevel.Notice + DeprecationLevel.Info ); return data; } diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index caa66bad..1cdd8536 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -41,7 +41,7 @@ namespace StardewModdingAPI.Framework.ModHelpers source: this.Mod, nounPhrase: $"{nameof(IModHelper)}.{nameof(IModHelper.Content)}", version: "3.14.0", - severity: DeprecationLevel.Notice + severity: DeprecationLevel.Info ); return this.ContentImpl; diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 46d65f6a..1a1bb9f8 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1670,7 +1670,7 @@ namespace StardewModdingAPI.Framework source: metadata, nounPhrase: $"{nameof(IAssetEditor)}", version: "3.14.0", - severity: DeprecationLevel.Notice, + severity: DeprecationLevel.Info, logStackTrace: false ); @@ -1683,7 +1683,7 @@ namespace StardewModdingAPI.Framework source: metadata, nounPhrase: $"{nameof(IAssetLoader)}", version: "3.14.0", - severity: DeprecationLevel.Notice, + severity: DeprecationLevel.Info, logStackTrace: false ); @@ -1715,7 +1715,7 @@ namespace StardewModdingAPI.Framework metadata, $"using {name} without bundling it", "3.14.7", - DeprecationLevel.Notice, + DeprecationLevel.Info, logStackTrace: false ); } diff --git a/src/SMAPI/Utilities/PerScreen.cs b/src/SMAPI/Utilities/PerScreen.cs index 54657ade..468df0bd 100644 --- a/src/SMAPI/Utilities/PerScreen.cs +++ b/src/SMAPI/Utilities/PerScreen.cs @@ -59,7 +59,7 @@ namespace StardewModdingAPI.Utilities null, $"calling the {nameof(PerScreen)} constructor with null", "3.14.0", - DeprecationLevel.Notice + DeprecationLevel.Info ); #else throw new ArgumentNullException(nameof(createNewState)); -- cgit From 7a3b0e180f62e2e3094510e7e12eb64af6089560 Mon Sep 17 00:00:00 2001 From: atravita-mods <94934860+atravita-mods@users.noreply.github.com> Date: Fri, 5 Aug 2022 17:21:53 -0400 Subject: Adds an error message for an invaild png --- src/SMAPI/Framework/ContentManagers/ModContentManager.cs | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index f3cf05d9..c825b38c 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -254,6 +254,8 @@ namespace StardewModdingAPI.Framework.ContentManagers { using FileStream stream = File.OpenRead(file.FullName); using SKBitmap bitmap = SKBitmap.Decode(stream); + if (bitmap is null) + throw new InvalidDataException("{file.FullName} appears not to be a valid image file."); rawPixels = SKPMColor.PreMultiply(bitmap.Pixels); width = bitmap.Width; height = bitmap.Height; -- cgit From 08eafe7d898780e22d01b69e820aaa665f3a2d56 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 8 Aug 2022 19:42:40 -0400 Subject: tweak new error text --- src/SMAPI/Framework/ContentManagers/ModContentManager.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index c825b38c..8ecbc4cc 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -254,8 +254,10 @@ namespace StardewModdingAPI.Framework.ContentManagers { using FileStream stream = File.OpenRead(file.FullName); using SKBitmap bitmap = SKBitmap.Decode(stream); + if (bitmap is null) - throw new InvalidDataException("{file.FullName} appears not to be a valid image file."); + throw new InvalidDataException($"Failed to load {file.FullName}. This doesn't seem to be a valid PNG image."); + rawPixels = SKPMColor.PreMultiply(bitmap.Pixels); width = bitmap.Width; height = bitmap.Height; -- cgit From 352fa4759e0b52ad25bd5d210639f57eabad6c89 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 8 Aug 2022 19:54:07 -0400 Subject: fix error when a mod is both duplicated and missing the DLL --- docs/release-notes.md | 1 + src/SMAPI/Framework/ModLoading/ModResolver.cs | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'src/SMAPI/Framework') diff --git a/docs/release-notes.md b/docs/release-notes.md index 3591c375..080a4f20 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -12,6 +12,7 @@ * Fixed SMAPI on Windows applying different DPI awareness settings than the game (thanks to spacechase0!). * Fixed Linux/macOS installer's color scheme question partly unreadable if the terminal background is dark. * Fixed error message when a mod loads an invalid PNG file (thanks to atravita!). + * Fixed error message when a mod is duplicated, but one of the copies is also missing the DLL file. This now shows the duplicate-mod message instead of the missing-DLL message. * Fixed macOS launcher using Terminal regardless of the system's default terminal (thanks to ishan!). * Fixed best practices in Linux/macOS launcher scripts (thanks to ishan!). * Improved translations. Thanks to KediDili (updated Turkish)! diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs index abc46d47..a487ba28 100644 --- a/src/SMAPI/Framework/ModLoading/ModResolver.cs +++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs @@ -47,7 +47,7 @@ namespace StardewModdingAPI.Framework.ModLoading IModMetadata metadata = new ModMetadata(folder.DisplayName, folder.Directory.FullName, rootPath, manifest, dataRecord, isIgnored: shouldIgnore); if (shouldIgnore) metadata.SetStatus(status, ModFailReason.DisabledByDotConvention, "disabled by dot convention"); - else + else if (metadata.Status == ModMetadataStatus.Failed) metadata.SetStatus(status, ModFailReason.InvalidManifest, folder.ManifestParseErrorText); yield return metadata; @@ -223,8 +223,8 @@ namespace StardewModdingAPI.Framework.ModLoading { foreach (IModMetadata mod in group) { - if (mod.Status == ModMetadataStatus.Failed) - continue; // don't replace metadata error + if (mod.Status == ModMetadataStatus.Failed && mod.FailReason != ModFailReason.InvalidManifest) + continue; string folderList = string.Join(", ", group.Select(p => p.GetRelativePathWithRoot()).OrderBy(p => p)); mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.Duplicate, $"you have multiple copies of this mod installed. To fix this, delete these folders and reinstall the mod: {folderList}."); -- cgit From e376386d250780c50f17c40f82419128b4e7beab Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 8 Aug 2022 21:43:46 -0400 Subject: set error code on exit (#868) --- docs/release-notes.md | 2 ++ src/SMAPI/Framework/ExitState.cs | 15 +++++++++++ src/SMAPI/Framework/SCore.cs | 54 ++++++++++++++++++++++++++-------------- 3 files changed, 53 insertions(+), 18 deletions(-) create mode 100644 src/SMAPI/Framework/ExitState.cs (limited to 'src/SMAPI/Framework') diff --git a/docs/release-notes.md b/docs/release-notes.md index 080a4f20..14421c1a 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -9,6 +9,8 @@ ## Upcoming release * For players: + * SMAPI now sets a success/error code when the game exits. + _This is used by your OS (like Windows) to decide whether to keep the console window open when the game ends._ * Fixed SMAPI on Windows applying different DPI awareness settings than the game (thanks to spacechase0!). * Fixed Linux/macOS installer's color scheme question partly unreadable if the terminal background is dark. * Fixed error message when a mod loads an invalid PNG file (thanks to atravita!). diff --git a/src/SMAPI/Framework/ExitState.cs b/src/SMAPI/Framework/ExitState.cs new file mode 100644 index 00000000..bc022d91 --- /dev/null +++ b/src/SMAPI/Framework/ExitState.cs @@ -0,0 +1,15 @@ +namespace StardewModdingAPI.Framework +{ + /// The SMAPI exit state. + internal enum ExitState + { + /// SMAPI didn't trigger an explicit exit. + None, + + /// The game is exiting normally. + GameExit, + + /// The game is exiting due to an error. + Crash + } +} diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 1a1bb9f8..cbb559c1 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; @@ -50,6 +49,7 @@ using StardewModdingAPI.Utilities; using StardewValley; using StardewValley.Menus; using StardewValley.Objects; +using StardewValley.SDKs; using xTile.Display; using LanguageCode = StardewValley.LocalizedContentManager.LanguageCode; using MiniMonoModHotfix = MonoMod.Utils.MiniMonoModHotfix; @@ -67,8 +67,11 @@ namespace StardewModdingAPI.Framework /**** ** Low-level components ****/ + /// A state which indicates whether SMAPI should exit immediately and any pending initialization should be cancelled. + private ExitState ExitState; + /// Whether the game should exit immediately and any pending initialization should be cancelled. - private bool IsExiting; + private bool IsExiting => this.ExitState != ExitState.None; /// Manages the SMAPI console window and log file. private readonly LogManager LogManager; @@ -297,22 +300,13 @@ namespace StardewModdingAPI.Framework this.IsGameRunning = true; StardewValley.Program.releaseBuild = true; // game's debug logic interferes with SMAPI opening the game window this.Game.Run(); + this.Dispose(isError: false); } catch (Exception ex) { this.LogManager.LogFatalLaunchError(ex); this.LogManager.PressAnyKeyToExit(); - } - finally - { - try - { - this.Dispose(); - } - catch (Exception ex) - { - this.Monitor.Log($"The game ended, but SMAPI wasn't able to dispose correctly. Technical details: {ex}", LogLevel.Error); - } + this.Dispose(isError: true); } } @@ -327,6 +321,14 @@ namespace StardewModdingAPI.Framework /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. [SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract", Justification = "May be disposed before SMAPI is fully initialized.")] public void Dispose() + { + this.Dispose(isError: true); // if we got here, SMAPI didn't detect the exit before it happened + } + + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// Whether the process is exiting due to an error or crash. + [SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract", Justification = "May be disposed before SMAPI is fully initialized.")] + public void Dispose(bool isError) { // skip if already disposed if (this.IsDisposed) @@ -349,13 +351,29 @@ namespace StardewModdingAPI.Framework // dispose core components this.IsGameRunning = false; - this.IsExiting = true; + if (this.ExitState == ExitState.None || isError) + this.ExitState = isError ? ExitState.Crash : ExitState.GameExit; this.ContentCore?.Dispose(); this.Game?.Dispose(); this.LogManager.Dispose(); // dispose last to allow for any last-second log messages - // end game (moved from Game1.OnExiting to let us clean up first) - Process.GetCurrentProcess().Kill(); + // clean up SDK + // This avoids Steam connection errors when it exits unexpectedly. The game avoids this + // by killing the entire process, but we can't set the error code if we do that. + try + { + FieldInfo? field = typeof(StardewValley.Program).GetField("_sdk", BindingFlags.NonPublic | BindingFlags.Static); + SDKHelper? sdk = field?.GetValue(null) as SDKHelper; + sdk?.Shutdown(); + } + catch + { + // well, at least we tried + } + + // end game with error code + // This helps the OS decide whether to keep the window open (e.g. Windows may keep it open on error). + Environment.Exit(this.ExitState == ExitState.Crash ? 1 : 0); } @@ -1250,7 +1268,7 @@ namespace StardewModdingAPI.Framework private void OnGameExiting() { this.Multiplayer.Disconnect(StardewValley.Multiplayer.DisconnectType.ClosedGame); - this.Dispose(); + this.Dispose(isError: false); } /// Raised when a mod network message is received. @@ -2239,7 +2257,7 @@ namespace StardewModdingAPI.Framework this.Monitor.LogFatal(message); this.LogManager.WriteCrashLog(); - this.IsExiting = true; + this.ExitState = ExitState.Crash; this.Game.Exit(); } -- cgit From 5ab87efaa07a0972fd59c88c8aab456a6133329d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 16 Aug 2022 22:03:21 -0400 Subject: log error if mod files are detected directly under Mods folder --- docs/release-notes.md | 1 + src/SMAPI/Framework/SCore.cs | 7 +++++++ 2 files changed, 8 insertions(+) (limited to 'src/SMAPI/Framework') diff --git a/docs/release-notes.md b/docs/release-notes.md index 7526e655..1734ca2d 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -9,6 +9,7 @@ ## Upcoming release * For players: + * Added error message if mod files are detected directly under `Mods` (instead of each mod having its own subfolder). * SMAPI now sets a success/error code when the game exits. _This is used by your OS (like Windows) to decide whether to keep the console window open when the game ends._ * Fixed SMAPI on Windows applying different DPI awareness settings than the game (thanks to spacechase0!). diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index cbb559c1..0f86ed6b 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -405,7 +405,14 @@ namespace StardewModdingAPI.Framework { string[] looseFiles = new DirectoryInfo(this.ModsPath).GetFiles().Select(p => p.Name).ToArray(); if (looseFiles.Any()) + { + if (looseFiles.Any(name => name.Equals("manifest.json", StringComparison.OrdinalIgnoreCase) || name.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))) + { + this.Monitor.Log($"Detected mod files directly inside the '{Path.GetFileName(this.ModsPath)}' folder. These will be ignored. Each mod must have its own subfolder instead.", LogLevel.Error); + } + this.Monitor.Log($" Ignored loose files: {string.Join(", ", looseFiles.OrderBy(p => p, StringComparer.OrdinalIgnoreCase))}"); + } } // load manifests -- cgit From 263130bafcf10e5ce2527eb8643b00052a41f9b5 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 20 Aug 2022 16:15:29 -0400 Subject: fix deprecation notices split into two messages unnecessarily --- src/SMAPI/Framework/Deprecations/DeprecationManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Framework/Deprecations/DeprecationManager.cs b/src/SMAPI/Framework/Deprecations/DeprecationManager.cs index 4597a764..f58f085e 100644 --- a/src/SMAPI/Framework/Deprecations/DeprecationManager.cs +++ b/src/SMAPI/Framework/Deprecations/DeprecationManager.cs @@ -124,7 +124,7 @@ namespace StardewModdingAPI.Framework.Deprecations } // log message - if (level == LogLevel.Trace) + if (level is LogLevel.Trace or LogLevel.Debug) { if (warning.LogStackTrace) message += $"\n{this.GetSimplifiedStackTrace(warning.StackTrace, warning.Mod)}"; -- cgit From f780d140f0c87de9f10d6f55d501c958316548d0 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 20 Aug 2022 16:36:15 -0400 Subject: fix early mod load errors incorrectly suppressed --- src/SMAPI/Framework/ModLoading/ModResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs index a487ba28..c5648c74 100644 --- a/src/SMAPI/Framework/ModLoading/ModResolver.cs +++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs @@ -47,7 +47,7 @@ namespace StardewModdingAPI.Framework.ModLoading IModMetadata metadata = new ModMetadata(folder.DisplayName, folder.Directory.FullName, rootPath, manifest, dataRecord, isIgnored: shouldIgnore); if (shouldIgnore) metadata.SetStatus(status, ModFailReason.DisabledByDotConvention, "disabled by dot convention"); - else if (metadata.Status == ModMetadataStatus.Failed) + else if (status == ModMetadataStatus.Failed) metadata.SetStatus(status, ModFailReason.InvalidManifest, folder.ManifestParseErrorText); yield return metadata; -- cgit