From f0e52061e34f3a397dbe1120590d062feb1be0ef Mon Sep 17 00:00:00 2001 From: Tyler Date: Mon, 5 Sep 2022 13:11:36 -0500 Subject: [PATCH 01/40] fix ComparableListWatcher not removing items in zero case --- .../StateTracking/FieldWatchers/ComparableListWatcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs index 0b13434a..b24f4178 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs @@ -63,7 +63,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers { if (this.LastValues.Count > 0) { - this.AddedImpl.AddRange(this.LastValues); + this.RemovedImpl.AddRange(this.LastValues); this.LastValues.Clear(); } return; From 715b9b09bae846e1f199ad2271283940c8fce7bd Mon Sep 17 00:00:00 2001 From: atravita-mods <94934860+atravita-mods@users.noreply.github.com> Date: Sun, 18 Sep 2022 12:05:46 -0400 Subject: [PATCH 02/40] Update ModScanner.cs Add a few more files to the ignored files like .7z --- src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs index a85ef109..d115810a 100644 --- a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs +++ b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs @@ -45,10 +45,14 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning ".png", ".psd", ".tif", + ".xcf", // gimp files // archives ".rar", ".zip", + ".7z", + ".tar", + ".tar.gz" // backup files ".backup", From e8da8fff5163eacd6ae7870eaa8c7dbc8285e3e7 Mon Sep 17 00:00:00 2001 From: Khloe Leclair Date: Mon, 26 Sep 2022 15:18:36 -0400 Subject: [PATCH 03/40] Initial work on a way for mods to return specific API instances to specific mods. --- .../Framework/ModHelpers/ModRegistryHelper.cs | 53 ++++++++++++++++--- src/SMAPI/IMod.cs | 6 +++ src/SMAPI/Mod.cs | 6 +++ 3 files changed, 59 insertions(+), 6 deletions(-) diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs index 348ba225..9ad3e3ae 100644 --- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs @@ -1,5 +1,7 @@ +using System; using System.Collections.Generic; using StardewModdingAPI.Framework.Reflection; +using StardewModdingAPI.Internal; namespace StardewModdingAPI.Framework.ModHelpers { @@ -15,8 +17,8 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Encapsulates monitoring and logging for the mod. private readonly IMonitor Monitor; - /// The mod IDs for APIs accessed by this instanced. - private readonly HashSet AccessedModApis = new(); + /// The APIs accessed by this instance. + private readonly Dictionary AccessedModApis = new(); /// Generates proxy classes to access mod APIs through an arbitrary interface. private readonly IInterfaceProxyFactory ProxyFactory; @@ -66,11 +68,50 @@ namespace StardewModdingAPI.Framework.ModHelpers return null; } - // get raw API + // get our cached API if one is available IModMetadata? mod = this.Registry.Get(uniqueID); - if (mod?.Api != null && this.AccessedModApis.Add(mod.Manifest.UniqueID)) - this.Monitor.Log($"Accessed mod-provided API for {mod.DisplayName}."); - return mod?.Api; + if (mod == null) + return null; + + if (this.AccessedModApis.ContainsKey(mod.Manifest.UniqueID)) + { + return this.AccessedModApis[mod.Manifest.UniqueID]; + } + + object? api; + + // safely request a specific API instance + try + { + api = mod.Mod?.GetApi(this.Mod.Manifest); + if (api != null && !api.GetType().IsPublic) + { + api = null; + this.Monitor.Log($"{mod.DisplayName} provided a specific API instance with a non-public type. This isn't currently supported, so the specific API won't be available to the requesting mod.", LogLevel.Warn); + } + + if (api != null) + this.Monitor.Log($"Accessed specific mod-provided API ({api.GetType().FullName}) for {mod.DisplayName}."); + } + catch (Exception ex) + { + this.Monitor.Log($"Failed loading specific mod-provided API for {mod.DisplayName}. Integrations with other mods may not work. Error: {ex.GetLogSummary()}", LogLevel.Error); + api = null; + } + + // fall back to the generic API instance + if (api == null) + { + api = mod.Api; + if (api != null) + { + this.Monitor.Log($"Accessed mod-provided API for {mod.DisplayName}."); + } + } + + // cache the API instance and return it + this.AccessedModApis[mod.Manifest.UniqueID] = api; + return api; } /// diff --git a/src/SMAPI/IMod.cs b/src/SMAPI/IMod.cs index b81ba0e3..6041bf66 100644 --- a/src/SMAPI/IMod.cs +++ b/src/SMAPI/IMod.cs @@ -25,5 +25,11 @@ namespace StardewModdingAPI /// Get an API that other mods can access. This is always called after . object? GetApi(); + + /// Get an API that a specific other mod can access. This method is called the first time the other mod calls for this mod. + /// The other mod's manifest. + /// Returns an API for another mod, or null if the other mod should use the general API returned from . + object? GetApi(IManifest manifest); + } } diff --git a/src/SMAPI/Mod.cs b/src/SMAPI/Mod.cs index f764752b..1a5f5594 100644 --- a/src/SMAPI/Mod.cs +++ b/src/SMAPI/Mod.cs @@ -30,6 +30,12 @@ namespace StardewModdingAPI return null; } + /// + public virtual object? GetApi(IManifest manifest) + { + return null; + } + /// Release or reset unmanaged resources. public void Dispose() { From c0e31d17a6d3f235c8a251e851c446e00c804cdb Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 28 Sep 2022 23:21:12 -0400 Subject: [PATCH 04/40] fix handling of GitHub prerelease versions marked as non-prerelease --- docs/release-notes.md | 4 ++++ src/SMAPI.Web/Controllers/ModsApiController.cs | 17 +++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index ab23b46e..ea459bcb 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -7,6 +7,10 @@ _If needed, you can update to SMAPI 3.16.0 first and then install the latest version._ --> +## Upcoming release +* For players: + * Fixed update alert shown for a prerelease version on GitHub if it's not marked as prerelease. + ## 3.16.2 Released 31 August 2022 for Stardew Valley 1.5.6 or later. diff --git a/src/SMAPI.Web/Controllers/ModsApiController.cs b/src/SMAPI.Web/Controllers/ModsApiController.cs index 401bba4f..71fb42c2 100644 --- a/src/SMAPI.Web/Controllers/ModsApiController.cs +++ b/src/SMAPI.Web/Controllers/ModsApiController.cs @@ -159,11 +159,20 @@ namespace StardewModdingAPI.Web.Controllers continue; } + // if there's only a prerelease version (e.g. from GitHub), don't override the main version + ISemanticVersion? curMain = data.Version; + ISemanticVersion? curPreview = data.PreviewVersion; + if (curPreview == null && curMain?.IsPrerelease() == true) + { + curPreview = curMain; + curMain = null; + } + // handle versions - if (this.IsNewer(data.Version, main?.Version)) - main = new ModEntryVersionModel(data.Version, data.Url!); - if (this.IsNewer(data.PreviewVersion, optional?.Version)) - optional = new ModEntryVersionModel(data.PreviewVersion, data.Url!); + if (this.IsNewer(curMain, main?.Version)) + main = new ModEntryVersionModel(curMain, data.Url!); + if (this.IsNewer(curPreview, optional?.Version)) + optional = new ModEntryVersionModel(curPreview, data.Url!); } // get unofficial version From c6b3446e9cb1d1e02db9db86f143ecfe75e9908c Mon Sep 17 00:00:00 2001 From: pizzaoverhead Date: Thu, 29 Sep 2022 13:33:45 +0100 Subject: [PATCH 05/40] Added checking for alternative Steam library install locations when looking for the Stardew Valley install. --- .../Framework/GameScanning/GameScanner.cs | 36 ++++++++++++++ .../GameScanning/SteamLibraryCollection.cs | 47 +++++++++++++++++++ src/SMAPI.Toolkit/SMAPI.Toolkit.csproj | 1 + 3 files changed, 84 insertions(+) create mode 100644 src/SMAPI.Toolkit/Framework/GameScanning/SteamLibraryCollection.cs diff --git a/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs index 8e1538a5..8e24dcdf 100644 --- a/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs +++ b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs @@ -9,6 +9,7 @@ using StardewModdingAPI.Toolkit.Utilities; using System.Reflection; #if SMAPI_FOR_WINDOWS using Microsoft.Win32; +using VdfParser; #endif namespace StardewModdingAPI.Toolkit.Framework.GameScanning @@ -158,7 +159,14 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning // via Steam library path string? steamPath = this.GetCurrentUserRegistryValue(@"Software\Valve\Steam", "SteamPath"); if (steamPath != null) + { yield return Path.Combine(steamPath.Replace('/', '\\'), @"steamapps\common\Stardew Valley"); + + // Check for Steam libraries in other locations + string? path = this.GetPathFromSteamLibrary(steamPath); + if (!string.IsNullOrWhiteSpace(path)) + yield return path; + } #endif // default GOG/Steam paths @@ -243,6 +251,34 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning using (openKey) return (string?)openKey.GetValue(name); } + + /// Get the game directory path from alternative Steam library locations. + /// The full path to the directory containing steam.exe. + /// The game directory, if found. + private string? GetPathFromSteamLibrary(string? steamPath) + { + string stardewAppId = "413150"; + if (steamPath != null) + { + string? libraryFoldersPath = Path.Combine(steamPath.Replace('/', '\\'), "steamapps\\libraryfolders.vdf"); + using FileStream fs = File.OpenRead(libraryFoldersPath); + VdfDeserializer deserializer = new VdfDeserializer(); + SteamLibraryCollection libraries = deserializer.Deserialize(fs); + if (libraries.libraryfolders != null) + { + var stardewLibrary = libraries.libraryfolders.FirstOrDefault(f => + { + var apps = f.Value?.apps; + return apps != null && apps.Any(a => a.Key.Equals(stardewAppId)); + }); + if (stardewLibrary.Value?.path != null) + { + return Path.Combine(stardewLibrary.Value.path.Replace("\\\\", "\\"), @"steamapps\common\Stardew Valley"); + } + } + } + return null; + } #endif } } diff --git a/src/SMAPI.Toolkit/Framework/GameScanning/SteamLibraryCollection.cs b/src/SMAPI.Toolkit/Framework/GameScanning/SteamLibraryCollection.cs new file mode 100644 index 00000000..7a186f69 --- /dev/null +++ b/src/SMAPI.Toolkit/Framework/GameScanning/SteamLibraryCollection.cs @@ -0,0 +1,47 @@ +#if SMAPI_FOR_WINDOWS +using System.Collections.Generic; + +namespace StardewModdingAPI.Toolkit.Framework.GameScanning +{ +#pragma warning disable IDE1006 // Model requires lowercase naming. +#pragma warning disable CS8618 // Required for model. + /// Model for Steam's libraryfolders.vdf. + public class SteamLibraryCollection + { + /// Each entry identifies a different location that part of the Steam games library is installed to. + public LibraryFolders libraryfolders { get; set; } + } + + /// A collection of LibraryFolders. Like a dictionary, but has contentstatsid used as an index also. + /// + /// +#pragma warning disable CS8714 // Required for model. + public class LibraryFolders : Dictionary +#pragma warning restore CS8714 + { + /// Index of the library, starting from "0". + public string contentstatsid { get; set; } + } + + /// A Steam library folder, containing information on the location and size of games installed there. + public class LibraryFolder + { + /// The escaped path to this Steam library folder. There will be a steam.exe here, but this may not be the one the player generally launches. + public string path { get; set; } + /// Label for the library, or "" + public string label { get; set; } + /// ~19-digit identifier. + public string contentid { get; set; } + /// Size of the library in bytes. May show 0 when size is non-zero. + public string totalsize { get; set; } + /// Used for downloads. + public string update_clean_bytes_tally { get; set; } + /// Normally "0". + public string time_last_update_corruption { get; set; } + /// List of Steam app IDs, and their current size in bytes. + public Dictionary apps { get; set; } + } +#pragma warning restore IDE1006 +#pragma warning restore CS8618 +} +#endif diff --git a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj index 7b79105f..411fd469 100644 --- a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj +++ b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj @@ -14,6 +14,7 @@ + From 2c2542657890d896750384bbaa2cb765379bad06 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 7 Oct 2022 00:16:00 -0400 Subject: [PATCH 06/40] fix issues with BundleExtraAssemblies --- docs/technical/mod-package.md | 2 ++ src/SMAPI.ModBuildConfig/build/smapi.targets | 20 ++++++++++++-------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/docs/technical/mod-package.md b/docs/technical/mod-package.md index ca78be55..c483754e 100644 --- a/docs/technical/mod-package.md +++ b/docs/technical/mod-package.md @@ -414,6 +414,8 @@ when you compile it. ## Release notes ### Upcoming release * Switched to the newer crossplatform `portable` debug symbols (thanks to lanturnalis!). +* Fixed `BundleExtraAssemblies` option being partly case-sensitive. +* Fixed `BundleExtraAssemblies` not applying `All` value to game assemblies. ### 4.0.1 Released 14 April 2022. diff --git a/src/SMAPI.ModBuildConfig/build/smapi.targets b/src/SMAPI.ModBuildConfig/build/smapi.targets index 12619439..b4fd312e 100644 --- a/src/SMAPI.ModBuildConfig/build/smapi.targets +++ b/src/SMAPI.ModBuildConfig/build/smapi.targets @@ -27,8 +27,12 @@ true + + <_BundleExtraAssembliesForGame>$([System.Text.RegularExpressions.Regex]::IsMatch('$(BundleExtraAssemblies)', '\bGame|All\b', RegexOptions.IgnoreCase)) + <_BundleExtraAssembliesForAny>$([System.Text.RegularExpressions.Regex]::IsMatch('$(BundleExtraAssemblies)', '\bGame|System|ThirdParty|All\b', RegexOptions.IgnoreCase)) + - true + true @@ -44,17 +48,17 @@ **********************************************--> - - - - + + + + - - + + - + From 5a0d337fcf6d18eed55334361b3eef3021912498 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 7 Oct 2022 00:21:09 -0400 Subject: [PATCH 07/40] update FluentHttpClient --- docs/release-notes.md | 3 +++ src/SMAPI.Toolkit/SMAPI.Toolkit.csproj | 2 +- src/SMAPI.Web/SMAPI.Web.csproj | 2 +- src/SMAPI/SMAPI.csproj | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index ea459bcb..04874729 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -11,6 +11,9 @@ * For players: * Fixed update alert shown for a prerelease version on GitHub if it's not marked as prerelease. +* For mod authors: + * Updated to [FluentHttpClient](https://github.com/Pathoschild/FluentHttpClient#readme) 4.2.0 (see [changes](https://github.com/Pathoschild/FluentHttpClient/blob/develop/RELEASE-NOTES.md#420)). + ## 3.16.2 Released 31 August 2022 for Stardew Valley 1.5.6 or later. diff --git a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj index 7b79105f..bd4f4e3d 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 d26cb6f8..81b187fe 100644 --- a/src/SMAPI.Web/SMAPI.Web.csproj +++ b/src/SMAPI.Web/SMAPI.Web.csproj @@ -25,7 +25,7 @@ - + diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index 36db0545..e5d8937c 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -26,7 +26,7 @@ - + From a7f03abe25128dba78d8c22802370a3f9a8aff11 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 8 Oct 2022 13:16:38 -0400 Subject: [PATCH 08/40] change square brackets to round ones in manifest name --- docs/release-notes.md | 1 + .../Serialization/Models/Manifest.cs | 53 +++++++++++++++---- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 04874729..4875d1cd 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -12,6 +12,7 @@ * Fixed update alert shown for a prerelease version on GitHub if it's not marked as prerelease. * For mod authors: + * SMAPI now treats square brackets in the manifest `Name` field as round brackets, to avoid breaking tools which parse log files. * Updated to [FluentHttpClient](https://github.com/Pathoschild/FluentHttpClient#readme) 4.2.0 (see [changes](https://github.com/Pathoschild/FluentHttpClient/blob/develop/RELEASE-NOTES.md#420)). ## 3.16.2 diff --git a/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs b/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs index da3ad608..8a449f0a 100644 --- a/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs +++ b/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Text; using Newtonsoft.Json; using StardewModdingAPI.Toolkit.Serialization.Converters; @@ -90,13 +91,13 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models [JsonConstructor] public Manifest(string uniqueId, string name, string author, string description, ISemanticVersion version, ISemanticVersion? minimumApiVersion, string? entryDll, IManifestContentPackFor? contentPackFor, IManifestDependency[]? dependencies, string[]? updateKeys) { - this.UniqueID = this.NormalizeWhitespace(uniqueId); - this.Name = this.NormalizeWhitespace(name); - this.Author = this.NormalizeWhitespace(author); - this.Description = this.NormalizeWhitespace(description); + this.UniqueID = this.NormalizeField(uniqueId); + this.Name = this.NormalizeField(name, replaceSquareBrackets: true); + this.Author = this.NormalizeField(author); + this.Description = this.NormalizeField(description); this.Version = version; this.MinimumApiVersion = minimumApiVersion; - this.EntryDll = this.NormalizeWhitespace(entryDll); + this.EntryDll = this.NormalizeField(entryDll); this.ContentPackFor = contentPackFor; this.Dependencies = dependencies ?? Array.Empty(); this.UpdateKeys = updateKeys ?? Array.Empty(); @@ -113,17 +114,47 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models /********* ** Private methods *********/ - /// Normalize whitespace in a raw string. + /// Normalize a manifest field to strip newlines, trim whitespace, and optionally strip square brackets. /// The input to strip. + /// Whether to replace square brackets with round ones. This is used in the mod name to avoid breaking the log format. #if NET5_0_OR_GREATER [return: NotNullIfNotNull("input")] #endif - private string? NormalizeWhitespace(string? input) + private string? NormalizeField(string? input, bool replaceSquareBrackets = false) { - return input - ?.Trim() - .Replace("\r", "") - .Replace("\n", ""); + input = input?.Trim(); + + if (!string.IsNullOrEmpty(input)) + { + StringBuilder? builder = null; + + for (int i = 0; i < input.Length; i++) + { + switch (input[i]) + { + case '\r': + case '\n': + builder ??= new StringBuilder(input); + builder[i] = ' '; + break; + + case '[' when replaceSquareBrackets: + builder ??= new StringBuilder(input); + builder[i] = '('; + break; + + case ']' when replaceSquareBrackets: + builder ??= new StringBuilder(input); + builder[i] = ')'; + break; + } + } + + if (builder != null) + input = builder.ToString(); + } + + return input; } } } From 7c90385d8df7bbf9469fc468480b26ebb134abd8 Mon Sep 17 00:00:00 2001 From: atravita-mods <94934860+atravita-mods@users.noreply.github.com> Date: Mon, 15 Aug 2022 19:13:24 -0400 Subject: [PATCH 09/40] Pre-calculate the strings for log levels. --- src/SMAPI/Framework/Monitor.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/SMAPI/Framework/Monitor.cs b/src/SMAPI/Framework/Monitor.cs index 6b53daff..8ba175e6 100644 --- a/src/SMAPI/Framework/Monitor.cs +++ b/src/SMAPI/Framework/Monitor.cs @@ -25,7 +25,9 @@ namespace StardewModdingAPI.Framework private readonly LogFileManager LogFile; /// The maximum length of the values. - private static readonly int MaxLevelLength = (from level in Enum.GetValues(typeof(LogLevel)).Cast() select level.ToString().Length).Max(); + private static readonly int MaxLevelLength = (from level in Enum.GetValues() select level.ToString().Length).Max(); + + private static readonly Dictionary LogStrings = Enum.GetValues().ToDictionary(k => k, v => v.ToString().ToUpper().PadRight(MaxLevelLength)); /// A cache of messages that should only be logged once. private readonly HashSet LogOnceCache = new(); @@ -147,7 +149,7 @@ namespace StardewModdingAPI.Framework /// The log level. private string GenerateMessagePrefix(string source, ConsoleLogLevel level) { - string levelStr = level.ToString().ToUpper().PadRight(Monitor.MaxLevelLength); + string levelStr = LogStrings[level]; int? playerIndex = this.GetScreenIdForLog(); return $"[{DateTime.Now:HH:mm:ss} {levelStr}{(playerIndex != null ? $" screen_{playerIndex}" : "")} {source}]"; From 78643710ce09197dbb5505fd8cc2361c8ada0830 Mon Sep 17 00:00:00 2001 From: atravita-mods <94934860+atravita-mods@users.noreply.github.com> Date: Mon, 15 Aug 2022 19:13:39 -0400 Subject: [PATCH 10/40] Use array pools in editing images. --- .../Framework/Content/AssetDataForImage.cs | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index 3393b22f..98d6725a 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Diagnostics.CodeAnalysis; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; @@ -47,44 +48,55 @@ namespace StardewModdingAPI.Framework.Content int areaHeight = sourceArea.Value.Height; if (areaX == 0 && areaY == 0 && areaWidth == source.Width && areaHeight == source.Height) + { sourceData = source.Data; + this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode); + } else { - sourceData = new Color[areaWidth * areaHeight]; - int i = 0; - for (int y = areaY, maxY = areaY + areaHeight - 1; y <= maxY; y++) + int pixelCount = areaWidth * areaHeight; + sourceData = ArrayPool.Shared.Rent(pixelCount); + + for (int y = areaY, maxY = areaY + areaHeight; y < maxY; y++) { - for (int x = areaX, maxX = areaX + areaWidth - 1; x <= maxX; x++) - { - int targetIndex = (y * source.Width) + x; - sourceData[i++] = source.Data[targetIndex]; - } + // avoiding an variable that increments allows the processor to re-arrange here. + int sourceIndex = (y * source.Width) + areaX; + int targetIndex = (y - areaY) * areaWidth; + Array.Copy(source.Data, sourceIndex, sourceData, targetIndex, areaWidth); } + + // apply + this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode); + + // return + ArrayPool.Shared.Return(sourceData); } } - - // apply - this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode); } /// public void PatchImage(Texture2D source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace) { + // validate + if (source == null) + throw new ArgumentNullException(nameof(source), "Can't patch from a null source texture."); + this.GetPatchBounds(ref sourceArea, ref targetArea, source.Width, source.Height); // validate source texture - if (source == null) - throw new ArgumentNullException(nameof(source), "Can't patch from a null source texture."); if (!source.Bounds.Contains(sourceArea.Value)) throw new ArgumentOutOfRangeException(nameof(sourceArea), "The source area is outside the bounds of the source texture."); // get source data int pixelCount = sourceArea.Value.Width * sourceArea.Value.Height; - Color[] sourceData = GC.AllocateUninitializedArray(pixelCount); + Color[] sourceData = ArrayPool.Shared.Rent(pixelCount); source.GetData(0, sourceArea, sourceData, 0, pixelCount); // apply this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode); + + // return + ArrayPool.Shared.Return(sourceData); } /// @@ -143,7 +155,7 @@ namespace StardewModdingAPI.Framework.Content if (patchMode == PatchMode.Overlay) { // get target data - Color[] mergedData = GC.AllocateUninitializedArray(pixelCount); + Color[] mergedData = ArrayPool.Shared.Rent(pixelCount); target.GetData(0, targetArea, mergedData, 0, pixelCount); // merge pixels @@ -175,6 +187,7 @@ namespace StardewModdingAPI.Framework.Content } target.SetData(0, targetArea, mergedData, 0, pixelCount); + ArrayPool.Shared.Return(mergedData); } else target.SetData(0, targetArea, sourceData, 0, pixelCount); From 4a1055e573e9d8b0aa654238889596be07c29193 Mon Sep 17 00:00:00 2001 From: atravita-mods <94934860+atravita-mods@users.noreply.github.com> Date: Tue, 16 Aug 2022 15:30:21 -0400 Subject: [PATCH 11/40] arraypool in the modcontentmanager, a bit of fussing --- .../Framework/Content/AssetDataForImage.cs | 15 ++++--- .../ContentManagers/ModContentManager.cs | 40 ++++++++++++------- .../Framework/ModLoading/AssemblyLoader.cs | 2 +- src/SMAPI/Framework/Monitor.cs | 7 +++- 4 files changed, 38 insertions(+), 26 deletions(-) diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index 98d6725a..46c2a22e 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -33,12 +33,12 @@ namespace StardewModdingAPI.Framework.Content /// public void PatchImage(IRawTextureData source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace) { - this.GetPatchBounds(ref sourceArea, ref targetArea, source.Width, source.Height); - - // validate source data + // nullcheck if (source == null) throw new ArgumentNullException(nameof(source), "Can't patch from null source data."); + this.GetPatchBounds(ref sourceArea, ref targetArea, source.Width, source.Height); + // get the pixels for the source area Color[] sourceData; { @@ -59,7 +59,6 @@ namespace StardewModdingAPI.Framework.Content for (int y = areaY, maxY = areaY + areaHeight; y < maxY; y++) { - // avoiding an variable that increments allows the processor to re-arrange here. int sourceIndex = (y * source.Width) + areaX; int targetIndex = (y - areaY) * areaWidth; Array.Copy(source.Data, sourceIndex, sourceData, targetIndex, areaWidth); @@ -77,13 +76,13 @@ namespace StardewModdingAPI.Framework.Content /// public void PatchImage(Texture2D source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace) { - // validate + // nullcheck if (source == null) throw new ArgumentNullException(nameof(source), "Can't patch from a null source texture."); this.GetPatchBounds(ref sourceArea, ref targetArea, source.Width, source.Height); - // validate source texture + // validate source bounds if (!source.Bounds.Contains(sourceArea.Value)) throw new ArgumentOutOfRangeException(nameof(sourceArea), "The source area is outside the bounds of the source texture."); @@ -161,8 +160,8 @@ namespace StardewModdingAPI.Framework.Content // merge pixels for (int i = 0; i < pixelCount; i++) { - Color above = sourceData[i]; - Color below = mergedData[i]; + ref Color above = ref sourceData[i]; + ref Color below = ref mergedData[i]; // shortcut transparency if (above.A < MinOpacity) diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index cc6f8372..dd30c225 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -1,9 +1,11 @@ using System; +using System.Buffers; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using BmFont; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; @@ -111,7 +113,7 @@ namespace StardewModdingAPI.Framework.ContentManagers if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string? contentManagerID, out IAssetName? relativePath)) { if (contentManagerID != this.Name) - throw this.GetLoadError(assetName, ContentLoadErrorType.AccessDenied, "can't load a different mod's managed asset key through this mod content manager."); + this.ThrowLoadError(assetName, ContentLoadErrorType.AccessDenied, "can't load a different mod's managed asset key through this mod content manager."); assetName = relativePath; } } @@ -123,7 +125,7 @@ namespace StardewModdingAPI.Framework.ContentManagers // get file FileInfo file = this.GetModFile(assetName.Name); if (!file.Exists) - throw this.GetLoadError(assetName, ContentLoadErrorType.AssetDoesNotExist, "the specified path doesn't exist."); + this.ThrowLoadError(assetName, ContentLoadErrorType.AssetDoesNotExist, "the specified path doesn't exist."); // load content asset = file.Extension.ToLower() switch @@ -141,7 +143,8 @@ namespace StardewModdingAPI.Framework.ContentManagers if (ex is SContentLoadException) throw; - throw this.GetLoadError(assetName, ContentLoadErrorType.Other, "an unexpected error occurred.", ex); + this.ThrowLoadError(assetName, ContentLoadErrorType.Other, "an unexpected error occurred.", ex); + return default; } // track & return asset @@ -189,7 +192,7 @@ namespace StardewModdingAPI.Framework.ContentManagers private T LoadDataFile(IAssetName assetName, FileInfo file) { if (!this.JsonHelper.ReadJsonFileIfExists(file.FullName, out T? asset)) - throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidData, "the JSON file is invalid."); // should never happen since we check for file existence before calling this method + this.ThrowLoadError(assetName, ContentLoadErrorType.InvalidData, "the JSON file is invalid."); // should never happen since we check for file existence before calling this method return asset; } @@ -301,7 +304,7 @@ namespace StardewModdingAPI.Framework.ContentManagers private T LoadXnbFile(IAssetName assetName) { if (typeof(IRawTextureData).IsAssignableFrom(typeof(T))) - throw this.GetLoadError(assetName, ContentLoadErrorType.Other, $"can't read XNB file as type {typeof(IRawTextureData)}; that type can only be read from a PNG file."); + this.ThrowLoadError(assetName, ContentLoadErrorType.Other, $"can't read XNB file as type {typeof(IRawTextureData)}; that type can only be read from a PNG file."); // the underlying content manager adds a .xnb extension implicitly, so // we need to strip it here to avoid trying to load a '.xnb.xnb' file. @@ -326,7 +329,8 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The file to load. private T HandleUnknownFileType(IAssetName assetName, FileInfo file) { - throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidName, $"unknown file extension '{file.Extension}'; must be one of '.fnt', '.json', '.png', '.tbin', '.tmx', or '.xnb'."); + this.ThrowLoadError(assetName, ContentLoadErrorType.InvalidName, $"unknown file extension '{file.Extension}'; must be one of '.fnt', '.json', '.png', '.tbin', '.tmx', or '.xnb'."); + return default; } /// Assert that the asset type is compatible with one of the allowed types. @@ -338,18 +342,20 @@ namespace StardewModdingAPI.Framework.ContentManagers private void AssertValidType(IAssetName assetName, FileInfo file, params Type[] validTypes) { if (!validTypes.Any(validType => validType.IsAssignableFrom(typeof(TAsset)))) - throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(TAsset)}'; must be type '{string.Join("' or '", validTypes.Select(p => p.FullName))}'."); + this.ThrowLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(TAsset)}'; must be type '{string.Join("' or '", validTypes.Select(p => p.FullName))}'."); } - /// Get an error which indicates that an asset couldn't be loaded. + /// Throws an error which indicates that an asset couldn't be loaded. /// Why loading an asset through the content pipeline failed. /// The asset name that failed to load. /// The reason the file couldn't be loaded. /// The underlying exception, if applicable. + [DoesNotReturn] [DebuggerStepThrough, DebuggerHidden] - private SContentLoadException GetLoadError(IAssetName assetName, ContentLoadErrorType errorType, string reasonPhrase, Exception? exception = null) + [MethodImpl(MethodImplOptions.NoInlining)] + private void ThrowLoadError(IAssetName assetName, ContentLoadErrorType errorType, string reasonPhrase, Exception? exception = null) { - return new(errorType, $"Failed loading asset '{assetName}' from {this.Name}: {reasonPhrase}", exception); + throw new SContentLoadException(errorType, $"Failed loading asset '{assetName}' from {this.Name}: {reasonPhrase}", exception); } /// Get a file from the mod folder. @@ -384,12 +390,14 @@ namespace StardewModdingAPI.Framework.ContentManagers private Texture2D PremultiplyTransparency(Texture2D texture) { // premultiply pixels - Color[] data = GC.AllocateUninitializedArray(texture.Width * texture.Height); - texture.GetData(data); + int count = texture.Width * texture.Height; + Color[] data = ArrayPool.Shared.Rent(count); + texture.GetData(data, 0, count); + bool changed = false; - for (int i = 0; i < data.Length; i++) + for (int i = 0; i < count; i++) { - Color pixel = data[i]; + ref Color pixel = ref data[i]; if (pixel.A is (byte.MinValue or byte.MaxValue)) continue; // no need to change fully transparent/opaque pixels @@ -398,8 +406,10 @@ namespace StardewModdingAPI.Framework.ContentManagers } if (changed) - texture.SetData(data); + texture.SetData(data, 0, count); + // return + ArrayPool.Shared.Return(data); return texture; } diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs index 01037870..ae08d972 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs @@ -221,7 +221,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// public static Assembly? ResolveAssembly(string name) { - string shortName = name.Split(new[] { ',' }, 2).First(); // get simple name (without version and culture) + string shortName = name.Split(',', 2).First(); // get simple name (without version and culture) return AppDomain.CurrentDomain .GetAssemblies() .FirstOrDefault(p => p.GetName().Name == shortName); diff --git a/src/SMAPI/Framework/Monitor.cs b/src/SMAPI/Framework/Monitor.cs index 8ba175e6..d33bf259 100644 --- a/src/SMAPI/Framework/Monitor.cs +++ b/src/SMAPI/Framework/Monitor.cs @@ -27,10 +27,13 @@ namespace StardewModdingAPI.Framework /// The maximum length of the values. private static readonly int MaxLevelLength = (from level in Enum.GetValues() select level.ToString().Length).Max(); + /// A mapping of console log levels to their string form. private static readonly Dictionary LogStrings = Enum.GetValues().ToDictionary(k => k, v => v.ToString().ToUpper().PadRight(MaxLevelLength)); + private readonly record struct LogOnceCacheEntry(string message, LogLevel level); + /// A cache of messages that should only be logged once. - private readonly HashSet LogOnceCache = new(); + private readonly HashSet LogOnceCache = new(); /// Get the screen ID that should be logged to distinguish between players in split-screen mode, if any. private readonly Func GetScreenIdForLog; @@ -86,7 +89,7 @@ namespace StardewModdingAPI.Framework /// public void LogOnce(string message, LogLevel level = LogLevel.Trace) { - if (this.LogOnceCache.Add($"{message}|{level}")) + if (this.LogOnceCache.Add(new LogOnceCacheEntry(message, level))) this.LogImpl(this.Source, message, (ConsoleLogLevel)level); } From 581763c36392e28ed6e05690ff84b66da5882e78 Mon Sep 17 00:00:00 2001 From: atravita-mods <94934860+atravita-mods@users.noreply.github.com> Date: Tue, 16 Aug 2022 16:46:10 -0400 Subject: [PATCH 12/40] Skip math if above is fully opaque. --- src/SMAPI/Framework/Content/AssetDataForImage.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index 46c2a22e..ea04f57a 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -160,13 +160,13 @@ namespace StardewModdingAPI.Framework.Content // merge pixels for (int i = 0; i < pixelCount; i++) { - ref Color above = ref sourceData[i]; - ref Color below = ref mergedData[i]; + Color above = sourceData[i]; + Color below = mergedData[i]; // shortcut transparency if (above.A < MinOpacity) continue; - if (below.A < MinOpacity) + if (below.A < MinOpacity || above.A == byte.MaxValue) mergedData[i] = above; // merge pixels From 0a2a1a08def3d97f21b4138d9e39c563389b492a Mon Sep 17 00:00:00 2001 From: atravita-mods <94934860+atravita-mods@users.noreply.github.com> Date: Tue, 16 Aug 2022 19:30:20 -0400 Subject: [PATCH 13/40] Favor record structs when there are four or fewer elements. --- .../Framework/Content/AssetDataForImage.cs | 41 +++++++++++++------ .../Framework/Content/AssetEditOperation.cs | 2 +- .../Framework/Content/AssetLoadOperation.cs | 2 +- .../ContentManagers/GameContentManager.cs | 4 +- 4 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index ea04f57a..5016bcd4 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -40,7 +40,7 @@ namespace StardewModdingAPI.Framework.Content this.GetPatchBounds(ref sourceArea, ref targetArea, source.Width, source.Height); // get the pixels for the source area - Color[] sourceData; + Color[] trimmedSourceData; { int areaX = sourceArea.Value.X; int areaY = sourceArea.Value.Y; @@ -49,26 +49,42 @@ namespace StardewModdingAPI.Framework.Content if (areaX == 0 && areaY == 0 && areaWidth == source.Width && areaHeight == source.Height) { - sourceData = source.Data; - this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode); + trimmedSourceData = source.Data; + this.PatchImageImpl(trimmedSourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode); } else { int pixelCount = areaWidth * areaHeight; - sourceData = ArrayPool.Shared.Rent(pixelCount); + trimmedSourceData = ArrayPool.Shared.Rent(pixelCount); - for (int y = areaY, maxY = areaY + areaHeight; y < maxY; y++) + // shortcut! If I want a horizontal slice of the texture + // I can copy the whole array in one pass + // Likely ~uncommon but Array.Copy significantly benefits + // from being able to do this. + if (areaWidth == source.Width && areaX == 0) { - int sourceIndex = (y * source.Width) + areaX; - int targetIndex = (y - areaY) * areaWidth; - Array.Copy(source.Data, sourceIndex, sourceData, targetIndex, areaWidth); + int sourceIndex = areaY * source.Width; + int targetIndex = 0; + + Array.Copy(source.Data, sourceIndex, trimmedSourceData, targetIndex, pixelCount); + } + else + { + // copying line-by-line + // Array.Copy isn't great at small scale + for (int y = areaY, maxY = areaY + areaHeight; y < maxY; y++) + { + int sourceIndex = (y * source.Width) + areaX; + int targetIndex = (y - areaY) * areaWidth; + Array.Copy(source.Data, sourceIndex, trimmedSourceData, targetIndex, areaWidth); + } } // apply - this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode); + this.PatchImageImpl(trimmedSourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode); // return - ArrayPool.Shared.Return(sourceData); + ArrayPool.Shared.Return(trimmedSourceData); } } } @@ -160,8 +176,9 @@ namespace StardewModdingAPI.Framework.Content // merge pixels for (int i = 0; i < pixelCount; i++) { - Color above = sourceData[i]; - Color below = mergedData[i]; + // should probably benchmark this... + ref Color above = ref sourceData[i]; + ref Color below = ref mergedData[i]; // shortcut transparency if (above.A < MinOpacity) diff --git a/src/SMAPI/Framework/Content/AssetEditOperation.cs b/src/SMAPI/Framework/Content/AssetEditOperation.cs index 11b8811b..893f59bd 100644 --- a/src/SMAPI/Framework/Content/AssetEditOperation.cs +++ b/src/SMAPI/Framework/Content/AssetEditOperation.cs @@ -8,5 +8,5 @@ namespace StardewModdingAPI.Framework.Content /// If there are multiple edits that apply to the same asset, the priority with which this one should be applied. /// The content pack on whose behalf the edit is being applied, if any. /// Apply the edit to an asset. - internal record AssetEditOperation(IModMetadata Mod, AssetEditPriority Priority, IModMetadata? OnBehalfOf, Action ApplyEdit); + internal readonly record struct AssetEditOperation(IModMetadata Mod, AssetEditPriority Priority, IModMetadata? OnBehalfOf, Action ApplyEdit); } diff --git a/src/SMAPI/Framework/Content/AssetLoadOperation.cs b/src/SMAPI/Framework/Content/AssetLoadOperation.cs index 7af07dfd..58886849 100644 --- a/src/SMAPI/Framework/Content/AssetLoadOperation.cs +++ b/src/SMAPI/Framework/Content/AssetLoadOperation.cs @@ -8,5 +8,5 @@ namespace StardewModdingAPI.Framework.Content /// If there are multiple loads that apply to the same asset, the priority with which this one should be applied. /// The content pack on whose behalf the asset is being loaded, if any. /// Load the initial value for an asset. - internal record AssetLoadOperation(IModMetadata Mod, IModMetadata? OnBehalfOf, AssetLoadPriority Priority, Func GetData); + internal readonly record struct AssetLoadOperation(IModMetadata Mod, IModMetadata? OnBehalfOf, AssetLoadPriority Priority, Func GetData); } diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index df7bdc59..a8c70356 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -172,7 +172,7 @@ namespace StardewModdingAPI.Framework.ContentManagers where T : notnull { // find matching loader - AssetLoadOperation? loader = null; + AssetLoadOperation loader = default; if (loadOperations?.Count > 0) { if (!this.AssertMaxOneRequiredLoader(info, loadOperations, out string? error)) @@ -183,7 +183,7 @@ namespace StardewModdingAPI.Framework.ContentManagers loader = loadOperations.OrderByDescending(p => p.Priority).FirstOrDefault(); } - if (loader == null) + if (loader.Mod == null) // aka, this is default. return null; // fetch asset from loader From 627100509c0a9c637b495473ef71f13093e885d2 Mon Sep 17 00:00:00 2001 From: atravita-mods <94934860+atravita-mods@users.noreply.github.com> Date: Wed, 17 Aug 2022 21:11:47 -0400 Subject: [PATCH 14/40] hide throwhelper from stack trace in dotnet 6 --- src/SMAPI/Framework/ContentManagers/ModContentManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index dd30c225..0c793808 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -353,6 +353,9 @@ namespace StardewModdingAPI.Framework.ContentManagers [DoesNotReturn] [DebuggerStepThrough, DebuggerHidden] [MethodImpl(MethodImplOptions.NoInlining)] +#if NET6_0_OR_GREATER + [StackTraceHidden] +#endif private void ThrowLoadError(IAssetName assetName, ContentLoadErrorType errorType, string reasonPhrase, Exception? exception = null) { throw new SContentLoadException(errorType, $"Failed loading asset '{assetName}' from {this.Name}: {reasonPhrase}", exception); From d29c01b8155a6fe2607066da6f0a5cfb45b28d3c Mon Sep 17 00:00:00 2001 From: atravita-mods <94934860+atravita-mods@users.noreply.github.com> Date: Thu, 18 Aug 2022 20:51:45 -0400 Subject: [PATCH 15/40] Partially revert "Favor record structs when there are four or fewer elements." This reverts commit f5d49515c4eddfb415903a89d70654cf9b6de299. --- .../Framework/Content/AssetDataForImage.cs | 38 +++++++++---------- .../Framework/Content/AssetEditOperation.cs | 2 +- .../Framework/Content/AssetLoadOperation.cs | 2 +- .../ContentManagers/GameContentManager.cs | 4 +- 4 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index 5016bcd4..5f175217 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -40,7 +40,7 @@ namespace StardewModdingAPI.Framework.Content this.GetPatchBounds(ref sourceArea, ref targetArea, source.Width, source.Height); // get the pixels for the source area - Color[] trimmedSourceData; + Color[] sourceData; { int areaX = sourceArea.Value.X; int areaY = sourceArea.Value.Y; @@ -49,42 +49,40 @@ namespace StardewModdingAPI.Framework.Content if (areaX == 0 && areaY == 0 && areaWidth == source.Width && areaHeight == source.Height) { - trimmedSourceData = source.Data; - this.PatchImageImpl(trimmedSourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode); + sourceData = source.Data; + this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode); } else { int pixelCount = areaWidth * areaHeight; - trimmedSourceData = ArrayPool.Shared.Rent(pixelCount); + sourceData = ArrayPool.Shared.Rent(pixelCount); - // shortcut! If I want a horizontal slice of the texture - // I can copy the whole array in one pass - // Likely ~uncommon but Array.Copy significantly benefits - // from being able to do this. - if (areaWidth == source.Width && areaX == 0) + if (areaX == 0 && areaWidth == source.Width) { - int sourceIndex = areaY * source.Width; - int targetIndex = 0; + // shortcut copying because the area to copy is contiguous. This is + // probably uncommon, but Array.Copy benefits a lot. + + int sourceIndex = areaY * areaWidth; + int targetIndex = 0; + Array.Copy(source.Data, sourceIndex, sourceData, targetIndex, areaWidth); - Array.Copy(source.Data, sourceIndex, trimmedSourceData, targetIndex, pixelCount); } else { - // copying line-by-line - // Array.Copy isn't great at small scale + // slower copying, line by line for (int y = areaY, maxY = areaY + areaHeight; y < maxY; y++) { int sourceIndex = (y * source.Width) + areaX; int targetIndex = (y - areaY) * areaWidth; - Array.Copy(source.Data, sourceIndex, trimmedSourceData, targetIndex, areaWidth); + Array.Copy(source.Data, sourceIndex, sourceData, targetIndex, areaWidth); } } // apply - this.PatchImageImpl(trimmedSourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode); + this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode); // return - ArrayPool.Shared.Return(trimmedSourceData); + ArrayPool.Shared.Return(sourceData); } } } @@ -176,9 +174,9 @@ namespace StardewModdingAPI.Framework.Content // merge pixels for (int i = 0; i < pixelCount; i++) { - // should probably benchmark this... - ref Color above = ref sourceData[i]; - ref Color below = ref mergedData[i]; + // ref locals here? Not sure. + Color above = sourceData[i]; + Color below = mergedData[i]; // shortcut transparency if (above.A < MinOpacity) diff --git a/src/SMAPI/Framework/Content/AssetEditOperation.cs b/src/SMAPI/Framework/Content/AssetEditOperation.cs index 893f59bd..11b8811b 100644 --- a/src/SMAPI/Framework/Content/AssetEditOperation.cs +++ b/src/SMAPI/Framework/Content/AssetEditOperation.cs @@ -8,5 +8,5 @@ namespace StardewModdingAPI.Framework.Content /// If there are multiple edits that apply to the same asset, the priority with which this one should be applied. /// The content pack on whose behalf the edit is being applied, if any. /// Apply the edit to an asset. - internal readonly record struct AssetEditOperation(IModMetadata Mod, AssetEditPriority Priority, IModMetadata? OnBehalfOf, Action ApplyEdit); + internal record AssetEditOperation(IModMetadata Mod, AssetEditPriority Priority, IModMetadata? OnBehalfOf, Action ApplyEdit); } diff --git a/src/SMAPI/Framework/Content/AssetLoadOperation.cs b/src/SMAPI/Framework/Content/AssetLoadOperation.cs index 58886849..7af07dfd 100644 --- a/src/SMAPI/Framework/Content/AssetLoadOperation.cs +++ b/src/SMAPI/Framework/Content/AssetLoadOperation.cs @@ -8,5 +8,5 @@ namespace StardewModdingAPI.Framework.Content /// If there are multiple loads that apply to the same asset, the priority with which this one should be applied. /// The content pack on whose behalf the asset is being loaded, if any. /// Load the initial value for an asset. - internal readonly record struct AssetLoadOperation(IModMetadata Mod, IModMetadata? OnBehalfOf, AssetLoadPriority Priority, Func GetData); + internal record AssetLoadOperation(IModMetadata Mod, IModMetadata? OnBehalfOf, AssetLoadPriority Priority, Func GetData); } diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index a8c70356..df7bdc59 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -172,7 +172,7 @@ namespace StardewModdingAPI.Framework.ContentManagers where T : notnull { // find matching loader - AssetLoadOperation loader = default; + AssetLoadOperation? loader = null; if (loadOperations?.Count > 0) { if (!this.AssertMaxOneRequiredLoader(info, loadOperations, out string? error)) @@ -183,7 +183,7 @@ namespace StardewModdingAPI.Framework.ContentManagers loader = loadOperations.OrderByDescending(p => p.Priority).FirstOrDefault(); } - if (loader.Mod == null) // aka, this is default. + if (loader == null) return null; // fetch asset from loader From ff523c619a040c8eb2d2f1c1a0d19c8fe83f1c75 Mon Sep 17 00:00:00 2001 From: atravita-mods <94934860+atravita-mods@users.noreply.github.com> Date: Fri, 19 Aug 2022 11:10:19 -0400 Subject: [PATCH 16/40] fix fast-track array copying --- src/SMAPI/Framework/Content/AssetDataForImage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index 5f175217..9c71328f 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -64,7 +64,7 @@ namespace StardewModdingAPI.Framework.Content int sourceIndex = areaY * areaWidth; int targetIndex = 0; - Array.Copy(source.Data, sourceIndex, sourceData, targetIndex, areaWidth); + Array.Copy(source.Data, sourceIndex, sourceData, targetIndex, pixelCount); } else From ce63efa2f45ee770fdb628a45f5a6b63544b0031 Mon Sep 17 00:00:00 2001 From: atravita-mods <94934860+atravita-mods@users.noreply.github.com> Date: Mon, 22 Aug 2022 19:32:45 -0400 Subject: [PATCH 17/40] Avoid making copy if the source image is just taller than the sourceArea. --- src/SMAPI/Framework/Content/AssetDataForImage.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index 9c71328f..bcdebff6 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -47,10 +47,12 @@ namespace StardewModdingAPI.Framework.Content int areaWidth = sourceArea.Value.Width; int areaHeight = sourceArea.Value.Height; - if (areaX == 0 && areaY == 0 && areaWidth == source.Width && areaHeight == source.Height) + if (areaX == 0 && areaY == 0 && areaWidth == source.Width && areaHeight <= source.Height) { + // It's actually fine if the source is taller than the sourceArea + // the "extra" bits on the end of the array can just be ignored. sourceData = source.Data; - this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode); + this.PatchImageImpl(sourceData, areaWidth, areaHeight, sourceArea.Value, targetArea.Value, patchMode); } else { From c1d5d19e43a9305ebf71696408fc8a0777794f55 Mon Sep 17 00:00:00 2001 From: atravita-mods <94934860+atravita-mods@users.noreply.github.com> Date: Mon, 22 Aug 2022 22:35:08 -0400 Subject: [PATCH 18/40] Skip transparent rows at the start and end when doing a patch overlay. --- .../Framework/Content/AssetDataForImage.cs | 51 +++++++++++++++++-- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index bcdebff6..90468316 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -1,6 +1,7 @@ using System; using System.Buffers; using System.Diagnostics.CodeAnalysis; +using System.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using StardewValley; @@ -166,9 +167,51 @@ namespace StardewModdingAPI.Framework.Content if (sourceArea.Size != targetArea.Size) throw new InvalidOperationException("The source and target areas must be the same size."); - // merge data - if (patchMode == PatchMode.Overlay) + if (patchMode == PatchMode.Replace) + target.SetData(0, targetArea, sourceData, 0, pixelCount); + else { + // merge data + + // Content packs have a habit of using large amounts of blank space. + // Adjusting bounds to ignore transparent pixels at the start and end. + + int startIndex = -1; + for (int i = 0; i < pixelCount; i++) + { + if (sourceData[i].A >= MinOpacity) + { + startIndex = i; + break; + } + } + + if (startIndex == -1) + return; + + int endIndex = -1; + for (int i = pixelCount - 1; i >= startIndex; i--) + { + if (sourceData[i].A >= MinOpacity) + { + endIndex = i; + break; + } + } + + if (endIndex == -1) + return; + + int topoffset = startIndex / sourceArea.Width; + int bottomoffset = endIndex / sourceArea.Width; + + // Update target rectangle + targetArea = new(targetArea.X, targetArea.Y + topoffset, targetArea.Width, bottomoffset - topoffset + 1); + + pixelCount = targetArea.Width * targetArea.Height; + + int sourceoffset = topoffset * sourceArea.Width; + // get target data Color[] mergedData = ArrayPool.Shared.Rent(pixelCount); target.GetData(0, targetArea, mergedData, 0, pixelCount); @@ -177,7 +220,7 @@ namespace StardewModdingAPI.Framework.Content for (int i = 0; i < pixelCount; i++) { // ref locals here? Not sure. - Color above = sourceData[i]; + Color above = sourceData[sourceoffset + i]; Color below = mergedData[i]; // shortcut transparency @@ -205,8 +248,6 @@ namespace StardewModdingAPI.Framework.Content target.SetData(0, targetArea, mergedData, 0, pixelCount); ArrayPool.Shared.Return(mergedData); } - else - target.SetData(0, targetArea, sourceData, 0, pixelCount); } } } From 09fd12ddfe89c5d36b1db66167dd741e4f8a7e0f Mon Sep 17 00:00:00 2001 From: atravita-mods <94934860+atravita-mods@users.noreply.github.com> Date: Mon, 22 Aug 2022 23:44:07 -0400 Subject: [PATCH 19/40] use startindex/endindex since I've already calculated those... --- src/SMAPI/Framework/Content/AssetDataForImage.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index 90468316..70fa369f 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -207,7 +207,6 @@ namespace StardewModdingAPI.Framework.Content // Update target rectangle targetArea = new(targetArea.X, targetArea.Y + topoffset, targetArea.Width, bottomoffset - topoffset + 1); - pixelCount = targetArea.Width * targetArea.Height; int sourceoffset = topoffset * sourceArea.Width; @@ -217,11 +216,11 @@ namespace StardewModdingAPI.Framework.Content target.GetData(0, targetArea, mergedData, 0, pixelCount); // merge pixels - for (int i = 0; i < pixelCount; i++) + for (int i = startIndex; i <= endIndex; i++) { // ref locals here? Not sure. - Color above = sourceData[sourceoffset + i]; - Color below = mergedData[i]; + Color above = sourceData[i]; + Color below = mergedData[i - sourceoffset]; // shortcut transparency if (above.A < MinOpacity) From a3b8546ec8975ef1941e7618cef773c41d5c423f Mon Sep 17 00:00:00 2001 From: atravita-mods <94934860+atravita-mods@users.noreply.github.com> Date: Mon, 22 Aug 2022 23:50:52 -0400 Subject: [PATCH 20/40] cleanup and comments --- src/SMAPI/Framework/Content/AssetDataForImage.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index 70fa369f..f00d2d4c 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -187,7 +187,7 @@ namespace StardewModdingAPI.Framework.Content } if (startIndex == -1) - return; + return; // apparently a completely blank texture? int endIndex = -1; for (int i = pixelCount - 1; i >= startIndex; i--) @@ -200,8 +200,9 @@ namespace StardewModdingAPI.Framework.Content } if (endIndex == -1) - return; + return; // should never happen + // Calculate new Y bounds int topoffset = startIndex / sourceArea.Width; int bottomoffset = endIndex / sourceArea.Width; From 496c438be244c6e51f34cbcf238913edf55a8618 Mon Sep 17 00:00:00 2001 From: atravita-mods <94934860+atravita-mods@users.noreply.github.com> Date: Tue, 23 Aug 2022 14:34:23 -0400 Subject: [PATCH 21/40] fix indexing again, because apparently I'm bad at math now? --- src/SMAPI/Framework/Content/AssetDataForImage.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index f00d2d4c..2bbcc60c 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -219,15 +219,17 @@ namespace StardewModdingAPI.Framework.Content // merge pixels for (int i = startIndex; i <= endIndex; i++) { + int targetIndex = i - sourceoffset; + // ref locals here? Not sure. Color above = sourceData[i]; - Color below = mergedData[i - sourceoffset]; + Color below = mergedData[targetIndex]; // shortcut transparency if (above.A < MinOpacity) continue; if (below.A < MinOpacity || above.A == byte.MaxValue) - mergedData[i] = above; + mergedData[targetIndex] = above; // merge pixels else @@ -236,7 +238,7 @@ namespace StardewModdingAPI.Framework.Content // premultiplied by the content pipeline. The formula is derived from // https://blogs.msdn.microsoft.com/shawnhar/2009/11/06/premultiplied-alpha/. float alphaBelow = 1 - (above.A / 255f); - mergedData[i] = new Color( + mergedData[targetIndex] = new Color( r: (int)(above.R + (below.R * alphaBelow)), g: (int)(above.G + (below.G * alphaBelow)), b: (int)(above.B + (below.B * alphaBelow)), From 798a56bd2e94e9e60b588222c730e313c3dbe075 Mon Sep 17 00:00:00 2001 From: atravita-mods <94934860+atravita-mods@users.noreply.github.com> Date: Sun, 28 Aug 2022 16:14:57 -0400 Subject: [PATCH 22/40] Avoid copying memory for contingous buffers. --- .../Framework/Content/AssetDataForImage.cs | 39 ++++++++----------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index 2bbcc60c..068634b3 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -40,6 +40,11 @@ namespace StardewModdingAPI.Framework.Content this.GetPatchBounds(ref sourceArea, ref targetArea, source.Width, source.Height); + // check to see if the Data is sufficiently long. + // while SMAPI's impl is going to be, it's not necessarily the case for mod impl. + if (source.Data.Length < (sourceArea.Value.Bottom - 1) * source.Width + sourceArea.Value.Right) + throw new ArgumentException("Source data insufficiently long for this operation."); + // get the pixels for the source area Color[] sourceData; { @@ -48,37 +53,24 @@ namespace StardewModdingAPI.Framework.Content int areaWidth = sourceArea.Value.Width; int areaHeight = sourceArea.Value.Height; - if (areaX == 0 && areaY == 0 && areaWidth == source.Width && areaHeight <= source.Height) + if (areaWidth == source.Width) { // It's actually fine if the source is taller than the sourceArea // the "extra" bits on the end of the array can just be ignored. sourceData = source.Data; - this.PatchImageImpl(sourceData, areaWidth, areaHeight, sourceArea.Value, targetArea.Value, patchMode); + this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode, areaY); } else { int pixelCount = areaWidth * areaHeight; sourceData = ArrayPool.Shared.Rent(pixelCount); - if (areaX == 0 && areaWidth == source.Width) + // slower copying, line by line + for (int y = areaY, maxY = areaY + areaHeight; y < maxY; y++) { - // shortcut copying because the area to copy is contiguous. This is - // probably uncommon, but Array.Copy benefits a lot. - - int sourceIndex = areaY * areaWidth; - int targetIndex = 0; - Array.Copy(source.Data, sourceIndex, sourceData, targetIndex, pixelCount); - - } - else - { - // slower copying, line by line - for (int y = areaY, maxY = areaY + areaHeight; y < maxY; y++) - { - int sourceIndex = (y * source.Width) + areaX; - int targetIndex = (y - areaY) * areaWidth; - Array.Copy(source.Data, sourceIndex, sourceData, targetIndex, areaWidth); - } + int sourceIndex = (y * source.Width) + areaX; + int targetIndex = (y - areaY) * areaWidth; + Array.Copy(source.Data, sourceIndex, sourceData, targetIndex, areaWidth); } // apply @@ -150,10 +142,11 @@ namespace StardewModdingAPI.Framework.Content /// The part of the to copy (or null to take the whole texture). This must be within the bounds of the texture. /// The part of the content to patch (or null to patch the whole texture). The original content within this area will be erased. This must be within the bounds of the existing spritesheet. /// Indicates how an image should be patched. + /// The row to start on, for the sourceData. /// One of the arguments is null. /// The is outside the bounds of the spritesheet. /// The content being read isn't an image. - private void PatchImageImpl(Color[] sourceData, int sourceWidth, int sourceHeight, Rectangle sourceArea, Rectangle targetArea, PatchMode patchMode) + private void PatchImageImpl(Color[] sourceData, int sourceWidth, int sourceHeight, Rectangle sourceArea, Rectangle targetArea, PatchMode patchMode, int startRow = 0) { // get texture Texture2D target = this.Data; @@ -168,7 +161,7 @@ namespace StardewModdingAPI.Framework.Content throw new InvalidOperationException("The source and target areas must be the same size."); if (patchMode == PatchMode.Replace) - target.SetData(0, targetArea, sourceData, 0, pixelCount); + target.SetData(0, targetArea, sourceData, startRow * sourceArea.Width, pixelCount); else { // merge data @@ -177,7 +170,7 @@ namespace StardewModdingAPI.Framework.Content // Adjusting bounds to ignore transparent pixels at the start and end. int startIndex = -1; - for (int i = 0; i < pixelCount; i++) + for (int i = startRow * sourceArea.Width; i < pixelCount; i++) { if (sourceData[i].A >= MinOpacity) { From 48d0f70ffd07df9ba364808a71407800834f95d3 Mon Sep 17 00:00:00 2001 From: atravita-mods <94934860+atravita-mods@users.noreply.github.com> Date: Thu, 6 Oct 2022 08:16:57 -0400 Subject: [PATCH 23/40] fix indexing math again. --- src/SMAPI/Framework/Content/AssetDataForImage.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index 068634b3..636d4a71 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -137,8 +137,8 @@ namespace StardewModdingAPI.Framework.Content /// Overwrite part of the image. /// The image data to patch into the content. - /// The pixel width of the source image. - /// The pixel height of the source image. + /// The pixel width of the original source image. + /// The pixel height of the original source image. /// The part of the to copy (or null to take the whole texture). This must be within the bounds of the texture. /// The part of the content to patch (or null to patch the whole texture). The original content within this area will be erased. This must be within the bounds of the existing spritesheet. /// Indicates how an image should be patched. @@ -183,7 +183,7 @@ namespace StardewModdingAPI.Framework.Content return; // apparently a completely blank texture? int endIndex = -1; - for (int i = pixelCount - 1; i >= startIndex; i--) + for (int i = startRow * sourceArea.Width + pixelCount - 1; i >= startIndex; i--) { if (sourceData[i].A >= MinOpacity) { From 40d5cd7c05d4e0a4e6894cd7b9f6d7d747716837 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 8 Oct 2022 17:42:32 -0400 Subject: [PATCH 24/40] use try..finally to make sure rented arrays are returned --- .../Framework/Content/AssetDataForImage.cs | 110 ++++++++++-------- .../ContentManagers/ModContentManager.cs | 37 +++--- 2 files changed, 81 insertions(+), 66 deletions(-) diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index 636d4a71..3abcd328 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -1,7 +1,6 @@ using System; using System.Buffers; using System.Diagnostics.CodeAnalysis; -using System.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using StardewValley; @@ -64,20 +63,23 @@ namespace StardewModdingAPI.Framework.Content { int pixelCount = areaWidth * areaHeight; sourceData = ArrayPool.Shared.Rent(pixelCount); - - // slower copying, line by line - for (int y = areaY, maxY = areaY + areaHeight; y < maxY; y++) + try { - int sourceIndex = (y * source.Width) + areaX; - int targetIndex = (y - areaY) * areaWidth; - Array.Copy(source.Data, sourceIndex, sourceData, targetIndex, areaWidth); + // slower copying, line by line + for (int y = areaY, maxY = areaY + areaHeight; y < maxY; y++) + { + int sourceIndex = (y * source.Width) + areaX; + int targetIndex = (y - areaY) * areaWidth; + Array.Copy(source.Data, sourceIndex, sourceData, targetIndex, areaWidth); + } + + // apply + this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode); + } + finally + { + ArrayPool.Shared.Return(sourceData); } - - // apply - this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode); - - // return - ArrayPool.Shared.Return(sourceData); } } } @@ -98,13 +100,15 @@ namespace StardewModdingAPI.Framework.Content // get source data int pixelCount = sourceArea.Value.Width * sourceArea.Value.Height; Color[] sourceData = ArrayPool.Shared.Rent(pixelCount); - source.GetData(0, sourceArea, sourceData, 0, pixelCount); - - // apply - this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode); - - // return - ArrayPool.Shared.Return(sourceData); + try + { + source.GetData(0, sourceArea, sourceData, 0, pixelCount); + this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode); + } + finally + { + ArrayPool.Shared.Return(sourceData); + } } /// @@ -207,41 +211,47 @@ namespace StardewModdingAPI.Framework.Content // get target data Color[] mergedData = ArrayPool.Shared.Rent(pixelCount); - target.GetData(0, targetArea, mergedData, 0, pixelCount); - - // merge pixels - for (int i = startIndex; i <= endIndex; i++) + try { - int targetIndex = i - sourceoffset; - - // ref locals here? Not sure. - Color above = sourceData[i]; - Color below = mergedData[targetIndex]; - - // shortcut transparency - if (above.A < MinOpacity) - continue; - if (below.A < MinOpacity || above.A == byte.MaxValue) - mergedData[targetIndex] = above; + target.GetData(0, targetArea, mergedData, 0, pixelCount); // merge pixels - else + for (int i = startIndex; i <= endIndex; i++) { - // This performs a conventional alpha blend for the pixels, which are already - // premultiplied by the content pipeline. The formula is derived from - // https://blogs.msdn.microsoft.com/shawnhar/2009/11/06/premultiplied-alpha/. - float alphaBelow = 1 - (above.A / 255f); - mergedData[targetIndex] = new Color( - r: (int)(above.R + (below.R * alphaBelow)), - g: (int)(above.G + (below.G * alphaBelow)), - b: (int)(above.B + (below.B * alphaBelow)), - alpha: Math.Max(above.A, below.A) - ); - } - } + int targetIndex = i - sourceoffset; - target.SetData(0, targetArea, mergedData, 0, pixelCount); - ArrayPool.Shared.Return(mergedData); + // ref locals here? Not sure. + Color above = sourceData[i]; + Color below = mergedData[targetIndex]; + + // shortcut transparency + if (above.A < MinOpacity) + continue; + if (below.A < MinOpacity || above.A == byte.MaxValue) + mergedData[targetIndex] = above; + + // merge pixels + else + { + // This performs a conventional alpha blend for the pixels, which are already + // premultiplied by the content pipeline. The formula is derived from + // https://blogs.msdn.microsoft.com/shawnhar/2009/11/06/premultiplied-alpha/. + float alphaBelow = 1 - (above.A / 255f); + mergedData[targetIndex] = new Color( + r: (int)(above.R + (below.R * alphaBelow)), + g: (int)(above.G + (below.G * alphaBelow)), + b: (int)(above.B + (below.B * alphaBelow)), + alpha: Math.Max(above.A, below.A) + ); + } + } + + target.SetData(0, targetArea, mergedData, 0, pixelCount); + } + finally + { + ArrayPool.Shared.Return(mergedData); + } } } } diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 0c793808..6b8a5874 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -395,25 +395,30 @@ namespace StardewModdingAPI.Framework.ContentManagers // premultiply pixels int count = texture.Width * texture.Height; Color[] data = ArrayPool.Shared.Rent(count); - texture.GetData(data, 0, count); - - bool changed = false; - for (int i = 0; i < count; i++) + try { - ref Color pixel = ref data[i]; - if (pixel.A is (byte.MinValue or byte.MaxValue)) - continue; // no need to change fully transparent/opaque pixels + texture.GetData(data, 0, count); - data[i] = new Color(pixel.R * pixel.A / byte.MaxValue, pixel.G * pixel.A / byte.MaxValue, pixel.B * pixel.A / byte.MaxValue, pixel.A); // slower version: Color.FromNonPremultiplied(data[i].ToVector4()) - changed = true; + bool changed = false; + for (int i = 0; i < count; i++) + { + ref Color pixel = ref data[i]; + if (pixel.A is (byte.MinValue or byte.MaxValue)) + continue; // no need to change fully transparent/opaque pixels + + data[i] = new Color(pixel.R * pixel.A / byte.MaxValue, pixel.G * pixel.A / byte.MaxValue, pixel.B * pixel.A / byte.MaxValue, pixel.A); // slower version: Color.FromNonPremultiplied(data[i].ToVector4()) + changed = true; + } + + if (changed) + texture.SetData(data, 0, count); + + return texture; + } + finally + { + ArrayPool.Shared.Return(data); } - - if (changed) - texture.SetData(data, 0, count); - - // return - ArrayPool.Shared.Return(data); - return texture; } /// Fix custom map tilesheet paths so they can be found by the content manager. From 2e0bc5ddfe90102fe5adbc90b2d53c5cbb8405fe Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 8 Oct 2022 17:45:50 -0400 Subject: [PATCH 25/40] tweak new code --- .../Framework/Content/AssetDataForImage.cs | 181 ++++++++---------- .../ContentManagers/ModContentManager.cs | 13 +- .../Framework/Logging/LogOnceCacheKey.cs | 10 + src/SMAPI/Framework/Monitor.cs | 14 +- 4 files changed, 103 insertions(+), 115 deletions(-) create mode 100644 src/SMAPI/Framework/Logging/LogOnceCacheKey.cs diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index 3abcd328..0380dd9e 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -33,71 +33,59 @@ namespace StardewModdingAPI.Framework.Content /// public void PatchImage(IRawTextureData source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace) { - // nullcheck if (source == null) throw new ArgumentNullException(nameof(source), "Can't patch from null source data."); + // get normalized bounds this.GetPatchBounds(ref sourceArea, ref targetArea, source.Width, source.Height); - - // check to see if the Data is sufficiently long. - // while SMAPI's impl is going to be, it's not necessarily the case for mod impl. if (source.Data.Length < (sourceArea.Value.Bottom - 1) * source.Width + sourceArea.Value.Right) - throw new ArgumentException("Source data insufficiently long for this operation."); + throw new ArgumentException("Can't apply image patch because the source image is smaller than the source area.", nameof(source)); + int areaX = sourceArea.Value.X; + int areaY = sourceArea.Value.Y; + int areaWidth = sourceArea.Value.Width; + int areaHeight = sourceArea.Value.Height; - // get the pixels for the source area - Color[] sourceData; + // shortcut: if the area width matches the source image, we can apply the image as-is without needing + // to copy the pixels into a smaller subset. It's fine if the source is taller than the area, since we'll + // just ignore the extra data at the end of the pixel array. + if (areaWidth == source.Width) { - int areaX = sourceArea.Value.X; - int areaY = sourceArea.Value.Y; - int areaWidth = sourceArea.Value.Width; - int areaHeight = sourceArea.Value.Height; + this.PatchImageImpl(source.Data, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode, areaY); + return; + } - if (areaWidth == source.Width) + // else copy the pixels within the smaller area & apply that + int pixelCount = areaWidth * areaHeight; + Color[] sourceData = ArrayPool.Shared.Rent(pixelCount); + try + { + for (int y = areaY, maxY = areaY + areaHeight; y < maxY; y++) { - // It's actually fine if the source is taller than the sourceArea - // the "extra" bits on the end of the array can just be ignored. - sourceData = source.Data; - this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode, areaY); + int sourceIndex = (y * source.Width) + areaX; + int targetIndex = (y - areaY) * areaWidth; + Array.Copy(source.Data, sourceIndex, sourceData, targetIndex, areaWidth); } - else - { - int pixelCount = areaWidth * areaHeight; - sourceData = ArrayPool.Shared.Rent(pixelCount); - try - { - // slower copying, line by line - for (int y = areaY, maxY = areaY + areaHeight; y < maxY; y++) - { - int sourceIndex = (y * source.Width) + areaX; - int targetIndex = (y - areaY) * areaWidth; - Array.Copy(source.Data, sourceIndex, sourceData, targetIndex, areaWidth); - } - // apply - this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode); - } - finally - { - ArrayPool.Shared.Return(sourceData); - } - } + this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode); + } + finally + { + ArrayPool.Shared.Return(sourceData); } } /// public void PatchImage(Texture2D source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace) { - // nullcheck if (source == null) throw new ArgumentNullException(nameof(source), "Can't patch from a null source texture."); + // get normalized bounds this.GetPatchBounds(ref sourceArea, ref targetArea, source.Width, source.Height); - - // validate source bounds if (!source.Bounds.Contains(sourceArea.Value)) throw new ArgumentOutOfRangeException(nameof(sourceArea), "The source area is outside the bounds of the source texture."); - // get source data + // get source data & apply int pixelCount = sourceArea.Value.Width * sourceArea.Value.Height; Color[] sourceData = ArrayPool.Shared.Rent(pixelCount); try @@ -164,94 +152,91 @@ namespace StardewModdingAPI.Framework.Content if (sourceArea.Size != targetArea.Size) throw new InvalidOperationException("The source and target areas must be the same size."); + // shortcut: replace the entire area if (patchMode == PatchMode.Replace) - target.SetData(0, targetArea, sourceData, startRow * sourceArea.Width, pixelCount); - else { - // merge data + target.SetData(0, targetArea, sourceData, startRow * sourceArea.Width, pixelCount); + return; + } - // Content packs have a habit of using large amounts of blank space. - // Adjusting bounds to ignore transparent pixels at the start and end. - - int startIndex = -1; + // skip transparent pixels at the start & end (e.g. large spritesheet with a few sprites replaced) + int startIndex = -1; + int endIndex = -1; + { for (int i = startRow * sourceArea.Width; i < pixelCount; i++) { - if (sourceData[i].A >= MinOpacity) + if (sourceData[i].A >= AssetDataForImage.MinOpacity) { startIndex = i; break; } } - if (startIndex == -1) - return; // apparently a completely blank texture? + return; // blank texture - int endIndex = -1; for (int i = startRow * sourceArea.Width + pixelCount - 1; i >= startIndex; i--) { - if (sourceData[i].A >= MinOpacity) + if (sourceData[i].A >= AssetDataForImage.MinOpacity) { endIndex = i; break; } } - if (endIndex == -1) - return; // should never happen + return; // ??? + } - // Calculate new Y bounds - int topoffset = startIndex / sourceArea.Width; - int bottomoffset = endIndex / sourceArea.Width; + // update target rectangle + int sourceOffset; + { + int topOffset = startIndex / sourceArea.Width; + int bottomOffset = endIndex / sourceArea.Width; - // Update target rectangle - targetArea = new(targetArea.X, targetArea.Y + topoffset, targetArea.Width, bottomoffset - topoffset + 1); + targetArea = new(targetArea.X, targetArea.Y + topOffset, targetArea.Width, bottomOffset - topOffset + 1); pixelCount = targetArea.Width * targetArea.Height; + sourceOffset = topOffset * sourceArea.Width; + } - int sourceoffset = topoffset * sourceArea.Width; + // apply + Color[] mergedData = ArrayPool.Shared.Rent(pixelCount); + try + { + target.GetData(0, targetArea, mergedData, 0, pixelCount); - // get target data - Color[] mergedData = ArrayPool.Shared.Rent(pixelCount); - try + for (int i = startIndex; i <= endIndex; i++) { - target.GetData(0, targetArea, mergedData, 0, pixelCount); + int targetIndex = i - sourceOffset; + + Color above = sourceData[i]; + Color below = mergedData[targetIndex]; + + // shortcut transparency + if (above.A < AssetDataForImage.MinOpacity) + continue; + if (below.A < AssetDataForImage.MinOpacity || above.A == byte.MaxValue) + mergedData[targetIndex] = above; // merge pixels - for (int i = startIndex; i <= endIndex; i++) + else { - int targetIndex = i - sourceoffset; - - // ref locals here? Not sure. - Color above = sourceData[i]; - Color below = mergedData[targetIndex]; - - // shortcut transparency - if (above.A < MinOpacity) - continue; - if (below.A < MinOpacity || above.A == byte.MaxValue) - mergedData[targetIndex] = above; - - // merge pixels - else - { - // This performs a conventional alpha blend for the pixels, which are already - // premultiplied by the content pipeline. The formula is derived from - // https://blogs.msdn.microsoft.com/shawnhar/2009/11/06/premultiplied-alpha/. - float alphaBelow = 1 - (above.A / 255f); - mergedData[targetIndex] = new Color( - r: (int)(above.R + (below.R * alphaBelow)), - g: (int)(above.G + (below.G * alphaBelow)), - b: (int)(above.B + (below.B * alphaBelow)), - alpha: Math.Max(above.A, below.A) - ); - } + // This performs a conventional alpha blend for the pixels, which are already + // premultiplied by the content pipeline. The formula is derived from + // https://blogs.msdn.microsoft.com/shawnhar/2009/11/06/premultiplied-alpha/. + float alphaBelow = 1 - (above.A / 255f); + mergedData[targetIndex] = new Color( + r: (int)(above.R + (below.R * alphaBelow)), + g: (int)(above.G + (below.G * alphaBelow)), + b: (int)(above.B + (below.B * alphaBelow)), + alpha: Math.Max(above.A, below.A) + ); } + } - target.SetData(0, targetArea, mergedData, 0, pixelCount); - } - finally - { - ArrayPool.Shared.Return(mergedData); - } + target.SetData(0, targetArea, mergedData, 0, pixelCount); + } + finally + { + ArrayPool.Shared.Return(mergedData); } } } diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 6b8a5874..72dcf6e1 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -241,7 +241,7 @@ namespace StardewModdingAPI.Framework.ContentManagers { using FileStream stream = File.OpenRead(file.FullName); Texture2D texture = Texture2D.FromStream(Game1.graphics.GraphicsDevice, stream); - texture = this.PremultiplyTransparency(texture); + this.PremultiplyTransparency(texture); return (T)(object)texture; } } @@ -345,17 +345,15 @@ namespace StardewModdingAPI.Framework.ContentManagers this.ThrowLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(TAsset)}'; must be type '{string.Join("' or '", validTypes.Select(p => p.FullName))}'."); } - /// Throws an error which indicates that an asset couldn't be loaded. + /// Throw an error which indicates that an asset couldn't be loaded. /// Why loading an asset through the content pipeline failed. /// The asset name that failed to load. /// The reason the file couldn't be loaded. /// The underlying exception, if applicable. + /// [DoesNotReturn] [DebuggerStepThrough, DebuggerHidden] [MethodImpl(MethodImplOptions.NoInlining)] -#if NET6_0_OR_GREATER - [StackTraceHidden] -#endif private void ThrowLoadError(IAssetName assetName, ContentLoadErrorType errorType, string reasonPhrase, Exception? exception = null) { throw new SContentLoadException(errorType, $"Failed loading asset '{assetName}' from {this.Name}: {reasonPhrase}", exception); @@ -390,9 +388,8 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The texture to premultiply. /// Returns a premultiplied texture. /// Based on code by David Gouveia. - private Texture2D PremultiplyTransparency(Texture2D texture) + private void PremultiplyTransparency(Texture2D texture) { - // premultiply pixels int count = texture.Width * texture.Height; Color[] data = ArrayPool.Shared.Rent(count); try @@ -412,8 +409,6 @@ namespace StardewModdingAPI.Framework.ContentManagers if (changed) texture.SetData(data, 0, count); - - return texture; } finally { diff --git a/src/SMAPI/Framework/Logging/LogOnceCacheKey.cs b/src/SMAPI/Framework/Logging/LogOnceCacheKey.cs new file mode 100644 index 00000000..4d31ffeb --- /dev/null +++ b/src/SMAPI/Framework/Logging/LogOnceCacheKey.cs @@ -0,0 +1,10 @@ +using System.Diagnostics.CodeAnalysis; + +namespace StardewModdingAPI.Framework.Logging +{ + /// The cache key for the . + /// The log message. + /// The log level. + [SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Local", Justification = "This is only used as a lookup key.")] + internal readonly record struct LogOnceCacheKey(string Message, LogLevel Level); +} diff --git a/src/SMAPI/Framework/Monitor.cs b/src/SMAPI/Framework/Monitor.cs index d33bf259..4ed2c9bb 100644 --- a/src/SMAPI/Framework/Monitor.cs +++ b/src/SMAPI/Framework/Monitor.cs @@ -25,15 +25,13 @@ namespace StardewModdingAPI.Framework private readonly LogFileManager LogFile; /// The maximum length of the values. - private static readonly int MaxLevelLength = (from level in Enum.GetValues() select level.ToString().Length).Max(); + private static readonly int MaxLevelLength = Enum.GetValues().Max(level => level.ToString().Length); - /// A mapping of console log levels to their string form. - private static readonly Dictionary LogStrings = Enum.GetValues().ToDictionary(k => k, v => v.ToString().ToUpper().PadRight(MaxLevelLength)); - - private readonly record struct LogOnceCacheEntry(string message, LogLevel level); + /// The cached representation for each level when added to a log header. + private static readonly Dictionary LogStrings = Enum.GetValues().ToDictionary(level => level, level => level.ToString().ToUpper().PadRight(Monitor.MaxLevelLength)); /// A cache of messages that should only be logged once. - private readonly HashSet LogOnceCache = new(); + private readonly HashSet LogOnceCache = new(); /// Get the screen ID that should be logged to distinguish between players in split-screen mode, if any. private readonly Func GetScreenIdForLog; @@ -89,7 +87,7 @@ namespace StardewModdingAPI.Framework /// public void LogOnce(string message, LogLevel level = LogLevel.Trace) { - if (this.LogOnceCache.Add(new LogOnceCacheEntry(message, level))) + if (this.LogOnceCache.Add(new LogOnceCacheKey(message, level))) this.LogImpl(this.Source, message, (ConsoleLogLevel)level); } @@ -152,7 +150,7 @@ namespace StardewModdingAPI.Framework /// The log level. private string GenerateMessagePrefix(string source, ConsoleLogLevel level) { - string levelStr = LogStrings[level]; + string levelStr = Monitor.LogStrings[level]; int? playerIndex = this.GetScreenIdForLog(); return $"[{DateTime.Now:HH:mm:ss} {levelStr}{(playerIndex != null ? $" screen_{playerIndex}" : "")} {source}]"; From a565ac9405a95d24f7cf945228935107e91bb89f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 8 Oct 2022 19:59:21 -0400 Subject: [PATCH 26/40] make GetApi methods mutually exclusive & improve docs --- .../Framework/ModHelpers/ModRegistryHelper.cs | 56 +++++++++---------- src/SMAPI/Framework/SCore.cs | 5 ++ src/SMAPI/IMod.cs | 12 ++-- 3 files changed, 37 insertions(+), 36 deletions(-) diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs index 9ad3e3ae..8cc73481 100644 --- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs @@ -68,49 +68,43 @@ namespace StardewModdingAPI.Framework.ModHelpers return null; } - // get our cached API if one is available + // get the target mod IModMetadata? mod = this.Registry.Get(uniqueID); if (mod == null) return null; - if (this.AccessedModApis.ContainsKey(mod.Manifest.UniqueID)) + // fetch API + if (!this.AccessedModApis.TryGetValue(mod.Manifest.UniqueID, out object? api)) { - return this.AccessedModApis[mod.Manifest.UniqueID]; - } + // if the target has a global API, this is mutually exclusive with per-mod APIs + if (mod.Api != null) + api = mod.Api; - object? api; - - // safely request a specific API instance - try - { - api = mod.Mod?.GetApi(this.Mod.Manifest); - if (api != null && !api.GetType().IsPublic) + // else try to get a per-mod API + else { - api = null; - this.Monitor.Log($"{mod.DisplayName} provided a specific API instance with a non-public type. This isn't currently supported, so the specific API won't be available to the requesting mod.", LogLevel.Warn); + try + { + api = mod.Mod?.GetApi(this.Mod.Manifest); + if (api != null && !api.GetType().IsPublic) + { + api = null; + this.Monitor.Log($"{mod.DisplayName} provides a per-mod API instance with a non-public type. This isn't currently supported, so the API won't be available to other mods.", LogLevel.Warn); + } + } + catch (Exception ex) + { + this.Monitor.Log($"Failed loading the per-mod API instance from {mod.DisplayName}. Integrations with other mods may not work. Error: {ex.GetLogSummary()}", LogLevel.Error); + api = null; + } } + // cache & log API access + this.AccessedModApis[mod.Manifest.UniqueID] = api; if (api != null) - this.Monitor.Log($"Accessed specific mod-provided API ({api.GetType().FullName}) for {mod.DisplayName}."); - } - catch (Exception ex) - { - this.Monitor.Log($"Failed loading specific mod-provided API for {mod.DisplayName}. Integrations with other mods may not work. Error: {ex.GetLogSummary()}", LogLevel.Error); - api = null; + this.Monitor.Log($"Accessed mod-provided API ({api.GetType().FullName}) for {mod.DisplayName}."); } - // fall back to the generic API instance - if (api == null) - { - api = mod.Api; - if (api != null) - { - this.Monitor.Log($"Accessed mod-provided API for {mod.DisplayName}."); - } - } - - // cache the API instance and return it - this.AccessedModApis[mod.Manifest.UniqueID] = api; return api; } diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 16ff2537..4ba0dd9c 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1779,6 +1779,11 @@ namespace StardewModdingAPI.Framework { this.Monitor.Log($"Failed loading mod-provided API for {metadata.DisplayName}. Integrations with other mods may not work. Error: {ex.GetLogSummary()}", LogLevel.Error); } + + // validate mod doesn't implement both GetApi() and GetApi(mod) + if (metadata.Api != null && metadata.Mod!.GetType().GetMethod(nameof(Mod.GetApi), new Type[] { typeof(IManifest) })!.DeclaringType != typeof(Mod)) + metadata.LogAsMod($"Mod implements both {nameof(Mod.GetApi)}() and {nameof(Mod.GetApi)}({nameof(IManifest)}), which isn't allowed. The latter will be ignored.", LogLevel.Error); + Context.HeuristicModsRunningCode.TryPop(out _); } diff --git a/src/SMAPI/IMod.cs b/src/SMAPI/IMod.cs index 6041bf66..4576246a 100644 --- a/src/SMAPI/IMod.cs +++ b/src/SMAPI/IMod.cs @@ -23,13 +23,15 @@ namespace StardewModdingAPI /// Provides simplified APIs for writing mods. void Entry(IModHelper helper); - /// Get an API that other mods can access. This is always called after . + /// Get an API that other mods can access. This is always called after , and is only called once even if multiple mods access it. + /// You can implement to provide one instance to all mods, or to provide a separate instance per mod. These are mutually exclusive, so you can only implement one of them. + /// Returns the API instance, or null if the mod has no API. object? GetApi(); - /// Get an API that a specific other mod can access. This method is called the first time the other mod calls for this mod. - /// The other mod's manifest. - /// Returns an API for another mod, or null if the other mod should use the general API returned from . + /// Get an API that other mods can access. This is always called after , and is called once per mod that accesses the API (even if they access it multiple times). + /// The manifest for the mod accessing the API. + /// Returns the API instance, or null if the mod has no API. Note that the manifest is provided for informational purposes only, and that denying API access to specific mods is strongly discouraged and may be considered abusive. + /// object? GetApi(IManifest manifest); - } } From 8d6670cfc8abf7e71197d2f621314fb04a0543b8 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 8 Oct 2022 20:33:01 -0400 Subject: [PATCH 27/40] pass mod info to GetApi instead --- src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs | 2 +- src/SMAPI/IMod.cs | 6 +++--- src/SMAPI/Mod.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs index 8cc73481..93edd597 100644 --- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs @@ -85,7 +85,7 @@ namespace StardewModdingAPI.Framework.ModHelpers { try { - api = mod.Mod?.GetApi(this.Mod.Manifest); + api = mod.Mod?.GetApi(this.Mod); if (api != null && !api.GetType().IsPublic) { api = null; diff --git a/src/SMAPI/IMod.cs b/src/SMAPI/IMod.cs index 4576246a..19d01311 100644 --- a/src/SMAPI/IMod.cs +++ b/src/SMAPI/IMod.cs @@ -24,14 +24,14 @@ namespace StardewModdingAPI void Entry(IModHelper helper); /// Get an API that other mods can access. This is always called after , and is only called once even if multiple mods access it. - /// You can implement to provide one instance to all mods, or to provide a separate instance per mod. These are mutually exclusive, so you can only implement one of them. + /// You can implement to provide one instance to all mods, or to provide a separate instance per mod. These are mutually exclusive, so you can only implement one of them. /// Returns the API instance, or null if the mod has no API. object? GetApi(); /// Get an API that other mods can access. This is always called after , and is called once per mod that accesses the API (even if they access it multiple times). - /// The manifest for the mod accessing the API. + /// The mod accessing the API. /// Returns the API instance, or null if the mod has no API. Note that the manifest is provided for informational purposes only, and that denying API access to specific mods is strongly discouraged and may be considered abusive. /// - object? GetApi(IManifest manifest); + object? GetApi(IModInfo mod); } } diff --git a/src/SMAPI/Mod.cs b/src/SMAPI/Mod.cs index 1a5f5594..01157886 100644 --- a/src/SMAPI/Mod.cs +++ b/src/SMAPI/Mod.cs @@ -31,7 +31,7 @@ namespace StardewModdingAPI } /// - public virtual object? GetApi(IManifest manifest) + public virtual object? GetApi(IModInfo mod) { return null; } From ab66266b4bc4d7b1a7ae76d05693e9dd30c03989 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 8 Oct 2022 21:32:10 -0400 Subject: [PATCH 28/40] update installer for VdfConverter & rework avoid custom models --- build/windows/prepare-install-package.ps1 | 2 +- .../Framework/GameScanning/GameScanner.cs | 44 ++++++++++------- .../GameScanning/SteamLibraryCollection.cs | 47 ------------------- src/SMAPI.Toolkit/SMAPI.Toolkit.csproj | 2 +- 4 files changed, 30 insertions(+), 65 deletions(-) delete mode 100644 src/SMAPI.Toolkit/Framework/GameScanning/SteamLibraryCollection.cs diff --git a/build/windows/prepare-install-package.ps1 b/build/windows/prepare-install-package.ps1 index 87a4fe01..002bcbca 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", "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")) { + 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", "VdfConverter.dll")) { cp "$smapiBin/$name" "$bundlePath/smapi-internal" } diff --git a/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs index 8e24dcdf..66465ffe 100644 --- a/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs +++ b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs @@ -24,6 +24,9 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning /// The current OS. private readonly Platform Platform; + /// The Steam app ID for Stardew Valley. + private const string SteamAppId = "413150"; + /********* ** Public methods @@ -146,7 +149,7 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning #if SMAPI_FOR_WINDOWS IDictionary registryKeys = new Dictionary { - [@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 413150"] = "InstallLocation", // Steam + [@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App " + GameScanner.SteamAppId] = "InstallLocation", // Steam [@"SOFTWARE\WOW6432Node\GOG.com\Games\1453375253"] = "PATH", // GOG on 64-bit Windows }; foreach (var pair in registryKeys) @@ -160,9 +163,10 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning string? steamPath = this.GetCurrentUserRegistryValue(@"Software\Valve\Steam", "SteamPath"); if (steamPath != null) { + // conventional path yield return Path.Combine(steamPath.Replace('/', '\\'), @"steamapps\common\Stardew Valley"); - // Check for Steam libraries in other locations + // from Steam's .vdf file string? path = this.GetPathFromSteamLibrary(steamPath); if (!string.IsNullOrWhiteSpace(path)) yield return path; @@ -257,26 +261,34 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning /// The game directory, if found. private string? GetPathFromSteamLibrary(string? steamPath) { - string stardewAppId = "413150"; - if (steamPath != null) + if (steamPath == null) + return null; + + // get raw .vdf data + string libraryFoldersPath = Path.Combine(steamPath.Replace('/', '\\'), "steamapps\\libraryfolders.vdf"); + using FileStream fileStream = File.OpenRead(libraryFoldersPath); + VdfDeserializer deserializer = new(); + dynamic libraries = deserializer.Deserialize(fileStream); + if (libraries?.libraryfolders is null) + return null; + + // get path from Stardew Valley app (if any) + foreach (dynamic pair in libraries.libraryfolders) { - string? libraryFoldersPath = Path.Combine(steamPath.Replace('/', '\\'), "steamapps\\libraryfolders.vdf"); - using FileStream fs = File.OpenRead(libraryFoldersPath); - VdfDeserializer deserializer = new VdfDeserializer(); - SteamLibraryCollection libraries = deserializer.Deserialize(fs); - if (libraries.libraryfolders != null) + dynamic library = pair.Value; + + foreach (dynamic app in library.apps) { - var stardewLibrary = libraries.libraryfolders.FirstOrDefault(f => + string key = app.Key; + if (key == GameScanner.SteamAppId) { - var apps = f.Value?.apps; - return apps != null && apps.Any(a => a.Key.Equals(stardewAppId)); - }); - if (stardewLibrary.Value?.path != null) - { - return Path.Combine(stardewLibrary.Value.path.Replace("\\\\", "\\"), @"steamapps\common\Stardew Valley"); + string path = library.path; + + return Path.Combine(path.Replace("\\\\", "\\"), "steamapps", "common", "Stardew Valley"); } } } + return null; } #endif diff --git a/src/SMAPI.Toolkit/Framework/GameScanning/SteamLibraryCollection.cs b/src/SMAPI.Toolkit/Framework/GameScanning/SteamLibraryCollection.cs deleted file mode 100644 index 7a186f69..00000000 --- a/src/SMAPI.Toolkit/Framework/GameScanning/SteamLibraryCollection.cs +++ /dev/null @@ -1,47 +0,0 @@ -#if SMAPI_FOR_WINDOWS -using System.Collections.Generic; - -namespace StardewModdingAPI.Toolkit.Framework.GameScanning -{ -#pragma warning disable IDE1006 // Model requires lowercase naming. -#pragma warning disable CS8618 // Required for model. - /// Model for Steam's libraryfolders.vdf. - public class SteamLibraryCollection - { - /// Each entry identifies a different location that part of the Steam games library is installed to. - public LibraryFolders libraryfolders { get; set; } - } - - /// A collection of LibraryFolders. Like a dictionary, but has contentstatsid used as an index also. - /// - /// -#pragma warning disable CS8714 // Required for model. - public class LibraryFolders : Dictionary -#pragma warning restore CS8714 - { - /// Index of the library, starting from "0". - public string contentstatsid { get; set; } - } - - /// A Steam library folder, containing information on the location and size of games installed there. - public class LibraryFolder - { - /// The escaped path to this Steam library folder. There will be a steam.exe here, but this may not be the one the player generally launches. - public string path { get; set; } - /// Label for the library, or "" - public string label { get; set; } - /// ~19-digit identifier. - public string contentid { get; set; } - /// Size of the library in bytes. May show 0 when size is non-zero. - public string totalsize { get; set; } - /// Used for downloads. - public string update_clean_bytes_tally { get; set; } - /// Normally "0". - public string time_last_update_corruption { get; set; } - /// List of Steam app IDs, and their current size in bytes. - public Dictionary apps { get; set; } - } -#pragma warning restore IDE1006 -#pragma warning restore CS8618 -} -#endif diff --git a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj index 411fd469..6080a85e 100644 --- a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj +++ b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj @@ -14,7 +14,7 @@ - + From a220e14f2d22f5d481c87bfd76d1b9eeaebf04e3 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 9 Oct 2022 13:50:24 -0400 Subject: [PATCH 29/40] polish recent changes & update release notes --- docs/release-notes.md | 7 +- .../Framework/ModScanning/ModScanner.cs | 2 +- src/SMAPI/Framework/SCore.cs | 67 ++++++++++--------- src/SMAPI/IMod.cs | 2 +- 4 files changed, 44 insertions(+), 34 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 4875d1cd..75e143e8 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -9,11 +9,16 @@ ## Upcoming release * For players: - * Fixed update alert shown for a prerelease version on GitHub if it's not marked as prerelease. + * The SMAPI installer now also detects game folders listed in Steam's `.vdf` library data on Windows (thanks to pizzaoverhead!). + * Optimized performance and memory usage (thanks to atravita!). + * Added more file extensions to ignore when searching for mod folders: `.7z`, `.tar`, `.tar.gz`, and `.xcf` (thanks to atravita!). + * Fixed update alerts incorrectly shown for prerelease versions on GitHub that aren't marked as prerelease. * For mod authors: + * When [providing a mod API for a C# mod](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Integrations), you can now get an optional parameter with the mod requesting the API (thanks to KhloeLeclair!). This avoids needing the pattern where each method needs the requesting mod's manifest. * SMAPI now treats square brackets in the manifest `Name` field as round brackets, to avoid breaking tools which parse log files. * Updated to [FluentHttpClient](https://github.com/Pathoschild/FluentHttpClient#readme) 4.2.0 (see [changes](https://github.com/Pathoschild/FluentHttpClient/blob/develop/RELEASE-NOTES.md#420)). + * Fixed `LocationListChanged` event not raised & memory leak occurring when a generated mine/volcano is removed (thanks to tylergibbs2!). ## 3.16.2 Released 31 August 2022 for Stardew Valley 1.5.6 or later. diff --git a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs index d115810a..5e9e3c35 100644 --- a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs +++ b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs @@ -52,7 +52,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning ".zip", ".7z", ".tar", - ".tar.gz" + ".tar.gz", // backup files ".backup", diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 4ba0dd9c..98eb2803 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1686,12 +1686,16 @@ namespace StardewModdingAPI.Framework this.Monitor.Log("Launching mods...", LogLevel.Debug); foreach (IModMetadata metadata in loadedMods) { + IMod mod = + metadata.Mod + ?? throw new InvalidOperationException($"The '{metadata.DisplayName}' mod is not initialized correctly."); // should never happen, but avoids nullability warnings + #if SMAPI_DEPRECATED // add interceptors - if (metadata.Mod?.Helper is ModHelper helper) + if (mod.Helper is ModHelper helper) { // ReSharper disable SuspiciousTypeConversion.Global - if (metadata.Mod is IAssetEditor editor) + if (mod is IAssetEditor editor) { SCore.DeprecationManager.Warn( source: metadata, @@ -1704,7 +1708,7 @@ namespace StardewModdingAPI.Framework this.ContentCore.Editors.Add(new ModLinked(metadata, editor)); } - if (metadata.Mod is IAssetLoader loader) + if (mod is IAssetLoader loader) { SCore.DeprecationManager.Warn( source: metadata, @@ -1749,41 +1753,42 @@ namespace StardewModdingAPI.Framework } #endif - // call entry method + // initialize mod Context.HeuristicModsRunningCode.Push(metadata); - try { - IMod mod = metadata.Mod!; - mod.Entry(mod.Helper!); - } - catch (Exception ex) - { - metadata.LogAsMod($"Mod crashed on entry and might not work correctly. Technical details:\n{ex.GetLogSummary()}", LogLevel.Error); - } - - // get mod API - try - { - object? api = metadata.Mod!.GetApi(); - if (api != null && !api.GetType().IsPublic) + // call entry method + try { - api = null; - this.Monitor.Log($"{metadata.DisplayName} provides an API instance with a non-public type. This isn't currently supported, so the API won't be available to other mods.", LogLevel.Warn); + mod.Entry(mod.Helper!); + } + catch (Exception ex) + { + metadata.LogAsMod($"Mod crashed on entry and might not work correctly. Technical details:\n{ex.GetLogSummary()}", LogLevel.Error); } - if (api != null) - this.Monitor.Log($" Found mod-provided API ({api.GetType().FullName})."); - metadata.SetApi(api); - } - catch (Exception ex) - { - this.Monitor.Log($"Failed loading mod-provided API for {metadata.DisplayName}. Integrations with other mods may not work. Error: {ex.GetLogSummary()}", LogLevel.Error); - } + // get mod API + try + { + object? api = mod.GetApi(); + if (api != null && !api.GetType().IsPublic) + { + api = null; + this.Monitor.Log($"{metadata.DisplayName} provides an API instance with a non-public type. This isn't currently supported, so the API won't be available to other mods.", LogLevel.Warn); + } - // validate mod doesn't implement both GetApi() and GetApi(mod) - if (metadata.Api != null && metadata.Mod!.GetType().GetMethod(nameof(Mod.GetApi), new Type[] { typeof(IManifest) })!.DeclaringType != typeof(Mod)) - metadata.LogAsMod($"Mod implements both {nameof(Mod.GetApi)}() and {nameof(Mod.GetApi)}({nameof(IManifest)}), which isn't allowed. The latter will be ignored.", LogLevel.Error); + if (api != null) + this.Monitor.Log($" Found mod-provided API ({api.GetType().FullName})."); + metadata.SetApi(api); + } + catch (Exception ex) + { + this.Monitor.Log($"Failed loading mod-provided API for {metadata.DisplayName}. Integrations with other mods may not work. Error: {ex.GetLogSummary()}", LogLevel.Error); + } + // validate mod doesn't implement both GetApi() and GetApi(mod) + if (metadata.Api != null && mod.GetType().GetMethod(nameof(Mod.GetApi), new Type[] { typeof(IModInfo) })!.DeclaringType != typeof(Mod)) + metadata.LogAsMod($"Mod implements both {nameof(Mod.GetApi)}() and {nameof(Mod.GetApi)}({nameof(IModInfo)}), which isn't allowed. The latter will be ignored.", LogLevel.Error); + } Context.HeuristicModsRunningCode.TryPop(out _); } diff --git a/src/SMAPI/IMod.cs b/src/SMAPI/IMod.cs index 19d01311..87c9880c 100644 --- a/src/SMAPI/IMod.cs +++ b/src/SMAPI/IMod.cs @@ -30,7 +30,7 @@ namespace StardewModdingAPI /// Get an API that other mods can access. This is always called after , and is called once per mod that accesses the API (even if they access it multiple times). /// The mod accessing the API. - /// Returns the API instance, or null if the mod has no API. Note that the manifest is provided for informational purposes only, and that denying API access to specific mods is strongly discouraged and may be considered abusive. + /// Returns the API instance, or null if the mod has no API. Note that is provided for informational purposes only, and that denying API access to specific mods is strongly discouraged and may be considered abusive. /// object? GetApi(IModInfo mod); } From 3d10d08a1ac281620b60c0e5fd1d51b2da896a0d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 9 Oct 2022 14:08:16 -0400 Subject: [PATCH 30/40] make deprecation warnings a bit stronger for the upcoming 4.0.0 release --- docs/release-notes.md | 3 ++- src/SMAPI/Framework/Deprecations/DeprecationManager.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 75e143e8..4352e2ba 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -16,7 +16,8 @@ * For mod authors: * When [providing a mod API for a C# mod](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Integrations), you can now get an optional parameter with the mod requesting the API (thanks to KhloeLeclair!). This avoids needing the pattern where each method needs the requesting mod's manifest. - * SMAPI now treats square brackets in the manifest `Name` field as round brackets, to avoid breaking tools which parse log files. + * SMAPI now treats square brackets in the manifest `Name` field as round ones to avoid breaking tools which parse log files. + * Made deprecation message wording stronger for the upcoming SMAPI 4.0.0 release. * Updated to [FluentHttpClient](https://github.com/Pathoschild/FluentHttpClient#readme) 4.2.0 (see [changes](https://github.com/Pathoschild/FluentHttpClient/blob/develop/RELEASE-NOTES.md#420)). * Fixed `LocationListChanged` event not raised & memory leak occurring when a generated mine/volcano is removed (thanks to tylergibbs2!). diff --git a/src/SMAPI/Framework/Deprecations/DeprecationManager.cs b/src/SMAPI/Framework/Deprecations/DeprecationManager.cs index f58f085e..5a5850d1 100644 --- a/src/SMAPI/Framework/Deprecations/DeprecationManager.cs +++ b/src/SMAPI/Framework/Deprecations/DeprecationManager.cs @@ -101,7 +101,7 @@ namespace StardewModdingAPI.Framework.Deprecations foreach (DeprecationWarning warning in this.QueuedWarnings.OrderBy(p => p.ModName).ThenBy(p => p.NounPhrase)) { // build message - string message = $"{warning.ModName} uses deprecated code ({warning.NounPhrase} is deprecated since SMAPI {warning.Version})."; + string message = $"{warning.ModName} uses deprecated code ({warning.NounPhrase}) and will break in the upcoming SMAPI 4.0.0."; // get log level LogLevel level; From 8dc12fd01c9274b045bafb04f02ef97fd8999c5d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 9 Oct 2022 14:30:40 -0400 Subject: [PATCH 31/40] optimize string splits --- docs/release-notes.md | 1 + .../Framework/Commands/Player/SetColorCommand.cs | 2 +- src/SMAPI.Tests/Utilities/KeybindListTests.cs | 6 +++--- src/SMAPI.Web/Framework/Clients/Nexus/NexusClient.cs | 4 ++-- src/SMAPI/Framework/Logging/LogManager.cs | 2 +- src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs | 2 +- src/SMAPI/Framework/SCore.cs | 2 +- src/SMAPI/Utilities/Keybind.cs | 4 ++-- 8 files changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 4352e2ba..e4324d40 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -11,6 +11,7 @@ * For players: * The SMAPI installer now also detects game folders listed in Steam's `.vdf` library data on Windows (thanks to pizzaoverhead!). * Optimized performance and memory usage (thanks to atravita!). + * Other internal optimizations. * Added more file extensions to ignore when searching for mod folders: `.7z`, `.tar`, `.tar.gz`, and `.xcf` (thanks to atravita!). * Fixed update alerts incorrectly shown for prerelease versions on GitHub that aren't marked as prerelease. diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetColorCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetColorCommand.cs index 12a51bc9..ea9f1d82 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetColorCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetColorCommand.cs @@ -63,7 +63,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player /// The color to set. private bool TryParseColor(string input, out Color color) { - string[] colorHexes = input.Split(new[] { ',' }, 3); + string[] colorHexes = input.Split(',', 3); if (int.TryParse(colorHexes[0], out int r) && int.TryParse(colorHexes[1], out int g) && int.TryParse(colorHexes[2], out int b)) { color = new Color(r, g, b); diff --git a/src/SMAPI.Tests/Utilities/KeybindListTests.cs b/src/SMAPI.Tests/Utilities/KeybindListTests.cs index c4c086de..c5fd5daf 100644 --- a/src/SMAPI.Tests/Utilities/KeybindListTests.cs +++ b/src/SMAPI.Tests/Utilities/KeybindListTests.cs @@ -136,11 +136,11 @@ namespace SMAPI.Tests.Utilities foreach (string rawPair in stateMap.Split(',')) { // parse values - string[] parts = rawPair.Split(new[] { ':' }, 2); + string[] parts = rawPair.Split(':', 2, StringSplitOptions.TrimEntries); if (!Enum.TryParse(parts[0], ignoreCase: true, out SButton curButton)) - Assert.Fail($"The state map is invalid: unknown button value '{parts[0].Trim()}'"); + Assert.Fail($"The state map is invalid: unknown button value '{parts[0]}'"); if (!Enum.TryParse(parts[1], ignoreCase: true, out SButtonState state)) - Assert.Fail($"The state map is invalid: unknown state value '{parts[1].Trim()}'"); + Assert.Fail($"The state map is invalid: unknown state value '{parts[1]}'"); // get state if (curButton == button) diff --git a/src/SMAPI.Web/Framework/Clients/Nexus/NexusClient.cs b/src/SMAPI.Web/Framework/Clients/Nexus/NexusClient.cs index 23b25f95..46c3092c 100644 --- a/src/SMAPI.Web/Framework/Clients/Nexus/NexusClient.cs +++ b/src/SMAPI.Web/Framework/Clients/Nexus/NexusClient.cs @@ -121,10 +121,10 @@ namespace StardewModdingAPI.Web.Framework.Clients.Nexus HtmlNode? node = doc.DocumentNode.SelectSingleNode("//div[contains(@class, 'site-notice')][contains(@class, 'warning')]"); if (node != null) { - string[] errorParts = node.InnerText.Trim().Split(new[] { '\n' }, 2, System.StringSplitOptions.RemoveEmptyEntries); + string[] errorParts = node.InnerText.Trim().Split('\n', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); string errorCode = errorParts[0]; string? errorText = errorParts.Length > 1 ? errorParts[1] : null; - switch (errorCode.Trim().ToLower()) + switch (errorCode.ToLower()) { case "not found": return null; diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs index c0b7c0ba..d5b33289 100644 --- a/src/SMAPI/Framework/Logging/LogManager.cs +++ b/src/SMAPI/Framework/Logging/LogManager.cs @@ -223,7 +223,7 @@ namespace StardewModdingAPI.Framework.Logging // show update alert if (File.Exists(Constants.UpdateMarker)) { - string[] rawUpdateFound = File.ReadAllText(Constants.UpdateMarker).Split(new[] { '|' }, 2); + string[] rawUpdateFound = File.ReadAllText(Constants.UpdateMarker).Split('|', 2); if (SemanticVersion.TryParse(rawUpdateFound[0], out ISemanticVersion? updateFound)) { if (Constants.ApiVersion.IsPrerelease() && updateFound.IsNewerThan(Constants.ApiVersion)) diff --git a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs index f5d449c5..4dd9ccc6 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs @@ -58,7 +58,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction); if (methodRef != null && methodRef.DeclaringType.FullName == this.FullTypeName && this.MethodNames.Contains(methodRef.Name)) { - string eventName = methodRef.Name.Split(new[] { '_' }, 2)[1]; + string eventName = methodRef.Name.Split('_', 2)[1]; this.MethodNames.Remove($"add_{eventName}"); this.MethodNames.Remove($"remove_{eventName}"); diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 98eb2803..114c4bb3 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1384,7 +1384,7 @@ namespace StardewModdingAPI.Framework } // check min length for specific types - switch (fields[SObject.objectInfoTypeIndex].Split(new[] { ' ' }, 2)[0]) + switch (fields[SObject.objectInfoTypeIndex].Split(' ', 2)[0]) { case "Cooking": if (fields.Length < SObject.objectInfoBuffDurationIndex + 1) diff --git a/src/SMAPI/Utilities/Keybind.cs b/src/SMAPI/Utilities/Keybind.cs index 3455ce77..3532620d 100644 --- a/src/SMAPI/Utilities/Keybind.cs +++ b/src/SMAPI/Utilities/Keybind.cs @@ -54,12 +54,12 @@ namespace StardewModdingAPI.Utilities } // parse buttons - string[] rawButtons = input.Split('+'); + string[] rawButtons = input.Split('+', StringSplitOptions.TrimEntries); SButton[] buttons = new SButton[rawButtons.Length]; List rawErrors = new List(); for (int i = 0; i < buttons.Length; i++) { - string rawButton = rawButtons[i].Trim(); + string rawButton = rawButtons[i]; if (string.IsNullOrWhiteSpace(rawButton)) rawErrors.Add("Invalid empty button value"); else if (!Enum.TryParse(rawButton, ignoreCase: true, out SButton button)) From d0704ef6f0040d10aa89e9cf1ee8d38098bff5df Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 9 Oct 2022 14:34:31 -0400 Subject: [PATCH 32/40] fix nullability warnings --- src/SMAPI.Tests/Utilities/SemanticVersionTests.cs | 4 ++-- src/SMAPI.Web/Views/LogParser/Index.cshtml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs b/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs index fedadba6..77c0da5f 100644 --- a/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs +++ b/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs @@ -382,11 +382,11 @@ namespace SMAPI.Tests.Utilities { // act string json = JsonConvert.SerializeObject(new SemanticVersion(versionStr)); - SemanticVersion after = JsonConvert.DeserializeObject(json); + SemanticVersion? after = JsonConvert.DeserializeObject(json); // assert Assert.IsNotNull(after, "The semantic version after deserialization is unexpectedly null."); - Assert.AreEqual(versionStr, after.ToString(), "The semantic version after deserialization doesn't match the input version."); + Assert.AreEqual(versionStr, after!.ToString(), "The semantic version after deserialization doesn't match the input version."); } diff --git a/src/SMAPI.Web/Views/LogParser/Index.cshtml b/src/SMAPI.Web/Views/LogParser/Index.cshtml index 24fe5fa2..b982bc0c 100644 --- a/src/SMAPI.Web/Views/LogParser/Index.cshtml +++ b/src/SMAPI.Web/Views/LogParser/Index.cshtml @@ -361,7 +361,7 @@ else if (log?.IsValid == true) ContentPacks: contentPacks?.TryGetValue(mod.Name, out LogModInfo[]? contentPackList) == true ? contentPackList : Array.Empty() )) .ToList(); - if (contentPacks?.TryGetValue("", out LogModInfo[] invalidPacks) == true) + if (contentPacks?.TryGetValue("", out LogModInfo[]? invalidPacks) == true) { modsWithContentPacks.Add(( Mod: new LogModInfo(ModType.CodeMod, "", "", "", ""), From d143ab1077d7a3fb8638f0cc494aadf8688a0952 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 9 Oct 2022 14:45:50 -0400 Subject: [PATCH 33/40] update to Harmony 2.2.2 --- build/0Harmony.dll | Bin 238592 -> 241152 bytes build/0Harmony.xml | 27 +++++++++++++++++++++++++-- docs/release-notes.md | 2 +- docs/technical/smapi.md | 4 ++-- 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/build/0Harmony.dll b/build/0Harmony.dll index 492255be297ccadecefc747c15558e8ce06dd3af..72ca2b61010fba570cfa42f15e917c84324bfbdc 100644 GIT binary patch literal 241152 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 literal 238592 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ξ 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 )AeI6k)@$+nRePk$Q`_JntoFS)M>L+< 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 diff --git a/build/0Harmony.xml b/build/0Harmony.xml index 8499d20b..f1b9b4cf 100644 --- a/build/0Harmony.xml +++ b/build/0Harmony.xml @@ -845,7 +845,7 @@ -

The beginning of an except filter block + The beginning of an except filter block (currently not supported to use in a patch) @@ -2660,6 +2660,18 @@ The optional operand The optional name + + + Creates a code match that calls a method + The lambda expression using the method + The optional name + + + + Creates a code match that calls a method + The lambda expression using the method + The optional name + Creates a code match @@ -3216,6 +3228,13 @@ The enum True if the instruction loads the constant + + + Tests if the code instruction loads a string constant + The + The string + True if the instruction loads the constant + Tests if the code instruction loads a field @@ -3346,7 +3365,11 @@ A file log for debugging - + + Set this to make Harmony write its log content to this stream + + + Full pathname of the log file, defaults to a file called harmony.log.txt on your Desktop diff --git a/docs/release-notes.md b/docs/release-notes.md index e4324d40..89883f87 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -19,7 +19,7 @@ * When [providing a mod API for a C# mod](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Integrations), you can now get an optional parameter with the mod requesting the API (thanks to KhloeLeclair!). This avoids needing the pattern where each method needs the requesting mod's manifest. * SMAPI now treats square brackets in the manifest `Name` field as round ones to avoid breaking tools which parse log files. * Made deprecation message wording stronger for the upcoming SMAPI 4.0.0 release. - * Updated to [FluentHttpClient](https://github.com/Pathoschild/FluentHttpClient#readme) 4.2.0 (see [changes](https://github.com/Pathoschild/FluentHttpClient/blob/develop/RELEASE-NOTES.md#420)). + * Updated dependencies: [Harmony](https://harmony.pardeike.net) 2.2.2 (see [changes](https://github.com/pardeike/Harmony/releases/tag/v2.2.2.0)) and [FluentHttpClient](https://github.com/Pathoschild/FluentHttpClient#readme) 4.2.0 (see [changes](https://github.com/Pathoschild/FluentHttpClient/blob/develop/RELEASE-NOTES.md#420)). * Fixed `LocationListChanged` event not raised & memory leak occurring when a generated mine/volcano is removed (thanks to tylergibbs2!). ## 3.16.2 diff --git a/docs/technical/smapi.md b/docs/technical/smapi.md index b8a1683b..d115aefa 100644 --- a/docs/technical/smapi.md +++ b/docs/technical/smapi.md @@ -78,8 +78,8 @@ the `SMAPI` project with debugging from Visual Studio or Rider should launch SMA debugger attached, so you can intercept errors and step through the code being executed. ### Custom Harmony build -SMAPI uses [a custom build of Harmony](https://github.com/Pathoschild/Harmony#readme), which is -included in the `build` folder. To use a different build, just replace `0Harmony.dll` in that +SMAPI uses [a custom build of Harmony 2.2.2](https://github.com/Pathoschild/Harmony#readme), which +is included in the `build` folder. To use a different build, just replace `0Harmony.dll` in that folder before compiling. ## Prepare a release From 42ff20cd92a3a28faca8de0c309396efa147f0e2 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 9 Oct 2022 15:01:25 -0400 Subject: [PATCH 34/40] suppress Harmony debug mode by default --- docs/release-notes.md | 2 ++ src/SMAPI/Framework/Models/SConfig.cs | 10 ++++++++-- src/SMAPI/Framework/SCore.cs | 9 +++++++++ src/SMAPI/SMAPI.config.json | 14 ++++++++------ 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 89883f87..4de0fa66 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -10,6 +10,8 @@ ## Upcoming release * For players: * The SMAPI installer now also detects game folders listed in Steam's `.vdf` library data on Windows (thanks to pizzaoverhead!). + * SMAPI now prevents mods from enabling Harmony debug mode, which impacts performance and creates a file on your desktop. + _You can allow debug mode by editing `smapi-internal/config.json` in your game folder._ * Optimized performance and memory usage (thanks to atravita!). * Other internal optimizations. * Added more file extensions to ignore when searching for mod folders: `.7z`, `.tar`, `.tar.gz`, and `.xcf` (thanks to atravita!). diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index 9444c046..b3061fba 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -23,7 +23,8 @@ namespace StardewModdingAPI.Framework.Models [nameof(LogNetworkTraffic)] = false, [nameof(RewriteMods)] = true, [nameof(UseRawImageLoading)] = true, - [nameof(UseCaseInsensitivePaths)] = Constants.Platform is Platform.Android or Platform.Linux + [nameof(UseCaseInsensitivePaths)] = Constants.Platform is Platform.Android or Platform.Linux, + [nameof(SuppressHarmonyDebugMode)] = true }; /// The default values for , to log changes if different. @@ -79,6 +80,9 @@ namespace StardewModdingAPI.Framework.Models /// The colors to use for text written to the SMAPI console. public ColorSchemeConfig ConsoleColors { get; set; } + /// Whether to prevent mods from enabling Harmony's debug mode, which impacts performance and creates a file on your desktop. Debug mode should never be enabled by a released mod. + public bool SuppressHarmonyDebugMode { get; set; } + /// The mod IDs SMAPI should ignore when performing update checks or validating update keys. public HashSet SuppressUpdateChecks { get; set; } @@ -99,8 +103,9 @@ namespace StardewModdingAPI.Framework.Models /// >Whether to make SMAPI file APIs case-insensitive, even on Linux. /// Whether SMAPI should log network traffic. /// The colors to use for text written to the SMAPI console. + /// Whether to prevent mods from enabling Harmony's debug mode, which impacts performance and creates a file on your desktop. Debug mode should never be enabled by a released mod. /// 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? 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, bool? suppressHarmonyDebugMode, string[]? suppressUpdateChecks) { this.DeveloperMode = developerMode; this.CheckForUpdates = checkForUpdates ?? (bool)SConfig.DefaultValues[nameof(this.CheckForUpdates)]; @@ -114,6 +119,7 @@ namespace StardewModdingAPI.Framework.Models this.UseCaseInsensitivePaths = useCaseInsensitivePaths ?? (bool)SConfig.DefaultValues[nameof(this.UseCaseInsensitivePaths)]; this.LogNetworkTraffic = logNetworkTraffic ?? (bool)SConfig.DefaultValues[nameof(this.LogNetworkTraffic)]; this.ConsoleColors = consoleColors; + this.SuppressHarmonyDebugMode = suppressHarmonyDebugMode ?? (bool)SConfig.DefaultValues[nameof(this.SuppressHarmonyDebugMode)]; this.SuppressUpdateChecks = new HashSet(suppressUpdateChecks ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); } diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 114c4bb3..3e6cd853 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -501,6 +501,15 @@ namespace StardewModdingAPI.Framework return; } + /********* + ** Prevent Harmony debug mode + *********/ + if (HarmonyLib.Harmony.DEBUG && this.Settings.SuppressHarmonyDebugMode) + { + HarmonyLib.Harmony.DEBUG = false; + this.Monitor.LogOnce("A mod enabled Harmony debug mode, which impacts performance and creates a file on your desktop. SMAPI will try to keep it disabled. (You can allow debug mode by editing the smapi-internal/config.json file.)", LogLevel.Warn); + } + #if SMAPI_DEPRECATED /********* ** Reload assets when interceptors are added/removed diff --git a/src/SMAPI/SMAPI.config.json b/src/SMAPI/SMAPI.config.json index 97e8e00c..2d4239ba 100644 --- a/src/SMAPI/SMAPI.config.json +++ b/src/SMAPI/SMAPI.config.json @@ -54,12 +54,6 @@ copy all the settings, or you may cause bugs due to overridden changes in future */ "UseCaseInsensitivePaths": null, - /** - * Whether to use the experimental Pintail API proxying library, instead of the original - * proxying built into SMAPI itself. - */ - "UsePintail": true, - /** * Whether to use raw image data when possible, instead of initializing an XNA Texture2D * instance through the GPU. @@ -138,6 +132,14 @@ copy all the settings, or you may cause bugs due to overridden changes in future } }, + /** + * Whether to prevent mods from enabling Harmony's debug mode, which impacts performance and + * creates a file on your desktop. Debug mode should never be enabled by a released mod. + * + * If you actually need debug mode to test your own mod, set this to false. + */ + "SuppressHarmonyDebugMode": true, + /** * The mod IDs SMAPI should ignore when performing update checks or validating update keys. */ From 9a15da5a173e5e218c16e2e4ef0af0c98968e1cb Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 9 Oct 2022 16:59:05 -0400 Subject: [PATCH 35/40] add 'strict mode' release with deprecated APIs stripped out --- docs/release-notes.md | 1 + .../Framework/LogParsing/LogParser.cs | 9 ++++++- .../Framework/LogParsing/Models/ParsedLog.cs | 3 +++ src/SMAPI.Web/Views/LogParser/Index.cshtml | 25 ++++++++++++++++--- .../wwwroot/Content/css/log-parser.css | 5 ++++ src/SMAPI/Framework/Logging/LogManager.cs | 10 +++++++- src/SMAPI/Framework/SCore.cs | 9 ++++--- 7 files changed, 53 insertions(+), 9 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 4de0fa66..5bf3b875 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -9,6 +9,7 @@ ## Upcoming release * For players: + * You can now download SMAPI 'strict mode' from the [Nexus optional files](https://www.nexusmods.com/stardewvalley/mods/2400/). This removes all deprecated APIs, which may significantly improve performance. However mods which still show deprecation warnings won't work. * The SMAPI installer now also detects game folders listed in Steam's `.vdf` library data on Windows (thanks to pizzaoverhead!). * SMAPI now prevents mods from enabling Harmony debug mode, which impacts performance and creates a file on your desktop. _You can allow debug mode by editing `smapi-internal/config.json` in your game folder._ diff --git a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs index 18ba754c..5e0dedf3 100644 --- a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs +++ b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs @@ -199,8 +199,15 @@ namespace StardewModdingAPI.Web.Framework.LogParsing log.ApiVersion = match.Groups["apiVersion"].Value; log.GameVersion = match.Groups["gameVersion"].Value; log.OperatingSystem = match.Groups["os"].Value; - smapiMod.OverrideVersion(log.ApiVersion); + const string strictModeSuffix = " (strict mode)"; + if (log.ApiVersion.EndsWith(strictModeSuffix)) + { + log.IsStrictMode = true; + log.ApiVersion = log.ApiVersion[..^strictModeSuffix.Length]; + } + + smapiMod.OverrideVersion(log.ApiVersion); log.ApiVersionParsed = smapiMod.GetParsedVersion(); } diff --git a/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs b/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs index 3f649199..cda0f653 100644 --- a/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs +++ b/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs @@ -25,6 +25,9 @@ namespace StardewModdingAPI.Web.Framework.LogParsing.Models /**** ** Log data ****/ + /// Whether SMAPI is running in strict mode, which disables all deprecated APIs. + public bool IsStrictMode { get; set; } + /// The SMAPI version. public string? ApiVersion { get; set; } diff --git a/src/SMAPI.Web/Views/LogParser/Index.cshtml b/src/SMAPI.Web/Views/LogParser/Index.cshtml index b982bc0c..28127903 100644 --- a/src/SMAPI.Web/Views/LogParser/Index.cshtml +++ b/src/SMAPI.Web/Views/LogParser/Index.cshtml @@ -40,7 +40,7 @@ } - + @@ -243,7 +243,7 @@ else if (log?.IsValid == true) @if (log?.IsValid == true) {
- @if (outdatedMods.Any() || errorHandler is null || hasOlderErrorHandler || isPyTkCompatibilityMode) + @if (outdatedMods.Any() || errorHandler is null || hasOlderErrorHandler || isPyTkCompatibilityMode || log.IsStrictMode) {

Suggested fixes

} @@ -329,7 +340,13 @@ else if (log?.IsValid == true) SMAPI: - @log.ApiVersion + + @log.ApiVersion + @if (log.IsStrictMode) + { + (strict mode) + } + Folder: diff --git a/src/SMAPI.Web/wwwroot/Content/css/log-parser.css b/src/SMAPI.Web/wwwroot/Content/css/log-parser.css index f136a96f..995f5aa9 100644 --- a/src/SMAPI.Web/wwwroot/Content/css/log-parser.css +++ b/src/SMAPI.Web/wwwroot/Content/css/log-parser.css @@ -73,6 +73,11 @@ table caption { margin-bottom: 0.5em; } +#fix-list li.notice { + background: #EEFFEE; + border-color: #080; +} + #fix-list li.important { background: #FCC; border-color: #800; diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs index d5b33289..ffffc9c7 100644 --- a/src/SMAPI/Framework/Logging/LogManager.cs +++ b/src/SMAPI/Framework/Logging/LogManager.cs @@ -269,7 +269,11 @@ namespace StardewModdingAPI.Framework.Logging public void LogIntro(string modsPath, IDictionary customSettings) { // log platform - this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Constants.GameVersion} (build {Constants.GetBuildVersionLabel()}) on {EnvironmentUtility.GetFriendlyPlatformName(Constants.Platform)}", LogLevel.Info); + this.Monitor.Log($"SMAPI {Constants.ApiVersion} " +#if !SMAPI_DEPRECATED + + "(strict mode) " +#endif + + $"with Stardew Valley {Constants.GameVersion} (build {Constants.GetBuildVersionLabel()}) on {EnvironmentUtility.GetFriendlyPlatformName(Constants.Platform)}", LogLevel.Info); // log basic info this.Monitor.Log($"Mods go here: {modsPath}", LogLevel.Info); @@ -280,6 +284,10 @@ namespace StardewModdingAPI.Framework.Logging // log custom settings if (customSettings.Any()) this.Monitor.Log($"Loaded with custom settings: {string.Join(", ", customSettings.OrderBy(p => p.Key).Select(p => $"{p.Key}: {p.Value}"))}"); + +#if !SMAPI_DEPRECATED + this.Monitor.Log("SMAPI is running in 'strict mode', which removes all deprecated APIs. This can significantly improve performance, but some mods may not work. You can reinstall SMAPI to disable it if you run into problems.", LogLevel.Info); +#endif } /// Log details for settings that don't match the default. diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 3e6cd853..4d6deb15 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1681,15 +1681,18 @@ namespace StardewModdingAPI.Framework // initialize translations this.ReloadTranslations(loaded); -#if SMAPI_DEPRECATED // set temporary PyTK compatibility mode // This is part of a three-part fix for PyTK 1.23.* and earlier. When removing this, // search 'Platonymous.Toolkit' to find the other part in SMAPI and Content Patcher. { IModInfo? pyTk = this.ModRegistry.Get("Platonymous.Toolkit"); - ModContentManager.EnablePyTkLegacyMode = pyTk is not null && pyTk.Manifest.Version.IsOlderThan("1.24.0"); - } + if (pyTk is not null && pyTk.Manifest.Version.IsOlderThan("1.24.0")) +#if SMAPI_DEPRECATED + ModContentManager.EnablePyTkLegacyMode = true; +#else + this.Monitor.Log("PyTK's image scaling is not compatible with SMAPI strict mode.", LogLevel.Warn); #endif + } // initialize loaded non-content-pack mods this.Monitor.Log("Launching mods...", LogLevel.Debug); From 037d7e357b169936fa858f6c8b9c1388ca75840b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 9 Oct 2022 17:39:11 -0400 Subject: [PATCH 36/40] set texture name earlier to support mods like SpriteMaster --- docs/release-notes.md | 1 + .../Framework/Content/AssetDataForImage.cs | 2 +- .../ContentManagers/BaseContentManager.cs | 2 +- .../ContentManagers/ModContentManager.cs | 4 +-- src/SMAPI/Framework/InternalExtensions.cs | 30 +++++++++++++++++++ 5 files changed, 35 insertions(+), 4 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 5bf3b875..da3db590 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -22,6 +22,7 @@ * When [providing a mod API for a C# mod](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Integrations), you can now get an optional parameter with the mod requesting the API (thanks to KhloeLeclair!). This avoids needing the pattern where each method needs the requesting mod's manifest. * SMAPI now treats square brackets in the manifest `Name` field as round ones to avoid breaking tools which parse log files. * Made deprecation message wording stronger for the upcoming SMAPI 4.0.0 release. + * The `Texture2D.Name` field is now set earlier to support mods like SpriteMaster. * Updated dependencies: [Harmony](https://harmony.pardeike.net) 2.2.2 (see [changes](https://github.com/pardeike/Harmony/releases/tag/v2.2.2.0)) and [FluentHttpClient](https://github.com/Pathoschild/FluentHttpClient#readme) 4.2.0 (see [changes](https://github.com/Pathoschild/FluentHttpClient/blob/develop/RELEASE-NOTES.md#420)). * Fixed `LocationListChanged` event not raised & memory leak occurring when a generated mine/volcano is removed (thanks to tylergibbs2!). diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index 0380dd9e..241c09a8 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -106,7 +106,7 @@ namespace StardewModdingAPI.Framework.Content return false; Texture2D original = this.Data; - Texture2D texture = new(Game1.graphics.GraphicsDevice, Math.Max(original.Width, minWidth), Math.Max(original.Height, minHeight)); + Texture2D texture = new Texture2D(Game1.graphics.GraphicsDevice, Math.Max(original.Width, minWidth), Math.Max(original.Height, minHeight)).SetName(original.Name); this.ReplaceWith(texture); this.PatchImage(original); return true; diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index 54f8e2a2..1e9f4ffe 100644 --- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs @@ -336,7 +336,7 @@ namespace StardewModdingAPI.Framework.ContentManagers { // track asset key if (value is Texture2D texture) - texture.Name = assetName.Name; + texture.SetName(assetName); // save to cache // Note: even if the asset was loaded and cached right before this method was called, diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 72dcf6e1..30dd4215 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -232,7 +232,7 @@ namespace StardewModdingAPI.Framework.ContentManagers return (T)raw; else { - Texture2D texture = new(Game1.graphics.GraphicsDevice, raw.Width, raw.Height); + Texture2D texture = new Texture2D(Game1.graphics.GraphicsDevice, raw.Width, raw.Height).SetName(assetName); texture.SetData(raw.Data); return (T)(object)texture; } @@ -240,7 +240,7 @@ namespace StardewModdingAPI.Framework.ContentManagers else { using FileStream stream = File.OpenRead(file.FullName); - Texture2D texture = Texture2D.FromStream(Game1.graphics.GraphicsDevice, stream); + Texture2D texture = Texture2D.FromStream(Game1.graphics.GraphicsDevice, stream).SetName(assetName); this.PremultiplyTransparency(texture); return (T)(object)texture; } diff --git a/src/SMAPI/Framework/InternalExtensions.cs b/src/SMAPI/Framework/InternalExtensions.cs index ba9bbcec..7ad30d35 100644 --- a/src/SMAPI/Framework/InternalExtensions.cs +++ b/src/SMAPI/Framework/InternalExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using Microsoft.Xna.Framework.Graphics; @@ -163,5 +164,34 @@ namespace StardewModdingAPI.Framework { return reflection.GetField(spriteBatch, "_beginCalled").GetValue(); } + + /**** + ** Texture2D + ****/ + /// Set the texture name field. + /// The texture whose name to set. + /// The asset name to set. + /// Returns the texture for chaining. + [return: NotNullIfNotNull("texture")] + public static Texture2D? SetName(this Texture2D? texture, IAssetName assetName) + { + if (texture != null) + texture.Name = assetName.Name; + + return texture; + } + + /// Set the texture name field. + /// The texture whose name to set. + /// The asset name to set. + /// Returns the texture for chaining. + [return: NotNullIfNotNull("texture")] + public static Texture2D? SetName(this Texture2D? texture, string assetName) + { + if (texture != null) + texture.Name = assetName; + + return texture; + } } } From b78b269cf53529bcb73fa99538a3e9eb23e283b0 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 9 Oct 2022 17:56:33 -0400 Subject: [PATCH 37/40] split PyTK raw-image-load check into a separate method so it can be patched by mods like SpriteMaster --- .../ContentManagers/ModContentManager.cs | 51 ++++++++++++------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 30dd4215..ddb6f6f5 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -204,31 +204,22 @@ namespace StardewModdingAPI.Framework.ContentManagers private T LoadImageFile(IAssetName assetName, FileInfo file) { this.AssertValidType(assetName, file, typeof(Texture2D), typeof(IRawTextureData)); - bool expectsRawData = typeof(T).IsAssignableTo(typeof(IRawTextureData)); - bool asRawData = expectsRawData || this.UseRawImageLoading; - + bool returnRawData = typeof(T).IsAssignableTo(typeof(IRawTextureData)); + bool loadRawData = + returnRawData + || ( + this.UseRawImageLoading #if SMAPI_DEPRECATED - // disable raw data if PyTK will rescale the image (until it supports raw data) - if (asRawData && !expectsRawData) - { - if (ModContentManager.EnablePyTkLegacyMode) - { - // PyTK intercepts Texture2D file loads to rescale them (e.g. for HD portraits), - // but doesn't support IRawTextureData loads yet. We can't just check if the - // current file has a '.pytk.json' rescale file though, since PyTK may still - // rescale it if the original asset or another edit gets rescaled. - asRawData = false; - this.Monitor.LogOnce("Enabled compatibility mode for PyTK 1.23.* or earlier. This won't cause any issues, but may impact performance. This will no longer be supported in the upcoming SMAPI 4.0.0.", LogLevel.Warn); - } - } + && !this.ShouldDisableIntermediateRawDataLoad(assetName, file) #endif + ); // load - if (asRawData) + if (loadRawData) { - IRawTextureData raw = this.LoadRawImageData(file, expectsRawData); + IRawTextureData raw = this.LoadRawImageData(file, returnRawData); - if (expectsRawData) + if (returnRawData) return (T)raw; else { @@ -246,6 +237,28 @@ namespace StardewModdingAPI.Framework.ContentManagers } } +#if SMAPI_DEPRECATED + /// Get whether to disable loading an image as before building a instance. This isn't called if the mod requested directly. + /// The type of asset being loaded. + /// The asset name relative to the loader root directory. + /// The file being loaded. + private bool ShouldDisableIntermediateRawDataLoad(IAssetName assetName, FileInfo file) + { + // disable raw data if PyTK will rescale the image (until it supports raw data) + if (ModContentManager.EnablePyTkLegacyMode) + { + // PyTK intercepts Texture2D file loads to rescale them (e.g. for HD portraits), + // but doesn't support IRawTextureData loads yet. We can't just check if the + // current file has a '.pytk.json' rescale file though, since PyTK may still + // rescale it if the original asset or another edit gets rescaled. + this.Monitor.LogOnce("Enabled compatibility mode for PyTK 1.23.* or earlier. This won't cause any issues, but may impact performance. This will no longer be supported in the upcoming SMAPI 4.0.0.", LogLevel.Warn); + return true; + } + + return false; + } +#endif + /// Load the raw image data from a file on disk. /// The file whose data to load. /// Whether the data is being loaded for an (true) or (false) instance. From 27856ebea25791d2d82a42a670327f6dd5c4ac23 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 9 Oct 2022 18:03:05 -0400 Subject: [PATCH 38/40] drop UseRawImageLoading option Raw image loading is now always enabled, except in PyTK compatibility mode. --- docs/release-notes.md | 1 + src/SMAPI/Framework/ContentCoordinator.cs | 10 +---- .../ContentManagers/ModContentManager.cs | 44 +++++++------------ src/SMAPI/Framework/Models/SConfig.cs | 8 +--- src/SMAPI/Framework/SCore.cs | 3 +- src/SMAPI/SMAPI.config.json | 6 --- 6 files changed, 20 insertions(+), 52 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index da3db590..b5a0adaf 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -16,6 +16,7 @@ * Optimized performance and memory usage (thanks to atravita!). * Other internal optimizations. * Added more file extensions to ignore when searching for mod folders: `.7z`, `.tar`, `.tar.gz`, and `.xcf` (thanks to atravita!). + * Removed `UseRawImageLoading` option. This is now always enabled, except when PyTK is installed. * Fixed update alerts incorrectly shown for prerelease versions on GitHub that aren't marked as prerelease. * For mod authors: diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 9e044b44..cf26307f 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -34,9 +34,6 @@ namespace StardewModdingAPI.Framework /// An asset key prefix for assets from SMAPI mod folders. private readonly string ManagedPrefix = "SMAPI"; - /// Whether to use raw image data when possible, instead of initializing an XNA Texture2D instance through the GPU. - private readonly bool UseRawImageLoading; - /// Get a file lookup for the given directory. private readonly Func GetFileLookup; @@ -139,8 +136,7 @@ namespace StardewModdingAPI.Framework /// Get a file lookup for the given directory. /// A callback to invoke when any asset names have been invalidated from the cache. /// Get the load/edit operations to apply to an asset by querying registered event handlers. - /// Whether to use raw image data when possible, instead of initializing an XNA Texture2D instance through the GPU. - public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset, Action onAssetLoaded, Func getFileLookup, Action> onAssetsInvalidated, Func requestAssetOperations, bool useRawImageLoading) + public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset, Action onAssetLoaded, Func getFileLookup, Action> onAssetsInvalidated, Func requestAssetOperations) { this.GetFileLookup = getFileLookup; this.Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor)); @@ -151,7 +147,6 @@ namespace StardewModdingAPI.Framework this.OnAssetsInvalidated = onAssetsInvalidated; this.RequestAssetOperations = requestAssetOperations; this.FullRootDirectory = Path.Combine(Constants.GamePath, rootDirectory); - this.UseRawImageLoading = useRawImageLoading; this.ContentManagers.Add( this.MainContentManager = new GameContentManager( name: "Game1.content", @@ -230,8 +225,7 @@ namespace StardewModdingAPI.Framework reflection: this.Reflection, jsonHelper: this.JsonHelper, onDisposing: this.OnDisposing, - fileLookup: this.GetFileLookup(rootDirectory), - useRawImageLoading: this.UseRawImageLoading + fileLookup: this.GetFileLookup(rootDirectory) ); this.ContentManagers.Add(manager); return manager; diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index ddb6f6f5..badbd766 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -30,9 +30,6 @@ namespace StardewModdingAPI.Framework.ContentManagers /********* ** Fields *********/ - /// Whether to use raw image data when possible, instead of initializing an XNA Texture2D instance through the GPU. - private readonly bool UseRawImageLoading; - /// Encapsulates SMAPI's JSON file parsing. private readonly JsonHelper JsonHelper; @@ -74,15 +71,13 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Encapsulates SMAPI's JSON file parsing. /// A callback to invoke when the content manager is being disposed. /// A lookup for files within the . - /// Whether to use raw image data when possible, instead of initializing an XNA Texture2D instance through the GPU. - public ModContentManager(string name, IContentManager gameContentManager, IServiceProvider serviceProvider, string modName, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onDisposing, IFileLookup fileLookup, bool useRawImageLoading) + public ModContentManager(string name, IContentManager gameContentManager, IServiceProvider serviceProvider, string modName, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onDisposing, IFileLookup fileLookup) : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: true) { this.GameContentManager = gameContentManager; this.FileLookup = fileLookup; this.JsonHelper = jsonHelper; this.ModName = modName; - this.UseRawImageLoading = useRawImageLoading; this.TryLocalizeKeys = false; } @@ -205,36 +200,27 @@ namespace StardewModdingAPI.Framework.ContentManagers { this.AssertValidType(assetName, file, typeof(Texture2D), typeof(IRawTextureData)); bool returnRawData = typeof(T).IsAssignableTo(typeof(IRawTextureData)); - bool loadRawData = - returnRawData - || ( - this.UseRawImageLoading + #if SMAPI_DEPRECATED - && !this.ShouldDisableIntermediateRawDataLoad(assetName, file) -#endif - ); - - // load - if (loadRawData) - { - IRawTextureData raw = this.LoadRawImageData(file, returnRawData); - - if (returnRawData) - return (T)raw; - else - { - Texture2D texture = new Texture2D(Game1.graphics.GraphicsDevice, raw.Width, raw.Height).SetName(assetName); - texture.SetData(raw.Data); - return (T)(object)texture; - } - } - else + if (!returnRawData && this.ShouldDisableIntermediateRawDataLoad(assetName, file)) { using FileStream stream = File.OpenRead(file.FullName); Texture2D texture = Texture2D.FromStream(Game1.graphics.GraphicsDevice, stream).SetName(assetName); this.PremultiplyTransparency(texture); return (T)(object)texture; } +#endif + + IRawTextureData raw = this.LoadRawImageData(file, returnRawData); + + if (returnRawData) + return (T)raw; + else + { + Texture2D texture = new Texture2D(Game1.graphics.GraphicsDevice, raw.Width, raw.Height).SetName(assetName); + texture.SetData(raw.Data); + return (T)(object)texture; + } } #if SMAPI_DEPRECATED diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index b3061fba..bceb0940 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -22,7 +22,6 @@ namespace StardewModdingAPI.Framework.Models [nameof(WebApiBaseUrl)] = "https://smapi.io/api/", [nameof(LogNetworkTraffic)] = false, [nameof(RewriteMods)] = true, - [nameof(UseRawImageLoading)] = true, [nameof(UseCaseInsensitivePaths)] = Constants.Platform is Platform.Android or Platform.Linux, [nameof(SuppressHarmonyDebugMode)] = true }; @@ -68,9 +67,6 @@ namespace StardewModdingAPI.Framework.Models /// Whether SMAPI should rewrite mods for compatibility. 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; set; } - /// Whether to make SMAPI file APIs case-insensitive, even on Linux. public bool UseCaseInsensitivePaths { get; set; } @@ -99,13 +95,12 @@ namespace StardewModdingAPI.Framework.Models /// The base URL for SMAPI's web API, used to perform update checks. /// The log contexts for which to enable verbose logging, which may show a lot more information to simplify troubleshooting. /// Whether SMAPI should rewrite mods for compatibility. - /// Whether to use raw image data when possible, instead of initializing an XNA Texture2D instance through the GPU. /// >Whether to make SMAPI file APIs case-insensitive, even on Linux. /// Whether SMAPI should log network traffic. /// The colors to use for text written to the SMAPI console. /// Whether to prevent mods from enabling Harmony's debug mode, which impacts performance and creates a file on your desktop. Debug mode should never be enabled by a released mod. /// 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? useRawImageLoading, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, ColorSchemeConfig consoleColors, bool? suppressHarmonyDebugMode, string[]? suppressUpdateChecks) + public SConfig(bool developerMode, bool? checkForUpdates, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, string[]? verboseLogging, bool? rewriteMods, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, ColorSchemeConfig consoleColors, bool? suppressHarmonyDebugMode, string[]? suppressUpdateChecks) { this.DeveloperMode = developerMode; this.CheckForUpdates = checkForUpdates ?? (bool)SConfig.DefaultValues[nameof(this.CheckForUpdates)]; @@ -115,7 +110,6 @@ namespace StardewModdingAPI.Framework.Models this.WebApiBaseUrl = webApiBaseUrl; this.VerboseLogging = new HashSet(verboseLogging ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); this.RewriteMods = rewriteMods ?? (bool)SConfig.DefaultValues[nameof(this.RewriteMods)]; - this.UseRawImageLoading = useRawImageLoading ?? (bool)SConfig.DefaultValues[nameof(this.UseRawImageLoading)]; this.UseCaseInsensitivePaths = useCaseInsensitivePaths ?? (bool)SConfig.DefaultValues[nameof(this.UseCaseInsensitivePaths)]; this.LogNetworkTraffic = logNetworkTraffic ?? (bool)SConfig.DefaultValues[nameof(this.LogNetworkTraffic)]; this.ConsoleColors = consoleColors; diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 4d6deb15..40979b09 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1333,8 +1333,7 @@ namespace StardewModdingAPI.Framework onAssetLoaded: this.OnAssetLoaded, onAssetsInvalidated: this.OnAssetsInvalidated, getFileLookup: this.GetFileLookup, - requestAssetOperations: this.RequestAssetOperations, - useRawImageLoading: this.Settings.UseRawImageLoading + requestAssetOperations: this.RequestAssetOperations ); if (this.ContentCore.Language != this.Translator.LocaleEnum) this.Translator.SetLocale(this.ContentCore.GetLocale(), this.ContentCore.Language); diff --git a/src/SMAPI/SMAPI.config.json b/src/SMAPI/SMAPI.config.json index 2d4239ba..635e3add 100644 --- a/src/SMAPI/SMAPI.config.json +++ b/src/SMAPI/SMAPI.config.json @@ -54,12 +54,6 @@ copy all the settings, or you may cause bugs due to overridden changes in future */ "UseCaseInsensitivePaths": null, - /** - * Whether to use raw image data when possible, instead of initializing an XNA Texture2D - * instance through the GPU. - */ - "UseRawImageLoading": true, - /** * Whether to add a section to the 'mod issues' list for mods which directly use potentially * sensitive .NET APIs like file or shell access. Note that many mods do this legitimately as From 4d2ad379b4198fa7a341c6ba2bb6455d488b414f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 9 Oct 2022 19:29:18 -0400 Subject: [PATCH 39/40] fix package error --- build/windows/prepare-install-package.ps1 | 6 +++++- src/SMAPI.Toolkit/SMAPI.Toolkit.csproj | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/build/windows/prepare-install-package.ps1 b/build/windows/prepare-install-package.ps1 index 002bcbca..71de1154 100644 --- a/build/windows/prepare-install-package.ps1 +++ b/build/windows/prepare-install-package.ps1 @@ -154,10 +154,14 @@ 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", "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", "VdfConverter.dll")) { + 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" } + if ($folder -eq "windows") { + cp "$smapiBin/VdfConverter.dll" "$bundlePath/smapi-internal" + } + cp "$smapiBin/SMAPI.config.json" "$bundlePath/smapi-internal/config.json" cp "$smapiBin/SMAPI.metadata.json" "$bundlePath/smapi-internal/metadata.json" if ($folder -eq "linux" -or $folder -eq "macOS") { diff --git a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj index 0086f38a..10f1df70 100644 --- a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj +++ b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj @@ -14,7 +14,7 @@ - + From ee77efcc976ef1a5ee64933a6174d2fac9c6d0f9 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 9 Oct 2022 19:42:24 -0400 Subject: [PATCH 40/40] prepare for release --- build/common.targets | 2 +- docs/release-notes.md | 12 +++++++----- docs/technical/mod-package.md | 4 +++- src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj | 2 +- 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 +- 8 files changed, 19 insertions(+), 15 deletions(-) diff --git a/build/common.targets b/build/common.targets index 7fe66fab..02cf69bd 100644 --- a/build/common.targets +++ b/build/common.targets @@ -7,7 +7,7 @@ repo. It imports the other MSBuild files as needed. - 3.16.2 + 3.17.0 SMAPI latest $(AssemblySearchPaths);{GAC} diff --git a/docs/release-notes.md b/docs/release-notes.md index b5a0adaf..7574d62b 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -7,20 +7,22 @@ _If needed, you can update to SMAPI 3.16.0 first and then install the latest version._ --> -## Upcoming release +## 3.17.0 +Released 09 October 2022 for Stardew Valley 1.5.6 or later. See [release highlights](https://www.patreon.com/posts/73090322). + * For players: - * You can now download SMAPI 'strict mode' from the [Nexus optional files](https://www.nexusmods.com/stardewvalley/mods/2400/). This removes all deprecated APIs, which may significantly improve performance. However mods which still show deprecation warnings won't work. - * The SMAPI installer now also detects game folders listed in Steam's `.vdf` library data on Windows (thanks to pizzaoverhead!). + * You can now download SMAPI 'strict mode' from [Nexus files](https://www.nexusmods.com/stardewvalley/mods/2400/?tab=files), which removes all deprecated APIs. This may significantly improve performance, but mods which still show deprecation warnings won't work. + * The SMAPI installer now also detects game folders in Steam's `.vdf` library data on Windows (thanks to pizzaoverhead!). * SMAPI now prevents mods from enabling Harmony debug mode, which impacts performance and creates a file on your desktop. _You can allow debug mode by editing `smapi-internal/config.json` in your game folder._ * Optimized performance and memory usage (thanks to atravita!). * Other internal optimizations. * Added more file extensions to ignore when searching for mod folders: `.7z`, `.tar`, `.tar.gz`, and `.xcf` (thanks to atravita!). - * Removed `UseRawImageLoading` option. This is now always enabled, except when PyTK is installed. + * Removed transitional `UseRawImageLoading` option added in 3.15.0. This is now always enabled, except when PyTK is installed. * Fixed update alerts incorrectly shown for prerelease versions on GitHub that aren't marked as prerelease. * For mod authors: - * When [providing a mod API for a C# mod](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Integrations), you can now get an optional parameter with the mod requesting the API (thanks to KhloeLeclair!). This avoids needing the pattern where each method needs the requesting mod's manifest. + * When [providing a mod API in a C# mod](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Integrations), you can now get the mod requesting it as an optional parameter (thanks to KhloeLeclair!). * SMAPI now treats square brackets in the manifest `Name` field as round ones to avoid breaking tools which parse log files. * Made deprecation message wording stronger for the upcoming SMAPI 4.0.0 release. * The `Texture2D.Name` field is now set earlier to support mods like SpriteMaster. diff --git a/docs/technical/mod-package.md b/docs/technical/mod-package.md index c483754e..77260a57 100644 --- a/docs/technical/mod-package.md +++ b/docs/technical/mod-package.md @@ -412,7 +412,9 @@ The NuGet package is generated automatically in `StardewModdingAPI.ModBuildConfi when you compile it. ## Release notes -### Upcoming release +### 4.0.2 +Released 09 October 2022. + * Switched to the newer crossplatform `portable` debug symbols (thanks to lanturnalis!). * Fixed `BundleExtraAssemblies` option being partly case-sensitive. * Fixed `BundleExtraAssemblies` not applying `All` value to game assemblies. diff --git a/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj b/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj index e25da168..cded6f65 100644 --- a/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj +++ b/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj @@ -10,7 +10,7 @@ Pathoschild.Stardew.ModBuildConfig Build package for SMAPI mods - 4.0.1 + 4.0.2 Pathoschild Automates the build configuration for crossplatform Stardew Valley SMAPI mods. For SMAPI 3.13.0 or later. MIT diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json index abe8b334..754e543a 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.16.2", + "Version": "3.17.0", "Description": "Adds SMAPI console commands that let you manipulate the game.", "UniqueID": "SMAPI.ConsoleCommands", "EntryDll": "ConsoleCommands.dll", - "MinimumApiVersion": "3.16.2" + "MinimumApiVersion": "3.17.0" } diff --git a/src/SMAPI.Mods.ErrorHandler/manifest.json b/src/SMAPI.Mods.ErrorHandler/manifest.json index b6764bc0..8f94e5ab 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.16.2", + "Version": "3.17.0", "Description": "Handles some common vanilla errors to log more useful info or avoid breaking the game.", "UniqueID": "SMAPI.ErrorHandler", "EntryDll": "ErrorHandler.dll", - "MinimumApiVersion": "3.16.2" + "MinimumApiVersion": "3.17.0" } diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json index a2657168..a9401fc3 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.16.2", + "Version": "3.17.0", "Description": "Automatically backs up all your saves once per day into its folder.", "UniqueID": "SMAPI.SaveBackup", "EntryDll": "SaveBackup.dll", - "MinimumApiVersion": "3.16.2" + "MinimumApiVersion": "3.17.0" } diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 493aebd9..31dafa8e 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.16.2"; + internal static string RawApiVersion = "3.17.0"; } /// Contains SMAPI's constants and assumptions.