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. SMAPI latest $(AssemblySearchPaths);{GAC} - $(DefineConstants);SMAPI_DEPRECATED true @@ -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()) - .Select(p => p.First()) - .ToArray(); - this.ReloadAssetInterceptorsQueue.Clear(); - - // log summary - this.Monitor.Log("Invalidating cached assets for new editors & loaders..."); - this.Monitor.Log( - " changed: " - + string.Join(", ", - interceptors - .GroupBy(p => p.Mod) - .OrderBy(p => p.Key.DisplayName) - .Select(modGroup => - $"{modGroup.Key.DisplayName} (" - + string.Join(", ", modGroup.GroupBy(p => p.WasAdded).ToDictionary(p => p.Key, p => p.Count()).Select(p => $"{(p.Key ? "added" : "removed")} {p.Value}")) - + ")" - ) - ) - + "." - ); - - // reload affected assets - this.ContentCore.InvalidateCache(asset => interceptors.Any(p => p.CanIntercept(asset))); - } -#endif - /********* ** Parse commands *********/ @@ -1718,19 +1675,6 @@ namespace StardewModdingAPI.Framework // initialize translations this.ReloadTranslations(loaded); - // set temporary PyTK compatibility mode - // 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. - { - IModInfo? pyTk = this.ModRegistry.Get("Platonymous.Toolkit"); - if (pyTk is not null && pyTk.Manifest.Version.IsOlderThan("1.24.0")) -#if SMAPI_DEPRECATED - ModContentManager.EnablePyTkLegacyMode = true; -#else - this.Monitor.Log("PyTK's image scaling is not compatible with SMAPI strict mode.", LogLevel.Warn); -#endif - } - // initialize loaded non-content-pack mods this.Monitor.Log("Launching mods...", LogLevel.Debug); foreach (IModMetadata metadata in loadedMods) @@ -1739,69 +1683,6 @@ namespace StardewModdingAPI.Framework metadata.Mod ?? throw new InvalidOperationException($"The '{metadata.DisplayName}' mod is not initialized correctly."); // should never happen, but avoids nullability warnings -#if SMAPI_DEPRECATED - // add interceptors - if (mod.Helper is ModHelper helper) - { - // ReSharper disable SuspiciousTypeConversion.Global - if (mod is IAssetEditor editor) - { - SCore.DeprecationManager.Warn( - source: metadata, - nounPhrase: $"{nameof(IAssetEditor)}", - version: "3.14.0", - severity: DeprecationLevel.PendingRemoval, - logStackTrace: false - ); - - this.ContentCore.Editors.Add(new ModLinked(metadata, editor)); - } - - if (mod is IAssetLoader loader) - { - SCore.DeprecationManager.Warn( - source: metadata, - nounPhrase: $"{nameof(IAssetLoader)}", - version: "3.14.0", - severity: DeprecationLevel.PendingRemoval, - logStackTrace: false - ); - - this.ContentCore.Loaders.Add(new ModLinked(metadata, loader)); - } - // ReSharper restore SuspiciousTypeConversion.Global - - ContentHelper content = helper.GetLegacyContentHelper(); - content.ObservableAssetEditors.CollectionChanged += (_, e) => this.OnAssetInterceptorsChanged(metadata, e.NewItems?.Cast(), e.OldItems?.Cast(), this.ContentCore.Editors); - content.ObservableAssetLoaders.CollectionChanged += (_, e) => this.OnAssetInterceptorsChanged(metadata, e.NewItems?.Cast(), e.OldItems?.Cast(), this.ContentCore.Loaders); - } - - // log deprecation warnings - if (metadata.HasWarnings(ModWarning.DetectedLegacyCachingDll, ModWarning.DetectedLegacyConfigurationDll, ModWarning.DetectedLegacyPermissionsDll)) - { - string?[] referenced = - new[] - { - metadata.Warnings.HasFlag(ModWarning.DetectedLegacyConfigurationDll) ? "System.Configuration.ConfigurationManager" : null, - metadata.Warnings.HasFlag(ModWarning.DetectedLegacyCachingDll) ? "System.Runtime.Caching" : null, - metadata.Warnings.HasFlag(ModWarning.DetectedLegacyPermissionsDll) ? "System.Security.Permissions" : null - } - .Where(p => p is not null) - .ToArray(); - - foreach (string? name in referenced) - { - DeprecationManager.Warn( - metadata, - $"using {name} without bundling it", - "3.14.7", - DeprecationLevel.PendingRemoval, - logStackTrace: false - ); - } - } -#endif - // initialize mod Context.HeuristicModsRunningCode.Push(metadata); { @@ -1847,31 +1728,6 @@ namespace StardewModdingAPI.Framework this.Monitor.Log("Mods loaded and ready!", LogLevel.Debug); } -#if SMAPI_DEPRECATED - /// Raised after a mod adds or removes asset interceptors. - /// The asset interceptor type (one of or ). - /// The mod metadata. - /// The interceptors that were added. - /// The interceptors that were removed. - /// A list of interceptors to update for the change. - private void OnAssetInterceptorsChanged(IModMetadata mod, IEnumerable? added, IEnumerable? removed, IList> list) - where T : notnull - { - foreach (T interceptor in added ?? Array.Empty()) - { - this.ReloadAssetInterceptorsQueue.Add(new AssetInterceptorChange(mod, interceptor, wasAdded: true)); - list.Add(new ModLinked(mod, interceptor)); - } - - foreach (T interceptor in removed ?? Array.Empty()) - { - this.ReloadAssetInterceptorsQueue.Add(new AssetInterceptorChange(mod, interceptor, wasAdded: false)); - foreach (ModLinked entry in list.Where(p => p.Mod == mod && object.ReferenceEquals(p.Data, interceptor)).ToArray()) - list.Remove(entry); - } - } -#endif - /// Load a given mod. /// The mod to load. /// The mods being loaded. @@ -2014,9 +1870,6 @@ namespace StardewModdingAPI.Framework { IModEvents events = new ModEvents(mod, this.EventManager); ICommandHelper commandHelper = new CommandHelper(mod, this.CommandManager); -#if SMAPI_DEPRECATED - ContentHelper contentHelper = new(contentCore, mod.DirectoryPath, mod, monitor, this.Reflection); -#endif GameContentHelper gameContentHelper = new(contentCore, mod, mod.DisplayName, monitor, this.Reflection); IModContentHelper modContentHelper = new ModContentHelper(contentCore, mod.DirectoryPath, mod, mod.DisplayName, gameContentHelper.GetUnderlyingContentManager(), this.Reflection); IContentPackHelper contentPackHelper = new ContentPackHelper( @@ -2029,11 +1882,7 @@ namespace StardewModdingAPI.Framework IModRegistry modRegistryHelper = new ModRegistryHelper(mod, this.ModRegistry, proxyFactory, monitor); IMultiplayerHelper multiplayerHelper = new MultiplayerHelper(mod, this.Multiplayer); - modHelper = new ModHelper(mod, mod.DirectoryPath, () => this.GetCurrentGameInstance().Input, events, -#if SMAPI_DEPRECATED - contentHelper, -#endif - gameContentHelper, modContentHelper, contentPackHelper, commandHelper, dataHelper, modRegistryHelper, reflectionHelper, multiplayerHelper, translationHelper); + modHelper = new ModHelper(mod, mod.DirectoryPath, () => this.GetCurrentGameInstance().Input, events, gameContentHelper, modContentHelper, contentPackHelper, commandHelper, dataHelper, modRegistryHelper, reflectionHelper, multiplayerHelper, translationHelper); } // init mod diff --git a/src/SMAPI/GameFramework.cs b/src/SMAPI/GameFramework.cs index 009865fe..98662f78 100644 --- a/src/SMAPI/GameFramework.cs +++ b/src/SMAPI/GameFramework.cs @@ -1,18 +1,8 @@ -#if SMAPI_DEPRECATED -using System; -#endif - namespace StardewModdingAPI { /// The game framework running the game. public enum GameFramework { -#if SMAPI_DEPRECATED - /// The XNA Framework, previously used on Windows. - [Obsolete("Stardew Valley no longer uses XNA Framework on any supported platform. This value will be removed in SMAPI 4.0.0.")] - Xna, -#endif - /// The MonoGame framework. MonoGame } diff --git a/src/SMAPI/IAssetEditor.cs b/src/SMAPI/IAssetEditor.cs deleted file mode 100644 index f3238ba9..00000000 --- a/src/SMAPI/IAssetEditor.cs +++ /dev/null @@ -1,23 +0,0 @@ -#if SMAPI_DEPRECATED -using System; -using StardewModdingAPI.Events; - -namespace StardewModdingAPI -{ - /// Edits matching content assets. - [Obsolete($"Use {nameof(IMod.Helper)}.{nameof(IModHelper.Events)}.{nameof(IModEvents.Content)} instead. This interface will be removed in SMAPI 4.0.0.")] - public interface IAssetEditor - { - /********* - ** Public methods - *********/ - /// Get whether this instance can edit the given asset. - /// Basic metadata about the asset being loaded. - bool CanEdit(IAssetInfo asset); - - /// Edit a matched asset. - /// A helper which encapsulates metadata about an asset and enables changes to it. - void Edit(IAssetData asset); - } -} -#endif diff --git a/src/SMAPI/IAssetInfo.cs b/src/SMAPI/IAssetInfo.cs index 20064946..f0cadffc 100644 --- a/src/SMAPI/IAssetInfo.cs +++ b/src/SMAPI/IAssetInfo.cs @@ -8,20 +8,10 @@ namespace StardewModdingAPI /********* ** Accessors *********/ -#if SMAPI_DEPRECATED /// The content's locale code, if the content is localized. - /// LEGACY NOTE: when reading this field from an or implementation, for non-localized assets it will return the current game locale (or an empty string for English) instead of null. -#else - /// The content's locale code, if the content is localized. -#endif string? Locale { get; } -#if SMAPI_DEPRECATED /// The asset name being read. - /// LEGACY NOTE: when reading this field from an or implementation, it's always equivalent to for backwards compatibility. -#else - /// The asset name being read. -#endif public IAssetName Name { get; } /// The with any locale codes stripped. @@ -30,20 +20,5 @@ namespace StardewModdingAPI /// The content data type. Type DataType { get; } - -#if SMAPI_DEPRECATED - /// The normalized asset name being read. The format may change between platforms; see to compare with a known path. - [Obsolete($"Use {nameof(Name)} or {nameof(NameWithoutLocale)} instead. This property will be removed in SMAPI 4.0.0.")] - string AssetName { get; } - - - /********* - ** Public methods - *********/ - /// Get whether the asset name being loaded matches a given name after normalization. - /// The expected asset path, relative to the game's content folder and without the .xnb extension or locale suffix (like 'Data\ObjectInformation'). - [Obsolete($"Use {nameof(Name)}.{nameof(IAssetName.IsEquivalentTo)} or {nameof(NameWithoutLocale)}.{nameof(IAssetName.IsEquivalentTo)} instead. This method will be removed in SMAPI 4.0.0.")] - bool AssetNameEquals(string path); -#endif } } diff --git a/src/SMAPI/IAssetLoader.cs b/src/SMAPI/IAssetLoader.cs deleted file mode 100644 index 205980a7..00000000 --- a/src/SMAPI/IAssetLoader.cs +++ /dev/null @@ -1,23 +0,0 @@ -#if SMAPI_DEPRECATED -using System; -using StardewModdingAPI.Events; - -namespace StardewModdingAPI -{ - /// Provides the initial version for matching assets loaded by the game. SMAPI will raise an error if two mods try to load the same asset; in most cases you should use instead. - [Obsolete($"Use {nameof(IMod.Helper)}.{nameof(IModHelper.Events)}.{nameof(IModEvents.Content)} instead. This interface will be removed in SMAPI 4.0.0.")] - public interface IAssetLoader - { - /********* - ** Public methods - *********/ - /// Get whether this instance can load the initial version of the given asset. - /// Basic metadata about the asset being loaded. - bool CanLoad(IAssetInfo asset); - - /// Load a matched asset. - /// Basic metadata about the asset being loaded. - T Load(IAssetInfo asset); - } -} -#endif diff --git a/src/SMAPI/ICommandHelper.cs b/src/SMAPI/ICommandHelper.cs index c92a09c2..afbcf2b0 100644 --- a/src/SMAPI/ICommandHelper.cs +++ b/src/SMAPI/ICommandHelper.cs @@ -16,14 +16,5 @@ namespace StardewModdingAPI /// The is not a valid format. /// There's already a command with that name. ICommandHelper Add(string name, string documentation, Action callback); - -#if SMAPI_DEPRECATED - /// Trigger a command. - /// The command name. - /// The command arguments. - /// Returns whether a matching command was triggered. - [Obsolete("Use mod-provided APIs to integrate with mods instead. This method will be removed in SMAPI 4.0.0.")] - bool Trigger(string name, string[] arguments); -#endif } } diff --git a/src/SMAPI/IContentHelper.cs b/src/SMAPI/IContentHelper.cs deleted file mode 100644 index b0e30a82..00000000 --- a/src/SMAPI/IContentHelper.cs +++ /dev/null @@ -1,84 +0,0 @@ -#if SMAPI_DEPRECATED -using System; -using System.Collections.Generic; -using System.Diagnostics.Contracts; -using Microsoft.Xna.Framework.Content; -using Microsoft.Xna.Framework.Graphics; -using StardewModdingAPI.Events; -using StardewValley; -using xTile; - -namespace StardewModdingAPI -{ - /// 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.")] - public interface IContentHelper : IModLinked - { - /********* - ** Accessors - *********/ - /// Interceptors which provide the initial versions of matching content assets. - [Obsolete($"Use {nameof(IMod.Helper)}.{nameof(IModHelper.Events)}.{nameof(IModEvents.Content)} instead. This property will be removed in SMAPI 4.0.0.")] - IList AssetLoaders { get; } - - /// Interceptors which edit matching content assets after they're loaded. - [Obsolete($"Use {nameof(IMod.Helper)}.{nameof(IModHelper.Events)}.{nameof(IModEvents.Content)} instead. This property will be removed in SMAPI 4.0.0.")] - IList AssetEditors { get; } - - /// The game's current locale code (like pt-BR). - string CurrentLocale { get; } - - /// The game's current locale as an enum value. - LocalizedContentManager.LanguageCode CurrentLocaleConstant { get; } - - - /********* - ** Public methods - *********/ - /// Load content from the game folder or mod folder (if not already cached), and return it. When loading a .png file, this must be called outside the game's draw loop. - /// The expected data type. The main supported types are , , (for mod content only), and data structures; other types may be supported by the game's content pipeline. - /// The asset key to fetch (if the is ), or the local path to a content file relative to the mod folder. - /// Where to search for a matching content asset. - /// The is empty or contains invalid characters. - /// The content asset couldn't be loaded (e.g. because it doesn't exist). - T Load(string key, ContentSource source = ContentSource.ModFolder) - where T : notnull; - - /// Normalize an asset name so it's consistent with those generated by the game. This is mainly useful for string comparisons like on generated asset names, and isn't necessary when passing asset names into other content helper methods. - /// The asset key. - /// The asset key is empty or contains invalid characters. - [Pure] - string NormalizeAssetName(string? assetName); - - /// Get the underlying key in the game's content cache for an asset. This can be used to load custom map tilesheets, but should be avoided when you can use the content API instead. This does not validate whether the asset exists. - /// The asset key to fetch (if the is ), or the local path to a content file relative to the mod folder. - /// Where to search for a matching content asset. - /// The is empty or contains invalid characters. - string GetActualAssetKey(string key, ContentSource source = ContentSource.ModFolder); - - /// Remove an asset from the content cache so it's reloaded on the next request. This will reload core game assets if needed, but references to the former asset will still show the previous content. - /// The asset key to invalidate in the content folder. - /// The is empty or contains invalid characters. - /// Returns whether the given asset key was cached. - bool InvalidateCache(string key); - - /// Remove all assets of the given type from the cache so they're reloaded on the next request. This can be a very expensive operation and should only be used in very specific cases. This will reload core game assets if needed, but references to the former assets will still show the previous content. - /// The asset type to remove from the cache. - /// Returns whether any assets were invalidated. - bool InvalidateCache() - where T : notnull; - - /// Remove matching assets from the content cache so they're reloaded on the next request. This will reload core game assets if needed, but references to the former asset will still show the previous content. - /// A predicate matching the assets to invalidate. - /// Returns whether any cache entries were invalidated. - bool InvalidateCache(Func predicate); - - /// Get a patch helper for arbitrary data. - /// The data type. - /// The asset data. - /// The asset name. This is only used for tracking purposes and has no effect on the patch helper. - IAssetData GetPatchHelper(T data, string? assetName = null) - where T : notnull; - } -} -#endif diff --git a/src/SMAPI/IContentPack.cs b/src/SMAPI/IContentPack.cs index 5047b172..5dac451d 100644 --- a/src/SMAPI/IContentPack.cs +++ b/src/SMAPI/IContentPack.cs @@ -1,9 +1,4 @@ using System; -#if SMAPI_DEPRECATED -using Microsoft.Xna.Framework.Content; -using Microsoft.Xna.Framework.Graphics; -using xTile; -#endif namespace StardewModdingAPI { @@ -48,22 +43,5 @@ namespace StardewModdingAPI /// The is not relative or contains directory climbing (../). void WriteJsonFile(string path, TModel data) where TModel : class; - -#if SMAPI_DEPRECATED - /// Load content from the content pack folder (if not already cached), and return it. When loading a .png file, this must be called outside the game's draw loop. - /// The expected data type. The main supported types are , , , and data structures; other types may be supported by the game's content pipeline. - /// The relative file path within the content pack (case-insensitive). - /// The is empty or contains invalid characters. - /// The content asset couldn't be loaded (e.g. because it doesn't exist). - [Obsolete($"Use {nameof(IContentPack.ModContent)}.{nameof(IModContentHelper.Load)} instead. This method will be removed in SMAPI 4.0.0.")] - T LoadAsset(string key) - where T : notnull; - - /// Get the underlying key in the game's content cache for an asset. This can be used to load custom map tilesheets, but should be avoided when you can use the content API instead. This does not validate whether the asset exists. - /// The relative file path within the content pack (case-insensitive). - /// The is empty or contains invalid characters. - [Obsolete($"Use {nameof(IContentPack.ModContent)}.{nameof(IModContentHelper.GetInternalAssetName)} instead. This method will be removed in SMAPI 4.0.0.")] - string GetActualAssetKey(string key); -#endif } } diff --git a/src/SMAPI/IModHelper.cs b/src/SMAPI/IModHelper.cs index a44d92c1..5e56bf82 100644 --- a/src/SMAPI/IModHelper.cs +++ b/src/SMAPI/IModHelper.cs @@ -1,6 +1,3 @@ -#if SMAPI_DEPRECATED -using System; -#endif using StardewModdingAPI.Events; namespace StardewModdingAPI @@ -27,12 +24,6 @@ namespace StardewModdingAPI /// This API is intended for reading content assets from the mod files (like game data, images, etc); see also which is intended for persisting internal mod data. IModContentHelper ModContent { get; } -#if SMAPI_DEPRECATED - /// An API for loading content assets. - [Obsolete($"Use {nameof(IGameContentHelper)} or {nameof(IModContentHelper)} instead.")] - IContentHelper Content { get; } -#endif - /// An API for managing content packs. IContentPackHelper ContentPacks { get; } diff --git a/src/SMAPI/Metadata/InstructionMetadata.cs b/src/SMAPI/Metadata/InstructionMetadata.cs index 1024b19c..e5b99e0b 100644 --- a/src/SMAPI/Metadata/InstructionMetadata.cs +++ b/src/SMAPI/Metadata/InstructionMetadata.cs @@ -53,11 +53,6 @@ namespace StardewModdingAPI.Metadata // detect Harmony & rewrite for SMAPI 3.12 (Harmony 1.x => 2.0 update) yield return new HarmonyRewriter(); - -#if SMAPI_DEPRECATED - // detect issues for SMAPI 4.0.0 - yield return new LegacyAssemblyFinder(); -#endif } else yield return new HarmonyRewriter(shouldRewrite: false); diff --git a/src/SMAPI/Utilities/PerScreen.cs b/src/SMAPI/Utilities/PerScreen.cs index 674ec760..ba6434e4 100644 --- a/src/SMAPI/Utilities/PerScreen.cs +++ b/src/SMAPI/Utilities/PerScreen.cs @@ -1,10 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -#if SMAPI_DEPRECATED -using StardewModdingAPI.Framework; -using StardewModdingAPI.Framework.Deprecations; -#endif namespace StardewModdingAPI.Utilities { @@ -51,22 +47,7 @@ namespace StardewModdingAPI.Utilities /// Create the initial state for a screen. public PerScreen(Func createNewState) { - if (createNewState is null) - { -#if SMAPI_DEPRECATED - createNewState = (() => default!); - SCore.DeprecationManager.Warn( - null, - $"calling the {nameof(PerScreen)} constructor with null", - "3.14.0", - DeprecationLevel.PendingRemoval - ); -#else - throw new ArgumentNullException(nameof(createNewState)); -#endif - } - - this.CreateNewState = createNewState; + this.CreateNewState = createNewState ?? throw new ArgumentNullException(nameof(createNewState)); } /// Get all active values by screen ID. This doesn't initialize the value for a screen ID if it's not created yet.