From e0ef8a20a5e7ccf1de32ff1a06f1aa62e37eb1db Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 19 Jun 2022 17:21:53 -0400 Subject: [PATCH 01/24] fix mod count in log parser metadata --- docs/release-notes.md | 4 ++++ .../Framework/LogParsing/LogParser.cs | 4 ++-- .../Framework/LogParsing/Models/LogModInfo.cs | 20 ++++++++++++++++--- src/SMAPI.Web/Views/LogParser/Index.cshtml | 2 +- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 496d016a..bb776fd7 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,6 +1,10 @@ ← [README](README.md) # Release notes +## Upcoming release +* For the web UI: + * Fixed the mod count in the log parser metadata. + ## 3.15.0 Released 17 June 2022 for Stardew Valley 1.5.6 or later. See [release highlights](https://www.patreon.com/posts/67877219). diff --git a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs index 55272b23..7fc8f958 100644 --- a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs +++ b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs @@ -77,8 +77,8 @@ namespace StardewModdingAPI.Web.Framework.LogParsing }; // parse log messages - LogModInfo smapiMod = new(name: "SMAPI", author: "Pathoschild", version: "", description: "", loaded: true); - LogModInfo gameMod = new(name: "game", author: "", version: "", description: "", loaded: true); + LogModInfo smapiMod = new(name: "SMAPI", author: "Pathoschild", version: "", description: "", loaded: true, isMod: false); + LogModInfo gameMod = new(name: "game", author: "", version: "", description: "", loaded: true, isMod: false); IDictionary> mods = new Dictionary>(); bool inModList = false; bool inContentPackList = false; diff --git a/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs b/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs index a6b9165c..4b80a830 100644 --- a/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs +++ b/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs @@ -39,9 +39,15 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models [MemberNotNullWhen(true, nameof(LogModInfo.UpdateVersion), nameof(LogModInfo.UpdateLink))] public bool HasUpdate => this.UpdateVersion != null && this.Version != this.UpdateVersion; - /// Whether the mod is a content pack for another mod. + /// Whether this is an actual mod (rather than a special entry for SMAPI or the game itself). + public bool IsMod { get; } + + /// Whether this is a C# code mod. + public bool IsCodeMod { get; } + + /// Whether this is a content pack for another mod. [MemberNotNullWhen(true, nameof(LogModInfo.ContentPackFor))] - public bool IsContentPack => !string.IsNullOrWhiteSpace(this.ContentPackFor); + public bool IsContentPack { get; } /********* @@ -57,7 +63,8 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models /// The name of the mod for which this is a content pack (if applicable). /// The number of errors logged by this mod. /// Whether the mod was loaded into the game. - public LogModInfo(string name, string author, string version, string description, string? updateVersion = null, string? updateLink = null, string? contentPackFor = null, int errors = 0, bool loaded = true) + /// Whether this is an actual mod (instead of a special entry for SMAPI or the game). + public LogModInfo(string name, string author, string version, string description, string? updateVersion = null, string? updateLink = null, string? contentPackFor = null, int errors = 0, bool loaded = true, bool isMod = true) { this.Name = name; this.Author = author; @@ -68,6 +75,13 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models this.ContentPackFor = contentPackFor; this.Errors = errors; this.Loaded = loaded; + + if (isMod) + { + this.IsMod = true; + this.IsContentPack = !string.IsNullOrWhiteSpace(this.ContentPackFor); + this.IsCodeMod = !this.IsContentPack; + } } /// Add an update alert for this mod. diff --git a/src/SMAPI.Web/Views/LogParser/Index.cshtml b/src/SMAPI.Web/Views/LogParser/Index.cshtml index 5e55906d..33239a2b 100644 --- a/src/SMAPI.Web/Views/LogParser/Index.cshtml +++ b/src/SMAPI.Web/Views/LogParser/Index.cshtml @@ -293,7 +293,7 @@ else if (log?.IsValid == true) Date: Mon, 20 Jun 2022 18:01:48 -0400 Subject: [PATCH 02/24] add flag to disable deprecated code --- build/common.targets | 18 ++++--- docs/technical/smapi.md | 1 + .../Framework/ModData/ModWarning.cs | 4 ++ src/SMAPI/Constants.cs | 4 ++ src/SMAPI/Framework/Content/AssetInfo.cs | 12 +++-- .../Content/AssetInterceptorChange.cs | 3 +- src/SMAPI/Framework/ContentCoordinator.cs | 20 +++++++- .../ContentManagers/GameContentManager.cs | 24 +++++++-- src/SMAPI/Framework/ContentPack.cs | 2 + .../Framework/ModHelpers/CommandHelper.cs | 4 ++ .../Framework/ModHelpers/ContentHelper.cs | 2 + src/SMAPI/Framework/ModHelpers/ModHelper.cs | 17 +++++-- .../Framework/ModLoading/AssemblyLoader.cs | 4 ++ .../Finders/LegacyAssemblyFinder.cs | 2 + .../ModLoading/InstructionHandleResult.cs | 2 + src/SMAPI/Framework/SCore.cs | 22 +++++++-- src/SMAPI/GameFramework.cs | 4 ++ src/SMAPI/IAssetEditor.cs | 2 + src/SMAPI/IAssetInfo.cs | 16 ++++-- src/SMAPI/IAssetLoader.cs | 2 + src/SMAPI/ICommandHelper.cs | 2 + src/SMAPI/IContentHelper.cs | 2 + src/SMAPI/IContentPack.cs | 4 ++ src/SMAPI/IModHelper.cs | 4 ++ src/SMAPI/Metadata/InstructionMetadata.cs | 2 + src/SMAPI/Utilities/PerScreen.cs | 49 +++++++++---------- 26 files changed, 173 insertions(+), 55 deletions(-) diff --git a/build/common.targets b/build/common.targets index 8b17c45a..92fd9a9a 100644 --- a/build/common.targets +++ b/build/common.targets @@ -5,6 +5,7 @@ SMAPIlatest$(AssemblySearchPaths);{GAC} + $(DefineConstants);SMAPI_DEPRECATEDenable @@ -20,14 +21,17 @@ - $(NoWarn);CS0436;CA1416;CS0809;NU1701 + $(NoWarn);CS0612;CS0618 + $(NoWarn);CS0436;CA1416;CS0809;NU1701 diff --git a/docs/technical/smapi.md b/docs/technical/smapi.md index 44b6e49f..90990ee4 100644 --- a/docs/technical/smapi.md +++ b/docs/technical/smapi.md @@ -62,6 +62,7 @@ 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.Toolkit/Framework/ModData/ModWarning.cs b/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs index 32c2ed6d..338192af 100644 --- a/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs +++ b/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs @@ -18,9 +18,11 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData /// The mod patches the game in a way that may impact stability. PatchesGame = 4, +#if SMAPI_FOR_WINDOWS /// 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, @@ -37,6 +39,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData /// 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, @@ -45,5 +48,6 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData /// 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 } } diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index db88563e..33468717 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -6,7 +6,9 @@ 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; @@ -77,6 +79,7 @@ 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 @@ -93,6 +96,7 @@ namespace StardewModdingAPI 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 773e3126..43feed27 100644 --- a/src/SMAPI/Framework/Content/AssetInfo.cs +++ b/src/SMAPI/Framework/Content/AssetInfo.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics; +#if SMAPI_DEPRECATED using StardewModdingAPI.Framework.Deprecations; +#endif namespace StardewModdingAPI.Framework.Content { @@ -29,6 +31,10 @@ namespace StardewModdingAPI.Framework.Content /// public IAssetName NameWithoutLocale => this.NameWithoutLocaleImpl ??= this.Name.GetBaseAssetName(); + /// + 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 @@ -50,9 +56,7 @@ namespace StardewModdingAPI.Framework.Content return this.NameWithoutLocale.Name; } } - - /// - public Type DataType { get; } +#endif /********* @@ -71,6 +75,7 @@ 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) @@ -90,6 +95,7 @@ namespace StardewModdingAPI.Framework.Content return this.NameWithoutLocale.IsEquivalentTo(path); } +#endif /********* diff --git a/src/SMAPI/Framework/Content/AssetInterceptorChange.cs b/src/SMAPI/Framework/Content/AssetInterceptorChange.cs index f3d4f3f4..3b5068dc 100644 --- a/src/SMAPI/Framework/Content/AssetInterceptorChange.cs +++ b/src/SMAPI/Framework/Content/AssetInterceptorChange.cs @@ -1,8 +1,8 @@ +#if SMAPI_DEPRECATED using System; using System.Reflection; using StardewModdingAPI.Internal; -#pragma warning disable CS0618 // obsolete asset interceptors deliberately supported here namespace StardewModdingAPI.Framework.Content { /// A wrapper for and for internal cache invalidation. @@ -103,3 +103,4 @@ namespace StardewModdingAPI.Framework.Content } } } +#endif diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 3e09ac62..9e044b44 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -12,7 +12,9 @@ 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; @@ -84,6 +86,7 @@ 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); @@ -91,6 +94,7 @@ namespace StardewModdingAPI.Framework /// 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 /********* @@ -102,6 +106,7 @@ 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>(); @@ -109,6 +114,7 @@ namespace StardewModdingAPI.Framework /// 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; } @@ -498,15 +504,25 @@ 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 ); } @@ -629,6 +645,7 @@ 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. @@ -639,7 +656,6 @@ namespace StardewModdingAPI.Framework AssetOperationGroup? group = this.RequestAssetOperations(info); // legacy load operations -#pragma warning disable CS0612, CS0618 // deprecated code if (this.Editors.Count > 0 || this.Loaders.Count > 0) { IAssetInfo legacyInfo = this.GetLegacyAssetInfo(info); @@ -738,7 +754,6 @@ namespace StardewModdingAPI.Framework ); } } -#pragma warning restore CS0612, CS0618 return group; } @@ -818,5 +833,6 @@ namespace StardewModdingAPI.Framework // else no change needed return asset; } +#endif } } diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index 446f4a67..df7bdc59 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -76,7 +76,11 @@ 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(info); + AssetOperationGroup? operations = this.Coordinator.GetAssetOperations +#if SMAPI_DEPRECATED + +#endif + (info); if (operations?.LoadOperations.Count > 0) { if (!this.AssertMaxOneRequiredLoader(info, operations.LoadOperations, out string? error)) @@ -129,7 +133,11 @@ 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(info); + AssetOperationGroup? operations = this.Coordinator.GetAssetOperations +#if SMAPI_DEPRECATED + +#endif + (info); IAssetData asset = this.ApplyLoader(info, operations?.LoadOperations) ?? new AssetDataForObject(info, this.RawLoad(assetName, useCache), this.AssertAndNormalizeAssetName, this.Reflection); @@ -294,7 +302,11 @@ 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)} and {nameof(IAssetLoader)} 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 SMAPI_DEPRECATED + + " and {nameof(IAssetLoader)}" +#endif + + " if possible to avoid conflicts.)"; return false; } @@ -349,6 +361,7 @@ 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"); @@ -361,7 +374,12 @@ namespace StardewModdingAPI.Framework.ContentManagers 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/ContentPack.cs b/src/SMAPI/Framework/ContentPack.cs index 70fe51f8..a1d977e4 100644 --- a/src/SMAPI/Framework/ContentPack.cs +++ b/src/SMAPI/Framework/ContentPack.cs @@ -96,6 +96,7 @@ 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) @@ -110,6 +111,7 @@ namespace StardewModdingAPI.Framework { return this.ModContent.GetInternalAssetName(key).Name; } +#endif /********* diff --git a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs index ddbd618a..21435f62 100644 --- a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs @@ -1,5 +1,7 @@ using System; +#if SMAPI_DEPRECATED using StardewModdingAPI.Framework.Deprecations; +#endif namespace StardewModdingAPI.Framework.ModHelpers { @@ -32,6 +34,7 @@ namespace StardewModdingAPI.Framework.ModHelpers 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) @@ -45,5 +48,6 @@ namespace StardewModdingAPI.Framework.ModHelpers return this.CommandManager.Trigger(name, arguments); } +#endif } } diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index 427adac2..9992cb52 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -1,3 +1,4 @@ +#if SMAPI_DEPRECATED using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -249,3 +250,4 @@ namespace StardewModdingAPI.Framework.ModHelpers } } } +#endif diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index 48973691..9ac3b6f7 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -1,7 +1,9 @@ using System; using System.IO; using StardewModdingAPI.Events; +#if SMAPI_DEPRECATED using StardewModdingAPI.Framework.Deprecations; +#endif using StardewModdingAPI.Framework.Input; namespace StardewModdingAPI.Framework.ModHelpers @@ -9,12 +11,14 @@ 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 /********* @@ -26,6 +30,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// public IModEvents Events { get; } +#if SMAPI_DEPRECATED /// [Obsolete($"Use {nameof(IGameContentHelper)} or {nameof(IModContentHelper)} instead.")] public IContentHelper Content @@ -42,6 +47,7 @@ namespace StardewModdingAPI.Framework.ModHelpers return this.ContentImpl; } } +#endif /// public IGameContentHelper GameContent { get; } @@ -82,7 +88,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. @@ -96,9 +101,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The path does not exist on disk. public ModHelper( IModMetadata mod, string modDirectory, Func currentInputState, IModEvents events, -#pragma warning disable CS0612 // deprecated code +#if SMAPI_DEPRECATED ContentHelper contentHelper, -#pragma warning restore CS0612 +#endif IGameContentHelper gameContentHelper, IModContentHelper modContentHelper, IContentPackHelper contentPackHelper, ICommandHelper commandHelper, IDataHelper dataHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper ) : base(mod) @@ -111,9 +116,9 @@ namespace StardewModdingAPI.Framework.ModHelpers // initialize this.DirectoryPath = modDirectory; -#pragma warning disable CS0612 // deprecated code +#if SMAPI_DEPRECATED this.ContentImpl = contentHelper ?? throw new ArgumentNullException(nameof(contentHelper)); -#pragma warning restore CS0612 +#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)); @@ -127,12 +132,14 @@ 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 e5aaa8ee..eb940c41 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs @@ -163,6 +163,7 @@ 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)) { @@ -185,6 +186,7 @@ namespace StardewModdingAPI.Framework.ModLoading 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)) @@ -452,6 +454,7 @@ 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); @@ -466,6 +469,7 @@ namespace StardewModdingAPI.Framework.ModLoading 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 index d3437b05..77380907 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/LegacyAssemblyFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/LegacyAssemblyFinder.cs @@ -1,3 +1,4 @@ +#if SMAPI_DEPRECATED using Mono.Cecil; using StardewModdingAPI.Framework.ModLoading.Framework; @@ -47,3 +48,4 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders } } } +#endif diff --git a/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs b/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs index 476c30d0..189ca64e 100644 --- a/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs +++ b/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs @@ -32,6 +32,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// 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, @@ -40,5 +41,6 @@ namespace StardewModdingAPI.Framework.ModLoading /// 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 } } diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 385a94ea..ff3eadf5 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -32,7 +32,9 @@ 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; @@ -139,8 +141,10 @@ 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. /// This property must be thread-safe, since it's accessed from a separate console input thread. @@ -483,6 +487,7 @@ namespace StardewModdingAPI.Framework return; } +#if SMAPI_DEPRECATED /********* ** Reload assets when interceptors are added/removed *********/ @@ -515,6 +520,7 @@ namespace StardewModdingAPI.Framework // reload affected assets this.ContentCore.InvalidateCache(asset => interceptors.Any(p => p.CanIntercept(asset))); } +#endif /********* ** Parse commands @@ -1646,9 +1652,9 @@ namespace StardewModdingAPI.Framework // initialize loaded non-content-pack mods this.Monitor.Log("Launching mods...", LogLevel.Debug); -#pragma warning disable CS0612, CS0618 // deprecated code foreach (IModMetadata metadata in loadedMods) { +#if SMAPI_DEPRECATED // add interceptors if (metadata.Mod?.Helper is ModHelper helper) { @@ -1684,7 +1690,6 @@ namespace StardewModdingAPI.Framework 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); } -#pragma warning restore CS0612, CS0618 // log deprecation warnings if (metadata.HasWarnings(ModWarning.DetectedLegacyCachingDll, ModWarning.DetectedLegacyConfigurationDll, ModWarning.DetectedLegacyPermissionsDll)) @@ -1710,6 +1715,7 @@ namespace StardewModdingAPI.Framework ); } } +#endif // call entry method Context.HeuristicModsRunningCode.Push(metadata); @@ -1750,6 +1756,7 @@ 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. @@ -1772,6 +1779,7 @@ namespace StardewModdingAPI.Framework list.Remove(entry); } } +#endif /// Load a given mod. /// The mod to load. @@ -1915,9 +1923,9 @@ namespace StardewModdingAPI.Framework { IModEvents events = new ModEvents(mod, this.EventManager); ICommandHelper commandHelper = new CommandHelper(mod, this.CommandManager); -#pragma warning disable CS0612 // deprecated code +#if SMAPI_DEPRECATED ContentHelper contentHelper = new(contentCore, mod.DirectoryPath, mod, monitor, this.Reflection); -#pragma warning restore CS0612 +#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( @@ -1930,7 +1938,11 @@ 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, contentHelper, gameContentHelper, modContentHelper, contentPackHelper, commandHelper, dataHelper, modRegistryHelper, reflectionHelper, multiplayerHelper, translationHelper); + modHelper = new ModHelper(mod, mod.DirectoryPath, () => this.GetCurrentGameInstance().Input, events, +#if SMAPI_DEPRECATED + contentHelper, +#endif + gameContentHelper, modContentHelper, contentPackHelper, commandHelper, dataHelper, modRegistryHelper, reflectionHelper, multiplayerHelper, translationHelper); } // init mod diff --git a/src/SMAPI/GameFramework.cs b/src/SMAPI/GameFramework.cs index 60fbe56e..009865fe 100644 --- a/src/SMAPI/GameFramework.cs +++ b/src/SMAPI/GameFramework.cs @@ -1,13 +1,17 @@ +#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 index 9f22ed83..f3238ba9 100644 --- a/src/SMAPI/IAssetEditor.cs +++ b/src/SMAPI/IAssetEditor.cs @@ -1,3 +1,4 @@ +#if SMAPI_DEPRECATED using System; using StardewModdingAPI.Events; @@ -19,3 +20,4 @@ namespace StardewModdingAPI void Edit(IAssetData asset); } } +#endif diff --git a/src/SMAPI/IAssetInfo.cs b/src/SMAPI/IAssetInfo.cs index 44fd91a5..20064946 100644 --- a/src/SMAPI/IAssetInfo.cs +++ b/src/SMAPI/IAssetInfo.cs @@ -8,25 +8,34 @@ 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. /// For example, if contains a locale like Data/Bundles.fr-FR, this will be the name without locale like Data/Bundles. If the name has no locale, this field is equivalent. public IAssetName NameWithoutLocale { get; } + /// 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; } - /// The content data type. - Type DataType { get; } - /********* ** Public methods @@ -35,5 +44,6 @@ namespace StardewModdingAPI /// 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 index 96b98793..205980a7 100644 --- a/src/SMAPI/IAssetLoader.cs +++ b/src/SMAPI/IAssetLoader.cs @@ -1,3 +1,4 @@ +#if SMAPI_DEPRECATED using System; using StardewModdingAPI.Events; @@ -19,3 +20,4 @@ namespace StardewModdingAPI T Load(IAssetInfo asset); } } +#endif diff --git a/src/SMAPI/ICommandHelper.cs b/src/SMAPI/ICommandHelper.cs index 9f1c345c..c92a09c2 100644 --- a/src/SMAPI/ICommandHelper.cs +++ b/src/SMAPI/ICommandHelper.cs @@ -17,11 +17,13 @@ namespace StardewModdingAPI /// 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 index 7637edf0..b0e30a82 100644 --- a/src/SMAPI/IContentHelper.cs +++ b/src/SMAPI/IContentHelper.cs @@ -1,3 +1,4 @@ +#if SMAPI_DEPRECATED using System; using System.Collections.Generic; using System.Diagnostics.Contracts; @@ -80,3 +81,4 @@ namespace StardewModdingAPI where T : notnull; } } +#endif diff --git a/src/SMAPI/IContentPack.cs b/src/SMAPI/IContentPack.cs index 73b1a860..5047b172 100644 --- a/src/SMAPI/IContentPack.cs +++ b/src/SMAPI/IContentPack.cs @@ -1,7 +1,9 @@ using System; +#if SMAPI_DEPRECATED using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using xTile; +#endif namespace StardewModdingAPI { @@ -47,6 +49,7 @@ namespace StardewModdingAPI 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). @@ -61,5 +64,6 @@ namespace StardewModdingAPI /// 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 15e4ed8d..a44d92c1 100644 --- a/src/SMAPI/IModHelper.cs +++ b/src/SMAPI/IModHelper.cs @@ -1,4 +1,6 @@ +#if SMAPI_DEPRECATED using System; +#endif using StardewModdingAPI.Events; namespace StardewModdingAPI @@ -25,9 +27,11 @@ 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 dce0c6b1..efa91d20 100644 --- a/src/SMAPI/Metadata/InstructionMetadata.cs +++ b/src/SMAPI/Metadata/InstructionMetadata.cs @@ -54,8 +54,10 @@ 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 b86310b8..54657ade 100644 --- a/src/SMAPI/Utilities/PerScreen.cs +++ b/src/SMAPI/Utilities/PerScreen.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; +#if SMAPI_DEPRECATED using StardewModdingAPI.Framework; using StardewModdingAPI.Framework.Deprecations; +#endif namespace StardewModdingAPI.Utilities { @@ -41,12 +43,31 @@ namespace StardewModdingAPI.Utilities /// Construct an instance. /// Limitation with nullable reference types: when the underlying type is nullable, this sets the default value to null regardless of whether you marked the type parameter nullable. To avoid that, set the default value with the 'createNewState' argument instead. public PerScreen() - : this(null, nullExpected: true) { } + { + this.CreateNewState = (() => default!); + } /// Construct an instance. /// Create the initial state for a screen. public PerScreen(Func createNewState) - : this(createNewState, nullExpected: false) { } + { + if (createNewState is null) + { +#if SMAPI_DEPRECATED + createNewState = (() => default!); + SCore.DeprecationManager.Warn( + null, + $"calling the {nameof(PerScreen)} constructor with null", + "3.14.0", + DeprecationLevel.Notice + ); +#else + throw new ArgumentNullException(nameof(createNewState)); +#endif + } + + this.CreateNewState = createNewState; + } /// Get all active values by screen ID. This doesn't initialize the value for a screen ID if it's not created yet. public IEnumerable> GetActiveValues() @@ -84,30 +105,6 @@ namespace StardewModdingAPI.Utilities /********* ** Private methods *********/ - /// Construct an instance. - /// Create the initial state for a screen. - /// Whether a null value is expected. - /// This constructor only exists to maintain backwards compatibility. In SMAPI 4.0.0, the overload that passes nullExpected: false should throw an exception instead. - private PerScreen(Func? createNewState, bool nullExpected) - { - if (createNewState is null) - { - createNewState = (() => default!); - - if (!nullExpected) - { - SCore.DeprecationManager.Warn( - null, - $"calling the {nameof(PerScreen)} constructor with null", - "3.14.0", - DeprecationLevel.Notice - ); - } - } - - this.CreateNewState = createNewState; - } - /// Remove screens which are no longer active. private void RemoveDeadScreens() { From c91fbc82f8803c400880a56fc1571abd76411fdf Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 20 Jun 2022 18:02:41 -0400 Subject: [PATCH 03/24] deprecate support for updating ancient versions of SMAPI --- docs/release-notes.md | 6 ++++++ src/SMAPI.Installer/InteractiveInstaller.cs | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index bb776fd7..16b47670 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,6 +1,12 @@ ← [README](README.md) # Release notes + + ## Upcoming release * For the web UI: * Fixed the mod count in the log parser metadata. diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs index 19cefd32..5a6aa747 100644 --- a/src/SMAPI.Installer/InteractiveInstaller.cs +++ b/src/SMAPI.Installer/InteractiveInstaller.cs @@ -54,6 +54,7 @@ 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 @@ -82,6 +83,7 @@ namespace StardewModdingApi.Installer 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 } @@ -477,8 +479,10 @@ namespace StardewModdingApi.Installer File.WriteAllText(paths.ApiConfigPath, text); } +#if SMAPI_DEPRECATED // remove obsolete appdata mods this.InteractivelyRemoveAppDataMods(paths.ModsDir, bundledModsDir); +#endif } } Console.WriteLine(); @@ -805,6 +809,7 @@ 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. @@ -887,6 +892,7 @@ namespace StardewModdingApi.Installer directory.Delete(recursive: true); } } +#endif /// Get whether a file or folder should be copied from the installer files. /// The file or folder info. From be086cf0056294bc5643bde3bb5b7b73aa4b362f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 22 Jun 2022 01:53:37 -0400 Subject: [PATCH 04/24] avoid unneeded overhead of ConcurrentQueue --- docs/release-notes.md | 3 ++ src/SMAPI/Framework/CommandQueue.cs | 47 +++++++++++++++++++++++++++++ src/SMAPI/Framework/SCore.cs | 43 +++++++++++++------------- 3 files changed, 72 insertions(+), 21 deletions(-) create mode 100644 src/SMAPI/Framework/CommandQueue.cs diff --git a/docs/release-notes.md b/docs/release-notes.md index 16b47670..622a146a 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -8,6 +8,9 @@ --> ## Upcoming release +* For players: + * Minor optimizations. + * For the web UI: * Fixed the mod count in the log parser metadata. diff --git a/src/SMAPI/Framework/CommandQueue.cs b/src/SMAPI/Framework/CommandQueue.cs new file mode 100644 index 00000000..c51016ad --- /dev/null +++ b/src/SMAPI/Framework/CommandQueue.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace StardewModdingAPI.Framework +{ + /// A thread-safe command queue optimized for infrequent changes. + internal class CommandQueue + { + /******** + ** Fields + ********/ + /// The underlying list of queued commands to parse and execute. + private readonly List RawCommandQueue = new(); + + + /******** + ** Public methods + ********/ + /// Add a command to the queue. + /// The command to add. + public void Add(string command) + { + lock (this.RawCommandQueue) + this.RawCommandQueue.Add(command); + } + + /// Remove and return all queued commands, if any. + /// The commands that were dequeued, in the order they were originally queued. + /// Returns whether any values were dequeued. + [SuppressMessage("ReSharper", "InconsistentlySynchronizedField", Justification = "Deliberately check if it's empty before locking unnecessarily.")] + public bool TryDequeue([NotNullWhen(true)] out string[]? queued) + { + if (this.RawCommandQueue.Count is 0) + { + queued = null; + return false; + } + + lock (this.RawCommandQueue) + { + queued = this.RawCommandQueue.ToArray(); + this.RawCommandQueue.Clear(); + return queued.Length > 0; + } + } + } +} diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index ff3eadf5..cc332225 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -147,8 +146,7 @@ namespace StardewModdingAPI.Framework #endif /// A list of queued commands to parse and execute. - /// This property must be thread-safe, since it's accessed from a separate console input thread. - private readonly ConcurrentQueue RawCommandQueue = new(); + private readonly CommandQueue RawCommandQueue = new(); /// A list of commands to execute on each screen. private readonly PerScreen> ScreenCommandQueue = new(() => new List()); @@ -437,7 +435,7 @@ namespace StardewModdingAPI.Framework () => this.LogManager.RunConsoleInputLoop( commandManager: this.CommandManager, reloadTranslations: this.ReloadTranslations, - handleInput: input => this.RawCommandQueue.Enqueue(input), + handleInput: input => this.RawCommandQueue.Add(input), continueWhile: () => this.IsGameRunning && !this.CancellationToken.IsCancellationRequested ) ).Start(); @@ -525,29 +523,32 @@ namespace StardewModdingAPI.Framework /********* ** Parse commands *********/ - while (this.RawCommandQueue.TryDequeue(out string? rawInput)) + if (this.RawCommandQueue.TryDequeue(out string[]? rawCommands)) { - // parse command - string? name; - string[]? args; - Command? command; - int screenId; - try + foreach (string rawInput in rawCommands) { - if (!this.CommandManager.TryParse(rawInput, out name, out args, out command, out screenId)) + // parse command + string? name; + string[]? args; + Command? command; + int screenId; + try { - this.Monitor.Log("Unknown command; type 'help' for a list of available commands.", LogLevel.Error); + if (!this.CommandManager.TryParse(rawInput, out name, out args, out command, out screenId)) + { + this.Monitor.Log("Unknown command; type 'help' for a list of available commands.", LogLevel.Error); + continue; + } + } + catch (Exception ex) + { + this.Monitor.Log($"Failed parsing that command:\n{ex.GetLogSummary()}", LogLevel.Error); continue; } - } - catch (Exception ex) - { - this.Monitor.Log($"Failed parsing that command:\n{ex.GetLogSummary()}", LogLevel.Error); - continue; - } - // queue command for screen - this.ScreenCommandQueue.GetValueForScreen(screenId).Add(new(command, name, args)); + // queue command for screen + this.ScreenCommandQueue.GetValueForScreen(screenId).Add(new(command, name, args)); + } } From 1021c3291088d8e47dc23c608aaa7ff1f81c9fd7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 22 Jun 2022 20:15:25 -0400 Subject: [PATCH 05/24] update fluent HTTP client --- src/SMAPI.Toolkit/SMAPI.Toolkit.csproj | 2 +- src/SMAPI.Web/SMAPI.Web.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj index e021993f..7b79105f 100644 --- a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj +++ b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/SMAPI.Web/SMAPI.Web.csproj b/src/SMAPI.Web/SMAPI.Web.csproj index 4c2569e1..0a460e9e 100644 --- a/src/SMAPI.Web/SMAPI.Web.csproj +++ b/src/SMAPI.Web/SMAPI.Web.csproj @@ -25,7 +25,7 @@ - + From 525ca7c9c99272c5aff557545c285f4307526589 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 24 Jun 2022 02:06:25 -0400 Subject: [PATCH 06/24] fix config.user.json overrides no longer applied --- docs/release-notes.md | 1 + src/SMAPI/Framework/Models/SConfig.cs | 30 +++++++++++++++------------ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 622a146a..8920662f 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -10,6 +10,7 @@ ## Upcoming release * For players: * Minor optimizations. + * Fixed `smapi-internal/config.user.json` overrides not applied after SMAPI 3.14.0. * For the web UI: * Fixed the mod count in the log parser metadata. diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index baef6144..62b15405 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -38,45 +38,49 @@ namespace StardewModdingAPI.Framework.Models /******** ** Accessors ********/ + // + // Note: properties must be writable to support merging config.user.json into it. + // + /// Whether to enable development features. - public bool DeveloperMode { get; private set; } + public bool DeveloperMode { get; set; } /// Whether to check for newer versions of SMAPI and mods on startup. - public bool CheckForUpdates { get; } + public bool CheckForUpdates { get; set; } /// Whether to add a section to the 'mod issues' list for mods which which directly use potentially sensitive .NET APIs like file or shell access. - public bool ParanoidWarnings { get; } + public bool ParanoidWarnings { get; set; } /// Whether to show beta versions as valid updates. - public bool UseBetaChannel { get; } + public bool UseBetaChannel { get; set; } /// SMAPI's GitHub project name, used to perform update checks. - public string GitHubProjectName { get; } + public string GitHubProjectName { get; set; } /// The base URL for SMAPI's web API, used to perform update checks. - public string WebApiBaseUrl { get; } + public string WebApiBaseUrl { get; set; } /// The log contexts for which to enable verbose logging, which may show a lot more information to simplify troubleshooting. /// The possible values are "*" (everything is verbose), "SMAPI", (SMAPI itself), or mod IDs. - public HashSet VerboseLogging { get; } + public HashSet VerboseLogging { get; set; } /// Whether SMAPI should rewrite mods for compatibility. - public bool RewriteMods { get; } + public bool RewriteMods { get; set; } /// Whether to use raw image data when possible, instead of initializing an XNA Texture2D instance through the GPU. - public bool UseRawImageLoading { get; } + public bool UseRawImageLoading { get; set; } /// Whether to make SMAPI file APIs case-insensitive, even on Linux. - public bool UseCaseInsensitivePaths { get; } + public bool UseCaseInsensitivePaths { get; set; } /// Whether SMAPI should log network traffic. Best combined with , which includes network metadata. - public bool LogNetworkTraffic { get; } + public bool LogNetworkTraffic { get; set; } /// The colors to use for text written to the SMAPI console. - public ColorSchemeConfig ConsoleColors { get; } + public ColorSchemeConfig ConsoleColors { get; set; } /// The mod IDs SMAPI should ignore when performing update checks or validating update keys. - public HashSet SuppressUpdateChecks { get; } + public HashSet SuppressUpdateChecks { get; set; } /******** From ca9efad7a7949ca87f3ccf7ec4d01332d1fad84b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 24 Jun 2022 17:00:39 -0400 Subject: [PATCH 07/24] avoid cancellation token for tracking exit state This apparently causes noticeable lag for a minority of players. --- src/SMAPI/Framework/SCore.cs | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index cc332225..16c168a0 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -66,8 +66,8 @@ namespace StardewModdingAPI.Framework /**** ** Low-level components ****/ - /// Tracks whether the game should exit immediately and any pending initialization should be cancelled. - private readonly CancellationTokenSource CancellationToken = new(); + /// Whether the game should exit immediately and any pending initialization should be cancelled. + private bool IsExiting; /// Manages the SMAPI console window and log file. private readonly LogManager LogManager; @@ -272,16 +272,6 @@ namespace StardewModdingAPI.Framework new TitleMenuPatcher(this.OnLoadStageChanged) ); - // add exit handler - this.CancellationToken.Token.Register(() => - { - if (this.IsGameRunning) - { - this.LogManager.WriteCrashLog(); - this.Game.Exit(); - } - }); - // set window titles this.UpdateWindowTitles(); } @@ -358,8 +348,8 @@ namespace StardewModdingAPI.Framework // dispose core components this.IsGameRunning = false; + this.IsExiting = true; this.ContentCore?.Dispose(); - this.CancellationToken.Dispose(); this.Game?.Dispose(); this.LogManager.Dispose(); // dispose last to allow for any last-second log messages @@ -374,7 +364,7 @@ namespace StardewModdingAPI.Framework /// Initialize mods before the first game asset is loaded. At this point the core content managers are loaded (so mods can load their own assets), but the game is mostly uninitialized. private void InitializeBeforeFirstAssetLoaded() { - if (this.CancellationToken.IsCancellationRequested) + if (this.IsExiting) { this.Monitor.Log("SMAPI shutting down: aborting initialization.", LogLevel.Warn); return; @@ -436,7 +426,7 @@ namespace StardewModdingAPI.Framework commandManager: this.CommandManager, reloadTranslations: this.ReloadTranslations, handleInput: input => this.RawCommandQueue.Add(input), - continueWhile: () => this.IsGameRunning && !this.CancellationToken.IsCancellationRequested + continueWhile: () => this.IsGameRunning && !this.IsExiting ) ).Start(); } @@ -479,7 +469,7 @@ namespace StardewModdingAPI.Framework ** Special cases *********/ // Abort if SMAPI is exiting. - if (this.CancellationToken.IsCancellationRequested) + if (this.IsExiting) { this.Monitor.Log("SMAPI shutting down: aborting update."); return; @@ -2233,7 +2223,10 @@ namespace StardewModdingAPI.Framework private void ExitGameImmediately(string message) { this.Monitor.LogFatal(message); - this.CancellationToken.Cancel(); + this.LogManager.WriteCrashLog(); + + this.IsExiting = true; + this.Game.Exit(); } /// Get the screen ID that should be logged to distinguish between players in split-screen mode, if any. From bbc476a2cf3cbe1d7600f630c54a3c66c5146eb1 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 26 Jun 2022 14:41:35 -0400 Subject: [PATCH 08/24] split local deploy into its own targets file --- build/common.targets | 80 +++++--------------------------- build/deploy-local-smapi.targets | 69 +++++++++++++++++++++++++++ build/find-game-folder.targets | 6 +++ src/SMAPI.sln | 1 + 4 files changed, 87 insertions(+), 69 deletions(-) create mode 100644 build/deploy-local-smapi.targets diff --git a/build/common.targets b/build/common.targets index 92fd9a9a..230758bd 100644 --- a/build/common.targets +++ b/build/common.targets @@ -1,3 +1,9 @@ + @@ -6,6 +12,8 @@ latest $(AssemblySearchPaths);{GAC} $(DefineConstants);SMAPI_DEPRECATED + pdbonly + true enable @@ -36,79 +44,13 @@ - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - pdbonly - true - + + diff --git a/build/deploy-local-smapi.targets b/build/deploy-local-smapi.targets new file mode 100644 index 00000000..80501788 --- /dev/null +++ b/build/deploy-local-smapi.targets @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/find-game-folder.targets b/build/find-game-folder.targets index ba7cb26c..b73b1169 100644 --- a/build/find-game-folder.targets +++ b/build/find-game-folder.targets @@ -1,3 +1,9 @@ + diff --git a/src/SMAPI.sln b/src/SMAPI.sln index 2e5ba0ea..8bf86487 100644 --- a/src/SMAPI.sln +++ b/src/SMAPI.sln @@ -27,6 +27,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{09CF91E5-5BAB-4650-A200-E5EA9A633046}" ProjectSection(SolutionItems) = preProject ..\build\common.targets = ..\build\common.targets + ..\build\deploy-local-smapi.targets = ..\build\deploy-local-smapi.targets ..\build\find-game-folder.targets = ..\build\find-game-folder.targets EndProjectSection EndProject From 8254a821cc7e7c1f6f8e528e70ba8a9956b13bc4 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 26 Jun 2022 15:03:25 -0400 Subject: [PATCH 09/24] copy .NET metadata files on local deploy --- build/deploy-local-smapi.targets | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/build/deploy-local-smapi.targets b/build/deploy-local-smapi.targets index 80501788..cb330e24 100644 --- a/build/deploy-local-smapi.targets +++ b/build/deploy-local-smapi.targets @@ -44,6 +44,14 @@ This assumes `find-game-folder.targets` has already been imported and validated. + + + + + + + + @@ -55,12 +63,12 @@ This assumes `find-game-folder.targets` has already been imported and validated. + - From 5e1212e99aa7cb8c0333a57fa60236df79b0ac6d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 28 Jun 2022 01:13:11 -0400 Subject: [PATCH 10/24] update schema for Content Patcher 1.27.0 --- docs/release-notes.md | 1 + src/SMAPI.Web/wwwroot/schemas/content-patcher.json | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 8920662f..4c504150 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -13,6 +13,7 @@ * Fixed `smapi-internal/config.user.json` overrides not applied after SMAPI 3.14.0. * For the web UI: + * Updated the JSON validator/schema for Content Patcher 1.27.0. * Fixed the mod count in the log parser metadata. ## 3.15.0 diff --git a/src/SMAPI.Web/wwwroot/schemas/content-patcher.json b/src/SMAPI.Web/wwwroot/schemas/content-patcher.json index f0fe74c2..631fbc63 100644 --- a/src/SMAPI.Web/wwwroot/schemas/content-patcher.json +++ b/src/SMAPI.Web/wwwroot/schemas/content-patcher.json @@ -14,9 +14,9 @@ "title": "Format version", "description": "The format version. You should always use the latest version to enable the latest features, avoid obsolete behavior, and reduce load times.", "type": "string", - "const": "1.26.0", + "pattern": "^1\\.27\\.[0-9]+$", "@errorMessages": { - "const": "Incorrect value '@value'. You should always use the latest format version (currently 1.26.0) to enable the latest features, avoid obsolete behavior, and reduce load times." + "pattern": "Incorrect value '@value'. You should always use the latest format version (currently 1.27.0) to enable the latest features, avoid obsolete behavior, and reduce load times." } }, "ConfigSchema": { From 6d11c41facb2e1397a25110517cc281f87be2caf Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 28 Jun 2022 18:17:27 -0400 Subject: [PATCH 11/24] migrate update checks to FluentHttpClient WebClient isn't needed for compatibility with macOS after the .NET 5 update in Stardew Valley 1.5.5, and causes noticeable lag for some players even when running on a background thread. --- build/deploy-local-smapi.targets | 4 ++ build/unix/prepare-install-package.sh | 2 +- build/windows/prepare-install-package.ps1 | 2 +- docs/release-notes.md | 5 +- .../Framework/Clients/WebApi/WebApiClient.cs | 56 +++++++------------ src/SMAPI.Toolkit/Serialization/JsonHelper.cs | 26 +++++---- src/SMAPI.Web/Startup.cs | 2 +- src/SMAPI/Framework/SCore.cs | 44 ++++++++++----- src/SMAPI/SMAPI.csproj | 1 + 9 files changed, 78 insertions(+), 64 deletions(-) diff --git a/build/deploy-local-smapi.targets b/build/deploy-local-smapi.targets index cb330e24..6ea5f0a2 100644 --- a/build/deploy-local-smapi.targets +++ b/build/deploy-local-smapi.targets @@ -35,6 +35,10 @@ This assumes `find-game-folder.targets` has already been imported and validated. + + + + diff --git a/build/unix/prepare-install-package.sh b/build/unix/prepare-install-package.sh index 01cd2080..1d805e00 100755 --- a/build/unix/prepare-install-package.sh +++ b/build/unix/prepare-install-package.sh @@ -134,7 +134,7 @@ for folder in ${folders[@]}; do cp -r "$smapiBin/i18n" "$bundlePath/smapi-internal" # bundle smapi-internal - for name in "0Harmony.dll" "0Harmony.xml" "Mono.Cecil.dll" "Mono.Cecil.Mdb.dll" "Mono.Cecil.Pdb.dll" "MonoMod.Common.dll" "Newtonsoft.Json.dll" "Pintail.dll" "TMXTile.dll" "SMAPI.Toolkit.dll" "SMAPI.Toolkit.pdb" "SMAPI.Toolkit.xml" "SMAPI.Toolkit.CoreInterfaces.dll" "SMAPI.Toolkit.CoreInterfaces.pdb" "SMAPI.Toolkit.CoreInterfaces.xml"; do + for name in "0Harmony.dll" "0Harmony.xml" "Mono.Cecil.dll" "Mono.Cecil.Mdb.dll" "Mono.Cecil.Pdb.dll" "MonoMod.Common.dll" "Newtonsoft.Json.dll" "Pathoschild.Http.Client.dll" "Pintail.dll" "TMXTile.dll" "SMAPI.Toolkit.dll" "SMAPI.Toolkit.pdb" "SMAPI.Toolkit.xml" "SMAPI.Toolkit.CoreInterfaces.dll" "SMAPI.Toolkit.CoreInterfaces.pdb" "SMAPI.Toolkit.CoreInterfaces.xml" "System.Net.Http.Formatting.dll"; do cp "$smapiBin/$name" "$bundlePath/smapi-internal" done diff --git a/build/windows/prepare-install-package.ps1 b/build/windows/prepare-install-package.ps1 index 7e3c6c86..87a4fe01 100644 --- a/build/windows/prepare-install-package.ps1 +++ b/build/windows/prepare-install-package.ps1 @@ -154,7 +154,7 @@ foreach ($folder in $folders) { cp -Recurse "$smapiBin/i18n" "$bundlePath/smapi-internal" # bundle smapi-internal - foreach ($name in @("0Harmony.dll", "0Harmony.xml", "Mono.Cecil.dll", "Mono.Cecil.Mdb.dll", "Mono.Cecil.Pdb.dll", "MonoMod.Common.dll", "Newtonsoft.Json.dll", "Pintail.dll", "TMXTile.dll", "SMAPI.Toolkit.dll", "SMAPI.Toolkit.pdb", "SMAPI.Toolkit.xml", "SMAPI.Toolkit.CoreInterfaces.dll", "SMAPI.Toolkit.CoreInterfaces.pdb", "SMAPI.Toolkit.CoreInterfaces.xml")) { + foreach ($name in @("0Harmony.dll", "0Harmony.xml", "Mono.Cecil.dll", "Mono.Cecil.Mdb.dll", "Mono.Cecil.Pdb.dll", "MonoMod.Common.dll", "Newtonsoft.Json.dll", "Pathoschild.Http.Client.dll", "Pintail.dll", "TMXTile.dll", "SMAPI.Toolkit.dll", "SMAPI.Toolkit.pdb", "SMAPI.Toolkit.xml", "SMAPI.Toolkit.CoreInterfaces.dll", "SMAPI.Toolkit.CoreInterfaces.pdb", "SMAPI.Toolkit.CoreInterfaces.xml", "System.Net.Http.Formatting.dll")) { cp "$smapiBin/$name" "$bundlePath/smapi-internal" } diff --git a/docs/release-notes.md b/docs/release-notes.md index 4c504150..414396f4 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -9,9 +9,12 @@ ## Upcoming release * For players: - * Minor optimizations. + * Fixed lag which occurred for some players since Stardew Valley 1.5.5. * Fixed `smapi-internal/config.user.json` overrides not applied after SMAPI 3.14.0. +* For mod authors: + * The [FluentHttpClient package](https://github.com/Pathoschild/FluentHttpClient#readme) is now loaded by SMAPI. + * For the web UI: * Updated the JSON validator/schema for Content Patcher 1.27.0. * Fixed the mod count in the log parser metadata. diff --git a/src/SMAPI.Toolkit/Framework/Clients/WebApi/WebApiClient.cs b/src/SMAPI.Toolkit/Framework/Clients/WebApi/WebApiClient.cs index d4282617..ef1904d4 100644 --- a/src/SMAPI.Toolkit/Framework/Clients/WebApi/WebApiClient.cs +++ b/src/SMAPI.Toolkit/Framework/Clients/WebApi/WebApiClient.cs @@ -1,27 +1,24 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net; -using Newtonsoft.Json; +using System.Threading.Tasks; +using Pathoschild.Http.Client; using StardewModdingAPI.Toolkit.Serialization; using StardewModdingAPI.Toolkit.Utilities; namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi { /// Provides methods for interacting with the SMAPI web API. - public class WebApiClient + public class WebApiClient : IDisposable { /********* ** Fields *********/ - /// The base URL for the web API. - private readonly Uri BaseUrl; - /// The API version number. private readonly ISemanticVersion Version; - /// The JSON serializer settings to use. - private readonly JsonSerializerSettings JsonSettings = new JsonHelper().JsonSettings; + /// The underlying HTTP client. + private readonly IClient Client; /********* @@ -32,8 +29,11 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi /// The web API version. public WebApiClient(string baseUrl, ISemanticVersion version) { - this.BaseUrl = new Uri(baseUrl); this.Version = version; + this.Client = new FluentClient(baseUrl) + .SetUserAgent($"SMAPI/{version}"); + + this.Client.Formatters.JsonFormatter.SerializerSettings = JsonHelper.CreateDefaultSettings(); } /// Get metadata about a set of mods from the web API. @@ -42,36 +42,22 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi /// The Stardew Valley version installed by the player. /// The OS on which the player plays. /// Whether to include extended metadata for each mod. - public IDictionary GetModInfo(ModSearchEntryModel[] mods, ISemanticVersion apiVersion, ISemanticVersion gameVersion, Platform platform, bool includeExtendedMetadata = false) + public async Task> GetModInfoAsync(ModSearchEntryModel[] mods, ISemanticVersion apiVersion, ISemanticVersion gameVersion, Platform platform, bool includeExtendedMetadata = false) { - return this.Post( - $"v{this.Version}/mods", - new ModSearchModel(mods, apiVersion, gameVersion, platform, includeExtendedMetadata) - ).ToDictionary(p => p.ID); + ModEntryModel[] result = await this.Client + .PostAsync( + $"v{this.Version}/mods", + new ModSearchModel(mods, apiVersion, gameVersion, platform, includeExtendedMetadata) + ) + .As(); + + return result.ToDictionary(p => p.ID); } - - /********* - ** Private methods - *********/ - /// Fetch the response from the backend API. - /// The body content type. - /// The expected response type. - /// The request URL, optionally excluding the base URL. - /// The body content to post. - private TResult Post(string url, TBody content) + /// + public void Dispose() { - // note: avoid HttpClient for macOS compatibility - using WebClient client = new(); - - Uri fullUrl = new(this.BaseUrl, url); - string data = JsonConvert.SerializeObject(content); - - client.Headers["Content-Type"] = "application/json"; - client.Headers["User-Agent"] = $"SMAPI/{this.Version}"; - string response = client.UploadString(fullUrl, data); - return JsonConvert.DeserializeObject(response, this.JsonSettings) - ?? throw new InvalidOperationException($"Could not parse the response from POST {url}."); + this.Client.Dispose(); } } } diff --git a/src/SMAPI.Toolkit/Serialization/JsonHelper.cs b/src/SMAPI.Toolkit/Serialization/JsonHelper.cs index 1a003c51..a5d7e2e8 100644 --- a/src/SMAPI.Toolkit/Serialization/JsonHelper.cs +++ b/src/SMAPI.Toolkit/Serialization/JsonHelper.cs @@ -15,21 +15,27 @@ namespace StardewModdingAPI.Toolkit.Serialization ** Accessors *********/ /// The JSON settings to use when serializing and deserializing files. - public JsonSerializerSettings JsonSettings { get; } = new() - { - Formatting = Formatting.Indented, - ObjectCreationHandling = ObjectCreationHandling.Replace, // avoid issue where default ICollection values are duplicated each time the config is loaded - Converters = new List - { - new SemanticVersionConverter(), - new StringEnumConverter() - } - }; + public JsonSerializerSettings JsonSettings { get; } = JsonHelper.CreateDefaultSettings(); /********* ** Public methods *********/ + /// Create an instance of the default JSON serializer settings. + public static JsonSerializerSettings CreateDefaultSettings() + { + return new() + { + Formatting = Formatting.Indented, + ObjectCreationHandling = ObjectCreationHandling.Replace, // avoid issue where default ICollection values are duplicated each time the config is loaded + Converters = new List + { + new SemanticVersionConverter(), + new StringEnumConverter() + } + }; + } + /// Read a JSON file. /// The model type. /// The absolute file path. diff --git a/src/SMAPI.Web/Startup.cs b/src/SMAPI.Web/Startup.cs index 9980d00c..54c25979 100644 --- a/src/SMAPI.Web/Startup.cs +++ b/src/SMAPI.Web/Startup.cs @@ -199,7 +199,7 @@ namespace StardewModdingAPI.Web /// The serializer settings to edit. private void ConfigureJsonNet(JsonSerializerSettings settings) { - foreach (JsonConverter converter in new JsonHelper().JsonSettings.Converters) + foreach (JsonConverter converter in JsonHelper.CreateDefaultSettings().Converters) settings.Converters.Add(converter); settings.Formatting = Formatting.Indented; diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 16c168a0..fdfe70fc 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -10,6 +10,7 @@ using System.Runtime.ExceptionServices; using System.Security; using System.Text; using System.Threading; +using System.Threading.Tasks; using Microsoft.Xna.Framework; #if SMAPI_FOR_WINDOWS using Microsoft.Win32; @@ -406,7 +407,7 @@ namespace StardewModdingAPI.Framework this.CheckForSoftwareConflicts(); // check for updates - this.CheckForUpdatesAsync(mods); + _ = this.CheckForUpdatesAsync(mods); // ignore task since the main thread doesn't need to wait for it } // update window titles @@ -1450,16 +1451,15 @@ namespace StardewModdingAPI.Framework /// Asynchronously check for a new version of SMAPI and any installed mods, and print alerts to the console if an update is available. /// The mods to include in the update check (if eligible). - private void CheckForUpdatesAsync(IModMetadata[] mods) + private async Task CheckForUpdatesAsync(IModMetadata[] mods) { - if (!this.Settings.CheckForUpdates) - return; - - new Thread(() => + try { + if (!this.Settings.CheckForUpdates) + return; + // create client - string url = this.Settings.WebApiBaseUrl; - WebApiClient client = new(url, Constants.ApiVersion); + using WebApiClient client = new(this.Settings.WebApiBaseUrl, Constants.ApiVersion); this.Monitor.Log("Checking for updates..."); // check SMAPI version @@ -1469,9 +1469,15 @@ namespace StardewModdingAPI.Framework try { // fetch update check - ModEntryModel response = client.GetModInfo(new[] { new ModSearchEntryModel("Pathoschild.SMAPI", Constants.ApiVersion, new[] { $"GitHub:{this.Settings.GitHubProjectName}" }) }, apiVersion: Constants.ApiVersion, gameVersion: Constants.GameVersion, platform: Constants.Platform).Single().Value; - updateFound = response.SuggestedUpdate?.Version; - updateUrl = response.SuggestedUpdate?.Url; + IDictionary response = await client.GetModInfoAsync( + mods: new[] { new ModSearchEntryModel("Pathoschild.SMAPI", Constants.ApiVersion, new[] { $"GitHub:{this.Settings.GitHubProjectName}" }) }, + apiVersion: Constants.ApiVersion, + gameVersion: Constants.GameVersion, + platform: Constants.Platform + ); + ModEntryModel updateInfo = response.Single().Value; + updateFound = updateInfo.SuggestedUpdate?.Version; + updateUrl = updateInfo.SuggestedUpdate?.Url; // log message if (updateFound != null) @@ -1480,10 +1486,10 @@ namespace StardewModdingAPI.Framework this.Monitor.Log(" SMAPI okay."); // show errors - if (response.Errors.Any()) + if (updateInfo.Errors.Any()) { this.Monitor.Log("Couldn't check for a new version of SMAPI. This won't affect your game, but you may not be notified of new versions if this keeps happening.", LogLevel.Warn); - this.Monitor.Log($"Error: {string.Join("\n", response.Errors)}"); + this.Monitor.Log($"Error: {string.Join("\n", updateInfo.Errors)}"); } } catch (Exception ex) @@ -1523,7 +1529,7 @@ namespace StardewModdingAPI.Framework // fetch results this.Monitor.Log($" Checking for updates to {searchMods.Count} mods..."); - IDictionary results = client.GetModInfo(searchMods.ToArray(), apiVersion: Constants.ApiVersion, gameVersion: Constants.GameVersion, platform: Constants.Platform); + IDictionary results = await client.GetModInfoAsync(searchMods.ToArray(), apiVersion: Constants.ApiVersion, gameVersion: Constants.GameVersion, platform: Constants.Platform); // extract update alerts & errors var updates = new List>(); @@ -1573,7 +1579,15 @@ namespace StardewModdingAPI.Framework ); } } - }).Start(); + } + catch (Exception ex) + { + this.Monitor.Log("Couldn't check for updates. This won't affect your game, but you won't be notified of SMAPI or mod updates if this keeps happening.", LogLevel.Warn); + this.Monitor.Log(ex is WebException && ex.InnerException == null + ? ex.Message + : ex.ToString() + ); + } } /// Create a directory path if it doesn't exist. diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index 3abefeab..c05512e9 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -25,6 +25,7 @@ + From 6b411d1dee49a636a8a8c9a88d9cfc0720c7c1dd Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 28 Jun 2022 20:48:28 -0400 Subject: [PATCH 12/24] fix deprecation check --- src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs b/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs index 338192af..4c76f417 100644 --- a/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs +++ b/src/SMAPI.Toolkit/Framework/ModData/ModWarning.cs @@ -18,7 +18,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData /// The mod patches the game in a way that may impact stability. PatchesGame = 4, -#if SMAPI_FOR_WINDOWS +#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, From bd88727948fc8066ec7d788ff4647b4c78168b3d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 28 Jun 2022 20:48:58 -0400 Subject: [PATCH 13/24] remove obsolete override SMAPI no longer raises UsesDynamic warnings. --- src/SMAPI.Web/wwwroot/SMAPI.metadata.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/SMAPI.Web/wwwroot/SMAPI.metadata.json b/src/SMAPI.Web/wwwroot/SMAPI.metadata.json index 16a89647..4e6f4669 100644 --- a/src/SMAPI.Web/wwwroot/SMAPI.metadata.json +++ b/src/SMAPI.Web/wwwroot/SMAPI.metadata.json @@ -107,12 +107,6 @@ "Default | UpdateKey": "Nexus:1726" }, - "Rubydew": { - "ID": "bwdy.rubydew", - "SuppressWarnings": "UsesDynamic", // mod explicitly loads DLLs for Linux/macOS compatibility - "Default | UpdateKey": "Nexus:3656" - }, - "SpaceCore": { "ID": "spacechase0.SpaceCore", "Default | UpdateKey": "Nexus:1348" From d717e246b67bd1b019dab26562be1dfdc8edc73a Mon Sep 17 00:00:00 2001 From: Ishan Jalan <44338423+strobel1ght@users.noreply.github.com> Date: Sat, 2 Jul 2022 08:42:35 +0530 Subject: [PATCH 14/24] Current version in mod check Added "(You have x.x.x)" to the mod update check. --- .DS_Store | Bin 0 -> 10244 bytes build/.DS_Store | Bin 0 -> 6148 bytes src/.DS_Store | Bin 0 -> 8196 bytes src/SMAPI/.DS_Store | Bin 0 -> 6148 bytes src/SMAPI/Framework/SCore.cs | 2 +- src/SMAPI/SMAPI.sln | 25 +++++++++++++++++++++++++ 6 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 .DS_Store create mode 100644 build/.DS_Store create mode 100644 src/.DS_Store create mode 100644 src/SMAPI/.DS_Store create mode 100644 src/SMAPI/SMAPI.sln diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..ba9468ef9378d82e8b742a8dcd4010c8c1d73e35 GIT binary patch literal 10244 zcmeHMU2GIp6u#fIrL%OP1GKha2R2=ZfGw;AZ23#Jf69N6ZRxiBEW0}+9hlCPo!xD* z)HEhO2%_;x<6q>-zsQ4%7+-u)6n#`Q!5AMf8udjJd{KGu%)PUeZt+bMftlpanRD(r z_wM=TJ9qcqWsIRCuiwX5m@y_(^Q5ZK?IxGk@3k41P&{LLdej|BTTXh0Z(s-z z2oMMm2oMMm2oU&xAVA;Q-X&KF8QcQ|0t5mCt|Q>B4`FJajQMg>NWOH?jc)-+mXn!Z z^i2Bz9uxLu%$Jiw@=zL6>>l8U!rx**gj0Xmb0-<|<)n}zoI!*$_%p-bp@7d$abfPx z5EC-E2M7cR%tXK|n+ld@d1f+e+W!4~dc-o_Tr~PMq_XmgS(S37TqPff4>_apTq>7$ zJ5t$wp4RI)R=W6{O7As|(WF|_>DalHX&YG{VCn|B?C&>i-5Kq0^0w~s*tqDBLvkpo z){T$1wKj#l;|HMQ{u8pkTxF>nk8n&G$L=N!R0XC;3r<2nYD#Ww4 z<=BVG>P3_@ojEMS+#3!mZsA0)r&E$<8FN{dWp0lo{bVe+&?%0V%Hl<8ep94oXm*t{ zN9#`v4DM5s>ihz6uxBJ^I<~vl$hstxYPCyT-I{Ui-C4uy@6R|^e%N-C>by+K%H*w7 z&Sc5dC0rOTUJS~tbIx9)uCQnfm7{sN^|8Kk1HbLPXQksV068N=31`*7c= zspp3J3#MzPEu+^?4I80(XbN4bu3xz5?gmX04pRX|*F-*T9?P4#<7z~f`5a-bQQ5E4 zXWHrL-YmMTo}+PS_GMb5w$JP7%o=pp{eTtPy-Kf3$;)a=IIdEnO7Bss=8) zLQblxX%|dd!RWP$g-njbIw$$tiNsclw-hP9EDm%e78QDkex+!vg>|tV>=0GhI6KXr zV`tdA>;n6QeZ{_GKeMas4*;`JiP@+^4HjW3ny?P7Xv2DRpa;9L2XXAj5KJ6_i!mI> zI8NYkoWv=d##49}FX3gpf-`snZ{r=D!+ZD;7x5WB$CtQ*@9_hE#IN`hf8m-`A=OFs z(n6_GYLc3zRZ^?eCT);5O1q>UDIuk#Ba$PHi3FtLvU#AGpl?4;a;nh$GhFHH%?qvJ z!7W?2ZNGI2?ebh+S~azI)rFU>T)nn!bNh7)3eFa3dL#Kce|IJ|Q@hw=LqhfBT>r#l8(fIxsifIxsifWV!MK$%EB z?4AF2|MUO)tNFGi;Je;IB&HvB;3<&oB!T#Srg1S@p|9=DT*dLw% literal 0 HcmV?d00001 diff --git a/build/.DS_Store b/build/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..decdd55791f83b4535087de523bd19903adeb123 GIT binary patch literal 6148 zcmeHK%}T>S5T5OiO(;SRiXH=A3&tP(!Aq$11&ruHr8cH$Fz!mz7@-t$)fe(jd>&_Z zx5c*hD$?%2>^D0*lQ7?g-3$P*W)|%LH~?Uw5|(Tnei2$Hosp9ER1q0HBh1s6FcBfn zKWCzi<3BP$dsl%BIK=SwbN|AZBu@LD_r*%(N_C}X)vUU88$60U=y&>A+U)eMX?7t* z5{~Rnco}!|wzGCBc)t_#Zciq}Q5QpQu3{dEyeTpsr7|~A3zlu!ZD(UJI67>&`<^#! zxP#+e5AD5!;n23$x3*8t+fT_e7w?KghM$|1RgEL~Kx16aYtZ9~;1B4d_!{I`B!>tD zTIpgGJ5^Ma-X|gM2=OtsG{pu*7%>bO29}lqeTZ81rDebrZWu5O%wvGg2N#vl*H|c& zM+XkF1wgEzTMF9LOHhuh(brfg#1#~#QV~@u(=7&5>F9TLoUgG^sM3My=7Z@sGu@#u z`F4E1E5m{L3e9L3FbqsGP*zii?*Fs-{r_Z=nHdHQ0}I6ftF(ev6HC%}>r8QU*Se^8 rs3a6uC>*DtA+KVLrK@-oRSNoDG7x=@g+lb8m>&U2gBc71f6Bl&h3tFQ}6pJmx7FMdD*w8J%1wpnge=XEDZ2zg&-JOAsOlRuO>@GqY zOEek?PyW4PG$!f;8h_%0iqWVsF~(>JiKvM`G5V+piSlARckXP%w(w>mN#`c_o_o%{ z=iWVMzH@KRUB(#N^F|9}^^7r@DyLc%HP){=I{XB*}TX%xc9|q)J7hNJTNakz|V&`RZf#3ofb4WI;aU!0HQnv z2nzKn4+wPPAx(yKTF^k1))d(TLREw*2823|$N6-k$&gM9D%2T-IzzZK!VCq$?xYtF zrZXf3joQcqkq72_fQQclmSWwkjrEr0@9$4lpX=J0Qa_y;u*~9^R@>n^e%f-(T%ceXCQTU~v>d}Nw!3-9@B-bG7?2fN z8Pn=co^0LP6yKCcOf|(%ZrhNc{>Cj+Q;NK-vH9LZW5?_X$2~1^E-mY#xyPOnTJeT z%PN?=X+>IUDc`MUk}u;8m^qJVV=^HORL(D{i`{#WcDC>By8mLeRaJMa%HVhtpoxbPUTmI#{qgD`T5|PI|(uL6f2cTJ_6yeJG!?9?4t&qgtaZ z2hxU>x;kpm59xLdzM7%EBa8W~SL*s2TsbO{&^=jJ~nv zga-=J~VAEFaDRH+rrOv~@E2DXN^(w)`Ejxd*< zVrSVo_BQ)~U0`3bOYA518~c-8W&Z$>PyrRoP=|Uf$7(dA1skvtJFp*}=t2rZ7{OtT z!o(q*Gr{v8kw;s=tyZI*PIU&PT~8WbpHC`6x+AI zvr9dA(}&ZyOGE%IyR9zHqpEdh+YA5&VoM;s7Wx6+%c3N}dw_>oSiMMLwj$P`t|~?0 z?C9*LKrxTYJTA3GV)knF4w(W)TOioA>fJJhh_+C$O|kVd1*IkhtlARWs8Hx>6@qPw zZB=9qg4wR_P$&qsO2Ia$Nk!3Ez{EOYdt?eEP5!SD{x!SIeq_J1E9@#3q6!*T;VvYw z1xf71KEm_?bYlR67$#(MTpz(WEF6Q6JYoAdPT+AofhX}4P7}VLA%t^$zknC<5?;o6 zypA{UCf>vQxPTAw5h4ByT*MFf8NbZJad$b6aitu`g;dV5UFRrCOUN%*m=9cqFtVQK z|9fuy{y!gF5!o1dVBUHFmAg~B+sXVJ9fdsCj#GVzDldHAw4i|sH9J!0b0Wk1YFb*arZJ<|x_)r~`n7N?5Y7Ss)ZAos*LB5DN8;7;=!%hfbK! zWwYZiGC*r*!v(Hc0A2X9euG|^$`GS(VJ}SvL)U#5rE+CuwQ5zZnsx6#$=n|X!_lA_ z^lzzmC1n~;?I65P;=Em7Ka<%oNV2%E3X&+sl)IZGi)7xEqbwSzT3-h&+p^pB&GGp7 zsNo#A?xf+2Pxf52_YWr%+uGRPIlXLm(rzYSG@Al{JSA%er|^o#f{8u*{VbK)BSxo{ z(+WmrfEi#0mWBbd0a~@CnK4h48DIu}$^h*T4l1E%u+XTs4s7W9Nbw3G3EK3QAao3R z1`CZCK@qwXQI`ty#1Oh1{f^1=3>F%7IS4f}e#eX~%nL=R(b4aybP%3KZkYjQV3vWh z?$+u2KmY#yKU>5-W`G&^R}6?s%WpMtO6F{xn;f0B4(bCc3FQ?UKTFV1M=|EoQM`q! a1pN*fh@QbhBYIHyBA{sCh8g%(20j1`T1^}P literal 0 HcmV?d00001 diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index fdfe70fc..283755d6 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1565,7 +1565,7 @@ namespace StardewModdingAPI.Framework this.Monitor.Newline(); this.Monitor.Log($"You can update {updates.Count} mod{(updates.Count != 1 ? "s" : "")}:", LogLevel.Alert); foreach ((IModMetadata mod, ISemanticVersion newVersion, string newUrl) in updates) - this.Monitor.Log($" {mod.DisplayName} {newVersion}: {newUrl}", LogLevel.Alert); + this.Monitor.Log($" {mod.DisplayName} {newVersion}: {newUrl} (You have {mod.Manifest.Version}", LogLevel.Alert); } else this.Monitor.Log(" All mods up to date."); diff --git a/src/SMAPI/SMAPI.sln b/src/SMAPI/SMAPI.sln new file mode 100644 index 00000000..1821b0b6 --- /dev/null +++ b/src/SMAPI/SMAPI.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 25.0.1700.5 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SMAPI", "SMAPI.csproj", "{A09B2199-7A4C-4F47-8406-9A73A2FEAA96}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A09B2199-7A4C-4F47-8406-9A73A2FEAA96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A09B2199-7A4C-4F47-8406-9A73A2FEAA96}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A09B2199-7A4C-4F47-8406-9A73A2FEAA96}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A09B2199-7A4C-4F47-8406-9A73A2FEAA96}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FD0C4E68-CA54-4AD5-8171-DA621027A9DF} + EndGlobalSection +EndGlobal From f3a4b316b7294fea897514a08c45bc9d926a00c6 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 5 Jul 2022 19:01:28 -0400 Subject: [PATCH 15/24] fix PyTK compatibility mode not handling some edge cases --- docs/release-notes.md | 2 ++ .../ContentManagers/ModContentManager.cs | 27 +++++-------------- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 414396f4..3bc8724b 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -11,6 +11,8 @@ * For players: * Fixed lag which occurred for some players since Stardew Valley 1.5.5. * Fixed `smapi-internal/config.user.json` overrides not applied after SMAPI 3.14.0. + * Fixed PyTK not rescaling images correctly in some cases. + _When PyTK 1.23.0 or earlier is installed, this will disable the main performance improvements in SMAPI 3.15.0._ * For mod authors: * The [FluentHttpClient package](https://github.com/Pathoschild/FluentHttpClient#readme) is now loaded by SMAPI. diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 8c5d0f84..f3cf05d9 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -47,9 +46,6 @@ namespace StardewModdingAPI.Framework.ContentManagers /// If a map tilesheet's image source has no file extensions, the file extensions to check for in the local mod folder. private static readonly string[] LocalTilesheetExtensions = { ".png", ".xnb" }; - /// A lookup of image file paths to whether they have PyTK scaling information. - private static readonly Dictionary IsPyTkScaled = new(StringComparer.OrdinalIgnoreCase); - /********* ** Accessors @@ -211,24 +207,13 @@ namespace StardewModdingAPI.Framework.ContentManagers { if (ModContentManager.EnablePyTkLegacyMode) { - if (!ModContentManager.IsPyTkScaled.TryGetValue(file.FullName, out bool isScaled)) - { - string? dirPath = file.DirectoryName; - string fileName = $"{Path.GetFileNameWithoutExtension(file.Name)}.pytk.json"; - - string path = dirPath is not null - ? Path.Combine(dirPath, fileName) - : fileName; - - ModContentManager.IsPyTkScaled[file.FullName] = isScaled = File.Exists(path); - } - - asRawData = !isScaled; - if (!asRawData) - this.Monitor.LogOnce("Enabled compatibility mode for PyTK scaled textures. This won't cause any issues, but may impact performance.", LogLevel.Warn); + // 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. + asRawData = false; + this.Monitor.LogOnce("Enabled compatibility mode for PyTK 1.23.0 or earlier. This won't cause any issues, but may impact performance.", LogLevel.Warn); } - else - asRawData = true; } // load From 0ba4fd1785fc6a6aa931f18552261f33c2a2c0ef Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 5 Jul 2022 22:09:46 -0400 Subject: [PATCH 16/24] detect missing/outdated Error Handler for 'suggested fixes' section --- docs/release-notes.md | 1 + .../Framework/LogParsing/LogParser.cs | 2 + .../Framework/LogParsing/Models/LogModInfo.cs | 32 ++++- .../Framework/LogParsing/Models/ParsedLog.cs | 3 + src/SMAPI.Web/Views/LogParser/Index.cshtml | 120 ++++++++++-------- 5 files changed, 105 insertions(+), 53 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 3bc8724b..8361a780 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -18,6 +18,7 @@ * The [FluentHttpClient package](https://github.com/Pathoschild/FluentHttpClient#readme) is now loaded by SMAPI. * For the web UI: + * The log parser now detects a missing or outdated Error Handler mod for its 'suggested fixes' section. * Updated the JSON validator/schema for Content Patcher 1.27.0. * Fixed the mod count in the log parser metadata. diff --git a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs index 7fc8f958..0efa62c5 100644 --- a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs +++ b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs @@ -200,6 +200,8 @@ namespace StardewModdingAPI.Web.Framework.LogParsing log.GameVersion = match.Groups["gameVersion"].Value; log.OperatingSystem = match.Groups["os"].Value; smapiMod.OverrideVersion(log.ApiVersion); + + log.ApiVersionParsed = smapiMod.GetParsedVersion(); } // mod path line diff --git a/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs b/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs index 4b80a830..557f08ff 100644 --- a/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs +++ b/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs @@ -1,10 +1,19 @@ +using System; using System.Diagnostics.CodeAnalysis; +using StardewModdingAPI.Toolkit; namespace StardewModdingAPI.Web.Framework.LogParsing.Models { /// Metadata about a mod or content pack in the log. public class LogModInfo { + /********* + ** Private fields + *********/ + /// The parsed mod version, if valid. + private Lazy ParsedVersionImpl; + + /********* ** Accessors *********/ @@ -68,7 +77,6 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models { this.Name = name; this.Author = author; - this.Version = version; this.Description = description; this.UpdateVersion = updateVersion; this.UpdateLink = updateLink; @@ -82,6 +90,8 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models this.IsContentPack = !string.IsNullOrWhiteSpace(this.ContentPackFor); this.IsCodeMod = !this.IsContentPack; } + + this.OverrideVersion(version); } /// Add an update alert for this mod. @@ -95,9 +105,29 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models /// Override the version number, for cases like SMAPI itself where the version is only known later during parsing. /// The new mod version. + [MemberNotNull(nameof(LogModInfo.Version), nameof(LogModInfo.ParsedVersionImpl))] public void OverrideVersion(string version) { this.Version = version; + this.ParsedVersionImpl = new Lazy(this.ParseVersion); + } + + /// Get the semantic version for this mod, if it's valid. + public ISemanticVersion? GetParsedVersion() + { + return this.ParsedVersionImpl.Value; + } + + + /********* + ** Private methods + *********/ + /// Get the semantic version for this mod, if it's valid. + public ISemanticVersion? ParseVersion() + { + return !string.IsNullOrWhiteSpace(this.Version) && SemanticVersion.TryParse(this.Version, out ISemanticVersion? version) + ? version + : null; } } } diff --git a/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs b/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs index 6951e434..3f649199 100644 --- a/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs +++ b/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs @@ -28,6 +28,9 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models /// The SMAPI version. public string? ApiVersion { get; set; } + /// The parsed SMAPI version, if it's valid. + public ISemanticVersion? ApiVersionParsed { get; set; } + /// The game version. public string? GameVersion { get; set; } diff --git a/src/SMAPI.Web/Views/LogParser/Index.cshtml b/src/SMAPI.Web/Views/LogParser/Index.cshtml index 33239a2b..8bf5d86d 100644 --- a/src/SMAPI.Web/Views/LogParser/Index.cshtml +++ b/src/SMAPI.Web/Views/LogParser/Index.cshtml @@ -8,24 +8,29 @@ @{ ViewData["Title"] = "SMAPI log parser"; + // get log info ParsedLog? log = Model!.ParsedLog; - IDictionary contentPacks = Model.GetContentPacksByMod(); + ISet screenIds = new HashSet(log?.Messages.Select(p => p.ScreenId) ?? Array.Empty()); + + // detect suggested fixes + LogModInfo[] outdatedMods = log?.Mods.Where(mod => mod.HasUpdate).ToArray() ?? Array.Empty(); + LogModInfo? errorHandler = log?.Mods.FirstOrDefault(p => p.IsCodeMod && p.Name == "Error Handler"); + bool hasOlderErrorHandler = errorHandler?.GetParsedVersion() is not null && log?.ApiVersionParsed is not null && log.ApiVersionParsed.IsNewerThan(errorHandler.GetParsedVersion()); + + // get filters IDictionary defaultFilters = Enum .GetValues() .ToDictionary(level => level.ToString().ToLower(), level => level != LogLevel.Trace); - IDictionary logLevels = Enum .GetValues() .ToDictionary(level => (int)level, level => level.ToString().ToLower()); - IDictionary logSections = Enum .GetValues() .ToDictionary(section => (int)section, section => section.ToString()); + // get form string curPageUrl = this.Url.PlainAction("Index", "LogParser", new { id = Model.PasteID }, absoluteUrl: true)!; - - ISet screenIds = new HashSet(log?.Messages.Select(p => p.ScreenId) ?? Array.Empty()); } @section Head { @@ -69,7 +74,7 @@ } - + @@ -248,7 +248,7 @@ else if (log?.IsValid == true)
    @if (errorHandler is null) { -
  • You don't have the Error Handler mod installed. This automatically prevents many game or mod errors. You can reinstall SMAPI to re-add it.
  • +
  • You don't have the Error Handler mod installed. This automatically prevents many game or mod errors. You can reinstall SMAPI to re-add it.
  • } @if (hasOlderErrorHandler) { diff --git a/src/SMAPI.Web/wwwroot/Content/css/log-parser.css b/src/SMAPI.Web/wwwroot/Content/css/log-parser.css index 1d457e35..f136a96f 100644 --- a/src/SMAPI.Web/wwwroot/Content/css/log-parser.css +++ b/src/SMAPI.Web/wwwroot/Content/css/log-parser.css @@ -53,6 +53,36 @@ table caption { opacity: 0.3; } +/********* +** Suggested fixes +*********/ +#fix-list { + padding-left: 1em; + margin-bottom: 2em; +} + +#fix-list li { + padding: 0.5em; + background: #FFC; + border: 1px solid #880; + border-radius: 5px; + list-style-type: none; +} + +#fix-list li:not(:last-child) { + margin-bottom: 0.5em; +} + +#fix-list li.important { + background: #FCC; + border-color: #800; +} + +#fix-list li::before { + content: "⚠ "; +} + + /********* ** Log metadata & filters *********/ @@ -84,10 +114,6 @@ table caption { min-height: 1.3em; } -#fix-list { - margin-bottom: 2em; -} - #updates { min-width: 10em; } From 454f3a45ba104f2495386f6fe30d8ddab6beb80e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 5 Jul 2022 23:19:53 -0400 Subject: [PATCH 18/24] fix trace logs not tracking reloaded map tilesheets as asset propagation --- docs/release-notes.md | 1 + src/SMAPI/Metadata/CoreAssetPropagator.cs | 64 +++++++++++++---------- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index a94bb295..2b491c57 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -16,6 +16,7 @@ * For mod authors: * The [FluentHttpClient package](https://github.com/Pathoschild/FluentHttpClient#readme) is now loaded by SMAPI. + * Fixed `TRACE` logs not tracking reloaded map tilesheets as a propagated asset. * For the web UI: * Improved the log parser's 'suggested fixes' section: diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index b783b2b9..1b4d1737 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -164,6 +164,7 @@ namespace StardewModdingAPI.Metadata var content = this.MainContentManager; string key = assetName.BaseName; changedWarpRoutes = false; + bool changed = false; /**** ** Special case: current map tilesheet @@ -175,7 +176,10 @@ namespace StardewModdingAPI.Metadata foreach (TileSheet tilesheet in Game1.currentLocation.map.TileSheets) { if (this.IsSameBaseName(assetName, tilesheet.ImageSource)) + { Game1.mapDisplayDevice.LoadTileSheet(tilesheet); + changed = true; + } } } @@ -184,8 +188,6 @@ namespace StardewModdingAPI.Metadata ****/ if (type == typeof(Map)) { - bool anyChanged = false; - if (!ignoreWorld) { foreach (LocationInfo info in this.GetLocationsWithInfo()) @@ -206,12 +208,12 @@ namespace StardewModdingAPI.Metadata var newWarps = GetWarpSet(location); changedWarpRoutes = changedWarpRoutes || oldWarps.Count != newWarps.Count || oldWarps.Any(p => !newWarps.Contains(p)); - anyChanged = true; + changed = true; } } } - return anyChanged; + return changed; } /**** @@ -223,7 +225,7 @@ namespace StardewModdingAPI.Metadata ** Animals ****/ case "animals/horse": - return !ignoreWorld && this.UpdatePetOrHorseSprites(assetName); + return changed | (!ignoreWorld && this.UpdatePetOrHorseSprites(assetName)); /**** ** Buildings @@ -239,7 +241,7 @@ namespace StardewModdingAPI.Metadata Farm farm = Game1.getFarm(); farm?.ApplyHousePaint(); - return removedFromCache || farm != null; + return changed | (removedFromCache || farm != null); } /**** @@ -253,7 +255,7 @@ namespace StardewModdingAPI.Metadata case "characters/farmer/farmer_base_bald": case "characters/farmer/farmer_girl_base": case "characters/farmer/farmer_girl_base_bald": - return !ignoreWorld && this.UpdatePlayerSprites(assetName); + return changed | (!ignoreWorld && this.UpdatePlayerSprites(assetName)); case "characters/farmer/hairstyles": // Game1.LoadContent FarmerRenderer.hairStylesTexture = this.LoadTexture(key); @@ -305,10 +307,10 @@ namespace StardewModdingAPI.Metadata return true; case "data/farmanimals": // FarmAnimal constructor - return !ignoreWorld && this.UpdateFarmAnimalData(); + return changed | (!ignoreWorld && this.UpdateFarmAnimalData()); case "data/hairdata": // Farmer.GetHairStyleMetadataFile - return this.UpdateHairData(); + return changed | this.UpdateHairData(); case "data/movies": // MovieTheater.GetMovieData case "data/moviesreactions": // MovieTheater.GetMovieReactions @@ -316,7 +318,7 @@ namespace StardewModdingAPI.Metadata return true; case "data/npcdispositions": // NPC constructor - return !ignoreWorld && this.UpdateNpcDispositions(content, assetName); + return changed | (!ignoreWorld && this.UpdateNpcDispositions(content, assetName)); case "data/npcgifttastes": // Game1.LoadContent Game1.NPCGiftTastes = content.Load>(key); @@ -428,7 +430,7 @@ namespace StardewModdingAPI.Metadata return true; case "loosesprites/suspensionbridge": // SuspensionBridge constructor - return !ignoreWorld && this.UpdateSuspensionBridges(content, assetName); + return changed | (!ignoreWorld && this.UpdateSuspensionBridges(content, assetName)); /**** ** Content\Maps @@ -456,16 +458,16 @@ namespace StardewModdingAPI.Metadata return true; } } - return false; + return changed; case "minigames/titlebuttons": // TitleMenu - return this.UpdateTitleButtons(content, assetName); + return changed | this.UpdateTitleButtons(content, assetName); /**** ** Content\Strings ****/ case "strings/stringsfromcsfiles": - return this.UpdateStringsFromCsFiles(content); + return changed | this.UpdateStringsFromCsFiles(content); /**** ** Content\TileSheets @@ -490,7 +492,7 @@ namespace StardewModdingAPI.Metadata return true; case "tilesheets/critters": // Critter constructor - return !ignoreWorld && this.UpdateCritterTextures(assetName); + return changed | (!ignoreWorld && this.UpdateCritterTextures(assetName)); case "tilesheets/crops": // Game1.LoadContent Game1.cropSpriteSheet = content.Load(key); @@ -559,27 +561,27 @@ namespace StardewModdingAPI.Metadata return true; case "terrainfeatures/mushroom_tree": // from Tree - return !ignoreWorld && this.UpdateTreeTextures(Tree.mushroomTree); + return changed | (!ignoreWorld && this.UpdateTreeTextures(Tree.mushroomTree)); case "terrainfeatures/tree_palm": // from Tree - return !ignoreWorld && this.UpdateTreeTextures(Tree.palmTree); + return changed | (!ignoreWorld && this.UpdateTreeTextures(Tree.palmTree)); case "terrainfeatures/tree1_fall": // from Tree case "terrainfeatures/tree1_spring": // from Tree case "terrainfeatures/tree1_summer": // from Tree case "terrainfeatures/tree1_winter": // from Tree - return !ignoreWorld && this.UpdateTreeTextures(Tree.bushyTree); + return changed | (!ignoreWorld && this.UpdateTreeTextures(Tree.bushyTree)); case "terrainfeatures/tree2_fall": // from Tree case "terrainfeatures/tree2_spring": // from Tree case "terrainfeatures/tree2_summer": // from Tree case "terrainfeatures/tree2_winter": // from Tree - return !ignoreWorld && this.UpdateTreeTextures(Tree.leafyTree); + return changed | (!ignoreWorld && this.UpdateTreeTextures(Tree.leafyTree)); case "terrainfeatures/tree3_fall": // from Tree case "terrainfeatures/tree3_spring": // from Tree case "terrainfeatures/tree3_winter": // from Tree - return !ignoreWorld && this.UpdateTreeTextures(Tree.pineTree); + return changed | (!ignoreWorld && this.UpdateTreeTextures(Tree.pineTree)); } /**** @@ -588,25 +590,29 @@ namespace StardewModdingAPI.Metadata if (!ignoreWorld) { // dynamic textures - if (assetName.StartsWith("animals/cat")) - return this.UpdatePetOrHorseSprites(assetName); - if (assetName.StartsWith("animals/dog")) - return this.UpdatePetOrHorseSprites(assetName); if (assetName.IsDirectlyUnderPath("Animals")) - return this.UpdateFarmAnimalSprites(assetName); + { + if (assetName.StartsWith("animals/cat")) + return changed | this.UpdatePetOrHorseSprites(assetName); + + if (assetName.StartsWith("animals/dog")) + return changed | this.UpdatePetOrHorseSprites(assetName); + + return changed | this.UpdateFarmAnimalSprites(assetName); + } if (assetName.IsDirectlyUnderPath("Buildings")) - return this.UpdateBuildings(assetName); + return changed | this.UpdateBuildings(assetName); if (assetName.StartsWith("LooseSprites/Fence")) - return this.UpdateFenceTextures(assetName); + return changed | this.UpdateFenceTextures(assetName); // dynamic data if (assetName.IsDirectlyUnderPath("Characters/Dialogue")) - return this.UpdateNpcDialogue(assetName); + return changed | this.UpdateNpcDialogue(assetName); if (assetName.IsDirectlyUnderPath("Characters/schedules")) - return this.UpdateNpcSchedules(assetName); + return changed | this.UpdateNpcSchedules(assetName); } return false; From 543e45f9bde5929ca3c12db23f7d5b2ae0c653cd Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 5 Jul 2022 23:21:51 -0400 Subject: [PATCH 19/24] remove artifacts, tweak text --- .DS_Store | Bin 10244 -> 0 bytes build/.DS_Store | Bin 6148 -> 0 bytes src/.DS_Store | Bin 8196 -> 0 bytes src/SMAPI/.DS_Store | Bin 6148 -> 0 bytes src/SMAPI/Framework/SCore.cs | 2 +- src/SMAPI/SMAPI.sln | 25 ------------------------- 6 files changed, 1 insertion(+), 26 deletions(-) delete mode 100644 .DS_Store delete mode 100644 build/.DS_Store delete mode 100644 src/.DS_Store delete mode 100644 src/SMAPI/.DS_Store delete mode 100644 src/SMAPI/SMAPI.sln diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index ba9468ef9378d82e8b742a8dcd4010c8c1d73e35..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10244 zcmeHMU2GIp6u#fIrL%OP1GKha2R2=ZfGw;AZ23#Jf69N6ZRxiBEW0}+9hlCPo!xD* z)HEhO2%_;x<6q>-zsQ4%7+-u)6n#`Q!5AMf8udjJd{KGu%)PUeZt+bMftlpanRD(r z_wM=TJ9qcqWsIRCuiwX5m@y_(^Q5ZK?IxGk@3k41P&{LLdej|BTTXh0Z(s-z z2oMMm2oMMm2oU&xAVA;Q-X&KF8QcQ|0t5mCt|Q>B4`FJajQMg>NWOH?jc)-+mXn!Z z^i2Bz9uxLu%$Jiw@=zL6>>l8U!rx**gj0Xmb0-<|<)n}zoI!*$_%p-bp@7d$abfPx z5EC-E2M7cR%tXK|n+ld@d1f+e+W!4~dc-o_Tr~PMq_XmgS(S37TqPff4>_apTq>7$ zJ5t$wp4RI)R=W6{O7As|(WF|_>DalHX&YG{VCn|B?C&>i-5Kq0^0w~s*tqDBLvkpo z){T$1wKj#l;|HMQ{u8pkTxF>nk8n&G$L=N!R0XC;3r<2nYD#Ww4 z<=BVG>P3_@ojEMS+#3!mZsA0)r&E$<8FN{dWp0lo{bVe+&?%0V%Hl<8ep94oXm*t{ zN9#`v4DM5s>ihz6uxBJ^I<~vl$hstxYPCyT-I{Ui-C4uy@6R|^e%N-C>by+K%H*w7 z&Sc5dC0rOTUJS~tbIx9)uCQnfm7{sN^|8Kk1HbLPXQksV068N=31`*7c= zspp3J3#MzPEu+^?4I80(XbN4bu3xz5?gmX04pRX|*F-*T9?P4#<7z~f`5a-bQQ5E4 zXWHrL-YmMTo}+PS_GMb5w$JP7%o=pp{eTtPy-Kf3$;)a=IIdEnO7Bss=8) zLQblxX%|dd!RWP$g-njbIw$$tiNsclw-hP9EDm%e78QDkex+!vg>|tV>=0GhI6KXr zV`tdA>;n6QeZ{_GKeMas4*;`JiP@+^4HjW3ny?P7Xv2DRpa;9L2XXAj5KJ6_i!mI> zI8NYkoWv=d##49}FX3gpf-`snZ{r=D!+ZD;7x5WB$CtQ*@9_hE#IN`hf8m-`A=OFs z(n6_GYLc3zRZ^?eCT);5O1q>UDIuk#Ba$PHi3FtLvU#AGpl?4;a;nh$GhFHH%?qvJ z!7W?2ZNGI2?ebh+S~azI)rFU>T)nn!bNh7)3eFa3dL#Kce|IJ|Q@hw=LqhfBT>r#l8(fIxsifIxsifWV!MK$%EB z?4AF2|MUO)tNFGi;Je;IB&HvB;3<&oB!T#Srg1S@p|9=DT*dLw% diff --git a/build/.DS_Store b/build/.DS_Store deleted file mode 100644 index decdd55791f83b4535087de523bd19903adeb123..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}T>S5T5OiO(;SRiXH=A3&tP(!Aq$11&ruHr8cH$Fz!mz7@-t$)fe(jd>&_Z zx5c*hD$?%2>^D0*lQ7?g-3$P*W)|%LH~?Uw5|(Tnei2$Hosp9ER1q0HBh1s6FcBfn zKWCzi<3BP$dsl%BIK=SwbN|AZBu@LD_r*%(N_C}X)vUU88$60U=y&>A+U)eMX?7t* z5{~Rnco}!|wzGCBc)t_#Zciq}Q5QpQu3{dEyeTpsr7|~A3zlu!ZD(UJI67>&`<^#! zxP#+e5AD5!;n23$x3*8t+fT_e7w?KghM$|1RgEL~Kx16aYtZ9~;1B4d_!{I`B!>tD zTIpgGJ5^Ma-X|gM2=OtsG{pu*7%>bO29}lqeTZ81rDebrZWu5O%wvGg2N#vl*H|c& zM+XkF1wgEzTMF9LOHhuh(brfg#1#~#QV~@u(=7&5>F9TLoUgG^sM3My=7Z@sGu@#u z`F4E1E5m{L3e9L3FbqsGP*zii?*Fs-{r_Z=nHdHQ0}I6ftF(ev6HC%}>r8QU*Se^8 rs3a6uC>*DtA+KVLrK@-oRSNoDG7x=@g+lb8m>&U2gBc71f6Bl&h3tFQ}6pJmx7FMdD*w8J%1wpnge=XEDZ2zg&-JOAsOlRuO>@GqY zOEek?PyW4PG$!f;8h_%0iqWVsF~(>JiKvM`G5V+piSlARckXP%w(w>mN#`c_o_o%{ z=iWVMzH@KRUB(#N^F|9}^^7r@DyLc%HP){=I{XB*}TX%xc9|q)J7hNJTNakz|V&`RZf#3ofb4WI;aU!0HQnv z2nzKn4+wPPAx(yKTF^k1))d(TLREw*2823|$N6-k$&gM9D%2T-IzzZK!VCq$?xYtF zrZXf3joQcqkq72_fQQclmSWwkjrEr0@9$4lpX=J0Qa_y;u*~9^R@>n^e%f-(T%ceXCQTU~v>d}Nw!3-9@B-bG7?2fN z8Pn=co^0LP6yKCcOf|(%ZrhNc{>Cj+Q;NK-vH9LZW5?_X$2~1^E-mY#xyPOnTJeT z%PN?=X+>IUDc`MUk}u;8m^qJVV=^HORL(D{i`{#WcDC>By8mLeRaJMa%HVhtpoxbPUTmI#{qgD`T5|PI|(uL6f2cTJ_6yeJG!?9?4t&qgtaZ z2hxU>x;kpm59xLdzM7%EBa8W~SL*s2TsbO{&^=jJ~nv zga-=J~VAEFaDRH+rrOv~@E2DXN^(w)`Ejxd*< zVrSVo_BQ)~U0`3bOYA518~c-8W&Z$>PyrRoP=|Uf$7(dA1skvtJFp*}=t2rZ7{OtT z!o(q*Gr{v8kw;s=tyZI*PIU&PT~8WbpHC`6x+AI zvr9dA(}&ZyOGE%IyR9zHqpEdh+YA5&VoM;s7Wx6+%c3N}dw_>oSiMMLwj$P`t|~?0 z?C9*LKrxTYJTA3GV)knF4w(W)TOioA>fJJhh_+C$O|kVd1*IkhtlARWs8Hx>6@qPw zZB=9qg4wR_P$&qsO2Ia$Nk!3Ez{EOYdt?eEP5!SD{x!SIeq_J1E9@#3q6!*T;VvYw z1xf71KEm_?bYlR67$#(MTpz(WEF6Q6JYoAdPT+AofhX}4P7}VLA%t^$zknC<5?;o6 zypA{UCf>vQxPTAw5h4ByT*MFf8NbZJad$b6aitu`g;dV5UFRrCOUN%*m=9cqFtVQK z|9fuy{y!gF5!o1dVBUHFmAg~B+sXVJ9fdsCj#GVzDldHAw4i|sH9J!0b0Wk1YFb*arZJ<|x_)r~`n7N?5Y7Ss)ZAos*LB5DN8;7;=!%hfbK! zWwYZiGC*r*!v(Hc0A2X9euG|^$`GS(VJ}SvL)U#5rE+CuwQ5zZnsx6#$=n|X!_lA_ z^lzzmC1n~;?I65P;=Em7Ka<%oNV2%E3X&+sl)IZGi)7xEqbwSzT3-h&+p^pB&GGp7 zsNo#A?xf+2Pxf52_YWr%+uGRPIlXLm(rzYSG@Al{JSA%er|^o#f{8u*{VbK)BSxo{ z(+WmrfEi#0mWBbd0a~@CnK4h48DIu}$^h*T4l1E%u+XTs4s7W9Nbw3G3EK3QAao3R z1`CZCK@qwXQI`ty#1Oh1{f^1=3>F%7IS4f}e#eX~%nL=R(b4aybP%3KZkYjQV3vWh z?$+u2KmY#yKU>5-W`G&^R}6?s%WpMtO6F{xn;f0B4(bCc3FQ?UKTFV1M=|EoQM`q! a1pN*fh@QbhBYIHyBA{sCh8g%(20j1`T1^}P diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 283755d6..d0effb03 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1565,7 +1565,7 @@ namespace StardewModdingAPI.Framework this.Monitor.Newline(); this.Monitor.Log($"You can update {updates.Count} mod{(updates.Count != 1 ? "s" : "")}:", LogLevel.Alert); foreach ((IModMetadata mod, ISemanticVersion newVersion, string newUrl) in updates) - this.Monitor.Log($" {mod.DisplayName} {newVersion}: {newUrl} (You have {mod.Manifest.Version}", LogLevel.Alert); + this.Monitor.Log($" {mod.DisplayName} {newVersion}: {newUrl} (you have {mod.Manifest.Version})", LogLevel.Alert); } else this.Monitor.Log(" All mods up to date."); diff --git a/src/SMAPI/SMAPI.sln b/src/SMAPI/SMAPI.sln deleted file mode 100644 index 1821b0b6..00000000 --- a/src/SMAPI/SMAPI.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 25.0.1700.5 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SMAPI", "SMAPI.csproj", "{A09B2199-7A4C-4F47-8406-9A73A2FEAA96}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A09B2199-7A4C-4F47-8406-9A73A2FEAA96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A09B2199-7A4C-4F47-8406-9A73A2FEAA96}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A09B2199-7A4C-4F47-8406-9A73A2FEAA96}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A09B2199-7A4C-4F47-8406-9A73A2FEAA96}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {FD0C4E68-CA54-4AD5-8171-DA621027A9DF} - EndGlobalSection -EndGlobal From 60b4a10d0fc103ab8eed1dadf0eed615cfbe7915 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 5 Jul 2022 23:30:09 -0400 Subject: [PATCH 20/24] update release notes --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 2b491c57..00f145fe 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -9,6 +9,7 @@ ## Upcoming release * For players: + * Update alerts now mention which mod version you have now (thanks to ishanjalan!). * Fixed lag which occurred for some players since Stardew Valley 1.5.5. * Fixed `smapi-internal/config.user.json` overrides not applied after SMAPI 3.14.0. * Fixed PyTK not rescaling images correctly in some cases. From 2347644a1fa3537b198d063232ba814769ffe044 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 6 Jul 2022 18:36:29 -0400 Subject: [PATCH 21/24] update compatibility list for broken CFAutomate unofficial update --- docs/release-notes.md | 1 + src/SMAPI.Web/wwwroot/SMAPI.metadata.json | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 00f145fe..0c3248e0 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -14,6 +14,7 @@ * Fixed `smapi-internal/config.user.json` overrides not applied after SMAPI 3.14.0. * Fixed PyTK not rescaling images correctly in some cases. _When PyTK 1.23.0 or earlier is installed, this will disable the main performance improvements in SMAPI 3.15.0._ + * Updated compatibility list. * For mod authors: * The [FluentHttpClient package](https://github.com/Pathoschild/FluentHttpClient#readme) is now loaded by SMAPI. diff --git a/src/SMAPI.Web/wwwroot/SMAPI.metadata.json b/src/SMAPI.Web/wwwroot/SMAPI.metadata.json index 4e6f4669..d654b181 100644 --- a/src/SMAPI.Web/wwwroot/SMAPI.metadata.json +++ b/src/SMAPI.Web/wwwroot/SMAPI.metadata.json @@ -166,8 +166,8 @@ *********/ "CFAutomate": { "ID": "Platonymous.CFAutomate", - "~2.12.9 | Status": "AssumeBroken", - "~2.12.9 | StatusReasonDetails": "causes runtime errors in newer versions of Automate" + "~2.12.11 | Status": "AssumeBroken", + "~2.12.11 | StatusReasonDetails": "causes runtime errors in newer versions of Automate" }, "Dynamic Game Assets": { "ID": "spacechase0.DynamicGameAssets", From 4d9fd63d9e890a10029508c7d7e31dcc0b579db7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 6 Jul 2022 19:24:49 -0400 Subject: [PATCH 22/24] update code annotations --- src/SMAPI.Installer/InteractiveInstaller.cs | 1 + src/SMAPI.ModBuildConfig/DeployModTask.cs | 3 +-- .../Framework/ItemRepository.cs | 2 +- src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs | 10 +++++----- .../Framework/LowLevelEnvironmentUtility.cs | 7 +++++-- .../Framework/Clients/ModDrop/ModDropClient.cs | 2 +- .../Framework/ContentManagers/BaseContentManager.cs | 3 +-- src/SMAPI/Framework/Logging/LogManager.cs | 2 +- src/SMAPI/Framework/ModLoading/ModMetadata.cs | 2 +- src/SMAPI/Framework/ModLoading/ModResolver.cs | 4 ++-- .../ModLoading/RewriteFacades/HarmonyInstanceFacade.cs | 2 +- .../ModLoading/RewriteFacades/SpriteBatchFacade.cs | 1 - src/SMAPI/Framework/Networking/MultiplayerPeerMod.cs | 2 +- src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs | 2 +- src/SMAPI/Framework/SCore.cs | 6 +++--- src/SMAPI/Framework/SGame.cs | 3 +++ src/SMAPI/Metadata/CoreAssetPropagator.cs | 2 +- src/SMAPI/Translation.cs | 2 +- src/SMAPI/Utilities/SDate.cs | 2 +- 19 files changed, 31 insertions(+), 27 deletions(-) diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs index 5a6aa747..fd1a6047 100644 --- a/src/SMAPI.Installer/InteractiveInstaller.cs +++ b/src/SMAPI.Installer/InteractiveInstaller.cs @@ -453,6 +453,7 @@ namespace StardewModdingApi.Installer } // find target folder + // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract -- avoid error if the Mods folder has invalid mods, since they're not validated yet ModFolder? targetMod = targetMods.FirstOrDefault(p => p.Manifest?.UniqueID?.Equals(sourceMod.Manifest.UniqueID, StringComparison.OrdinalIgnoreCase) == true); DirectoryInfo defaultTargetFolder = new(Path.Combine(paths.ModsPath, sourceMod.Directory.Name)); DirectoryInfo targetFolder = targetMod?.Directory ?? defaultTargetFolder; diff --git a/src/SMAPI.ModBuildConfig/DeployModTask.cs b/src/SMAPI.ModBuildConfig/DeployModTask.cs index c7026ee1..88412d92 100644 --- a/src/SMAPI.ModBuildConfig/DeployModTask.cs +++ b/src/SMAPI.ModBuildConfig/DeployModTask.cs @@ -227,8 +227,7 @@ namespace StardewModdingAPI.ModBuildConfig string fromPath = entry.Value.FullName; string toPath = Path.Combine(modFolderPath, entry.Key); - // ReSharper disable once AssignNullToNotNullAttribute -- not applicable in this context - Directory.CreateDirectory(Path.GetDirectoryName(toPath)); + Directory.CreateDirectory(Path.GetDirectoryName(toPath)!); File.Copy(fromPath, toPath, overwrite: true); } diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs index 3722e155..88ddfe6b 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs @@ -30,7 +30,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework /// Get all spawnable items. /// The item types to fetch (or null for any type). /// Whether to include flavored variants like "Sunflower Honey". - [SuppressMessage("ReSharper", "AccessToModifiedClosure", Justification = "TryCreate invokes the lambda immediately.")] + [SuppressMessage("ReSharper", "AccessToModifiedClosure", Justification = $"{nameof(ItemRepository.TryCreate)} invokes the lambda immediately.")] public IEnumerable GetAll(ItemType[]? itemTypes = null, bool includeVariants = true) { // diff --git a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs index 7f06d170..3bdd145a 100644 --- a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs +++ b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs @@ -283,8 +283,8 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki } /// The response model for the MediaWiki parse API. - [SuppressMessage("ReSharper", "ClassNeverInstantiated.Local")] - [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")] + [SuppressMessage("ReSharper", "ClassNeverInstantiated.Local", Justification = "Used via JSON deserialization.")] + [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local", Justification = "Used via JSON deserialization.")] private class ResponseModel { /********* @@ -306,9 +306,9 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki } /// The inner response model for the MediaWiki parse API. - [SuppressMessage("ReSharper", "ClassNeverInstantiated.Local")] - [SuppressMessage("ReSharper", "CollectionNeverUpdated.Local")] - [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")] + [SuppressMessage("ReSharper", "ClassNeverInstantiated.Local", Justification = "Used via JSON deserialization.")] + [SuppressMessage("ReSharper", "CollectionNeverUpdated.Local", Justification = "Used via JSON deserialization.")] + [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local", Justification = "Used via JSON deserialization.")] private class ResponseParseModel { /********* diff --git a/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs b/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs index 6978567e..118b71b2 100644 --- a/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs +++ b/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs @@ -21,6 +21,7 @@ namespace StardewModdingAPI.Toolkit.Framework /// Get the OS name from the system uname command. /// The buffer to fill with the resulting string. [DllImport("libc")] + [SuppressMessage("ReSharper", "IdentifierTypo", Justification = "This is the actual external command name.")] static extern int uname(IntPtr buffer); @@ -51,7 +52,6 @@ namespace StardewModdingAPI.Toolkit.Framework /// Get the human-readable OS name and version. /// The current platform. - [SuppressMessage("ReSharper", "EmptyGeneralCatchClause", Justification = "Error suppressed deliberately to fallback to default behaviour.")] public static string GetFriendlyPlatformName(string platform) { #if SMAPI_FOR_WINDOWS @@ -65,7 +65,10 @@ namespace StardewModdingAPI.Toolkit.Framework return result ?? "Windows"; } - catch { } + catch + { + // fallback to default behavior + } #endif string name = Environment.OSVersion.ToString(); diff --git a/src/SMAPI.Web/Framework/Clients/ModDrop/ModDropClient.cs b/src/SMAPI.Web/Framework/Clients/ModDrop/ModDropClient.cs index c60b2c90..f5a5f930 100644 --- a/src/SMAPI.Web/Framework/Clients/ModDrop/ModDropClient.cs +++ b/src/SMAPI.Web/Framework/Clients/ModDrop/ModDropClient.cs @@ -42,7 +42,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.ModDrop /// Get update check info about a mod. /// The mod ID. - [SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "The nullability is validated in this method.")] + [SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract", Justification = "The nullability is validated in this method.")] public async Task GetModData(string id) { IModPage page = new GenericModPage(this.SiteKey, id); diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index d7be0c37..54f8e2a2 100644 --- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs @@ -111,7 +111,6 @@ namespace StardewModdingAPI.Framework.ContentManagers } /// - [SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalse", Justification = "Copied as-is from game code")] public sealed override string LoadBaseString(string path) { try @@ -119,7 +118,7 @@ namespace StardewModdingAPI.Framework.ContentManagers // copied as-is from LocalizedContentManager.LoadBaseString // This is only changed to call this.Load instead of base.Load, to support mod assets this.ParseStringPath(path, out string assetName, out string key); - Dictionary strings = this.Load>(assetName, LanguageCode.en); + Dictionary? strings = this.Load?>(assetName, LanguageCode.en); return strings != null && strings.ContainsKey(key) ? this.GetString(strings, key) : path; diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs index d811ed5c..c0b7c0ba 100644 --- a/src/SMAPI/Framework/Logging/LogManager.cs +++ b/src/SMAPI/Framework/Logging/LogManager.cs @@ -400,7 +400,7 @@ namespace StardewModdingAPI.Framework.Logging /// The loaded mods. /// The mods which could not be loaded. /// Whether to log issues for mods which directly use potentially sensitive .NET APIs like file or shell access. - [SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "Manifests aren't guaranteed non-null at this point in the loading process.")] + [SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract", Justification = "Manifests aren't guaranteed non-null at this point in the loading process.")] private void LogModWarnings(IEnumerable mods, IModMetadata[] skippedMods, bool logParanoidWarnings) { // get mods with warnings diff --git a/src/SMAPI/Framework/ModLoading/ModMetadata.cs b/src/SMAPI/Framework/ModLoading/ModMetadata.cs index aa4d2d8c..ac7a6bbd 100644 --- a/src/SMAPI/Framework/ModLoading/ModMetadata.cs +++ b/src/SMAPI/Framework/ModLoading/ModMetadata.cs @@ -83,7 +83,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// [MemberNotNullWhen(true, nameof(ModMetadata.ContentPack))] - [SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "The manifest may be null for broken mods while loading.")] + [SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract", Justification = "The manifest may be null for broken mods while loading.")] public bool IsContentPack => this.Manifest?.ContentPackFor != null; /// The fake content packs created by this mod, if any. diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs index 3e7144f9..abc46d47 100644 --- a/src/SMAPI/Framework/ModLoading/ModResolver.cs +++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs @@ -60,8 +60,8 @@ namespace StardewModdingAPI.Framework.ModLoading /// Get an update URL for an update key (if valid). /// Get a file lookup for the given directory. /// Whether to validate that files referenced in the manifest (like ) exist on disk. This can be disabled to only validate the manifest itself. - [SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "Manifest values may be null before they're validated.")] - [SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalse", Justification = "Manifest values may be null before they're validated.")] + [SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract", Justification = "Manifest values may be null before they're validated.")] + [SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract", Justification = "Manifest values may be null before they're validated.")] public void ValidateManifests(IEnumerable mods, ISemanticVersion apiVersion, Func getUpdateUrl, Func getFileLookup, bool validateFilesExist = true) { mods = mods.ToArray(); diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs index 9c8ba2b0..be45272e 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs @@ -28,7 +28,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades return new Harmony(id); } - [SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalse", Justification = "If the user passes a null original method, we let it fail in the underlying Harmony instance instead of handling it here.")] + [SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract", Justification = "If the user passes a null original method, we let it fail in the underlying Harmony instance instead of handling it here.")] public DynamicMethod Patch(MethodBase original, HarmonyMethod? prefix = null, HarmonyMethod? postfix = null, HarmonyMethod? transpiler = null) { // In Harmony 1.x you could target a virtual method that's not implemented by the diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs index 67569424..3eb31df3 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs @@ -10,7 +10,6 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades /// Provides method signatures that can be injected into mod code for compatibility with mods written for XNA Framework before Stardew Valley 1.5.5. /// This is public to support SMAPI rewriting and should not be referenced directly by mods. [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Used via assembly rewriting")] - [SuppressMessage("ReSharper", "CS0109", Justification = "The 'new' modifier applies when compiled on Linux/macOS.")] [SuppressMessage("ReSharper", "CS1591", Justification = "Documentation not needed for facade classes.")] public class SpriteBatchFacade : SpriteBatch { diff --git a/src/SMAPI/Framework/Networking/MultiplayerPeerMod.cs b/src/SMAPI/Framework/Networking/MultiplayerPeerMod.cs index 1e150508..cc936489 100644 --- a/src/SMAPI/Framework/Networking/MultiplayerPeerMod.cs +++ b/src/SMAPI/Framework/Networking/MultiplayerPeerMod.cs @@ -22,7 +22,7 @@ namespace StardewModdingAPI.Framework.Networking *********/ /// Construct an instance. /// The mod metadata. - [SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "The ID shouldn't be null, but we should handle it to avoid an error just in case.")] + [SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract", Justification = "The ID shouldn't be null, but we should handle it to avoid an error just in case.")] public MultiplayerPeerMod(RemoteContextModModel mod) { this.Name = mod.Name; diff --git a/src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs b/src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs index 94b13378..dac41629 100644 --- a/src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs +++ b/src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs @@ -14,7 +14,7 @@ namespace StardewModdingAPI.Framework.Rendering { /// A map display device which reimplements the default logic. /// This is an exact copy of , except that private fields are protected and all methods are virtual. - [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Field naming deliberately matches " + nameof(XnaDisplayDevice) + " to minimize differences.")] + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = $"Field naming deliberately matches {nameof(XnaDisplayDevice)} to minimize differences.")] internal class SXnaDisplayDevice : IDisplayDevice { /********* diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index d0effb03..d9969d4d 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -325,7 +325,7 @@ namespace StardewModdingAPI.Framework } /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - [SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "May be disposed before SMAPI is fully initialized.")] + [SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract", Justification = "May be disposed before SMAPI is fully initialized.")] public void Dispose() { // skip if already disposed @@ -1285,7 +1285,7 @@ namespace StardewModdingAPI.Framework private LocalizedContentManager CreateContentManager(IServiceProvider serviceProvider, string rootDirectory) { // Game1._temporaryContent initializing from SGame constructor - // ReSharper disable once ConditionIsAlwaysTrueOrFalse -- this is the method that initializes it + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- this is the method that initializes it if (this.ContentCore == null) { this.ContentCore = new ContentCoordinator( @@ -1808,7 +1808,7 @@ namespace StardewModdingAPI.Framework string relativePath = mod.GetRelativePathWithRoot(); if (mod.IsContentPack) this.Monitor.Log($" {mod.DisplayName} (from {relativePath}) [content pack]..."); - // ReSharper disable once ConstantConditionalAccessQualifier -- mod may be invalid at this point + // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract -- mod may be invalid at this point else if (mod.Manifest?.EntryDll != null) this.Monitor.Log($" {mod.DisplayName} (from {relativePath}{Path.DirectorySeparatorChar}{mod.Manifest.EntryDll})..."); // don't use Path.Combine here, since EntryDLL might not be valid else diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index 38043e1c..feb0988a 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -252,6 +252,7 @@ namespace StardewModdingAPI.Framework [SuppressMessage("ReSharper", "LocalVariableHidesMember", Justification = "copied from game code as-is")] [SuppressMessage("ReSharper", "MergeIntoPattern", Justification = "copied from game code as-is")] [SuppressMessage("ReSharper", "PossibleLossOfFraction", Justification = "copied from game code as-is")] + [SuppressMessage("ReSharper", "PossibleNullReferenceException", Justification = "copied from game code as-is")] [SuppressMessage("ReSharper", "RedundantArgumentDefaultValue", Justification = "copied from game code as-is")] [SuppressMessage("ReSharper", "RedundantBaseQualifier", Justification = "copied from game code as-is")] [SuppressMessage("ReSharper", "RedundantCast", Justification = "copied from game code as-is")] @@ -261,6 +262,8 @@ namespace StardewModdingAPI.Framework [SuppressMessage("ReSharper", "MergeIntoPattern", Justification = "copied from game code as-is")] [SuppressMessage("SMAPI.CommonErrors", "AvoidImplicitNetFieldCast", Justification = "copied from game code as-is")] [SuppressMessage("SMAPI.CommonErrors", "AvoidNetField", Justification = "copied from game code as-is")] + + [SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalse", Justification = "Deliberate to minimize chance of errors when copying event calls into new versions of this code.")] private void DrawImpl(GameTime gameTime, RenderTarget2D target_screen) { var events = this.Events; diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 1b4d1737..1ef9a8f2 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -966,7 +966,7 @@ namespace StardewModdingAPI.Metadata { // get suspension bridges field var field = this.Reflection.GetField?>(location, nameof(IslandNorth.suspensionBridges), required: false); - // ReSharper disable once ConditionIsAlwaysTrueOrFalse -- field is nullable when required: false + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract -- field is nullable when required: false if (field == null || !typeof(IEnumerable).IsAssignableFrom(field.FieldInfo.FieldType)) continue; diff --git a/src/SMAPI/Translation.cs b/src/SMAPI/Translation.cs index 01cb92b2..5cc119d9 100644 --- a/src/SMAPI/Translation.cs +++ b/src/SMAPI/Translation.cs @@ -121,7 +121,7 @@ namespace StardewModdingAPI /// Get a string representation of the given translation. /// The translation key. /// Limitation with nullable reference types: if there's no text and you disabled the fallback via , this will return null but the return value will still be marked non-nullable. - [SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "The null check is required due to limitations in nullable type annotations (see remarks).")] + [SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract", Justification = "The null check is required due to limitations in nullable type annotations (see remarks).")] public static implicit operator string(Translation translation) { return translation?.ToString()!; diff --git a/src/SMAPI/Utilities/SDate.cs b/src/SMAPI/Utilities/SDate.cs index 1d4e4489..c7ad41bd 100644 --- a/src/SMAPI/Utilities/SDate.cs +++ b/src/SMAPI/Utilities/SDate.cs @@ -250,7 +250,7 @@ namespace StardewModdingAPI.Utilities /// The year. /// Whether to allow 0 spring Y1 as a valid date. /// One of the arguments has an invalid value (like day 35). - [SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "The nullability is validated in this constructor.")] + [SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract", Justification = "The nullability is validated in this constructor.")] private SDate(int day, string season, int year, bool allowDayZero) { season = season?.Trim().ToLowerInvariant()!; // null-checked below From 9c9552531f8f085cded6ed5158503e75b00be1ff Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 6 Jul 2022 19:25:15 -0400 Subject: [PATCH 23/24] fix build warnings --- src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs | 2 +- src/SMAPI.Toolkit/ModToolkit.cs | 2 +- .../Serialization/Converters/SemanticVersionConverter.cs | 7 ++++++- .../Serialization/Converters/SimpleReadOnlyConverter.cs | 7 ++++++- src/SMAPI/Framework/ModHelpers/ModHelper.cs | 1 + src/SMAPI/Framework/Models/SConfig.cs | 2 +- src/SMAPI/Framework/SCore.cs | 2 +- src/SMAPI/Framework/Serialization/KeybindConverter.cs | 5 ++++- src/SMAPI/Utilities/SDate.cs | 4 ++-- 9 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs b/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs index 118b71b2..f464f4bb 100644 --- a/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs +++ b/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs @@ -22,7 +22,7 @@ namespace StardewModdingAPI.Toolkit.Framework /// The buffer to fill with the resulting string. [DllImport("libc")] [SuppressMessage("ReSharper", "IdentifierTypo", Justification = "This is the actual external command name.")] - static extern int uname(IntPtr buffer); + private static extern int uname(IntPtr buffer); /********* diff --git a/src/SMAPI.Toolkit/ModToolkit.cs b/src/SMAPI.Toolkit/ModToolkit.cs index 0df75a31..55b9bdd8 100644 --- a/src/SMAPI.Toolkit/ModToolkit.cs +++ b/src/SMAPI.Toolkit/ModToolkit.cs @@ -65,7 +65,7 @@ namespace StardewModdingAPI.Toolkit /// The file path for the SMAPI metadata file. public ModDatabase GetModDatabase(string metadataPath) { - MetadataModel metadata = JsonConvert.DeserializeObject(File.ReadAllText(metadataPath)); + MetadataModel metadata = JsonConvert.DeserializeObject(File.ReadAllText(metadataPath)) ?? new MetadataModel(); ModDataRecord[] records = metadata.ModData.Select(pair => new ModDataRecord(pair.Key, pair.Value)).ToArray(); return new ModDatabase(records, this.GetUpdateUrl); } diff --git a/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs b/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs index c32c3185..913d54e0 100644 --- a/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs +++ b/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs @@ -48,7 +48,12 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters return this.ReadObject(JObject.Load(reader)); case JsonToken.String: - return this.ReadString(JToken.Load(reader).Value(), path); + { + string? value = JToken.Load(reader).Value(); + return value is not null + ? this.ReadString(value, path) + : null; + } default: throw new SParseException($"Can't parse {nameof(ISemanticVersion)} from {reader.TokenType} node (path: {reader.Path})."); diff --git a/src/SMAPI.Toolkit/Serialization/Converters/SimpleReadOnlyConverter.cs b/src/SMAPI.Toolkit/Serialization/Converters/SimpleReadOnlyConverter.cs index 1c59f5e7..cdf2ed77 100644 --- a/src/SMAPI.Toolkit/Serialization/Converters/SimpleReadOnlyConverter.cs +++ b/src/SMAPI.Toolkit/Serialization/Converters/SimpleReadOnlyConverter.cs @@ -42,7 +42,12 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters return this.ReadObject(JObject.Load(reader), path); case JsonToken.String: - return this.ReadString(JToken.Load(reader).Value(), path); + { + string? value = JToken.Load(reader).Value(); + return value is not null + ? this.ReadString(value, path) + : null; + } default: throw new SParseException($"Can't parse {typeof(T).Name} from {reader.TokenType} node (path: {reader.Path})."); diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index 9ac3b6f7..caa66bad 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -88,6 +88,7 @@ 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. diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index 62b15405..9444c046 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -100,7 +100,7 @@ namespace StardewModdingAPI.Framework.Models /// Whether SMAPI should log network traffic. /// The colors to use for text written to the SMAPI console. /// The mod IDs SMAPI should ignore when performing update checks or validating update keys. - public SConfig(bool developerMode, bool? checkForUpdates, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, string[]? verboseLogging, bool? rewriteMods, bool? usePintail, bool? useRawImageLoading, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, ColorSchemeConfig consoleColors, string[]? suppressUpdateChecks) + public SConfig(bool developerMode, bool? checkForUpdates, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, string[]? verboseLogging, bool? rewriteMods, bool? useRawImageLoading, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, ColorSchemeConfig consoleColors, string[]? suppressUpdateChecks) { this.DeveloperMode = developerMode; this.CheckForUpdates = checkForUpdates ?? (bool)SConfig.DefaultValues[nameof(this.CheckForUpdates)]; diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index d9969d4d..46d65f6a 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -191,7 +191,7 @@ namespace StardewModdingAPI.Framework string logPath = this.GetLogPath(); // init basics - this.Settings = JsonConvert.DeserializeObject(File.ReadAllText(Constants.ApiConfigPath)); + this.Settings = JsonConvert.DeserializeObject(File.ReadAllText(Constants.ApiConfigPath)) ?? throw new InvalidOperationException("The 'smapi-internal/config.json' file is missing or invalid. You can reinstall SMAPI to fix this."); if (File.Exists(Constants.ApiUserConfigPath)) JsonConvert.PopulateObject(File.ReadAllText(Constants.ApiUserConfigPath), this.Settings); if (developerMode.HasValue) diff --git a/src/SMAPI/Framework/Serialization/KeybindConverter.cs b/src/SMAPI/Framework/Serialization/KeybindConverter.cs index 539f1291..f7b8e67e 100644 --- a/src/SMAPI/Framework/Serialization/KeybindConverter.cs +++ b/src/SMAPI/Framework/Serialization/KeybindConverter.cs @@ -49,7 +49,10 @@ namespace StardewModdingAPI.Framework.Serialization case JsonToken.String: { - string str = JToken.Load(reader).Value(); + string? str = JToken.Load(reader).Value(); + + if (str is null) + return new Keybind(Array.Empty()); if (objectType == typeof(Keybind)) { diff --git a/src/SMAPI/Utilities/SDate.cs b/src/SMAPI/Utilities/SDate.cs index c7ad41bd..06ee8b91 100644 --- a/src/SMAPI/Utilities/SDate.cs +++ b/src/SMAPI/Utilities/SDate.cs @@ -278,11 +278,11 @@ namespace StardewModdingAPI.Utilities /// Get whether a date represents 0 spring Y1, which is the date during the in-game intro. /// The day of month. - /// The season name. + /// The normalized season name. /// The year. private bool IsDayZero(int day, string season, int year) { - return day == 0 && season?.Trim().ToLower() == "spring" && year == 1; + return day == 0 && season == "spring" && year == 1; } /// Get the day of week for a given date. From bcb9e25d8666d2c1384515063ffbf987c36b8b0e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 6 Jul 2022 22:25:45 -0400 Subject: [PATCH 24/24] prepare for release --- build/common.targets | 2 +- docs/release-notes.md | 12 ++++++------ src/SMAPI.Mods.ConsoleCommands/manifest.json | 4 ++-- src/SMAPI.Mods.ErrorHandler/manifest.json | 4 ++-- src/SMAPI.Mods.SaveBackup/manifest.json | 4 ++-- src/SMAPI/Constants.cs | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/build/common.targets b/build/common.targets index 230758bd..230bef41 100644 --- a/build/common.targets +++ b/build/common.targets @@ -7,7 +7,7 @@ repo. It imports the other MSBuild files as needed. - 3.15.0 + 3.15.1 SMAPI latest $(AssemblySearchPaths);{GAC} diff --git a/docs/release-notes.md b/docs/release-notes.md index 0c3248e0..8b258fb3 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -7,10 +7,12 @@ _If needed, you can update to SMAPI 3.15.0 first and then install to the latest version._ --> -## Upcoming release +## 3.15.1 +Released 06 July 2022 for Stardew Valley 1.5.6 or later. + * For players: - * Update alerts now mention which mod version you have now (thanks to ishanjalan!). - * Fixed lag which occurred for some players since Stardew Valley 1.5.5. + * Added current version to update alerts (thanks to ishan!). + * Fixed lag for some players since Stardew Valley 1.5.5. * Fixed `smapi-internal/config.user.json` overrides not applied after SMAPI 3.14.0. * Fixed PyTK not rescaling images correctly in some cases. _When PyTK 1.23.0 or earlier is installed, this will disable the main performance improvements in SMAPI 3.15.0._ @@ -21,9 +23,7 @@ * Fixed `TRACE` logs not tracking reloaded map tilesheets as a propagated asset. * For the web UI: - * Improved the log parser's 'suggested fixes' section: - * added warning if Error Handler is missing or outdated; - * improved visual styles. + * Added log parser suggested fix for missing/outdated Error Handler, and improved visual styles. * Updated the JSON validator/schema for Content Patcher 1.27.0. * Fixed the mod count in the log parser metadata. diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json index 300de9d2..3c2dec19 100644 --- a/src/SMAPI.Mods.ConsoleCommands/manifest.json +++ b/src/SMAPI.Mods.ConsoleCommands/manifest.json @@ -1,9 +1,9 @@ { "Name": "Console Commands", "Author": "SMAPI", - "Version": "3.15.0", + "Version": "3.15.1", "Description": "Adds SMAPI console commands that let you manipulate the game.", "UniqueID": "SMAPI.ConsoleCommands", "EntryDll": "ConsoleCommands.dll", - "MinimumApiVersion": "3.15.0" + "MinimumApiVersion": "3.15.1" } diff --git a/src/SMAPI.Mods.ErrorHandler/manifest.json b/src/SMAPI.Mods.ErrorHandler/manifest.json index 15a1e0f3..28b4b149 100644 --- a/src/SMAPI.Mods.ErrorHandler/manifest.json +++ b/src/SMAPI.Mods.ErrorHandler/manifest.json @@ -1,9 +1,9 @@ { "Name": "Error Handler", "Author": "SMAPI", - "Version": "3.15.0", + "Version": "3.15.1", "Description": "Handles some common vanilla errors to log more useful info or avoid breaking the game.", "UniqueID": "SMAPI.ErrorHandler", "EntryDll": "ErrorHandler.dll", - "MinimumApiVersion": "3.15.0" + "MinimumApiVersion": "3.15.1" } diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json index 1a11742c..1944575b 100644 --- a/src/SMAPI.Mods.SaveBackup/manifest.json +++ b/src/SMAPI.Mods.SaveBackup/manifest.json @@ -1,9 +1,9 @@ { "Name": "Save Backup", "Author": "SMAPI", - "Version": "3.15.0", + "Version": "3.15.1", "Description": "Automatically backs up all your saves once per day into its folder.", "UniqueID": "SMAPI.SaveBackup", "EntryDll": "SaveBackup.dll", - "MinimumApiVersion": "3.15.0" + "MinimumApiVersion": "3.15.1" } diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 33468717..d733e61e 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -52,7 +52,7 @@ namespace StardewModdingAPI internal static int? LogScreenId { get; set; } /// SMAPI's current raw semantic version. - internal static string RawApiVersion = "3.15.0"; + internal static string RawApiVersion = "3.15.1"; } /// Contains SMAPI's constants and assumptions.