summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/SMAPI.Mods.ConsoleCommands/manifest.json4
-rw-r--r--src/SMAPI.Mods.ErrorHandler/manifest.json4
-rw-r--r--src/SMAPI.Mods.SaveBackup/manifest.json4
-rw-r--r--src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs1
-rw-r--r--src/SMAPI.Toolkit/ModToolkit.cs2
-rw-r--r--src/SMAPI/Constants.cs4
-rw-r--r--src/SMAPI/Context.cs5
-rw-r--r--src/SMAPI/Framework/Content/AssetInfo.cs4
-rw-r--r--src/SMAPI/Framework/Content/AssetInterceptorChange.cs10
-rw-r--r--src/SMAPI/Framework/ContentCoordinator.cs18
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManager.cs12
-rw-r--r--src/SMAPI/Framework/ContentManagers/ModContentManager.cs2
-rw-r--r--src/SMAPI/Framework/Deprecations/DeprecationManager.cs41
-rw-r--r--src/SMAPI/Framework/Events/ManagedEvent.cs12
-rw-r--r--src/SMAPI/Framework/Logging/LogManager.cs5
-rw-r--r--src/SMAPI/Framework/ModHelpers/CommandHelper.cs2
-rw-r--r--src/SMAPI/Framework/ModHelpers/ModHelper.cs2
-rw-r--r--src/SMAPI/Framework/ModLoading/AssemblyLoader.cs5
-rw-r--r--src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs3
-rw-r--r--src/SMAPI/Framework/ModRegistry.cs9
-rw-r--r--src/SMAPI/Framework/SCore.cs18
-rw-r--r--src/SMAPI/Metadata/InstructionMetadata.cs1
-rw-r--r--src/SMAPI/Utilities/PerScreen.cs2
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