Merge branch 'develop' into stable
This commit is contained in:
commit
5731b015a0
|
@ -1,7 +1,7 @@
|
||||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!--set general build properties -->
|
<!--set general build properties -->
|
||||||
<Version>3.14.3</Version>
|
<Version>3.14.4</Version>
|
||||||
<Product>SMAPI</Product>
|
<Product>SMAPI</Product>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths>
|
<AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths>
|
||||||
|
|
|
@ -1,6 +1,16 @@
|
||||||
← [README](README.md)
|
← [README](README.md)
|
||||||
|
|
||||||
# Release notes
|
# Release notes
|
||||||
|
## 3.14.4
|
||||||
|
Released 15 May 2022 for Stardew Valley 1.5.6 or later.
|
||||||
|
|
||||||
|
* For players:
|
||||||
|
* Improved performance for mods using deprecated APIs.
|
||||||
|
|
||||||
|
* For mod authors:
|
||||||
|
* Removed warning for mods which use `dynamic`.
|
||||||
|
_This no longer causes errors on Linux/macOS after Stardew Valley 1.5.5._
|
||||||
|
|
||||||
## 3.14.3
|
## 3.14.3
|
||||||
Released 12 May 2022 for Stardew Valley 1.5.6 or later.
|
Released 12 May 2022 for Stardew Valley 1.5.6 or later.
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"Name": "Console Commands",
|
"Name": "Console Commands",
|
||||||
"Author": "SMAPI",
|
"Author": "SMAPI",
|
||||||
"Version": "3.14.3",
|
"Version": "3.14.4",
|
||||||
"Description": "Adds SMAPI console commands that let you manipulate the game.",
|
"Description": "Adds SMAPI console commands that let you manipulate the game.",
|
||||||
"UniqueID": "SMAPI.ConsoleCommands",
|
"UniqueID": "SMAPI.ConsoleCommands",
|
||||||
"EntryDll": "ConsoleCommands.dll",
|
"EntryDll": "ConsoleCommands.dll",
|
||||||
"MinimumApiVersion": "3.14.3"
|
"MinimumApiVersion": "3.14.4"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"Name": "Error Handler",
|
"Name": "Error Handler",
|
||||||
"Author": "SMAPI",
|
"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.",
|
"Description": "Handles some common vanilla errors to log more useful info or avoid breaking the game.",
|
||||||
"UniqueID": "SMAPI.ErrorHandler",
|
"UniqueID": "SMAPI.ErrorHandler",
|
||||||
"EntryDll": "ErrorHandler.dll",
|
"EntryDll": "ErrorHandler.dll",
|
||||||
"MinimumApiVersion": "3.14.3"
|
"MinimumApiVersion": "3.14.4"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"Name": "Save Backup",
|
"Name": "Save Backup",
|
||||||
"Author": "SMAPI",
|
"Author": "SMAPI",
|
||||||
"Version": "3.14.3",
|
"Version": "3.14.4",
|
||||||
"Description": "Automatically backs up all your saves once per day into its folder.",
|
"Description": "Automatically backs up all your saves once per day into its folder.",
|
||||||
"UniqueID": "SMAPI.SaveBackup",
|
"UniqueID": "SMAPI.SaveBackup",
|
||||||
"EntryDll": "SaveBackup.dll",
|
"EntryDll": "SaveBackup.dll",
|
||||||
"MinimumApiVersion": "3.14.3"
|
"MinimumApiVersion": "3.14.4"
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
|
||||||
PatchesGame = 4,
|
PatchesGame = 4,
|
||||||
|
|
||||||
/// <summary>The mod uses the <c>dynamic</c> keyword which won't work on Linux/macOS.</summary>
|
/// <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,
|
UsesDynamic = 8,
|
||||||
|
|
||||||
/// <summary>The mod references specialized 'unvalidated update tick' events which may impact stability.</summary>
|
/// <summary>The mod references specialized 'unvalidated update tick' events which may impact stability.</summary>
|
||||||
|
|
|
@ -57,7 +57,7 @@ namespace StardewModdingAPI.Toolkit
|
||||||
/// <summary>Extract mod metadata from the wiki compatibility list.</summary>
|
/// <summary>Extract mod metadata from the wiki compatibility list.</summary>
|
||||||
public async Task<WikiModList> GetWikiCompatibilityListAsync()
|
public async Task<WikiModList> GetWikiCompatibilityListAsync()
|
||||||
{
|
{
|
||||||
WikiClient client = new(this.UserAgent);
|
using WikiClient client = new(this.UserAgent);
|
||||||
return await client.FetchModsAsync();
|
return await client.FetchModsAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ namespace StardewModdingAPI
|
||||||
internal static int? LogScreenId { get; set; }
|
internal static int? LogScreenId { get; set; }
|
||||||
|
|
||||||
/// <summary>SMAPI's current raw semantic version.</summary>
|
/// <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>
|
/// <summary>Contains SMAPI's constants and assumptions.</summary>
|
||||||
|
@ -84,7 +84,7 @@ namespace StardewModdingAPI
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
SCore.DeprecationManager.Warn(
|
SCore.DeprecationManager.Warn(
|
||||||
source: SCore.DeprecationManager.GetModFromStack(),
|
source: null,
|
||||||
nounPhrase: $"{nameof(Constants)}.{nameof(Constants.ExecutionPath)}",
|
nounPhrase: $"{nameof(Constants)}.{nameof(Constants.ExecutionPath)}",
|
||||||
version: "3.14.0",
|
version: "3.14.0",
|
||||||
severity: DeprecationLevel.Notice
|
severity: DeprecationLevel.Notice
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using StardewModdingAPI.Enums;
|
using StardewModdingAPI.Enums;
|
||||||
using StardewModdingAPI.Events;
|
using StardewModdingAPI.Events;
|
||||||
|
using StardewModdingAPI.Framework;
|
||||||
using StardewModdingAPI.Utilities;
|
using StardewModdingAPI.Utilities;
|
||||||
using StardewValley;
|
using StardewValley;
|
||||||
using StardewValley.Menus;
|
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>
|
/// <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;
|
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
|
** Accessors
|
||||||
|
|
|
@ -36,7 +36,7 @@ namespace StardewModdingAPI.Framework.Content
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
SCore.DeprecationManager.Warn(
|
SCore.DeprecationManager.Warn(
|
||||||
source: SCore.DeprecationManager.GetModFromStack(),
|
source: null,
|
||||||
nounPhrase: $"{nameof(IAssetInfo)}.{nameof(IAssetInfo.AssetName)}",
|
nounPhrase: $"{nameof(IAssetInfo)}.{nameof(IAssetInfo.AssetName)}",
|
||||||
version: "3.14.0",
|
version: "3.14.0",
|
||||||
severity: DeprecationLevel.Notice,
|
severity: DeprecationLevel.Notice,
|
||||||
|
@ -76,7 +76,7 @@ namespace StardewModdingAPI.Framework.Content
|
||||||
public bool AssetNameEquals(string path)
|
public bool AssetNameEquals(string path)
|
||||||
{
|
{
|
||||||
SCore.DeprecationManager.Warn(
|
SCore.DeprecationManager.Warn(
|
||||||
source: SCore.DeprecationManager.GetModFromStack(),
|
source: null,
|
||||||
nounPhrase: $"{nameof(IAssetInfo)}.{nameof(IAssetInfo.AssetNameEquals)}",
|
nounPhrase: $"{nameof(IAssetInfo)}.{nameof(IAssetInfo.AssetNameEquals)}",
|
||||||
version: "3.14.0",
|
version: "3.14.0",
|
||||||
severity: DeprecationLevel.Notice,
|
severity: DeprecationLevel.Notice,
|
||||||
|
|
|
@ -64,6 +64,7 @@ namespace StardewModdingAPI.Framework.Content
|
||||||
// check edit
|
// check edit
|
||||||
if (this.Instance is IAssetEditor editor)
|
if (this.Instance is IAssetEditor editor)
|
||||||
{
|
{
|
||||||
|
Context.HeuristicModsRunningCode.Push(this.Mod);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (editor.CanEdit<TAsset>(asset))
|
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);
|
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
|
// check load
|
||||||
if (this.Instance is IAssetLoader loader)
|
if (this.Instance is IAssetLoader loader)
|
||||||
{
|
{
|
||||||
|
Context.HeuristicModsRunningCode.Push(this.Mod);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (loader.CanLoad<TAsset>(asset))
|
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);
|
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;
|
return false;
|
||||||
|
|
|
@ -595,6 +595,7 @@ namespace StardewModdingAPI.Framework
|
||||||
foreach (ModLinked<IAssetLoader> loader in this.Loaders)
|
foreach (ModLinked<IAssetLoader> loader in this.Loaders)
|
||||||
{
|
{
|
||||||
// check if loader applies
|
// check if loader applies
|
||||||
|
Context.HeuristicModsRunningCode.Push(loader.Mod);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!loader.Data.CanLoad<T>(legacyInfo))
|
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);
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Context.HeuristicModsRunningCode.TryPop(out _);
|
||||||
|
}
|
||||||
|
|
||||||
// add operation
|
// add operation
|
||||||
group ??= new AssetOperationGroup(new List<AssetLoadOperation>(), new List<AssetEditOperation>());
|
group ??= new AssetOperationGroup(new List<AssetLoadOperation>(), new List<AssetEditOperation>());
|
||||||
|
@ -617,9 +622,7 @@ namespace StardewModdingAPI.Framework
|
||||||
Mod: loader.Mod,
|
Mod: loader.Mod,
|
||||||
OnBehalfOf: null,
|
OnBehalfOf: null,
|
||||||
Priority: AssetLoadPriority.Exclusive,
|
Priority: AssetLoadPriority.Exclusive,
|
||||||
GetData: assetInfo => loader.Data.Load<T>(
|
GetData: assetInfo => loader.Data.Load<T>(this.GetLegacyAssetInfo(assetInfo))
|
||||||
this.GetLegacyAssetInfo(assetInfo)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -629,6 +632,7 @@ namespace StardewModdingAPI.Framework
|
||||||
foreach (var editor in this.Editors)
|
foreach (var editor in this.Editors)
|
||||||
{
|
{
|
||||||
// check if editor applies
|
// check if editor applies
|
||||||
|
Context.HeuristicModsRunningCode.Push(editor.Mod);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!editor.Data.CanEdit<T>(legacyInfo))
|
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);
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Context.HeuristicModsRunningCode.TryPop(out _);
|
||||||
|
}
|
||||||
|
|
||||||
// HACK
|
// HACK
|
||||||
//
|
//
|
||||||
|
@ -672,9 +680,7 @@ namespace StardewModdingAPI.Framework
|
||||||
Mod: editor.Mod,
|
Mod: editor.Mod,
|
||||||
OnBehalfOf: null,
|
OnBehalfOf: null,
|
||||||
Priority: priority,
|
Priority: priority,
|
||||||
ApplyEdit: assetData => editor.Data.Edit<T>(
|
ApplyEdit: assetData => editor.Data.Edit<T>(this.GetLegacyAssetData(assetData))
|
||||||
this.GetLegacyAssetData(assetData)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
@ -177,6 +177,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
||||||
// fetch asset from loader
|
// fetch asset from loader
|
||||||
IModMetadata mod = loader.Mod;
|
IModMetadata mod = loader.Mod;
|
||||||
T data;
|
T data;
|
||||||
|
Context.HeuristicModsRunningCode.Push(loader.Mod);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
data = (T)loader.GetData(info);
|
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);
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Context.HeuristicModsRunningCode.TryPop(out _);
|
||||||
|
}
|
||||||
|
|
||||||
// return matched asset
|
// return matched asset
|
||||||
return this.TryFixAndValidateLoadedAsset(info, data, loader)
|
return this.TryFixAndValidateLoadedAsset(info, data, loader)
|
||||||
|
@ -229,6 +234,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
||||||
|
|
||||||
// try edit
|
// try edit
|
||||||
object prevAsset = asset.Data;
|
object prevAsset = asset.Data;
|
||||||
|
Context.HeuristicModsRunningCode.Push(editor.Mod);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
editor.ApplyEdit(asset);
|
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);
|
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
|
// 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)
|
if (asset.Data == null)
|
||||||
{
|
{
|
||||||
mod.LogAsMod($"Mod incorrectly set asset '{info.Name}'{this.GetOnBehalfOfLabel(editor.OnBehalfOf)} to a null value; ignoring override.", LogLevel.Warn);
|
mod.LogAsMod($"Mod incorrectly set asset '{info.Name}'{this.GetOnBehalfOfLabel(editor.OnBehalfOf)} to a null value; ignoring override.", LogLevel.Warn);
|
||||||
|
|
|
@ -121,7 +121,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (ex is not SContentLoadException)
|
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
|
// track & return asset
|
||||||
|
|
|
@ -37,13 +37,6 @@ namespace StardewModdingAPI.Framework.Deprecations
|
||||||
this.ModRegistry = modRegistry;
|
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>
|
/// <summary>Get a mod from its unique ID.</summary>
|
||||||
/// <param name="modId">The mod's unique ID.</param>
|
/// <param name="modId">The mod's unique ID.</param>
|
||||||
public IModMetadata? GetMod(string modId)
|
public IModMetadata? GetMod(string modId)
|
||||||
|
@ -52,7 +45,7 @@ namespace StardewModdingAPI.Framework.Deprecations
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Log a deprecation warning.</summary>
|
/// <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="nounPhrase">A noun phrase describing what is deprecated.</param>
|
||||||
/// <param name="version">The SMAPI version which deprecated it.</param>
|
/// <param name="version">The SMAPI version which deprecated it.</param>
|
||||||
/// <param name="severity">How deprecated the code is.</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>
|
/// <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)
|
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
|
// skip if already warned
|
||||||
string cacheKey = $"{source?.DisplayName ?? "<unknown>"}::{nounPhrase}::{version}";
|
string cacheKey = $"{heuristicSource?.Manifest.UniqueID ?? "<unknown>"}::{nounPhrase}::{version}";
|
||||||
if (this.LoggedDeprecations.Contains(cacheKey))
|
if (this.LoggedDeprecations.Contains(cacheKey))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// warn if valid
|
|
||||||
ImmutableStackTrace stack = ImmutableStackTrace.Get(skipFrames: 1);
|
|
||||||
if (!this.ShouldSuppress(stack, unlessStackIncludes))
|
|
||||||
{
|
|
||||||
this.LoggedDeprecations.Add(cacheKey);
|
this.LoggedDeprecations.Add(cacheKey);
|
||||||
this.QueuedWarnings.Add(new DeprecationWarning(source, nounPhrase, version, severity, stack, logStackTrace));
|
|
||||||
|
// get more accurate source
|
||||||
|
if (stack is not null)
|
||||||
|
source ??= heuristicSource!;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
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>
|
/// <summary>A placeholder method used to track deprecated code for which a separate warning will be shown.</summary>
|
||||||
|
|
|
@ -104,6 +104,8 @@ namespace StardewModdingAPI.Framework.Events
|
||||||
// raise event
|
// raise event
|
||||||
foreach (ManagedEventHandler<TEventArgs> handler in this.GetHandlers())
|
foreach (ManagedEventHandler<TEventArgs> handler in this.GetHandlers())
|
||||||
{
|
{
|
||||||
|
Context.HeuristicModsRunningCode.Push(handler.SourceMod);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
handler.Handler(null, args);
|
handler.Handler(null, args);
|
||||||
|
@ -112,6 +114,10 @@ namespace StardewModdingAPI.Framework.Events
|
||||||
{
|
{
|
||||||
this.LogError(handler, ex);
|
this.LogError(handler, ex);
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Context.HeuristicModsRunningCode.TryPop(out _);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,6 +132,8 @@ namespace StardewModdingAPI.Framework.Events
|
||||||
// raise event
|
// raise event
|
||||||
foreach (ManagedEventHandler<TEventArgs> handler in this.GetHandlers())
|
foreach (ManagedEventHandler<TEventArgs> handler in this.GetHandlers())
|
||||||
{
|
{
|
||||||
|
Context.HeuristicModsRunningCode.Push(handler.SourceMod);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
invoke(handler.SourceMod, args => handler.Handler(null, args));
|
invoke(handler.SourceMod, args => handler.Handler(null, args));
|
||||||
|
@ -134,6 +142,10 @@ namespace StardewModdingAPI.Framework.Events
|
||||||
{
|
{
|
||||||
this.LogError(handler, ex);
|
this.LogError(handler, ex);
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Context.HeuristicModsRunningCode.TryPop(out _);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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",
|
"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."
|
"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."
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
||||||
public bool Trigger(string name, string[] arguments)
|
public bool Trigger(string name, string[] arguments)
|
||||||
{
|
{
|
||||||
SCore.DeprecationManager.Warn(
|
SCore.DeprecationManager.Warn(
|
||||||
source: SCore.DeprecationManager.GetMod(this.ModID),
|
source: this.Mod,
|
||||||
nounPhrase: $"{nameof(IModHelper)}.{nameof(IModHelper.ConsoleCommands)}.{nameof(ICommandHelper.Trigger)}",
|
nounPhrase: $"{nameof(IModHelper)}.{nameof(IModHelper.ConsoleCommands)}.{nameof(ICommandHelper.Trigger)}",
|
||||||
version: "3.8.1",
|
version: "3.8.1",
|
||||||
severity: DeprecationLevel.Notice
|
severity: DeprecationLevel.Notice
|
||||||
|
|
|
@ -33,7 +33,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
SCore.DeprecationManager.Warn(
|
SCore.DeprecationManager.Warn(
|
||||||
source: SCore.DeprecationManager.GetMod(this.ModID),
|
source: this.Mod,
|
||||||
nounPhrase: $"{nameof(IModHelper)}.{nameof(IModHelper.Content)}",
|
nounPhrase: $"{nameof(IModHelper)}.{nameof(IModHelper.Content)}",
|
||||||
version: "3.14.0",
|
version: "3.14.0",
|
||||||
severity: DeprecationLevel.Notice
|
severity: DeprecationLevel.Notice
|
||||||
|
|
|
@ -414,11 +414,6 @@ namespace StardewModdingAPI.Framework.ModLoading
|
||||||
mod.SetWarning(ModWarning.UsesUnvalidatedUpdateTick);
|
mod.SetWarning(ModWarning.UsesUnvalidatedUpdateTick);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case InstructionHandleResult.DetectedDynamic:
|
|
||||||
template = $"{logPrefix}Detected 'dynamic' keyword ($phrase) in assembly {filename}.";
|
|
||||||
mod.SetWarning(ModWarning.UsesDynamic);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case InstructionHandleResult.DetectedConsoleAccess:
|
case InstructionHandleResult.DetectedConsoleAccess:
|
||||||
template = $"{logPrefix}Detected direct console access ($phrase) in assembly {filename}.";
|
template = $"{logPrefix}Detected direct console access ($phrase) in assembly {filename}.";
|
||||||
mod.SetWarning(ModWarning.AccessesConsole);
|
mod.SetWarning(ModWarning.AccessesConsole);
|
||||||
|
|
|
@ -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>
|
/// <summary>The instruction is compatible, but affects the save serializer in a way that may make saves unloadable without the mod.</summary>
|
||||||
DetectedSaveSerializer,
|
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>
|
/// <summary>The instruction is compatible, but references <see cref="ISpecializedEvents.UnvalidatedUpdateTicking"/> or <see cref="ISpecializedEvents.UnvalidatedUpdateTicked"/> which may impact stability.</summary>
|
||||||
DetectedUnvalidatedUpdateTick,
|
DetectedUnvalidatedUpdateTick,
|
||||||
|
|
||||||
|
|
|
@ -99,14 +99,10 @@ namespace StardewModdingAPI.Framework
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Get the mod metadata from the closest assembly registered as a source of deprecation warnings.</summary>
|
/// <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>
|
/// <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)
|
foreach (StackFrame frame in frames)
|
||||||
{
|
{
|
||||||
IModMetadata? mod = this.GetFrom(frame);
|
IModMetadata? mod = this.GetFrom(frame);
|
||||||
|
@ -114,7 +110,6 @@ namespace StardewModdingAPI.Framework
|
||||||
return mod;
|
return mod;
|
||||||
}
|
}
|
||||||
|
|
||||||
// no known assembly found
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1677,6 +1677,7 @@ namespace StardewModdingAPI.Framework
|
||||||
#pragma warning restore CS0612, CS0618
|
#pragma warning restore CS0612, CS0618
|
||||||
|
|
||||||
// call entry method
|
// call entry method
|
||||||
|
Context.HeuristicModsRunningCode.Push(metadata);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
IMod mod = metadata.Mod!;
|
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);
|
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
|
// unlock mod integrations
|
||||||
|
@ -1852,7 +1854,7 @@ namespace StardewModdingAPI.Framework
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// get mod instance
|
// 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;
|
failReason = ModFailReason.LoadFailed;
|
||||||
return false;
|
return false;
|
||||||
|
@ -1954,11 +1956,12 @@ namespace StardewModdingAPI.Framework
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Load a mod's entry class.</summary>
|
/// <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="modAssembly">The mod assembly.</param>
|
||||||
/// <param name="mod">The loaded instance.</param>
|
/// <param name="mod">The loaded instance.</param>
|
||||||
/// <param name="error">The error indicating why loading failed (if applicable).</param>
|
/// <param name="error">The error indicating why loading failed (if applicable).</param>
|
||||||
/// <returns>Returns whether the mod entry class was successfully loaded.</returns>
|
/// <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;
|
mod = null;
|
||||||
|
|
||||||
|
@ -1976,7 +1979,16 @@ namespace StardewModdingAPI.Framework
|
||||||
}
|
}
|
||||||
|
|
||||||
// get implementation
|
// get implementation
|
||||||
|
Context.HeuristicModsRunningCode.Push(metadata);
|
||||||
|
try
|
||||||
|
{
|
||||||
mod = (Mod?)modAssembly.CreateInstance(modEntries[0].ToString());
|
mod = (Mod?)modAssembly.CreateInstance(modEntries[0].ToString());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Context.HeuristicModsRunningCode.TryPop(out _);
|
||||||
|
}
|
||||||
|
|
||||||
if (mod == null)
|
if (mod == null)
|
||||||
{
|
{
|
||||||
error = "its entry class couldn't be instantiated.";
|
error = "its entry class couldn't be instantiated.";
|
||||||
|
|
|
@ -67,7 +67,6 @@ namespace StardewModdingAPI.Metadata
|
||||||
/****
|
/****
|
||||||
** detect code which may impact game stability
|
** 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 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);
|
yield return new EventFinder(typeof(ISpecializedEvents).FullName!, new[] { nameof(ISpecializedEvents.UnvalidatedUpdateTicked), nameof(ISpecializedEvents.UnvalidatedUpdateTicking) }, InstructionHandleResult.DetectedUnvalidatedUpdateTick);
|
||||||
|
|
||||||
|
|
|
@ -97,7 +97,7 @@ namespace StardewModdingAPI.Utilities
|
||||||
if (!nullExpected)
|
if (!nullExpected)
|
||||||
{
|
{
|
||||||
SCore.DeprecationManager.Warn(
|
SCore.DeprecationManager.Warn(
|
||||||
SCore.DeprecationManager.GetModFromStack(),
|
null,
|
||||||
$"calling the {nameof(PerScreen<T>)} constructor with null",
|
$"calling the {nameof(PerScreen<T>)} constructor with null",
|
||||||
"3.14.0",
|
"3.14.0",
|
||||||
DeprecationLevel.Notice
|
DeprecationLevel.Notice
|
||||||
|
|
Loading…
Reference in New Issue