diff --git a/build/common.targets b/build/common.targets
index aec6e879..cba06cbd 100644
--- a/build/common.targets
+++ b/build/common.targets
@@ -11,7 +11,6 @@ repo. It imports the other MSBuild files as needed.
SMAPIlatest$(AssemblySearchPaths);{GAC}
- $(DefineConstants);SMAPI_DEPRECATEDtrue
@@ -34,13 +33,10 @@ repo. It imports the other MSBuild files as needed.
warning | builds | summary | rationale
┄┄┄┄┄┄┄ | ┄┄┄┄┄┄┄┄┄┄ | ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ | ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
CS0436 | all | local type conflicts with imported type | SMAPI needs to use certain low-level code during very early compatibility checks, before it's safe to load any other DLLs.
- CS0612 | deprecated | member is obsolete | internal references to deprecated code when deprecated code is enabled.
- CS0618 | deprecated | member is obsolete (with message) | internal references to deprecated code when deprecated code is enabled.
CA1416 | all | platform code available on all platforms | Compiler doesn't recognize the #if constants used by SMAPI.
CS0809 | all | obsolete overload for non-obsolete member | This is deliberate to signal to mods that certain APIs are only implemented for the game and shouldn't be called by mods.
NU1701 | all | NuGet package targets older .NET version | All such packages are carefully tested to make sure they do work.
-->
- $(NoWarn);CS0612;CS0618$(NoWarn);CS0436;CA1416;CS0809;NU1701
diff --git a/docs/release-notes.md b/docs/release-notes.md
index 921a3796..1b9bb3d7 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -1,18 +1,16 @@
← [README](README.md)
# Release notes
-
-
## Upcoming release for Stardew Valley 1.6
* For players:
* Updated for Stardew Valley 1.6.
+ * Improved performance.
+ * Removed support for seamlessly updating from SMAPI 2.11.3 and earlier (released in 2019).
+ _If needed, you can update to SMAPI 3.18.0 first and then install the latest version._
* For mod authors:
* Updated to .NET 6.
+ * Removed all deprecated APIs.
## Upcoming release
* For players:
diff --git a/docs/technical/smapi.md b/docs/technical/smapi.md
index d1591143..cdcb4b1f 100644
--- a/docs/technical/smapi.md
+++ b/docs/technical/smapi.md
@@ -64,7 +64,6 @@ SMAPI uses a small number of conditional compilation constants, which you can se
flag | purpose
---- | -------
`SMAPI_FOR_WINDOWS` | Whether SMAPI is being compiled for Windows; if not set, the code assumes Linux/macOS. Set automatically in `common.targets`.
-`SMAPI_DEPRECATED` | Whether to include deprecated code in the build.
## Compile from source code
### Main project
diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs
index 22f88f2f..eaca9764 100644
--- a/src/SMAPI.Installer/InteractiveInstaller.cs
+++ b/src/SMAPI.Installer/InteractiveInstaller.cs
@@ -41,7 +41,7 @@ namespace StardewModdingApi.Installer
{
string GetInstallPath(string path) => Path.Combine(installDir.FullName, path);
- // current files
+ // installed files
yield return GetInstallPath("StardewModdingAPI"); // Linux/macOS only
yield return GetInstallPath("StardewModdingAPI.deps.json");
yield return GetInstallPath("StardewModdingAPI.dll");
@@ -54,38 +54,8 @@ namespace StardewModdingApi.Installer
yield return GetInstallPath("smapi-internal");
yield return GetInstallPath("steam_appid.txt");
-#if SMAPI_DEPRECATED
- // obsolete
- yield return GetInstallPath("libgdiplus.dylib"); // before 3.13 (macOS only)
- yield return GetInstallPath(Path.Combine("Mods", ".cache")); // 1.3-1.4
- yield return GetInstallPath(Path.Combine("Mods", "TrainerMod")); // *–2.0 (renamed to ConsoleCommands)
- yield return GetInstallPath("Mono.Cecil.Rocks.dll"); // 1.3–1.8
- yield return GetInstallPath("StardewModdingAPI-settings.json"); // 1.0-1.4
- yield return GetInstallPath("StardewModdingAPI.AssemblyRewriters.dll"); // 1.3-2.5.5
- yield return GetInstallPath("0Harmony.dll"); // moved in 2.8
- yield return GetInstallPath("0Harmony.pdb"); // moved in 2.8
- yield return GetInstallPath("Mono.Cecil.dll"); // moved in 2.8
- yield return GetInstallPath("Newtonsoft.Json.dll"); // moved in 2.8
- yield return GetInstallPath("StardewModdingAPI.config.json"); // moved in 2.8
- yield return GetInstallPath("StardewModdingAPI.crash.marker"); // moved in 2.8
- yield return GetInstallPath("StardewModdingAPI.metadata.json"); // moved in 2.8
- yield return GetInstallPath("StardewModdingAPI.update.marker"); // moved in 2.8
- yield return GetInstallPath("StardewModdingAPI.Toolkit.dll"); // moved in 2.8
- yield return GetInstallPath("StardewModdingAPI.Toolkit.pdb"); // moved in 2.8
- yield return GetInstallPath("StardewModdingAPI.Toolkit.xml"); // moved in 2.8
- yield return GetInstallPath("StardewModdingAPI.Toolkit.CoreInterfaces.dll"); // moved in 2.8
- yield return GetInstallPath("StardewModdingAPI.Toolkit.CoreInterfaces.pdb"); // moved in 2.8
- yield return GetInstallPath("StardewModdingAPI.Toolkit.CoreInterfaces.xml"); // moved in 2.8
- yield return GetInstallPath("StardewModdingAPI-x64.exe"); // before 3.13
-
- if (modsDir.Exists)
- {
- foreach (DirectoryInfo modDir in modsDir.EnumerateDirectories())
- yield return Path.Combine(modDir.FullName, ".cache"); // 1.4–1.7
- }
-#endif
-
- yield return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley", "ErrorLogs"); // remove old log files
+ // old log files
+ yield return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley", "ErrorLogs");
}
/// Handles writing text to the console.
@@ -516,11 +486,6 @@ namespace StardewModdingApi.Installer
.Replace(@"""UseScheme"": ""AutoDetect""", $@"""UseScheme"": ""{scheme}""");
File.WriteAllText(paths.ApiConfigPath, text);
}
-
-#if SMAPI_DEPRECATED
- // remove obsolete appdata mods
- this.InteractivelyRemoveAppDataMods(paths.ModsDir, bundledModsDir, allowUserInput);
-#endif
}
}
Console.WriteLine();
@@ -848,92 +813,6 @@ namespace StardewModdingApi.Installer
}
}
-#if SMAPI_DEPRECATED
- /// Interactively move mods out of the app data directory.
- /// The directory which should contain all mods.
- /// The installer directory containing packaged mods.
- /// Whether the installer can ask for user input from the terminal.
- private void InteractivelyRemoveAppDataMods(DirectoryInfo properModsDir, DirectoryInfo packagedModsDir, bool allowUserInput)
- {
- // get packaged mods to delete
- string[] packagedModNames = packagedModsDir.GetDirectories().Select(p => p.Name).ToArray();
-
- // get path
- string appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley");
- DirectoryInfo modDir = new(Path.Combine(appDataPath, "Mods"));
-
- // check if migration needed
- if (!modDir.Exists)
- return;
- this.PrintDebug($"Found an obsolete mod path: {modDir.FullName}");
- this.PrintDebug(" Support for mods here was dropped in SMAPI 1.0 (it was never officially supported).");
-
- // move mods if no conflicts (else warn)
- foreach (FileSystemInfo entry in modDir.EnumerateFileSystemInfos().Where(this.ShouldCopy))
- {
- // get type
- bool isDir = entry is DirectoryInfo;
- if (!isDir && entry is not FileInfo)
- continue; // should never happen
-
- // delete packaged mods (newer version bundled into SMAPI)
- if (isDir && packagedModNames.Contains(entry.Name, StringComparer.OrdinalIgnoreCase))
- {
- this.PrintDebug($" Deleting {entry.Name} because it's bundled into SMAPI...");
- this.InteractivelyDelete(entry.FullName, allowUserInput);
- continue;
- }
-
- // check paths
- string newPath = Path.Combine(properModsDir.FullName, entry.Name);
- if (isDir ? Directory.Exists(newPath) : File.Exists(newPath))
- {
- this.PrintWarning($" Can't move {entry.Name} because it already exists in your game's mod directory.");
- continue;
- }
-
- // move into mods
- this.PrintDebug($" Moving {entry.Name} into the game's mod directory...");
- this.Move(entry, newPath);
- }
-
- // delete if empty
- if (modDir.EnumerateFileSystemInfos().Any())
- this.PrintWarning(" You have files in this folder which couldn't be moved automatically. These will be ignored by SMAPI.");
- else
- {
- this.PrintDebug(" Deleted empty directory.");
- modDir.Delete(recursive: true);
- }
- }
-
- /// Move a filesystem entry to a new parent directory.
- /// The filesystem entry to move.
- /// The destination path.
- /// We can't use or , because those don't work across partitions.
- private void Move(FileSystemInfo entry, string newPath)
- {
- // file
- if (entry is FileInfo file)
- {
- file.CopyTo(newPath);
- file.Delete();
- }
-
- // directory
- else
- {
- Directory.CreateDirectory(newPath);
-
- DirectoryInfo directory = (DirectoryInfo)entry;
- foreach (FileSystemInfo child in directory.EnumerateFileSystemInfos().Where(this.ShouldCopy))
- this.Move(child, Path.Combine(newPath, child.Name));
-
- directory.Delete(recursive: true);
- }
- }
-#endif
-
/// Get whether a file or folder should be copied from the installer files.
/// The file or folder info.
private bool ShouldCopy(FileSystemInfo entry)
diff --git a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs
index 25056b5e..e1c33865 100644
--- a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs
+++ b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs
@@ -2,9 +2,6 @@ using System;
using System.Reflection;
using StardewModdingAPI.Events;
using StardewModdingAPI.Internal.Patching;
-#if SMAPI_DEPRECATED
-using StardewModdingAPI.Mods.ErrorHandler.ModPatches;
-#endif
using StardewModdingAPI.Mods.ErrorHandler.Patches;
using StardewValley;
@@ -42,11 +39,6 @@ namespace StardewModdingAPI.Mods.ErrorHandler
new SaveGamePatcher(this.Monitor, this.OnSaveContentRemoved),
new SpriteBatchPatcher(),
new UtilityPatcher()
-
-#if SMAPI_DEPRECATED
- // mod patches
- , new PyTkPatcher(helper.ModRegistry)
-#endif
);
// hook events
diff --git a/src/SMAPI.Mods.ErrorHandler/ModPatches/PyTkPatcher.cs b/src/SMAPI.Mods.ErrorHandler/ModPatches/PyTkPatcher.cs
deleted file mode 100644
index f084902a..00000000
--- a/src/SMAPI.Mods.ErrorHandler/ModPatches/PyTkPatcher.cs
+++ /dev/null
@@ -1,81 +0,0 @@
-#if SMAPI_DEPRECATED
-using System;
-using System.Diagnostics.CodeAnalysis;
-using System.Reflection;
-using HarmonyLib;
-using Microsoft.Xna.Framework;
-using Microsoft.Xna.Framework.Graphics;
-using StardewModdingAPI.Framework;
-using StardewModdingAPI.Framework.Content;
-using StardewModdingAPI.Internal;
-using StardewModdingAPI.Internal.Patching;
-
-//
-// This is part of a three-part fix for PyTK 1.23.* and earlier. When removing this, search
-// 'Platonymous.Toolkit' to find the other part in SMAPI and Content Patcher.
-//
-
-namespace StardewModdingAPI.Mods.ErrorHandler.ModPatches
-{
- /// Harmony patches for the PyTK mod for compatibility with newer SMAPI versions.
- /// Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.
- [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
- [SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
- [SuppressMessage("ReSharper", "StringLiteralTypo", Justification = "'Platonymous' is part of the mod ID.")]
- internal class PyTkPatcher : BasePatcher
- {
- /*********
- ** Fields
- *********/
- /// The PyTK mod metadata, if it's installed.
- private static IModMetadata? PyTk;
-
-
- /*********
- ** Public methods
- *********/
- /// Construct an instance.
- /// The mod registry from which to read PyTK metadata.
- public PyTkPatcher(IModRegistry modRegistry)
- {
- IModMetadata? pyTk = (IModMetadata?)modRegistry.Get(@"Platonymous.Toolkit");
- if (pyTk is not null && pyTk.Manifest.Version.IsOlderThan("1.24.0"))
- PyTkPatcher.PyTk = pyTk;
- }
-
- ///
- public override void Apply(Harmony harmony, IMonitor monitor)
- {
- try
- {
- // get mod info
- IModMetadata? pyTk = PyTkPatcher.PyTk;
- if (pyTk is null)
- return;
-
- // get patch method
- const string patchMethodName = "PatchImage";
- MethodInfo? patch = AccessTools.Method(pyTk.Mod!.GetType(), patchMethodName);
- if (patch is null)
- {
- monitor.Log("Failed applying compatibility patch for PyTK. Its image scaling feature may not work correctly.", LogLevel.Warn);
- monitor.Log($"Couldn't find patch method '{pyTk.Mod.GetType().FullName}.{patchMethodName}'.");
- return;
- }
-
- // apply patch
- harmony = new($"{harmony.Id}.compatibility-patches.PyTK");
- harmony.Patch(
- original: AccessTools.Method(typeof(AssetDataForImage), nameof(AssetDataForImage.PatchImage), new[] { typeof(Texture2D), typeof(Rectangle), typeof(Rectangle), typeof(PatchMode) }),
- prefix: new HarmonyMethod(patch)
- );
- }
- catch (Exception ex)
- {
- monitor.Log("Failed applying compatibility patch for PyTK. Its image scaling feature may not work correctly.", LogLevel.Warn);
- monitor.Log(ex.GetLogSummary());
- }
- }
- }
-}
-#endif
diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs b/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs
index 4c76f417..b3082fa2 100644
--- a/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs
+++ b/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs
@@ -18,36 +18,19 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData
/// The mod patches the game in a way that may impact stability.
PatchesGame = 4,
-#if SMAPI_DEPRECATED
- /// The mod uses the dynamic keyword which won't work on Linux/macOS.
- [Obsolete("This value is no longer used by SMAPI and will be removed in the upcoming SMAPI 4.0.0.")]
- UsesDynamic = 8,
-#endif
-
/// The mod references specialized 'unvalidated update tick' events which may impact stability.
- UsesUnvalidatedUpdateTick = 16,
+ UsesUnvalidatedUpdateTick = 8,
/// The mod has no update keys set.
- NoUpdateKeys = 32,
+ NoUpdateKeys = 16,
/// Uses .NET APIs for reading and writing to the console.
- AccessesConsole = 64,
+ AccessesConsole = 32,
/// Uses .NET APIs for filesystem access.
- AccessesFilesystem = 128,
+ AccessesFilesystem = 64,
/// Uses .NET APIs for shell or process access.
- AccessesShell = 256,
-
-#if SMAPI_DEPRECATED
- /// References the legacy System.Configuration.ConfigurationManager assembly and doesn't include a copy in the mod folder, so it'll break in SMAPI 4.0.0.
- DetectedLegacyConfigurationDll = 512,
-
- /// References the legacy System.Runtime.Caching assembly and doesn't include a copy in the mod folder, so it'll break in SMAPI 4.0.0.
- DetectedLegacyCachingDll = 1024,
-
- /// References the legacy System.Security.Permissions assembly and doesn't include a copy in the mod folder, so it'll break in SMAPI 4.0.0.
- DetectedLegacyPermissionsDll = 2048
-#endif
+ AccessesShell = 128
}
}
diff --git a/src/SMAPI.Web/Views/LogParser/Index.cshtml b/src/SMAPI.Web/Views/LogParser/Index.cshtml
index b3ea7db2..ae3c0505 100644
--- a/src/SMAPI.Web/Views/LogParser/Index.cshtml
+++ b/src/SMAPI.Web/Views/LogParser/Index.cshtml
@@ -18,7 +18,7 @@
LogModInfo? errorHandler = log?.Mods.FirstOrDefault(p => p.IsCodeMod && p.Name == "Error Handler");
bool missingErrorHandler = errorHandler is null && log?.OperatingSystem?.Contains("Android Unix", StringComparison.OrdinalIgnoreCase) != true;
bool hasOlderErrorHandler = errorHandler?.GetParsedVersion() is not null && log?.ApiVersionParsed is not null && log.ApiVersionParsed.IsNewerThan(errorHandler.GetParsedVersion());
- bool isPyTkCompatibilityMode = log?.ApiVersionParsed?.IsOlderThan("3.15.0") is false && log.Mods.Any(p => p.IsCodeMod && p.Name == "PyTK" && p.GetParsedVersion()?.IsOlderThan("1.24.0") is true);
+ bool isPyTkCompatibilityMode = log?.ApiVersionParsed?.IsBetween("3.15.0", "3.19.0") is true && log.Mods.Any(p => p.IsCodeMod && p.Name == "PyTK" && p.GetParsedVersion()?.IsOlderThan("1.24.0") is true);
// get filters
IDictionary defaultFilters = Enum
diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs
index 0d743d7c..ee33045b 100644
--- a/src/SMAPI/Constants.cs
+++ b/src/SMAPI/Constants.cs
@@ -6,9 +6,6 @@ using System.Reflection;
using Mono.Cecil;
using StardewModdingAPI.Enums;
using StardewModdingAPI.Framework;
-#if SMAPI_DEPRECATED
-using StardewModdingAPI.Framework.Deprecations;
-#endif
using StardewModdingAPI.Framework.ModLoading;
using StardewModdingAPI.Toolkit.Framework;
using StardewModdingAPI.Toolkit.Utilities;
@@ -79,25 +76,6 @@ namespace StardewModdingAPI
/// The game framework running the game.
public static GameFramework GameFramework { get; } = EarlyConstants.GameFramework;
-#if SMAPI_DEPRECATED
- /// The path to the game folder.
- [Obsolete($"Use {nameof(Constants)}.{nameof(GamePath)} instead. This property will be removed in SMAPI 4.0.0.")]
- public static string ExecutionPath
- {
- get
- {
- SCore.DeprecationManager.Warn(
- source: null,
- nounPhrase: $"{nameof(Constants)}.{nameof(Constants.ExecutionPath)}",
- version: "3.14.0",
- severity: DeprecationLevel.PendingRemoval
- );
-
- return Constants.GamePath;
- }
- }
-#endif
-
/// The path to the game folder.
public static string GamePath { get; } = EarlyConstants.GamePath;
diff --git a/src/SMAPI/Framework/Content/AssetInfo.cs b/src/SMAPI/Framework/Content/AssetInfo.cs
index 52ef02e6..30dc325a 100644
--- a/src/SMAPI/Framework/Content/AssetInfo.cs
+++ b/src/SMAPI/Framework/Content/AssetInfo.cs
@@ -1,9 +1,6 @@
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Graphics;
-#if SMAPI_DEPRECATED
-using StardewModdingAPI.Framework.Deprecations;
-#endif
namespace StardewModdingAPI.Framework.Content
{
@@ -34,30 +31,6 @@ namespace StardewModdingAPI.Framework.Content
///
public Type DataType { get; }
-#if SMAPI_DEPRECATED
- ///
- [Obsolete($"Use {nameof(AssetInfo.Name)} or {nameof(AssetInfo.NameWithoutLocale)} instead. This property will be removed in SMAPI 4.0.0.")]
- public string AssetName
- {
- get
- {
- SCore.DeprecationManager.Warn(
- source: null,
- nounPhrase: $"{nameof(IAssetInfo)}.{nameof(IAssetInfo.AssetName)}",
- version: "3.14.0",
- severity: DeprecationLevel.PendingRemoval,
- unlessStackIncludes: new[]
- {
- $"{typeof(AssetInterceptorChange).FullName}.{nameof(AssetInterceptorChange.CanIntercept)}",
- $"{typeof(ContentCoordinator).FullName}.{nameof(ContentCoordinator.GetAssetOperations)}"
- }
- );
-
- return this.NameWithoutLocale.Name;
- }
- }
-#endif
-
/*********
** Public methods
@@ -75,28 +48,6 @@ namespace StardewModdingAPI.Framework.Content
this.GetNormalizedPath = getNormalizedPath;
}
-#if SMAPI_DEPRECATED
- ///
- [Obsolete($"Use {nameof(Name)}.{nameof(IAssetName.IsEquivalentTo)} or {nameof(AssetInfo.NameWithoutLocale)}.{nameof(IAssetName.IsEquivalentTo)} instead. This method will be removed in SMAPI 4.0.0.")]
- public bool AssetNameEquals(string path)
- {
- SCore.DeprecationManager.Warn(
- source: null,
- nounPhrase: $"{nameof(IAssetInfo)}.{nameof(IAssetInfo.AssetNameEquals)}",
- version: "3.14.0",
- severity: DeprecationLevel.PendingRemoval,
- unlessStackIncludes: new[]
- {
- $"{typeof(AssetInterceptorChange).FullName}.{nameof(AssetInterceptorChange.CanIntercept)}",
- $"{typeof(ContentCoordinator).FullName}.{nameof(ContentCoordinator.GetAssetOperations)}"
- }
- );
-
-
- return this.NameWithoutLocale.IsEquivalentTo(path);
- }
-#endif
-
/*********
** Protected methods
diff --git a/src/SMAPI/Framework/Content/AssetInterceptorChange.cs b/src/SMAPI/Framework/Content/AssetInterceptorChange.cs
deleted file mode 100644
index 3b5068dc..00000000
--- a/src/SMAPI/Framework/Content/AssetInterceptorChange.cs
+++ /dev/null
@@ -1,106 +0,0 @@
-#if SMAPI_DEPRECATED
-using System;
-using System.Reflection;
-using StardewModdingAPI.Internal;
-
-namespace StardewModdingAPI.Framework.Content
-{
- /// A wrapper for and for internal cache invalidation.
- internal class AssetInterceptorChange
- {
- /*********
- ** Accessors
- *********/
- /// The mod which registered the interceptor.
- public IModMetadata Mod { get; }
-
- /// The interceptor instance.
- public object Instance { get; }
-
- /// Whether the asset interceptor was added since the last tick. Mutually exclusive with .
- public bool WasAdded { get; }
-
- /// Whether the asset interceptor was removed since the last tick. Mutually exclusive with .
- public bool WasRemoved => this.WasAdded;
-
-
- /*********
- ** Public methods
- *********/
- /// Construct an instance.
- /// The mod registering the interceptor.
- /// The interceptor. This must be an or instance.
- /// Whether the asset interceptor was added since the last tick; else removed.
- public AssetInterceptorChange(IModMetadata mod, object instance, bool wasAdded)
- {
- this.Mod = mod ?? throw new ArgumentNullException(nameof(mod));
- this.Instance = instance ?? throw new ArgumentNullException(nameof(instance));
- this.WasAdded = wasAdded;
-
- if (instance is not (IAssetEditor or IAssetLoader))
- throw new InvalidCastException($"The provided {nameof(instance)} value must be an {nameof(IAssetEditor)} or {nameof(IAssetLoader)} instance.");
- }
-
- /// Get whether this instance can intercept the given asset.
- /// Basic metadata about the asset being loaded.
- public bool CanIntercept(IAssetInfo asset)
- {
- MethodInfo? canIntercept = this.GetType().GetMethod(nameof(this.CanInterceptImpl), BindingFlags.Instance | BindingFlags.NonPublic);
- if (canIntercept == null)
- throw new InvalidOperationException($"SMAPI couldn't access the {nameof(AssetInterceptorChange)}.{nameof(this.CanInterceptImpl)} implementation.");
-
- return (bool)canIntercept.MakeGenericMethod(asset.DataType).Invoke(this, new object[] { asset })!;
- }
-
-
- /*********
- ** Private methods
- *********/
- /// Get whether this instance can intercept the given asset.
- /// The asset type.
- /// Basic metadata about the asset being loaded.
- private bool CanInterceptImpl(IAssetInfo asset)
- {
- // check edit
- if (this.Instance is IAssetEditor editor)
- {
- Context.HeuristicModsRunningCode.Push(this.Mod);
- try
- {
- if (editor.CanEdit(asset))
- return true;
- }
- catch (Exception ex)
- {
- 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(asset))
- return true;
- }
- catch (Exception ex)
- {
- 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;
- }
- }
-}
-#endif
diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs
index 86415a5f..71495d7e 100644
--- a/src/SMAPI/Framework/ContentCoordinator.cs
+++ b/src/SMAPI/Framework/ContentCoordinator.cs
@@ -12,9 +12,6 @@ using StardewModdingAPI.Framework.Content;
using StardewModdingAPI.Framework.ContentManagers;
using StardewModdingAPI.Framework.Reflection;
using StardewModdingAPI.Framework.Utilities;
-#if SMAPI_DEPRECATED
-using StardewModdingAPI.Internal;
-#endif
using StardewModdingAPI.Metadata;
using StardewModdingAPI.Toolkit.Serialization;
using StardewModdingAPI.Toolkit.Utilities.PathLookups;
@@ -83,16 +80,6 @@ namespace StardewModdingAPI.Framework
/// The cached asset load/edit operations to apply, indexed by asset name.
private readonly TickCacheDictionary AssetOperationsByKey = new();
-#if SMAPI_DEPRECATED
- /// A cache of asset operation groups created for legacy implementations.
- [Obsolete("This only exists to support legacy code and will be removed in SMAPI 4.0.0.")]
- private readonly Dictionary> LegacyLoaderCache = new(ReferenceEqualityComparer.Instance);
-
- /// A cache of asset operation groups created for legacy implementations.
- [Obsolete("This only exists to support legacy code and will be removed in SMAPI 4.0.0.")]
- private readonly Dictionary> LegacyEditorCache = new(ReferenceEqualityComparer.Instance);
-#endif
-
/*********
** Accessors
@@ -103,16 +90,6 @@ namespace StardewModdingAPI.Framework
/// The current language as a constant.
public LocalizedContentManager.LanguageCode Language => this.MainContentManager.Language;
-#if SMAPI_DEPRECATED
- /// Interceptors which provide the initial versions of matching assets.
- [Obsolete("This only exists to support legacy code and will be removed in SMAPI 4.0.0.")]
- public IList> Loaders { get; } = new List>();
-
- /// Interceptors which edit matching assets after they're loaded.
- [Obsolete("This only exists to support legacy code and will be removed in SMAPI 4.0.0.")]
- public IList> Editors { get; } = new List>();
-#endif
-
/// The absolute path to the .
public string FullRootDirectory { get; }
@@ -267,9 +244,9 @@ namespace StardewModdingAPI.Framework
{
// The game clears LocalizedContentManager.localizedAssetNames after returning to the title screen. That
// causes an inconsistency in the SMAPI asset cache, which leads to an edge case where assets already
- // provided by mods via IAssetLoader when playing in non-English are ignored.
+ // provided by mods via a load operation when playing in non-English are ignored.
//
- // For example, let's say a mod provides the 'Data\mail' asset through IAssetLoader when playing in
+ // For example, let's say a mod provides the 'Data\mail' asset via a load operation when playing in
// Portuguese. Here's the normal load process after it's loaded:
// 1. The game requests Data\mail.
// 2. SMAPI sees that it's already cached, and calls LoadRaw to bypass asset interception.
@@ -499,25 +476,13 @@ namespace StardewModdingAPI.Framework
return invalidatedAssets.Keys;
}
-#if SMAPI_DEPRECATED
- /// Get the asset load and edit operations to apply to a given asset if it's (re)loaded now.
- /// The asset type.
- /// The asset info to load or edit.
- public AssetOperationGroup? GetAssetOperations(IAssetInfo info)
- where T : notnull
-#else
/// Get the asset load and edit operations to apply to a given asset if it's (re)loaded now.
/// The asset info to load or edit.
public AssetOperationGroup? GetAssetOperations(IAssetInfo info)
-#endif
{
return this.AssetOperationsByKey.GetOrSet(
info.Name,
-#if SMAPI_DEPRECATED
- () => this.GetAssetOperationsWithoutCache(info)
-#else
() => this.RequestAssetOperations(info)
-#endif
);
}
@@ -639,195 +604,5 @@ namespace StardewModdingAPI.Framework
return map;
}
-
-#if SMAPI_DEPRECATED
- /// Get the asset load and edit operations to apply to a given asset if it's (re)loaded now, ignoring the cache.
- /// The asset type.
- /// The asset info to load or edit.
- private AssetOperationGroup? GetAssetOperationsWithoutCache(IAssetInfo info)
- where T : notnull
- {
- // new content API
- AssetOperationGroup? group = this.RequestAssetOperations(info);
-
- // legacy load operations
- if (this.Editors.Count > 0 || this.Loaders.Count > 0)
- {
- IAssetInfo legacyInfo = this.GetLegacyAssetInfo(info);
-
- foreach (ModLinked loader in this.Loaders)
- {
- // check if loader applies
- Context.HeuristicModsRunningCode.Push(loader.Mod);
- try
- {
- if (!loader.Data.CanLoad(legacyInfo))
- continue;
- }
- catch (Exception ex)
- {
- 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(), new List());
- group.LoadOperations.Add(
- this.GetOrCreateLegacyOperation(
- cache: this.LegacyLoaderCache,
- editor: loader.Data,
- dataType: info.DataType,
- create: () => new AssetLoadOperation(
- Mod: loader.Mod,
- OnBehalfOf: null,
- Priority: AssetLoadPriority.Exclusive,
- GetData: assetInfo => loader.Data.Load(this.GetLegacyAssetInfo(assetInfo))
- )
- )
- );
- }
-
- // legacy edit operations
- foreach (var editor in this.Editors)
- {
- // check if editor applies
- Context.HeuristicModsRunningCode.Push(editor.Mod);
- try
- {
- if (!editor.Data.CanEdit(legacyInfo))
- continue;
- }
- catch (Exception ex)
- {
- 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
- //
- // If two editors have the same priority, they're applied in registration order (so
- // whichever was registered first is applied first). Mods often depend on this
- // behavior, like Json Assets registering its interceptors before Content Patcher.
- //
- // Unfortunately the old & new content APIs have separate lists, so new-API
- // interceptors always ran before old-API interceptors with the same priority,
- // regardless of the registration order *between* APIs. Since the new API works in
- // a fundamentally different way (i.e. loads/edits are defined on asset request
- // instead of by registering a global 'editor' or 'loader' class), there's no way
- // to track registration order between them.
- //
- // Until we drop the old content API in SMAPI 4.0.0, this sets the priority for
- // specific legacy editors to maintain compatibility.
- AssetEditPriority priority = editor.Data.GetType().FullName switch
- {
- "JsonAssets.Framework.ContentInjector1" => AssetEditPriority.Default - 1, // must be applied before Content Patcher
- _ => AssetEditPriority.Default
- };
-
- // add operation
- group ??= new AssetOperationGroup(new List(), new List());
- group.EditOperations.Add(
- this.GetOrCreateLegacyOperation(
- cache: this.LegacyEditorCache,
- editor: editor.Data,
- dataType: info.DataType,
- create: () => new AssetEditOperation(
- Mod: editor.Mod,
- OnBehalfOf: null,
- Priority: priority,
- ApplyEdit: assetData => editor.Data.Edit(this.GetLegacyAssetData(assetData))
- )
- )
- );
- }
- }
-
- return group;
- }
-
- /// Get a cached asset operation group for a legacy or instance, creating it if needed.
- /// The editor type (one of or ).
- /// The operation model type.
- /// The cached operation groups for the interceptor type.
- /// The legacy asset interceptor.
- /// The asset data type.
- /// Create the asset operation group if it's not cached yet.
- private TOperation GetOrCreateLegacyOperation(Dictionary> cache, TInterceptor editor, Type dataType, Func create)
- where TInterceptor : class
- {
- if (!cache.TryGetValue(editor, out Dictionary? cacheByType))
- cache[editor] = cacheByType = new Dictionary();
-
- if (!cacheByType.TryGetValue(dataType, out TOperation? operation))
- cacheByType[dataType] = operation = create();
-
- return operation;
- }
-
- /// Get an asset info compatible with legacy and instances, which always expect the base name.
- /// The asset info.
- private IAssetInfo GetLegacyAssetInfo(IAssetInfo asset)
- {
- return new AssetInfo(
- locale: this.GetLegacyLocale(asset),
- assetName: this.GetLegacyAssetName(asset.Name),
- type: asset.DataType,
- getNormalizedPath: this.MainContentManager.AssertAndNormalizeAssetName
- );
- }
-
- /// Get an asset data compatible with legacy and instances, which always expect the base name.
- /// The asset data.
- private IAssetData GetLegacyAssetData(IAssetData asset)
- {
- return new AssetDataForObject(
- locale: this.GetLegacyLocale(asset),
- assetName: this.GetLegacyAssetName(asset.Name),
- data: asset.Data,
- getNormalizedPath: this.MainContentManager.AssertAndNormalizeAssetName,
- reflection: this.Reflection,
- onDataReplaced: asset.ReplaceWith
- );
- }
-
- /// Get the value compatible with legacy and instances, which expect the locale to default to the current game locale or an empty string.
- /// The non-legacy asset info to map.
- private string GetLegacyLocale(IAssetInfo asset)
- {
- return asset.Locale ?? this.GetLocale();
- }
-
- /// Get an asset name compatible with legacy and instances, which always expect the base name.
- /// The asset name to map.
- /// Returns the legacy asset name if needed, or the if no change is needed.
- private IAssetName GetLegacyAssetName(IAssetName asset)
- {
- // strip _international suffix
- const string internationalSuffix = "_international";
- if (asset.Name.EndsWith(internationalSuffix))
- {
- return new AssetName(
- baseName: asset.Name[..^internationalSuffix.Length],
- localeCode: null,
- languageCode: null
- );
- }
-
- // else strip locale
- if (asset.LocaleCode != null)
- return new AssetName(asset.BaseName, null, null);
-
- // else no change needed
- return asset;
- }
-#endif
}
}
diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
index df7bdc59..4d92db0f 100644
--- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
@@ -8,7 +8,6 @@ using System.Reflection;
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Events;
using StardewModdingAPI.Framework.Content;
-using StardewModdingAPI.Framework.Deprecations;
using StardewModdingAPI.Framework.Exceptions;
using StardewModdingAPI.Framework.Reflection;
using StardewModdingAPI.Framework.Utilities;
@@ -25,7 +24,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
/*********
** Fields
*********/
- /// The assets currently being intercepted by instances. This is used to prevent infinite loops when a loader loads a new asset.
+ /// The assets currently being intercepted by instances. This is used to prevent infinite loops when a loader loads a new asset.
private readonly ContextHash AssetsBeingLoaded = new();
/// Whether the next load is the first for any game content manager.
@@ -76,11 +75,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
// custom asset from a loader
string locale = this.GetLocale();
IAssetInfo info = new AssetInfo(locale, assetName, typeof(T), this.AssertAndNormalizeAssetName);
- AssetOperationGroup? operations = this.Coordinator.GetAssetOperations
-#if SMAPI_DEPRECATED
-
-#endif
- (info);
+ AssetOperationGroup? operations = this.Coordinator.GetAssetOperations(info);
if (operations?.LoadOperations.Count > 0)
{
if (!this.AssertMaxOneRequiredLoader(info, operations.LoadOperations, out string? error))
@@ -133,11 +128,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
data = this.AssetsBeingLoaded.Track(assetName.Name, () =>
{
IAssetInfo info = new AssetInfo(assetName.LocaleCode, assetName, typeof(T), this.AssertAndNormalizeAssetName);
- AssetOperationGroup? operations = this.Coordinator.GetAssetOperations
-#if SMAPI_DEPRECATED
-
-#endif
- (info);
+ AssetOperationGroup? operations = this.Coordinator.GetAssetOperations(info);
IAssetData asset =
this.ApplyLoader(info, operations?.LoadOperations)
?? new AssetDataForObject(info, this.RawLoad(assetName, useCache), this.AssertAndNormalizeAssetName, this.Reflection);
@@ -302,11 +293,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
? $"Multiple mods want to provide the '{info.Name}' asset: {string.Join(", ", loaderNames)}"
: $"The '{loaderNames[0]}' mod wants to provide the '{info.Name}' asset multiple times";
- error = $"{errorPhrase}. An asset can't be loaded multiple times, so SMAPI will use the default asset instead. Uninstall one of the mods to fix this. (Message for modders: you should avoid {nameof(AssetLoadPriority)}.{nameof(AssetLoadPriority.Exclusive)}"
-#if SMAPI_DEPRECATED
- + " and {nameof(IAssetLoader)}"
-#endif
- + " if possible to avoid conflicts.)";
+ error = $"{errorPhrase}. An asset can't be loaded multiple times, so SMAPI will use the default asset instead. Uninstall one of the mods to fix this. (Message for modders: you should avoid {nameof(AssetLoadPriority)}.{nameof(AssetLoadPriority.Exclusive)} if possible to avoid conflicts.)";
return false;
}
@@ -361,25 +348,8 @@ namespace StardewModdingAPI.Framework.ContentManagers
// handle mismatch
if (loadedMap.TileSheets.Count <= vanillaSheet.Index || loadedMap.TileSheets[vanillaSheet.Index].Id != vanillaSheet.Id)
{
-#if SMAPI_DEPRECATED
- // only show warning if not farm map
- // This is temporary: mods shouldn't do this for any vanilla map, but these are the ones we know will crash. Showing a warning for others instead gives modders time to update their mods, while still simplifying troubleshooting.
- bool isFarmMap = info.Name.IsEquivalentTo("Maps/Farm") || info.Name.IsEquivalentTo("Maps/Farm_Combat") || info.Name.IsEquivalentTo("Maps/Farm_Fishing") || info.Name.IsEquivalentTo("Maps/Farm_Foraging") || info.Name.IsEquivalentTo("Maps/Farm_FourCorners") || info.Name.IsEquivalentTo("Maps/Farm_Island") || info.Name.IsEquivalentTo("Maps/Farm_Mining");
-
- string reason = $"{this.GetOnBehalfOfLabel(loader.OnBehalfOf, parenthetical: false) ?? "mod"} reordered the original tilesheets, which {(isFarmMap ? "would cause a crash" : "often causes crashes")}.\nTechnical details for mod author: Expected order: {string.Join(", ", vanillaTilesheetRefs.Select(p => p.Id))}. See https://stardewvalleywiki.com/Modding:Maps#Tilesheet_order for help.";
-
- SCore.DeprecationManager.PlaceholderWarn("3.8.2", DeprecationLevel.PendingRemoval);
- if (isFarmMap)
- {
- mod.LogAsMod($"SMAPI blocked a '{info.Name}' map load: {reason}", LogLevel.Error);
- return false;
- }
-
- mod.LogAsMod($"SMAPI found an issue with a '{info.Name}' map load: {reason}", LogLevel.Warn);
-#else
mod.LogAsMod($"SMAPI found an issue with a '{info.Name}' map load: {this.GetOnBehalfOfLabel(loader.OnBehalfOf, parenthetical: false) ?? "mod"} reordered the original tilesheets, which often causes crashes.\nTechnical details for mod author: Expected order: {string.Join(", ", vanillaTilesheetRefs.Select(p => p.Id))}. See https://stardewvalleywiki.com/Modding:Maps#Tilesheet_order for help.", LogLevel.Error);
return false;
-#endif
}
}
}
diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
index 8cdb65eb..dd4061ac 100644
--- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
@@ -46,15 +46,6 @@ namespace StardewModdingAPI.Framework.ContentManagers
private static readonly string[] LocalTilesheetExtensions = { ".png", ".xnb" };
- /*********
- ** Accessors
- *********/
-#if SMAPI_DEPRECATED
- /// Whether to enable legacy compatibility mode for PyTK scale-up textures.
- internal static bool EnablePyTkLegacyMode;
-#endif
-
-
/*********
** Public methods
*********/
@@ -201,16 +192,6 @@ namespace StardewModdingAPI.Framework.ContentManagers
this.AssertValidType(assetName, file, typeof(Texture2D), typeof(IRawTextureData));
bool returnRawData = typeof(T).IsAssignableTo(typeof(IRawTextureData));
-#if SMAPI_DEPRECATED
- if (!returnRawData && this.ShouldDisableIntermediateRawDataLoad(assetName, file))
- {
- using FileStream stream = File.OpenRead(file.FullName);
- Texture2D texture = Texture2D.FromStream(Game1.graphics.GraphicsDevice, stream).SetName(assetName);
- this.PremultiplyTransparency(texture);
- return (T)(object)texture;
- }
-#endif
-
IRawTextureData raw = this.LoadRawImageData(file, returnRawData);
if (returnRawData)
@@ -223,28 +204,6 @@ namespace StardewModdingAPI.Framework.ContentManagers
}
}
-#if SMAPI_DEPRECATED
- /// Get whether to disable loading an image as before building a instance. This isn't called if the mod requested directly.
- /// The type of asset being loaded.
- /// The asset name relative to the loader root directory.
- /// The file being loaded.
- private bool ShouldDisableIntermediateRawDataLoad(IAssetName assetName, FileInfo file)
- {
- // disable raw data if PyTK will rescale the image (until it supports raw data)
- if (ModContentManager.EnablePyTkLegacyMode)
- {
- // PyTK intercepts Texture2D file loads to rescale them (e.g. for HD portraits),
- // but doesn't support IRawTextureData loads yet. We can't just check if the
- // current file has a '.pytk.json' rescale file though, since PyTK may still
- // rescale it if the original asset or another edit gets rescaled.
- this.Monitor.LogOnce("Enabled compatibility mode for PyTK 1.23.* or earlier. This won't cause any issues, but may impact performance. This will no longer be supported in the upcoming SMAPI 4.0.0.", LogLevel.Warn);
- return true;
- }
-
- return false;
- }
-#endif
-
/// Load the raw image data from a file on disk.
/// The file whose data to load.
/// Whether the data is being loaded for an (true) or (false) instance.
@@ -523,7 +482,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
// ignore file-not-found errors
// TODO: while it's useful to suppress an asset-not-found error here to avoid
// confusion, this is a pretty naive approach. Even if the file doesn't exist,
- // the file may have been loaded through an IAssetLoader which failed. So even
+ // the file may have been loaded through a load operation which failed. So even
// if the content file doesn't exist, that doesn't mean the error here is a
// content-not-found error. Unfortunately XNA doesn't provide a good way to
// detect the error type.
diff --git a/src/SMAPI/Framework/ContentPack.cs b/src/SMAPI/Framework/ContentPack.cs
index a1d977e4..b6d0c07e 100644
--- a/src/SMAPI/Framework/ContentPack.cs
+++ b/src/SMAPI/Framework/ContentPack.cs
@@ -96,23 +96,6 @@ namespace StardewModdingAPI.Framework
}
}
-#if SMAPI_DEPRECATED
- ///
- [Obsolete($"Use {nameof(IContentPack.ModContent)}.{nameof(IModContentHelper.Load)} instead. This method will be removed in SMAPI 4.0.0.")]
- public T LoadAsset(string key)
- where T : notnull
- {
- return this.ModContent.Load(key);
- }
-
- ///
- [Obsolete($"Use {nameof(IContentPack.ModContent)}.{nameof(IModContentHelper.GetInternalAssetName)} instead. This method will be removed in SMAPI 4.0.0.")]
- public string GetActualAssetKey(string key)
- {
- return this.ModContent.GetInternalAssetName(key).Name;
- }
-#endif
-
/*********
** Private methods
diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs
index ffffc9c7..d5b33289 100644
--- a/src/SMAPI/Framework/Logging/LogManager.cs
+++ b/src/SMAPI/Framework/Logging/LogManager.cs
@@ -269,11 +269,7 @@ namespace StardewModdingAPI.Framework.Logging
public void LogIntro(string modsPath, IDictionary customSettings)
{
// log platform
- this.Monitor.Log($"SMAPI {Constants.ApiVersion} "
-#if !SMAPI_DEPRECATED
- + "(strict mode) "
-#endif
- + $"with Stardew Valley {Constants.GameVersion} (build {Constants.GetBuildVersionLabel()}) on {EnvironmentUtility.GetFriendlyPlatformName(Constants.Platform)}", LogLevel.Info);
+ this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Constants.GameVersion} (build {Constants.GetBuildVersionLabel()}) on {EnvironmentUtility.GetFriendlyPlatformName(Constants.Platform)}", LogLevel.Info);
// log basic info
this.Monitor.Log($"Mods go here: {modsPath}", LogLevel.Info);
@@ -284,10 +280,6 @@ namespace StardewModdingAPI.Framework.Logging
// log custom settings
if (customSettings.Any())
this.Monitor.Log($"Loaded with custom settings: {string.Join(", ", customSettings.OrderBy(p => p.Key).Select(p => $"{p.Key}: {p.Value}"))}");
-
-#if !SMAPI_DEPRECATED
- this.Monitor.Log("SMAPI is running in 'strict mode', which removes all deprecated APIs. This can significantly improve performance, but some mods may not work. You can reinstall SMAPI to disable it if you run into problems.", LogLevel.Info);
-#endif
}
/// Log details for settings that don't match the default.
diff --git a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs
index 90edc137..d3c5a1f9 100644
--- a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs
+++ b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs
@@ -1,7 +1,4 @@
using System;
-#if SMAPI_DEPRECATED
-using StardewModdingAPI.Framework.Deprecations;
-#endif
namespace StardewModdingAPI.Framework.ModHelpers
{
@@ -33,21 +30,5 @@ namespace StardewModdingAPI.Framework.ModHelpers
this.CommandManager.Add(this.Mod, name, documentation, callback);
return this;
}
-
-#if SMAPI_DEPRECATED
- ///
- [Obsolete("Use mod-provided APIs to integrate with mods instead. This method will be removed in SMAPI 4.0.0.")]
- public bool Trigger(string name, string[] arguments)
- {
- SCore.DeprecationManager.Warn(
- source: this.Mod,
- nounPhrase: $"{nameof(IModHelper)}.{nameof(IModHelper.ConsoleCommands)}.{nameof(ICommandHelper.Trigger)}",
- version: "3.8.1",
- severity: DeprecationLevel.PendingRemoval
- );
-
- return this.CommandManager.Trigger(name, arguments);
- }
-#endif
}
}
diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs
deleted file mode 100644
index 152b264c..00000000
--- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs
+++ /dev/null
@@ -1,253 +0,0 @@
-#if SMAPI_DEPRECATED
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Diagnostics.CodeAnalysis;
-using System.Diagnostics.Contracts;
-using System.IO;
-using System.Linq;
-using StardewModdingAPI.Framework.Content;
-using StardewModdingAPI.Framework.ContentManagers;
-using StardewModdingAPI.Framework.Deprecations;
-using StardewModdingAPI.Framework.Exceptions;
-using StardewModdingAPI.Framework.Reflection;
-using StardewValley;
-
-namespace StardewModdingAPI.Framework.ModHelpers
-{
- /// Provides an API for loading content assets.
- [Obsolete($"Use {nameof(IMod.Helper)}.{nameof(IModHelper.GameContent)} or {nameof(IMod.Helper)}.{nameof(IModHelper.ModContent)} instead. This interface will be removed in SMAPI 4.0.0.")]
- internal class ContentHelper : BaseHelper, IContentHelper
- {
- /*********
- ** Fields
- *********/
- /// SMAPI's core content logic.
- private readonly ContentCoordinator ContentCore;
-
- /// A content manager for this mod which manages files from the game's Content folder.
- private readonly IContentManager GameContentManager;
-
- /// A content manager for this mod which manages files from the mod's folder.
- private readonly ModContentManager ModContentManager;
-
- /// Encapsulates monitoring and logging.
- private readonly IMonitor Monitor;
-
- /// Simplifies access to private code.
- private readonly Reflector Reflection;
-
-
- /*********
- ** Accessors
- *********/
- ///
- public string CurrentLocale => this.GameContentManager.GetLocale();
-
- ///
- public LocalizedContentManager.LanguageCode CurrentLocaleConstant => this.GameContentManager.Language;
-
- /// The observable implementation of .
- internal ObservableCollection ObservableAssetEditors { get; } = new();
-
- /// The observable implementation of .
- internal ObservableCollection ObservableAssetLoaders { get; } = new();
-
- ///
- public IList AssetLoaders
- {
- get
- {
- SCore.DeprecationManager.Warn(
- source: this.Mod,
- nounPhrase: $"{nameof(IContentHelper)}.{nameof(IContentHelper.AssetLoaders)}",
- version: "3.14.0",
- severity: DeprecationLevel.PendingRemoval
- );
-
- return this.ObservableAssetLoaders;
- }
- }
-
- ///
- public IList AssetEditors
- {
- get
- {
- SCore.DeprecationManager.Warn(
- source: this.Mod,
- nounPhrase: $"{nameof(IContentHelper)}.{nameof(IContentHelper.AssetEditors)}",
- version: "3.14.0",
- severity: DeprecationLevel.PendingRemoval
- );
-
- return this.ObservableAssetEditors;
- }
- }
-
-
- /*********
- ** Public methods
- *********/
- /// Construct an instance.
- /// SMAPI's core content logic.
- /// The absolute path to the mod folder.
- /// The mod using this instance.
- /// Encapsulates monitoring and logging.
- /// Simplifies access to private code.
- public ContentHelper(ContentCoordinator contentCore, string modFolderPath, IModMetadata mod, IMonitor monitor, Reflector reflection)
- : base(mod)
- {
- string managedAssetPrefix = contentCore.GetManagedAssetPrefix(mod.Manifest.UniqueID);
-
- this.ContentCore = contentCore;
- this.GameContentManager = contentCore.CreateGameContentManager(managedAssetPrefix + ".content");
- this.ModContentManager = contentCore.CreateModContentManager(managedAssetPrefix, this.Mod.DisplayName, modFolderPath, this.GameContentManager);
- this.Monitor = monitor;
- this.Reflection = reflection;
- }
-
- ///
- public T Load(string key, ContentSource source = ContentSource.ModFolder)
- where T : notnull
- {
- IAssetName assetName = this.ContentCore.ParseAssetName(key, allowLocales: source == ContentSource.GameContent);
-
- try
- {
- this.AssertAndNormalizeAssetName(key);
- switch (source)
- {
- case ContentSource.GameContent:
- if (assetName.Name.EndsWith(".xnb", StringComparison.OrdinalIgnoreCase))
- {
- assetName = this.ContentCore.ParseAssetName(assetName.Name[..^4], allowLocales: true);
- SCore.DeprecationManager.Warn(
- this.Mod,
- "loading assets from the Content folder with a .xnb file extension",
- "3.14.0",
- DeprecationLevel.Info
- );
- }
-
- return this.GameContentManager.LoadLocalized(assetName, this.CurrentLocaleConstant, useCache: false);
-
- case ContentSource.ModFolder:
- try
- {
- return this.ModContentManager.LoadExact(assetName, useCache: false);
- }
- catch (SContentLoadException ex) when (ex.ErrorType == ContentLoadErrorType.AssetDoesNotExist)
- {
- // legacy behavior: you can load a .xnb file without the file extension
- try
- {
- IAssetName newName = this.ContentCore.ParseAssetName(assetName.Name + ".xnb", allowLocales: false);
- if (this.ModContentManager.DoesAssetExist(newName))
- {
- T data = this.ModContentManager.LoadExact(newName, useCache: false);
- SCore.DeprecationManager.Warn(
- this.Mod,
- "loading XNB files from the mod folder without the .xnb file extension",
- "3.14.0",
- DeprecationLevel.Info
- );
- return data;
- }
- }
- catch { /* legacy behavior failed, rethrow original error */ }
-
- throw;
- }
-
- default:
- throw new SContentLoadException(ContentLoadErrorType.Other, $"{this.Mod.DisplayName} failed loading content asset '{key}' from {source}: unknown content source '{source}'.");
- }
- }
- catch (Exception ex) when (ex is not SContentLoadException)
- {
- throw new SContentLoadException(ContentLoadErrorType.Other, $"{this.Mod.DisplayName} failed loading content asset '{key}' from {source}.", ex);
- }
- }
-
- ///
- [Pure]
- public string NormalizeAssetName(string? assetName)
- {
- return this.ModContentManager.AssertAndNormalizeAssetName(assetName);
- }
-
- ///
- public string GetActualAssetKey(string key, ContentSource source = ContentSource.ModFolder)
- {
- switch (source)
- {
- case ContentSource.GameContent:
- return this.GameContentManager.AssertAndNormalizeAssetName(key);
-
- case ContentSource.ModFolder:
- return this.ModContentManager.GetInternalAssetKey(key).Name;
-
- default:
- throw new NotSupportedException($"Unknown content source '{source}'.");
- }
- }
-
- ///
- public bool InvalidateCache(string key)
- {
- string actualKey = this.GetActualAssetKey(key, ContentSource.GameContent);
- this.Monitor.Log($"Requested cache invalidation for '{actualKey}'.");
- return this.ContentCore.InvalidateCache(asset => asset.Name.IsEquivalentTo(actualKey)).Any();
- }
-
- ///
- public bool InvalidateCache()
- where T : notnull
- {
- this.Monitor.Log($"Requested cache invalidation for all assets of type {typeof(T)}. This is an expensive operation and should be avoided if possible.");
- return this.ContentCore.InvalidateCache((_, _, type) => typeof(T).IsAssignableFrom(type)).Any();
- }
-
- ///
- public bool InvalidateCache(Func predicate)
- {
- this.Monitor.Log("Requested cache invalidation for all assets matching a predicate.");
- return this.ContentCore.InvalidateCache(predicate).Any();
- }
-
- ///
- public IAssetData GetPatchHelper(T data, string? assetName = null)
- where T : notnull
- {
- if (data == null)
- throw new ArgumentNullException(nameof(data), "Can't get a patch helper for a null value.");
-
- assetName ??= $"temp/{Guid.NewGuid():N}";
-
- return new AssetDataForObject(
- locale: this.CurrentLocale,
- assetName: this.ContentCore.ParseAssetName(assetName, allowLocales: true/* no way to know if it's a game or mod asset here*/),
- data: data,
- getNormalizedPath: this.NormalizeAssetName,
- reflection: this.Reflection
- );
- }
-
-
- /*********
- ** Private methods
- *********/
- /// Assert that the given key has a valid format.
- /// The asset key to check.
- /// The asset key is empty or contains invalid characters.
- [SuppressMessage("ReSharper", "ParameterOnlyUsedForPreconditionCheck.Local", Justification = "Parameter is only used for assertion checks by design.")]
- private void AssertAndNormalizeAssetName(string key)
- {
- this.ModContentManager.AssertAndNormalizeAssetName(key);
- if (Path.IsPathRooted(key))
- throw new ArgumentException("The asset key must not be an absolute path.");
- }
- }
-}
-#endif
diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs
index 531289d0..d1cf357e 100644
--- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs
+++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs
@@ -1,9 +1,6 @@
using System;
using System.IO;
using StardewModdingAPI.Events;
-#if SMAPI_DEPRECATED
-using StardewModdingAPI.Framework.Deprecations;
-#endif
using StardewModdingAPI.Framework.Input;
namespace StardewModdingAPI.Framework.ModHelpers
@@ -11,16 +8,6 @@ namespace StardewModdingAPI.Framework.ModHelpers
/// Provides simplified APIs for writing mods.
internal class ModHelper : BaseHelper, IModHelper, IDisposable
{
-#if SMAPI_DEPRECATED
- /*********
- ** Fields
- *********/
- /// The backing field for .
- [Obsolete("This only exists to support legacy code and will be removed in SMAPI 4.0.0.")]
- private readonly ContentHelper ContentImpl;
-#endif
-
-
/*********
** Accessors
*********/
@@ -30,25 +17,6 @@ namespace StardewModdingAPI.Framework.ModHelpers
///
public IModEvents Events { get; }
-#if SMAPI_DEPRECATED
- ///
- [Obsolete($"Use {nameof(IGameContentHelper)} or {nameof(IModContentHelper)} instead.")]
- public IContentHelper Content
- {
- get
- {
- SCore.DeprecationManager.Warn(
- source: this.Mod,
- nounPhrase: $"{nameof(IModHelper)}.{nameof(IModHelper.Content)}",
- version: "3.14.0",
- severity: DeprecationLevel.PendingRemoval
- );
-
- return this.ContentImpl;
- }
- }
-#endif
-
///
public IGameContentHelper GameContent { get; }
@@ -88,7 +56,6 @@ namespace StardewModdingAPI.Framework.ModHelpers
/// The full path to the mod's folder.
/// Manages the game's input state for the current player instance. That may not be the main player in split-screen mode.
/// Manages access to events raised by SMAPI.
- /// An API for loading content assets.
/// An API for loading content assets from the game's Content folder or via .
/// An API for loading content assets from your mod's files.
/// An API for managing content packs.
@@ -100,13 +67,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
/// An API for reading translations stored in the mod's i18n folder.
/// An argument is null or empty.
/// The path does not exist on disk.
- public ModHelper(
- IModMetadata mod, string modDirectory, Func currentInputState, IModEvents events,
-#if SMAPI_DEPRECATED
- ContentHelper contentHelper,
-#endif
- IGameContentHelper gameContentHelper, IModContentHelper modContentHelper, IContentPackHelper contentPackHelper, ICommandHelper commandHelper, IDataHelper dataHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper
- )
+ public ModHelper(IModMetadata mod, string modDirectory, Func currentInputState, IModEvents events, IGameContentHelper gameContentHelper, IModContentHelper modContentHelper, IContentPackHelper contentPackHelper, ICommandHelper commandHelper, IDataHelper dataHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper)
: base(mod)
{
// validate directory
@@ -117,9 +78,6 @@ namespace StardewModdingAPI.Framework.ModHelpers
// initialize
this.DirectoryPath = modDirectory;
-#if SMAPI_DEPRECATED
- this.ContentImpl = contentHelper ?? throw new ArgumentNullException(nameof(contentHelper));
-#endif
this.GameContent = gameContentHelper ?? throw new ArgumentNullException(nameof(gameContentHelper));
this.ModContent = modContentHelper ?? throw new ArgumentNullException(nameof(modContentHelper));
this.ContentPacks = contentPackHelper ?? throw new ArgumentNullException(nameof(contentPackHelper));
@@ -133,15 +91,6 @@ namespace StardewModdingAPI.Framework.ModHelpers
this.Events = events;
}
-#if SMAPI_DEPRECATED
- /// Get the underlying instance for .
- [Obsolete("This only exists to support legacy code and will be removed in SMAPI 4.0.0.")]
- public ContentHelper GetLegacyContentHelper()
- {
- return this.ContentImpl;
- }
-#endif
-
/****
** Mod config file
****/
diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
index a4354e57..17432085 100644
--- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
+++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
@@ -170,31 +170,6 @@ namespace StardewModdingAPI.Framework.ModLoading
this.AssemblyDefinitionResolver.Add(assembly.Definition);
}
-#if SMAPI_DEPRECATED
- // special case: clear legacy-DLL warnings if the mod bundles a copy
- if (mod.Warnings.HasFlag(ModWarning.DetectedLegacyCachingDll))
- {
- if (File.Exists(Path.Combine(mod.DirectoryPath, "System.Runtime.Caching.dll")))
- mod.RemoveWarning(ModWarning.DetectedLegacyCachingDll);
- else
- {
- // remove duplicate warnings (System.Runtime.Caching.dll references these)
- mod.RemoveWarning(ModWarning.DetectedLegacyConfigurationDll);
- mod.RemoveWarning(ModWarning.DetectedLegacyPermissionsDll);
- }
- }
- if (mod.Warnings.HasFlag(ModWarning.DetectedLegacyConfigurationDll))
- {
- if (File.Exists(Path.Combine(mod.DirectoryPath, "System.Configuration.ConfigurationManager.dll")))
- mod.RemoveWarning(ModWarning.DetectedLegacyConfigurationDll);
- }
- if (mod.Warnings.HasFlag(ModWarning.DetectedLegacyPermissionsDll))
- {
- if (File.Exists(Path.Combine(mod.DirectoryPath, "System.Security.Permissions.dll")))
- mod.RemoveWarning(ModWarning.DetectedLegacyPermissionsDll);
- }
-#endif
-
// throw if incompatibilities detected
if (!assumeCompatible && mod.Warnings.HasFlag(ModWarning.BrokenCodeLoaded))
throw new IncompatibleInstructionException();
@@ -478,23 +453,6 @@ namespace StardewModdingAPI.Framework.ModLoading
mod.SetWarning(ModWarning.AccessesShell);
break;
-#if SMAPI_DEPRECATED
- case InstructionHandleResult.DetectedLegacyCachingDll:
- template = $"{logPrefix}Detected reference to System.Runtime.Caching.dll, which will be removed in SMAPI 4.0.0.";
- mod.SetWarning(ModWarning.DetectedLegacyCachingDll);
- break;
-
- case InstructionHandleResult.DetectedLegacyConfigurationDll:
- template = $"{logPrefix}Detected reference to System.Configuration.ConfigurationManager.dll, which will be removed in SMAPI 4.0.0.";
- mod.SetWarning(ModWarning.DetectedLegacyConfigurationDll);
- break;
-
- case InstructionHandleResult.DetectedLegacyPermissionsDll:
- template = $"{logPrefix}Detected reference to System.Security.Permissions.dll, which will be removed in SMAPI 4.0.0.";
- mod.SetWarning(ModWarning.DetectedLegacyPermissionsDll);
- break;
-#endif
-
case InstructionHandleResult.None:
break;
diff --git a/src/SMAPI/Framework/ModLoading/Finders/LegacyAssemblyFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/LegacyAssemblyFinder.cs
deleted file mode 100644
index 77380907..00000000
--- a/src/SMAPI/Framework/ModLoading/Finders/LegacyAssemblyFinder.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-#if SMAPI_DEPRECATED
-using Mono.Cecil;
-using StardewModdingAPI.Framework.ModLoading.Framework;
-
-namespace StardewModdingAPI.Framework.ModLoading.Finders
-{
- /// Detects assembly references which will break in SMAPI 4.0.0.
- internal class LegacyAssemblyFinder : BaseInstructionHandler
- {
- /*********
- ** Public methods
- *********/
- /// Construct an instance.
- public LegacyAssemblyFinder()
- : base(defaultPhrase: "legacy assembly references") { }
-
-
- ///
- public override bool Handle(ModuleDefinition module)
- {
- foreach (AssemblyNameReference assembly in module.AssemblyReferences)
- {
- InstructionHandleResult flag = this.GetFlag(assembly);
- if (flag is InstructionHandleResult.None)
- continue;
-
- this.MarkFlag(flag);
- }
-
- return false;
- }
-
-
- /*********
- ** Private methods
- *********/
- /// Get the instruction handle flag for the given assembly reference, if any.
- /// The assembly reference.
- private InstructionHandleResult GetFlag(AssemblyNameReference assemblyRef)
- {
- return assemblyRef.Name switch
- {
- "System.Configuration.ConfigurationManager" => InstructionHandleResult.DetectedLegacyConfigurationDll,
- "System.Runtime.Caching" => InstructionHandleResult.DetectedLegacyCachingDll,
- "System.Security.Permission" => InstructionHandleResult.DetectedLegacyPermissionsDll,
- _ => InstructionHandleResult.None
- };
- }
- }
-}
-#endif
diff --git a/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs b/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs
index 189ca64e..e3f108cb 100644
--- a/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs
+++ b/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs
@@ -30,17 +30,6 @@ namespace StardewModdingAPI.Framework.ModLoading
DetectedFilesystemAccess,
/// The instruction accesses the OS shell or processes directly.
- DetectedShellAccess,
-
-#if SMAPI_DEPRECATED
- /// The module references the legacy System.Configuration.ConfigurationManager assembly and doesn't include a copy in the mod folder, so it'll break in SMAPI 4.0.0.
- DetectedLegacyConfigurationDll,
-
- /// The module references the legacy System.Runtime.Caching assembly and doesn't include a copy in the mod folder, so it'll break in SMAPI 4.0.0.
- DetectedLegacyCachingDll,
-
- /// The module references the legacy System.Security.Permissions assembly and doesn't include a copy in the mod folder, so it'll break in SMAPI 4.0.0.
- DetectedLegacyPermissionsDll
-#endif
+ DetectedShellAccess
}
}
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index 68e96393..166d007d 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -30,9 +30,6 @@ using StardewModdingAPI.Framework.Networking;
using StardewModdingAPI.Framework.Reflection;
using StardewModdingAPI.Framework.Rendering;
using StardewModdingAPI.Framework.Serialization;
-#if SMAPI_DEPRECATED
-using StardewModdingAPI.Framework.StateTracking.Comparers;
-#endif
using StardewModdingAPI.Framework.StateTracking.Snapshots;
using StardewModdingAPI.Framework.Utilities;
using StardewModdingAPI.Internal;
@@ -143,11 +140,6 @@ namespace StardewModdingAPI.Framework
/// The maximum number of consecutive attempts SMAPI should make to recover from an update error.
private readonly Countdown UpdateCrashTimer = new(60); // 60 ticks = roughly one second
-#if SMAPI_DEPRECATED
- /// Asset interceptors added or removed since the last tick.
- private readonly List ReloadAssetInterceptorsQueue = new();
-#endif
-
/// A list of queued commands to parse and execute.
private readonly CommandQueue RawCommandQueue = new();
@@ -544,41 +536,6 @@ namespace StardewModdingAPI.Framework
this.Monitor.LogOnce("A mod enabled Harmony debug mode, which impacts performance and creates a file on your desktop. SMAPI will try to keep it disabled. (You can allow debug mode by editing the smapi-internal/config.json file.)", LogLevel.Warn);
}
-#if SMAPI_DEPRECATED
- /*********
- ** Reload assets when interceptors are added/removed
- *********/
- if (this.ReloadAssetInterceptorsQueue.Any())
- {
- // get unique interceptors
- AssetInterceptorChange[] interceptors = this.ReloadAssetInterceptorsQueue
- .GroupBy(p => p.Instance, new ObjectReferenceComparer