diff options
Diffstat (limited to 'src')
23 files changed, 110 insertions, 60 deletions
diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json index 0e2b023d..78d26802 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.14.3", + "Version": "3.14.4", "Description": "Adds SMAPI console commands that let you manipulate the game.", "UniqueID": "SMAPI.ConsoleCommands", "EntryDll": "ConsoleCommands.dll", - "MinimumApiVersion": "3.14.3" + "MinimumApiVersion": "3.14.4" } diff --git a/src/SMAPI.Mods.ErrorHandler/manifest.json b/src/SMAPI.Mods.ErrorHandler/manifest.json index f449b3bd..be1030f4 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.14.3", + "Version": "3.14.4", "Description": "Handles some common vanilla errors to log more useful info or avoid breaking the game.", "UniqueID": "SMAPI.ErrorHandler", "EntryDll": "ErrorHandler.dll", - "MinimumApiVersion": "3.14.3" + "MinimumApiVersion": "3.14.4" } diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json index 23e241b5..8a50162e 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.14.3", + "Version": "3.14.4", "Description": "Automatically backs up all your saves once per day into its folder.", "UniqueID": "SMAPI.SaveBackup", "EntryDll": "SaveBackup.dll", - "MinimumApiVersion": "3.14.3" + "MinimumApiVersion": "3.14.4" } diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs b/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs index afebba87..cf804df4 100644 --- a/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs +++ b/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs @@ -19,6 +19,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData PatchesGame = 4, /// <summary>The mod uses the <c>dynamic</c> keyword which won't work on Linux/macOS.</summary> + [Obsolete("This value is no longer used by SMAPI and will be removed in the upcoming SMAPI 4.0.0.")] UsesDynamic = 8, /// <summary>The mod references specialized 'unvalidated update tick' events which may impact stability.</summary> diff --git a/src/SMAPI.Toolkit/ModToolkit.cs b/src/SMAPI.Toolkit/ModToolkit.cs index ce14b057..0df75a31 100644 --- a/src/SMAPI.Toolkit/ModToolkit.cs +++ b/src/SMAPI.Toolkit/ModToolkit.cs @@ -57,7 +57,7 @@ namespace StardewModdingAPI.Toolkit /// <summary>Extract mod metadata from the wiki compatibility list.</summary> public async Task<WikiModList> GetWikiCompatibilityListAsync() { - WikiClient client = new(this.UserAgent); + using WikiClient client = new(this.UserAgent); return await client.FetchModsAsync(); } diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index ef729f4f..357b8db8 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -50,7 +50,7 @@ namespace StardewModdingAPI internal static int? LogScreenId { get; set; } /// <summary>SMAPI's current raw semantic version.</summary> - internal static string RawApiVersion = "3.14.3"; + internal static string RawApiVersion = "3.14.4"; } /// <summary>Contains SMAPI's constants and assumptions.</summary> @@ -84,7 +84,7 @@ namespace StardewModdingAPI get { SCore.DeprecationManager.Warn( - source: SCore.DeprecationManager.GetModFromStack(), + source: null, nounPhrase: $"{nameof(Constants)}.{nameof(Constants.ExecutionPath)}", version: "3.14.0", severity: DeprecationLevel.Notice diff --git a/src/SMAPI/Context.cs b/src/SMAPI/Context.cs index aa4ecf35..c822908e 100644 --- a/src/SMAPI/Context.cs +++ b/src/SMAPI/Context.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using StardewModdingAPI.Enums; using StardewModdingAPI.Events; +using StardewModdingAPI.Framework; using StardewModdingAPI.Utilities; using StardewValley; using StardewValley.Menus; @@ -41,6 +42,10 @@ namespace StardewModdingAPI /// <summary>Whether the in-game world is completely unloaded and not in the process of being loaded. The world may still exist in memory at this point, but should be ignored.</summary> internal static bool IsWorldFullyUnloaded => Context.LoadStage is LoadStage.ReturningToTitle or LoadStage.None; + /// <summary>If SMAPI is currently waiting for mod code, the mods to which it belongs (with the most recent at the top of the stack).</summary> + /// <remarks><strong>This is heuristic only.</strong> It provides a quick way to identify the most likely mod for deprecation warnings, but it should be followed with a more accurate check if needed.</remarks> + internal static Stack<IModMetadata> HeuristicModsRunningCode { get; } = new(); + /********* ** Accessors diff --git a/src/SMAPI/Framework/Content/AssetInfo.cs b/src/SMAPI/Framework/Content/AssetInfo.cs index a4f0a408..773e3126 100644 --- a/src/SMAPI/Framework/Content/AssetInfo.cs +++ b/src/SMAPI/Framework/Content/AssetInfo.cs @@ -36,7 +36,7 @@ namespace StardewModdingAPI.Framework.Content get { SCore.DeprecationManager.Warn( - source: SCore.DeprecationManager.GetModFromStack(), + source: null, nounPhrase: $"{nameof(IAssetInfo)}.{nameof(IAssetInfo.AssetName)}", version: "3.14.0", severity: DeprecationLevel.Notice, @@ -76,7 +76,7 @@ namespace StardewModdingAPI.Framework.Content public bool AssetNameEquals(string path) { SCore.DeprecationManager.Warn( - source: SCore.DeprecationManager.GetModFromStack(), + source: null, nounPhrase: $"{nameof(IAssetInfo)}.{nameof(IAssetInfo.AssetNameEquals)}", version: "3.14.0", severity: DeprecationLevel.Notice, diff --git a/src/SMAPI/Framework/Content/AssetInterceptorChange.cs b/src/SMAPI/Framework/Content/AssetInterceptorChange.cs index fc8199e8..f3d4f3f4 100644 --- a/src/SMAPI/Framework/Content/AssetInterceptorChange.cs +++ b/src/SMAPI/Framework/Content/AssetInterceptorChange.cs @@ -64,6 +64,7 @@ namespace StardewModdingAPI.Framework.Content // check edit if (this.Instance is IAssetEditor editor) { + Context.HeuristicModsRunningCode.Push(this.Mod); try { if (editor.CanEdit<TAsset>(asset)) @@ -73,11 +74,16 @@ namespace StardewModdingAPI.Framework.Content { this.Mod.LogAsMod($"Mod failed when checking whether it could edit asset '{asset.Name}'. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); } + finally + { + Context.HeuristicModsRunningCode.TryPop(out _); + } } // check load if (this.Instance is IAssetLoader loader) { + Context.HeuristicModsRunningCode.Push(this.Mod); try { if (loader.CanLoad<TAsset>(asset)) @@ -87,6 +93,10 @@ namespace StardewModdingAPI.Framework.Content { this.Mod.LogAsMod($"Mod failed when checking whether it could load asset '{asset.Name}'. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); } + finally + { + Context.HeuristicModsRunningCode.TryPop(out _); + } } return false; diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index a24581a0..2b13f57a 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -595,6 +595,7 @@ namespace StardewModdingAPI.Framework foreach (ModLinked<IAssetLoader> loader in this.Loaders) { // check if loader applies + Context.HeuristicModsRunningCode.Push(loader.Mod); try { if (!loader.Data.CanLoad<T>(legacyInfo)) @@ -605,6 +606,10 @@ namespace StardewModdingAPI.Framework loader.Mod.LogAsMod($"Mod failed when checking whether it could load asset '{legacyInfo.Name}', and will be ignored. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); continue; } + finally + { + Context.HeuristicModsRunningCode.TryPop(out _); + } // add operation group ??= new AssetOperationGroup(new List<AssetLoadOperation>(), new List<AssetEditOperation>()); @@ -617,9 +622,7 @@ namespace StardewModdingAPI.Framework Mod: loader.Mod, OnBehalfOf: null, Priority: AssetLoadPriority.Exclusive, - GetData: assetInfo => loader.Data.Load<T>( - this.GetLegacyAssetInfo(assetInfo) - ) + GetData: assetInfo => loader.Data.Load<T>(this.GetLegacyAssetInfo(assetInfo)) ) ) ); @@ -629,6 +632,7 @@ namespace StardewModdingAPI.Framework foreach (var editor in this.Editors) { // check if editor applies + Context.HeuristicModsRunningCode.Push(editor.Mod); try { if (!editor.Data.CanEdit<T>(legacyInfo)) @@ -639,6 +643,10 @@ namespace StardewModdingAPI.Framework editor.Mod.LogAsMod($"Mod crashed when checking whether it could edit asset '{legacyInfo.Name}', and will be ignored. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); continue; } + finally + { + Context.HeuristicModsRunningCode.TryPop(out _); + } // HACK // @@ -672,9 +680,7 @@ namespace StardewModdingAPI.Framework Mod: editor.Mod, OnBehalfOf: null, Priority: priority, - ApplyEdit: assetData => editor.Data.Edit<T>( - this.GetLegacyAssetData(assetData) - ) + ApplyEdit: assetData => editor.Data.Edit<T>(this.GetLegacyAssetData(assetData)) ) ) ); diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index 2aa50542..1c603f85 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -177,6 +177,7 @@ namespace StardewModdingAPI.Framework.ContentManagers // fetch asset from loader IModMetadata mod = loader.Mod; T data; + Context.HeuristicModsRunningCode.Push(loader.Mod); try { data = (T)loader.GetData(info); @@ -187,6 +188,10 @@ namespace StardewModdingAPI.Framework.ContentManagers mod.LogAsMod($"Mod crashed when loading asset '{info.Name}'{this.GetOnBehalfOfLabel(loader.OnBehalfOf)}. SMAPI will use the default asset instead. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); return null; } + finally + { + Context.HeuristicModsRunningCode.TryPop(out _); + } // return matched asset return this.TryFixAndValidateLoadedAsset(info, data, loader) @@ -229,6 +234,7 @@ namespace StardewModdingAPI.Framework.ContentManagers // try edit object prevAsset = asset.Data; + Context.HeuristicModsRunningCode.Push(editor.Mod); try { editor.ApplyEdit(asset); @@ -238,9 +244,13 @@ namespace StardewModdingAPI.Framework.ContentManagers { mod.LogAsMod($"Mod crashed when editing asset '{info.Name}'{this.GetOnBehalfOfLabel(editor.OnBehalfOf)}, which may cause errors in-game. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); } + finally + { + Context.HeuristicModsRunningCode.TryPop(out _); + } // validate edit - // ReSharper disable once ConditionIsAlwaysTrueOrFalse -- it's only guaranteed non-null after this method + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- it's only guaranteed non-null after this method if (asset.Data == null) { mod.LogAsMod($"Mod incorrectly set asset '{info.Name}'{this.GetOnBehalfOfLabel(editor.OnBehalfOf)} to a null value; ignoring override.", LogLevel.Warn); diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 85e109c8..38a21383 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -121,7 +121,7 @@ namespace StardewModdingAPI.Framework.ContentManagers } catch (Exception ex) when (ex is not SContentLoadException) { - throw this.GetLoadError(assetName, ContentLoadErrorType.Other, "an unexpected occurred.", ex); + throw this.GetLoadError(assetName, ContentLoadErrorType.Other, "an unexpected error occurred.", ex); } // track & return asset diff --git a/src/SMAPI/Framework/Deprecations/DeprecationManager.cs b/src/SMAPI/Framework/Deprecations/DeprecationManager.cs index 288abde2..3bbbd7b8 100644 --- a/src/SMAPI/Framework/Deprecations/DeprecationManager.cs +++ b/src/SMAPI/Framework/Deprecations/DeprecationManager.cs @@ -37,13 +37,6 @@ namespace StardewModdingAPI.Framework.Deprecations this.ModRegistry = modRegistry; } - /// <summary>Get a mod for the closest assembly registered as a source of deprecation warnings.</summary> - /// <returns>Returns the source name, or <c>null</c> if no registered assemblies were found.</returns> - public IModMetadata? GetModFromStack() - { - return this.ModRegistry.GetFromStack(); - } - /// <summary>Get a mod from its unique ID.</summary> /// <param name="modId">The mod's unique ID.</param> public IModMetadata? GetMod(string modId) @@ -52,7 +45,7 @@ namespace StardewModdingAPI.Framework.Deprecations } /// <summary>Log a deprecation warning.</summary> - /// <param name="source">The mod which used the deprecated code, if known.</param> + /// <param name="source">The mod which used the deprecated code, or <c>null</c> to get it heuristically. Note that getting it heuristically is very slow in some cases, and should be avoided if at all possible.</param> /// <param name="nounPhrase">A noun phrase describing what is deprecated.</param> /// <param name="version">The SMAPI version which deprecated it.</param> /// <param name="severity">How deprecated the code is.</param> @@ -60,18 +53,38 @@ namespace StardewModdingAPI.Framework.Deprecations /// <param name="logStackTrace">Whether to log a stack trace showing where the deprecated code is in the mod.</param> public void Warn(IModMetadata? source, string nounPhrase, string version, DeprecationLevel severity, string[]? unlessStackIncludes = null, bool logStackTrace = true) { + // get heuristic source + // The call stack is usually the most reliable way to get the source if it's unknown. This is *very* slow + // though, especially before we check whether this is a duplicate warning. The initial cache check uses a + // quick heuristic method if at all possible to avoid that. + IModMetadata? heuristicSource = source; + ImmutableStackTrace? stack = null; + if (heuristicSource is null) + Context.HeuristicModsRunningCode.TryPeek(out heuristicSource); + if (heuristicSource is null) + { + stack = ImmutableStackTrace.Get(skipFrames: 1); + heuristicSource = this.ModRegistry.GetFromStack(stack.GetFrames()); + } + // skip if already warned - string cacheKey = $"{source?.DisplayName ?? "<unknown>"}::{nounPhrase}::{version}"; + string cacheKey = $"{heuristicSource?.Manifest.UniqueID ?? "<unknown>"}::{nounPhrase}::{version}"; if (this.LoggedDeprecations.Contains(cacheKey)) return; + this.LoggedDeprecations.Add(cacheKey); - // warn if valid - ImmutableStackTrace stack = ImmutableStackTrace.Get(skipFrames: 1); - if (!this.ShouldSuppress(stack, unlessStackIncludes)) + // get more accurate source + if (stack is not null) + source ??= heuristicSource!; + else { - this.LoggedDeprecations.Add(cacheKey); - this.QueuedWarnings.Add(new DeprecationWarning(source, nounPhrase, version, severity, stack, logStackTrace)); + stack ??= ImmutableStackTrace.Get(skipFrames: 1); + source ??= this.ModRegistry.GetFromStack(stack.GetFrames()); } + + // log unless suppressed + if (!this.ShouldSuppress(stack, unlessStackIncludes)) + this.QueuedWarnings.Add(new DeprecationWarning(source, nounPhrase, version, severity, stack, logStackTrace)); } /// <summary>A placeholder method used to track deprecated code for which a separate warning will be shown.</summary> diff --git a/src/SMAPI/Framework/Events/ManagedEvent.cs b/src/SMAPI/Framework/Events/ManagedEvent.cs index 8a3ca839..a72d8d04 100644 --- a/src/SMAPI/Framework/Events/ManagedEvent.cs +++ b/src/SMAPI/Framework/Events/ManagedEvent.cs @@ -104,6 +104,8 @@ namespace StardewModdingAPI.Framework.Events // raise event foreach (ManagedEventHandler<TEventArgs> handler in this.GetHandlers()) { + Context.HeuristicModsRunningCode.Push(handler.SourceMod); + try { handler.Handler(null, args); @@ -112,6 +114,10 @@ namespace StardewModdingAPI.Framework.Events { this.LogError(handler, ex); } + finally + { + Context.HeuristicModsRunningCode.TryPop(out _); + } } } @@ -126,6 +132,8 @@ namespace StardewModdingAPI.Framework.Events // raise event foreach (ManagedEventHandler<TEventArgs> handler in this.GetHandlers()) { + Context.HeuristicModsRunningCode.Push(handler.SourceMod); + try { invoke(handler.SourceMod, args => handler.Handler(null, args)); @@ -134,6 +142,10 @@ namespace StardewModdingAPI.Framework.Events { this.LogError(handler, ex); } + finally + { + Context.HeuristicModsRunningCode.TryPop(out _); + } } } diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs index b94807b5..ed5b6959 100644 --- a/src/SMAPI/Framework/Logging/LogManager.cs +++ b/src/SMAPI/Framework/Logging/LogManager.cs @@ -511,11 +511,6 @@ namespace StardewModdingAPI.Framework.Logging "These mods have no update keys in their manifest. SMAPI may not notify you about updates for these", "mods. Consider notifying the mod authors about this problem." ); - - // not crossplatform - this.LogModWarningGroup(modsWithWarnings, ModWarning.UsesDynamic, LogLevel.Debug, "Not crossplatform", - "These mods use the 'dynamic' keyword, and won't work on Linux/macOS." - ); } } diff --git a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs index f39ed42e..ddbd618a 100644 --- a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs @@ -37,7 +37,7 @@ namespace StardewModdingAPI.Framework.ModHelpers public bool Trigger(string name, string[] arguments) { SCore.DeprecationManager.Warn( - source: SCore.DeprecationManager.GetMod(this.ModID), + source: this.Mod, nounPhrase: $"{nameof(IModHelper)}.{nameof(IModHelper.ConsoleCommands)}.{nameof(ICommandHelper.Trigger)}", version: "3.8.1", severity: DeprecationLevel.Notice diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index 008195d9..48973691 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -33,7 +33,7 @@ namespace StardewModdingAPI.Framework.ModHelpers get { SCore.DeprecationManager.Warn( - source: SCore.DeprecationManager.GetMod(this.ModID), + source: this.Mod, nounPhrase: $"{nameof(IModHelper)}.{nameof(IModHelper.Content)}", version: "3.14.0", severity: DeprecationLevel.Notice diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs index a6756e0e..fb5ebc01 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs @@ -414,11 +414,6 @@ namespace StardewModdingAPI.Framework.ModLoading mod.SetWarning(ModWarning.UsesUnvalidatedUpdateTick); break; - case InstructionHandleResult.DetectedDynamic: - template = $"{logPrefix}Detected 'dynamic' keyword ($phrase) in assembly {filename}."; - mod.SetWarning(ModWarning.UsesDynamic); - break; - case InstructionHandleResult.DetectedConsoleAccess: template = $"{logPrefix}Detected direct console access ($phrase) in assembly {filename}."; mod.SetWarning(ModWarning.AccessesConsole); diff --git a/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs b/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs index baffc50e..e3f108cb 100644 --- a/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs +++ b/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs @@ -20,9 +20,6 @@ namespace StardewModdingAPI.Framework.ModLoading /// <summary>The instruction is compatible, but affects the save serializer in a way that may make saves unloadable without the mod.</summary> DetectedSaveSerializer, - /// <summary>The instruction is compatible, but uses the <c>dynamic</c> keyword which won't work on Linux/macOS.</summary> - DetectedDynamic, - /// <summary>The instruction is compatible, but references <see cref="ISpecializedEvents.UnvalidatedUpdateTicking"/> or <see cref="ISpecializedEvents.UnvalidatedUpdateTicked"/> which may impact stability.</summary> DetectedUnvalidatedUpdateTick, diff --git a/src/SMAPI/Framework/ModRegistry.cs b/src/SMAPI/Framework/ModRegistry.cs index 1ae5643f..1b0a076f 100644 --- a/src/SMAPI/Framework/ModRegistry.cs +++ b/src/SMAPI/Framework/ModRegistry.cs @@ -99,14 +99,10 @@ namespace StardewModdingAPI.Framework } /// <summary>Get the mod metadata from the closest assembly registered as a source of deprecation warnings.</summary> + /// <param name="frames">The call stack to analyze.</param> /// <returns>Returns the mod's metadata, or <c>null</c> if no registered assemblies were found.</returns> - public IModMetadata? GetFromStack() + public IModMetadata? GetFromStack(StackFrame[] frames) { - // get stack frames - StackTrace stack = new(); - StackFrame[] frames = stack.GetFrames(); - - // search stack for a source assembly foreach (StackFrame frame in frames) { IModMetadata? mod = this.GetFrom(frame); @@ -114,7 +110,6 @@ namespace StardewModdingAPI.Framework return mod; } - // no known assembly found return null; } } diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 5ae4fdbb..7042e83a 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1677,6 +1677,7 @@ namespace StardewModdingAPI.Framework #pragma warning restore CS0612, CS0618 // call entry method + Context.HeuristicModsRunningCode.Push(metadata); try { IMod mod = metadata.Mod!; @@ -1705,6 +1706,7 @@ namespace StardewModdingAPI.Framework { this.Monitor.Log($"Failed loading mod-provided API for {metadata.DisplayName}. Integrations with other mods may not work. Error: {ex.GetLogSummary()}", LogLevel.Error); } + Context.HeuristicModsRunningCode.TryPop(out _); } // unlock mod integrations @@ -1852,7 +1854,7 @@ namespace StardewModdingAPI.Framework try { // get mod instance - if (!this.TryLoadModEntry(modAssembly, out Mod? modEntry, out errorReasonPhrase)) + if (!this.TryLoadModEntry(mod, modAssembly, out Mod? modEntry, out errorReasonPhrase)) { failReason = ModFailReason.LoadFailed; return false; @@ -1954,11 +1956,12 @@ namespace StardewModdingAPI.Framework } /// <summary>Load a mod's entry class.</summary> + /// <param name="metadata">The mod metadata whose entry class is being loaded.</param> /// <param name="modAssembly">The mod assembly.</param> /// <param name="mod">The loaded instance.</param> /// <param name="error">The error indicating why loading failed (if applicable).</param> /// <returns>Returns whether the mod entry class was successfully loaded.</returns> - private bool TryLoadModEntry(Assembly modAssembly, [NotNullWhen(true)] out Mod? mod, [NotNullWhen(false)] out string? error) + private bool TryLoadModEntry(IModMetadata metadata, Assembly modAssembly, [NotNullWhen(true)] out Mod? mod, [NotNullWhen(false)] out string? error) { mod = null; @@ -1976,7 +1979,16 @@ namespace StardewModdingAPI.Framework } // get implementation - mod = (Mod?)modAssembly.CreateInstance(modEntries[0].ToString()); + Context.HeuristicModsRunningCode.Push(metadata); + try + { + mod = (Mod?)modAssembly.CreateInstance(modEntries[0].ToString()); + } + finally + { + Context.HeuristicModsRunningCode.TryPop(out _); + } + if (mod == null) { error = "its entry class couldn't be instantiated."; diff --git a/src/SMAPI/Metadata/InstructionMetadata.cs b/src/SMAPI/Metadata/InstructionMetadata.cs index 9b56f963..4d512546 100644 --- a/src/SMAPI/Metadata/InstructionMetadata.cs +++ b/src/SMAPI/Metadata/InstructionMetadata.cs @@ -67,7 +67,6 @@ namespace StardewModdingAPI.Metadata /**** ** detect code which may impact game stability ****/ - yield return new TypeFinder("System.Runtime.CompilerServices.CallSite", InstructionHandleResult.DetectedDynamic); yield return new FieldFinder(typeof(SaveGame).FullName!, new[] { nameof(SaveGame.serializer), nameof(SaveGame.farmerSerializer), nameof(SaveGame.locationSerializer) }, InstructionHandleResult.DetectedSaveSerializer); yield return new EventFinder(typeof(ISpecializedEvents).FullName!, new[] { nameof(ISpecializedEvents.UnvalidatedUpdateTicked), nameof(ISpecializedEvents.UnvalidatedUpdateTicking) }, InstructionHandleResult.DetectedUnvalidatedUpdateTick); diff --git a/src/SMAPI/Utilities/PerScreen.cs b/src/SMAPI/Utilities/PerScreen.cs index 1c4c56fe..b86310b8 100644 --- a/src/SMAPI/Utilities/PerScreen.cs +++ b/src/SMAPI/Utilities/PerScreen.cs @@ -97,7 +97,7 @@ namespace StardewModdingAPI.Utilities if (!nullExpected) { SCore.DeprecationManager.Warn( - SCore.DeprecationManager.GetModFromStack(), + null, $"calling the {nameof(PerScreen<T>)} constructor with null", "3.14.0", DeprecationLevel.Notice |