From a546fd113f431bd8888da50aad087213193c937e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 25 May 2022 18:02:48 -0400 Subject: add experimental image load rewrite --- .../Framework/ContentManagers/ModContentManager.cs | 41 +++++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) (limited to 'src/SMAPI/Framework/ContentManagers') diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 1b94b8c6..055dcc5f 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -7,6 +7,7 @@ using BmFont; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; +using SkiaSharp; using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Toolkit.Serialization; @@ -25,6 +26,9 @@ namespace StardewModdingAPI.Framework.ContentManagers /********* ** Fields *********/ + /// Whether to use a newer approach when loading image files from mod folder which may be faster. + private readonly bool UseExperimentalImageLoading; + /// Encapsulates SMAPI's JSON file parsing. private readonly JsonHelper JsonHelper; @@ -57,13 +61,15 @@ 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 . - 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) + /// Whether to use a newer approach when loading image files from mod folder which may be faster. + 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 useExperimentalImageLoading) : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: true) { this.GameContentManager = gameContentManager; this.FileLookup = fileLookup; this.JsonHelper = jsonHelper; this.ModName = modName; + this.UseExperimentalImageLoading = useExperimentalImageLoading; this.TryLocalizeKeys = false; } @@ -187,10 +193,35 @@ namespace StardewModdingAPI.Framework.ContentManagers throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(Texture2D)}'."); // load - using FileStream stream = File.OpenRead(file.FullName); - Texture2D texture = Texture2D.FromStream(Game1.graphics.GraphicsDevice, stream); - texture = this.PremultiplyTransparency(texture); - return (T)(object)texture; + if (this.UseExperimentalImageLoading) + { + // load raw data + using FileStream stream = File.OpenRead(file.FullName); + using SKBitmap bitmap = SKBitmap.Decode(stream); + SKPMColor[] rawPixels = SKPMColor.PreMultiply(bitmap.Pixels); + + // convert to XNA pixel format + Color[] pixels = new Color[rawPixels.Length]; + for (int i = pixels.Length - 1; i >= 0; i--) + { + SKPMColor pixel = rawPixels[i]; + pixels[i] = pixel.Alpha == 0 + ? Color.Transparent + : new Color(r: pixel.Red, g: pixel.Green, b: pixel.Blue, alpha: pixel.Alpha); + } + + // create texture + Texture2D texture = new(Game1.graphics.GraphicsDevice, bitmap.Width, bitmap.Height); + texture.SetData(pixels); + return (T)(object)texture; + } + else + { + using FileStream stream = File.OpenRead(file.FullName); + Texture2D texture = Texture2D.FromStream(Game1.graphics.GraphicsDevice, stream); + texture = this.PremultiplyTransparency(texture); + return (T)(object)texture; + } } /// Load an unpacked image file (.tbin or .tmx). -- cgit From 4708385f696d2e47d24e795f210752f4b0224bff Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 25 May 2022 22:46:51 -0400 Subject: add IRawTextureData asset type --- .../ContentManagers/GameContentManager.cs | 4 ++++ .../Framework/ContentManagers/ModContentManager.cs | 23 ++++++++++++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) (limited to 'src/SMAPI/Framework/ContentManagers') diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index 4390d472..446f4a67 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -9,6 +9,7 @@ using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Events; using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.Deprecations; +using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Framework.Utilities; using StardewModdingAPI.Internal; @@ -93,6 +94,9 @@ namespace StardewModdingAPI.Framework.ContentManagers /// public override T LoadExact(IAssetName assetName, bool useCache) { + if (typeof(IRawTextureData).IsAssignableFrom(typeof(T))) + throw new SContentLoadException(ContentLoadErrorType.Other, $"Can't load {nameof(IRawTextureData)} assets from the game content pipeline. This asset type is only available for mod files."); + // raise first-load callback if (GameContentManager.IsFirstLoad) { diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 055dcc5f..eb4f4555 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -8,6 +8,7 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using SkiaSharp; +using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Toolkit.Serialization; @@ -188,12 +189,17 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The file to load. private T LoadImageFile(IAssetName assetName, FileInfo file) { - // validate + // validate type + bool asRawData = false; if (typeof(T) != typeof(Texture2D)) - throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(Texture2D)}'."); + { + asRawData = typeof(T) == typeof(IRawTextureData); + if (!asRawData) + throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(Texture2D)}' or '{typeof(IRawTextureData)}'."); + } // load - if (this.UseExperimentalImageLoading) + if (asRawData || this.UseExperimentalImageLoading) { // load raw data using FileStream stream = File.OpenRead(file.FullName); @@ -211,9 +217,14 @@ namespace StardewModdingAPI.Framework.ContentManagers } // create texture - Texture2D texture = new(Game1.graphics.GraphicsDevice, bitmap.Width, bitmap.Height); - texture.SetData(pixels); - return (T)(object)texture; + if (asRawData) + return (T)(object)new RawTextureData(bitmap.Width, bitmap.Height, pixels); + else + { + Texture2D texture = new(Game1.graphics.GraphicsDevice, bitmap.Width, bitmap.Height); + texture.SetData(pixels); + return (T)(object)texture; + } } else { -- cgit From 769475166ab3b92cd3763bb86e364a8b2c7d914f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 26 May 2022 00:51:11 -0400 Subject: enable raw image loading by default, rename setting --- src/SMAPI/Framework/ContentManagers/ModContentManager.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'src/SMAPI/Framework/ContentManagers') diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index eb4f4555..f0e9b1b9 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -27,8 +27,8 @@ namespace StardewModdingAPI.Framework.ContentManagers /********* ** Fields *********/ - /// Whether to use a newer approach when loading image files from mod folder which may be faster. - private readonly bool UseExperimentalImageLoading; + /// 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; @@ -62,15 +62,15 @@ 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 a newer approach when loading image files from mod folder which may be faster. - 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 useExperimentalImageLoading) + /// 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) : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: true) { this.GameContentManager = gameContentManager; this.FileLookup = fileLookup; this.JsonHelper = jsonHelper; this.ModName = modName; - this.UseExperimentalImageLoading = useExperimentalImageLoading; + this.UseRawImageLoading = useRawImageLoading; this.TryLocalizeKeys = false; } @@ -199,7 +199,7 @@ namespace StardewModdingAPI.Framework.ContentManagers } // load - if (asRawData || this.UseExperimentalImageLoading) + if (asRawData || this.UseRawImageLoading) { // load raw data using FileStream stream = File.OpenRead(file.FullName); -- cgit From 5ffa260e02fc8a6eae40c1fd798da2a4877262c0 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 26 May 2022 01:41:49 -0400 Subject: add validation error when loading XNB file as IRawTextureData --- src/SMAPI/Framework/ContentManagers/ModContentManager.cs | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/SMAPI/Framework/ContentManagers') diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index f0e9b1b9..44c9d8e4 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -258,6 +258,9 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The asset name relative to the loader root directory. private T LoadXnbFile(IAssetName assetName) { + if (typeof(T) == typeof(IRawTextureData)) + 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."); + // 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. IAssetName loadName = assetName.Name.EndsWith(".xnb", StringComparison.OrdinalIgnoreCase) -- cgit From 4f6965eef3c7690dcfc1e1946362a84f68f40767 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 28 May 2022 15:07:19 -0400 Subject: encapsulate loading the raw image data for mod patching --- .../Framework/ContentManagers/ModContentManager.cs | 53 +++++++++++++++------- 1 file changed, 36 insertions(+), 17 deletions(-) (limited to 'src/SMAPI/Framework/ContentManagers') diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 44c9d8e4..fb031ccb 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; @@ -201,27 +202,13 @@ namespace StardewModdingAPI.Framework.ContentManagers // load if (asRawData || this.UseRawImageLoading) { - // load raw data - using FileStream stream = File.OpenRead(file.FullName); - using SKBitmap bitmap = SKBitmap.Decode(stream); - SKPMColor[] rawPixels = SKPMColor.PreMultiply(bitmap.Pixels); - - // convert to XNA pixel format - Color[] pixels = new Color[rawPixels.Length]; - for (int i = pixels.Length - 1; i >= 0; i--) - { - SKPMColor pixel = rawPixels[i]; - pixels[i] = pixel.Alpha == 0 - ? Color.Transparent - : new Color(r: pixel.Red, g: pixel.Green, b: pixel.Blue, alpha: pixel.Alpha); - } + this.LoadRawImageData(file, out int width, out int height, out Color[] pixels, asRawData); - // create texture if (asRawData) - return (T)(object)new RawTextureData(bitmap.Width, bitmap.Height, pixels); + return (T)(object)new RawTextureData(width, height, pixels); else { - Texture2D texture = new(Game1.graphics.GraphicsDevice, bitmap.Width, bitmap.Height); + Texture2D texture = new(Game1.graphics.GraphicsDevice, width, height); texture.SetData(pixels); return (T)(object)texture; } @@ -235,6 +222,38 @@ namespace StardewModdingAPI.Framework.ContentManagers } } + /// Load the raw image data from a file on disk. + /// The file whose data to load. + /// The pixel width for the loaded image data. + /// The pixel height for the loaded image data. + /// The premultiplied pixel data. + /// Whether the data is being loaded for an (true) or (false) instance. + /// This is separate to let framework mods intercept the data before it's loaded, if needed. + [SuppressMessage("ReSharper", "UnusedParameter.Local", Justification = "The 'forRawData' parameter is only added for mods which may intercept this method.")] + [SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "The 'forRawData' parameter is only added for mods which may intercept this method.")] + private void LoadRawImageData(FileInfo file, out int width, out int height, out Color[] pixels, bool forRawData) + { + // load raw data + SKPMColor[] rawPixels; + { + using FileStream stream = File.OpenRead(file.FullName); + using SKBitmap bitmap = SKBitmap.Decode(stream); + rawPixels = SKPMColor.PreMultiply(bitmap.Pixels); + width = bitmap.Width; + height = bitmap.Height; + } + + // convert to XNA pixel format + pixels = new Color[rawPixels.Length]; + for (int i = pixels.Length - 1; i >= 0; i--) + { + SKPMColor pixel = rawPixels[i]; + pixels[i] = pixel.Alpha == 0 + ? Color.Transparent + : new Color(r: pixel.Red, g: pixel.Green, b: pixel.Blue, alpha: pixel.Alpha); + } + } + /// Load an unpacked image file (.tbin or .tmx). /// The type of asset to load. /// The asset name relative to the loader root directory. -- cgit From 9d21e0bbecb64243cf98e6c9a10ed5aa4ceed2bf Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 28 May 2022 19:17:33 -0400 Subject: simplify pixel conversion loop --- src/SMAPI/Framework/ContentManagers/ModContentManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/SMAPI/Framework/ContentManagers') diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index fb031ccb..e1d9ce78 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -245,7 +245,7 @@ namespace StardewModdingAPI.Framework.ContentManagers // convert to XNA pixel format pixels = new Color[rawPixels.Length]; - for (int i = pixels.Length - 1; i >= 0; i--) + for (int i = 0; i < pixels.Length; i++) { SKPMColor pixel = rawPixels[i]; pixels[i] = pixel.Alpha == 0 -- cgit From 5585f5e876459e1764e44754b5b7d933fa1456de Mon Sep 17 00:00:00 2001 From: Ameisen <14104310+ameisen@users.noreply.github.com> Date: Sun, 29 May 2022 18:11:23 -0500 Subject: Refactored ModContentManager.cs so it actually fit on my 1440p screens. Changed LocalTilesheetExtensions into an array. Marked 'CreateTemporary' as 'Obsolete' which is conventional for methods that only throw. Moved the type validation logic into its own method as it's largely shared for each loader. Changed allocators to use `GC.AllocateUninitializedArray`, as the data does not need to be initialized. Changed `LoadRawImageData` to use a `ValueTuple` return instead of returning with multiple `out`s, which is bad practice. Preferred rethrowing handlers rather than exception filters (which generate bizarre and _very difficult to patch_ code). Marked GetLoadError as debugger step through and hidden, as it's just an exception generator. Marked PremultiplyTransparency, GetContentKeyForTilesheetImageSource, and LoadRawImageData as static as they have no dependency on instance data (nor should they). Fixed `.xnb` extension search to properly use OrdinalIgnoreCase. --- .../Framework/ContentManagers/ModContentManager.cs | 239 ++++++++++++++++----- 1 file changed, 183 insertions(+), 56 deletions(-) (limited to 'src/SMAPI/Framework/ContentManagers') diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index e1d9ce78..fe5aaf5d 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -1,11 +1,11 @@ using System; -using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Drawing; using System.Globalization; using System.IO; using System.Linq; using BmFont; -using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using SkiaSharp; @@ -19,11 +19,12 @@ using StardewValley; using xTile; using xTile.Format; using xTile.Tiles; +using Color = Microsoft.Xna.Framework.Color; namespace StardewModdingAPI.Framework.ContentManagers { /// A content manager which handles reading files from a SMAPI mod folder with support for unpacked files. - internal class ModContentManager : BaseContentManager + internal sealed class ModContentManager : BaseContentManager { /********* ** Fields @@ -44,7 +45,7 @@ namespace StardewModdingAPI.Framework.ContentManagers private readonly IFileLookup FileLookup; /// If a map tilesheet's image source has no file extensions, the file extensions to check for in the local mod folder. - private static readonly HashSet LocalTilesheetExtensions = new(StringComparer.OrdinalIgnoreCase) { ".png", ".xnb" }; + private static readonly string[] LocalTilesheetExtensions = { ".png", ".xnb" }; /********* @@ -64,8 +65,21 @@ namespace StardewModdingAPI.Framework.ContentManagers /// 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) - : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: true) + 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 + ) : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: true) { this.GameContentManager = gameContentManager; this.FileLookup = fileLookup; @@ -102,7 +116,14 @@ 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."); + { + throw this.GetLoadError( + assetName, + ContentLoadErrorType.AccessDenied, + "can't load a different mod's managed asset key through this mod content manager." + ); + } + assetName = relativePath; } } @@ -127,7 +148,11 @@ namespace StardewModdingAPI.Framework.ContentManagers _ => this.HandleUnknownFileType(assetName, file) }; } - catch (Exception ex) when (ex is not SContentLoadException) + catch (SContentLoadException) + { + throw; + } + catch (Exception ex) { throw this.GetLoadError(assetName, ContentLoadErrorType.Other, "an unexpected error occurred.", ex); } @@ -138,6 +163,7 @@ namespace StardewModdingAPI.Framework.ContentManagers } /// + [Obsolete($"Temporary {nameof(ModContentManager)}s are unsupported")] public override LocalizedContentManager CreateTemporary() { throw new NotSupportedException("Can't create a temporary mod content manager."); @@ -157,6 +183,67 @@ namespace StardewModdingAPI.Framework.ContentManagers /********* ** Private methods *********/ + /// + /// Validates that the provided type is compatible with . + /// + /// Type to validate compatibility of. + /// Type to validate compatibility against. + /// The asset name relative to the loader root directory. + /// The file being loaded. + /// The exception to throw if the type validation fails, otherwise . + /// if the type validation succeeds, otherwise + private bool ValidateType( + IAssetName assetName, + FileInfo file, + [NotNullWhen(false)] out SContentLoadException? exception + ) + { + if (typeof(TInput).IsAssignableFrom(typeof(TExpected))) + { + exception = null; + return true; + } + + exception = this.GetLoadError( + assetName, + ContentLoadErrorType.InvalidData, + $"can't read file with extension '{file.Extension}' as type '{typeof(TInput)}'; must be type '{typeof(TExpected)}'." + ); + return false; + } + + /// + /// Validates that the provided type + /// is compatible with or + /// + /// Type to validate compatibility of. + /// First type to validate compatibility against. + /// /// Second type to validate compatibility against. + /// The asset name relative to the loader root directory. + /// The file being loaded. + /// The exception to throw if the type validation fails, otherwise . + /// if the type validation succeeds, otherwise + private bool ValidateType( + IAssetName assetName, + FileInfo file, + [NotNullWhen(false)] out SContentLoadException? exception + ) + { + if (typeof(TInput).IsAssignableFrom(typeof(TExpected0)) || typeof(TInput).IsAssignableFrom(typeof(TExpected1))) + { + exception = null; + return true; + } + + exception = this.GetLoadError( + assetName, + ContentLoadErrorType.InvalidData, + $"can't read file with extension '{file.Extension}' as type '{typeof(TInput)}'; must be type '{typeof(TExpected0)}' or '{typeof(TExpected1)}'." + ); + return false; + } + + /// Load an unpacked font file (.fnt). /// The type of asset to load. /// The asset name relative to the loader root directory. @@ -164,8 +251,10 @@ namespace StardewModdingAPI.Framework.ContentManagers private T LoadFont(IAssetName assetName, FileInfo file) { // validate - if (!typeof(T).IsAssignableFrom(typeof(XmlSource))) - throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(XmlSource)}'."); + if (!this.ValidateType(assetName, file, out var exception)) + { + throw exception; + } // load string source = File.ReadAllText(file.FullName); @@ -179,7 +268,10 @@ 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 + { + // should never happen as we check for file existence before calling this method + throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidData, "the JSON file is invalid."); + } return asset; } @@ -191,24 +283,23 @@ namespace StardewModdingAPI.Framework.ContentManagers private T LoadImageFile(IAssetName assetName, FileInfo file) { // validate type - bool asRawData = false; - if (typeof(T) != typeof(Texture2D)) + if (!this.ValidateType(assetName, file, out var exception)) { - asRawData = typeof(T) == typeof(IRawTextureData); - if (!asRawData) - throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(Texture2D)}' or '{typeof(IRawTextureData)}'."); + throw exception; } + bool asRawData = typeof(T).IsAssignableTo(typeof(IRawTextureData)); + // load if (asRawData || this.UseRawImageLoading) { - this.LoadRawImageData(file, out int width, out int height, out Color[] pixels, asRawData); + (Size size, Color[] pixels) = ModContentManager.LoadRawImageData(file, asRawData); if (asRawData) - return (T)(object)new RawTextureData(width, height, pixels); + return (T)(object)new RawTextureData(size.Width, size.Height, pixels); else { - Texture2D texture = new(Game1.graphics.GraphicsDevice, width, height); + Texture2D texture = new(Game1.graphics.GraphicsDevice, size.Width, size.Height); texture.SetData(pixels); return (T)(object)texture; } @@ -217,34 +308,32 @@ namespace StardewModdingAPI.Framework.ContentManagers { using FileStream stream = File.OpenRead(file.FullName); Texture2D texture = Texture2D.FromStream(Game1.graphics.GraphicsDevice, stream); - texture = this.PremultiplyTransparency(texture); + texture = ModContentManager.PremultiplyTransparency(texture); return (T)(object)texture; } } /// Load the raw image data from a file on disk. /// The file whose data to load. - /// The pixel width for the loaded image data. - /// The pixel height for the loaded image data. - /// The premultiplied pixel data. /// Whether the data is being loaded for an (true) or (false) instance. /// This is separate to let framework mods intercept the data before it's loaded, if needed. [SuppressMessage("ReSharper", "UnusedParameter.Local", Justification = "The 'forRawData' parameter is only added for mods which may intercept this method.")] [SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "The 'forRawData' parameter is only added for mods which may intercept this method.")] - private void LoadRawImageData(FileInfo file, out int width, out int height, out Color[] pixels, bool forRawData) + private static (Size Size, Color[] Data) LoadRawImageData(FileInfo file, bool forRawData) { + Size size; + // load raw data SKPMColor[] rawPixels; { using FileStream stream = File.OpenRead(file.FullName); using SKBitmap bitmap = SKBitmap.Decode(stream); rawPixels = SKPMColor.PreMultiply(bitmap.Pixels); - width = bitmap.Width; - height = bitmap.Height; + size = new(bitmap.Width, bitmap.Height); } // convert to XNA pixel format - pixels = new Color[rawPixels.Length]; + var pixels = GC.AllocateUninitializedArray(rawPixels.Length); for (int i = 0; i < pixels.Length; i++) { SKPMColor pixel = rawPixels[i]; @@ -252,6 +341,8 @@ namespace StardewModdingAPI.Framework.ContentManagers ? Color.Transparent : new Color(r: pixel.Red, g: pixel.Green, b: pixel.Blue, alpha: pixel.Alpha); } + + return (size, pixels); } /// Load an unpacked image file (.tbin or .tmx). @@ -261,8 +352,10 @@ namespace StardewModdingAPI.Framework.ContentManagers private T LoadMapFile(IAssetName assetName, FileInfo file) { // validate - if (typeof(T) != typeof(Map)) - throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(Map)}'."); + if (!this.ValidateType(assetName, file, out var exception)) + { + throw exception; + } // load FormatManager formatManager = FormatManager.Instance; @@ -277,8 +370,14 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The asset name relative to the loader root directory. private T LoadXnbFile(IAssetName assetName) { - if (typeof(T) == typeof(IRawTextureData)) - 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."); + 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." + ); + } // 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. @@ -303,7 +402,11 @@ 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'."); + throw this.GetLoadError( + assetName, + ContentLoadErrorType.InvalidName, + $"unknown file extension '{file.Extension}'; must be one of: '.fnt', '.json', '.png', '.tbin', '.tmx', or '.xnb'." + ); } /// Get an error which indicates that an asset couldn't be loaded. @@ -311,6 +414,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The asset name that failed to load. /// The reason the file couldn't be loaded. /// The underlying exception, if applicable. + [DebuggerStepThrough, DebuggerHidden] private SContentLoadException GetLoadError(IAssetName assetName, ContentLoadErrorType errorType, string reasonPhrase, Exception? exception = null) { return new(errorType, $"Failed loading asset '{assetName}' from {this.Name}: {reasonPhrase}", exception); @@ -325,16 +429,16 @@ namespace StardewModdingAPI.Framework.ContentManagers FileInfo file = this.FileLookup.GetFile(path); // try with default image extensions - if (!file.Exists && typeof(Texture2D).IsAssignableFrom(typeof(T)) && !ModContentManager.LocalTilesheetExtensions.Contains(file.Extension)) + if (file.Exists || !typeof(Texture2D).IsAssignableFrom(typeof(T)) || ModContentManager.LocalTilesheetExtensions.Contains(file.Extension)) + return file; + + foreach (string extension in ModContentManager.LocalTilesheetExtensions) { - foreach (string extension in ModContentManager.LocalTilesheetExtensions) + FileInfo result = new(file.FullName + extension); + if (result.Exists) { - FileInfo result = new(file.FullName + extension); - if (result.Exists) - { - file = result; - break; - } + file = result; + break; } } @@ -345,10 +449,10 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The texture to premultiply. /// Returns a premultiplied texture. /// Based on code by David Gouveia. - private Texture2D PremultiplyTransparency(Texture2D texture) + private static Texture2D PremultiplyTransparency(Texture2D texture) { // premultiply pixels - Color[] data = new Color[texture.Width * texture.Height]; + Color[] data = GC.AllocateUninitializedArray(texture.Width * texture.Height); texture.GetData(data); bool changed = false; for (int i = 0; i < data.Length; i++) @@ -357,7 +461,12 @@ namespace StardewModdingAPI.Framework.ContentManagers 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()) + 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; } @@ -370,7 +479,10 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Fix custom map tilesheet paths so they can be found by the content manager. /// The map whose tilesheets to fix. /// The relative map path within the mod folder. - /// Whether to undo the game's eager tilesheet path prefixing for maps loaded from an .xnb file, which incorrectly prefixes tilesheet paths with the map's local asset key folder. + /// + /// Whether to undo the game's eager tilesheet path prefixing for maps loaded from an .xnb file, + /// which incorrectly prefixes tilesheet paths with the map's local asset key folder. + /// /// A map tilesheet couldn't be resolved. private void FixTilesheetPaths(Map map, string relativeMapPath, bool fixEagerPathPrefixes) { @@ -388,18 +500,28 @@ namespace StardewModdingAPI.Framework.ContentManagers // reverse incorrect eager tilesheet path prefixing if (fixEagerPathPrefixes && relativeMapFolder.Length > 0 && imageSource.StartsWith(relativeMapFolder)) - imageSource = imageSource.Substring(relativeMapFolder.Length + 1); + imageSource = imageSource[(relativeMapFolder.Length + 1)..]; // validate tilesheet path string errorPrefix = $"{this.ModName} loaded map '{relativeMapPath}' with invalid tilesheet path '{imageSource}'."; if (Path.IsPathRooted(imageSource) || PathUtilities.GetSegments(imageSource).Contains("..")) - throw new SContentLoadException(ContentLoadErrorType.InvalidData, $"{errorPrefix} Tilesheet paths must be a relative path without directory climbing (../)."); + { + throw new SContentLoadException( + ContentLoadErrorType.InvalidData, + $"{errorPrefix} Tilesheet paths must be a relative path without directory climbing (../)." + ); + } // load best match try { if (!this.TryGetTilesheetAssetName(relativeMapFolder, imageSource, out IAssetName? assetName, out string? error)) - throw new SContentLoadException(ContentLoadErrorType.InvalidData, $"{errorPrefix} {error}"); + { + throw new SContentLoadException( + ContentLoadErrorType.InvalidData, + $"{errorPrefix} {error}" + ); + } if (assetName is not null) { @@ -409,7 +531,11 @@ namespace StardewModdingAPI.Framework.ContentManagers tilesheet.ImageSource = assetName.Name; } } - catch (Exception ex) when (ex is not SContentLoadException) + catch (SContentLoadException) + { + throw; + } + catch (Exception ex) { throw new SContentLoadException(ContentLoadErrorType.InvalidData, $"{errorPrefix} The tilesheet couldn't be loaded.", ex); } @@ -425,7 +551,6 @@ namespace StardewModdingAPI.Framework.ContentManagers /// See remarks on . private bool TryGetTilesheetAssetName(string modRelativeMapFolder, string relativePath, out IAssetName? assetName, out string? error) { - assetName = null; error = null; // nothing to do @@ -440,7 +565,7 @@ namespace StardewModdingAPI.Framework.ContentManagers // opened in Tiled, while still mapping it to the vanilla 'Maps/spring_town' asset at runtime. { string filename = Path.GetFileName(relativePath); - if (filename.StartsWith(".")) + if (filename.StartsWith('.')) relativePath = Path.Combine(Path.GetDirectoryName(relativePath) ?? "", filename.TrimStart('.')); } @@ -455,10 +580,11 @@ namespace StardewModdingAPI.Framework.ContentManagers } // get from game assets - IAssetName contentKey = this.Coordinator.ParseAssetName(this.GetContentKeyForTilesheetImageSource(relativePath), allowLocales: false); + AssetName contentKey = this.Coordinator.ParseAssetName(ModContentManager.GetContentKeyForTilesheetImageSource(relativePath), allowLocales: false); try { - this.GameContentManager.LoadLocalized(contentKey, this.GameContentManager.Language, useCache: true); // no need to bypass cache here, since we're not storing the asset + // no need to bypass cache here, since we're not storing the asset + this.GameContentManager.LoadLocalized(contentKey, this.GameContentManager.Language, useCache: true); assetName = contentKey; return true; } @@ -476,6 +602,7 @@ namespace StardewModdingAPI.Framework.ContentManagers } // not found + assetName = null; error = "The tilesheet couldn't be found relative to either map file or the game's content folder."; return false; } @@ -486,16 +613,16 @@ namespace StardewModdingAPI.Framework.ContentManagers { // get file path string path = Path.Combine(this.GameContentManager.FullRootDirectory, key); - if (!path.EndsWith(".xnb")) + if (!path.EndsWith(".xnb", StringComparison.OrdinalIgnoreCase)) path += ".xnb"; // get file - return new FileInfo(path).Exists; + return File.Exists(path); } /// Get the asset key for a tilesheet in the game's Maps content folder. /// The tilesheet image source. - private string GetContentKeyForTilesheetImageSource(string relativePath) + private static string GetContentKeyForTilesheetImageSource(string relativePath) { string key = relativePath; string topFolder = PathUtilities.GetSegments(key, limit: 2)[0]; @@ -506,7 +633,7 @@ namespace StardewModdingAPI.Framework.ContentManagers // remove file extension from unpacked file if (key.EndsWith(".png", StringComparison.OrdinalIgnoreCase)) - key = key.Substring(0, key.Length - 4); + key = key[..^4]; return key; } -- cgit From 87c7095e8d7bfc1c1819e7d17cfea3d07c65dfea Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 30 May 2022 01:22:50 -0400 Subject: apply style conventions --- .../Framework/ContentManagers/ModContentManager.cs | 155 +++++---------------- 1 file changed, 37 insertions(+), 118 deletions(-) (limited to 'src/SMAPI/Framework/ContentManagers') diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index fe5aaf5d..d4a30f71 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -65,21 +65,8 @@ namespace StardewModdingAPI.Framework.ContentManagers /// 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 - ) : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: true) + 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) + : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: true) { this.GameContentManager = gameContentManager; this.FileLookup = fileLookup; @@ -116,14 +103,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." - ); - } - + throw this.GetLoadError(assetName, ContentLoadErrorType.AccessDenied, "can't load a different mod's managed asset key through this mod content manager."); assetName = relativePath; } } @@ -148,12 +128,11 @@ namespace StardewModdingAPI.Framework.ContentManagers _ => this.HandleUnknownFileType(assetName, file) }; } - catch (SContentLoadException) - { - throw; - } catch (Exception ex) { + if (ex is SContentLoadException) + throw; + throw this.GetLoadError(assetName, ContentLoadErrorType.Other, "an unexpected error occurred.", ex); } @@ -183,20 +162,14 @@ namespace StardewModdingAPI.Framework.ContentManagers /********* ** Private methods *********/ - /// - /// Validates that the provided type is compatible with . - /// + /// Validates that the provided type is compatible with . /// Type to validate compatibility of. /// Type to validate compatibility against. /// The asset name relative to the loader root directory. /// The file being loaded. /// The exception to throw if the type validation fails, otherwise . /// if the type validation succeeds, otherwise - private bool ValidateType( - IAssetName assetName, - FileInfo file, - [NotNullWhen(false)] out SContentLoadException? exception - ) + private bool ValidateType(IAssetName assetName, FileInfo file, [NotNullWhen(false)] out SContentLoadException? exception) { if (typeof(TInput).IsAssignableFrom(typeof(TExpected))) { @@ -204,18 +177,11 @@ namespace StardewModdingAPI.Framework.ContentManagers return true; } - exception = this.GetLoadError( - assetName, - ContentLoadErrorType.InvalidData, - $"can't read file with extension '{file.Extension}' as type '{typeof(TInput)}'; must be type '{typeof(TExpected)}'." - ); + exception = this.GetLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(TInput)}'; must be type '{typeof(TExpected)}'."); return false; } - /// - /// Validates that the provided type - /// is compatible with or - /// + /// Validates that the provided type is compatible with or . /// Type to validate compatibility of. /// First type to validate compatibility against. /// /// Second type to validate compatibility against. @@ -223,11 +189,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The file being loaded. /// The exception to throw if the type validation fails, otherwise . /// if the type validation succeeds, otherwise - private bool ValidateType( - IAssetName assetName, - FileInfo file, - [NotNullWhen(false)] out SContentLoadException? exception - ) + private bool ValidateType(IAssetName assetName, FileInfo file, [NotNullWhen(false)] out SContentLoadException? exception) { if (typeof(TInput).IsAssignableFrom(typeof(TExpected0)) || typeof(TInput).IsAssignableFrom(typeof(TExpected1))) { @@ -235,11 +197,7 @@ namespace StardewModdingAPI.Framework.ContentManagers return true; } - exception = this.GetLoadError( - assetName, - ContentLoadErrorType.InvalidData, - $"can't read file with extension '{file.Extension}' as type '{typeof(TInput)}'; must be type '{typeof(TExpected0)}' or '{typeof(TExpected1)}'." - ); + exception = this.GetLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(TInput)}'; must be type '{typeof(TExpected0)}' or '{typeof(TExpected1)}'."); return false; } @@ -252,9 +210,7 @@ namespace StardewModdingAPI.Framework.ContentManagers { // validate if (!this.ValidateType(assetName, file, out var exception)) - { throw exception; - } // load string source = File.ReadAllText(file.FullName); @@ -268,10 +224,7 @@ namespace StardewModdingAPI.Framework.ContentManagers private T LoadDataFile(IAssetName assetName, FileInfo file) { if (!this.JsonHelper.ReadJsonFileIfExists(file.FullName, out T? asset)) - { - // should never happen as we check for file existence before calling this method - throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidData, "the JSON file is invalid."); - } + throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidData, "the JSON file is invalid."); // should never happen since we check for file existence before calling this method return asset; } @@ -284,16 +237,14 @@ namespace StardewModdingAPI.Framework.ContentManagers { // validate type if (!this.ValidateType(assetName, file, out var exception)) - { throw exception; - } bool asRawData = typeof(T).IsAssignableTo(typeof(IRawTextureData)); // load if (asRawData || this.UseRawImageLoading) { - (Size size, Color[] pixels) = ModContentManager.LoadRawImageData(file, asRawData); + (Size size, Color[] pixels) = this.LoadRawImageData(file, asRawData); if (asRawData) return (T)(object)new RawTextureData(size.Width, size.Height, pixels); @@ -308,7 +259,7 @@ namespace StardewModdingAPI.Framework.ContentManagers { using FileStream stream = File.OpenRead(file.FullName); Texture2D texture = Texture2D.FromStream(Game1.graphics.GraphicsDevice, stream); - texture = ModContentManager.PremultiplyTransparency(texture); + texture = this.PremultiplyTransparency(texture); return (T)(object)texture; } } @@ -319,7 +270,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// This is separate to let framework mods intercept the data before it's loaded, if needed. [SuppressMessage("ReSharper", "UnusedParameter.Local", Justification = "The 'forRawData' parameter is only added for mods which may intercept this method.")] [SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "The 'forRawData' parameter is only added for mods which may intercept this method.")] - private static (Size Size, Color[] Data) LoadRawImageData(FileInfo file, bool forRawData) + private (Size Size, Color[] Data) LoadRawImageData(FileInfo file, bool forRawData) { Size size; @@ -353,9 +304,7 @@ namespace StardewModdingAPI.Framework.ContentManagers { // validate if (!this.ValidateType(assetName, file, out var exception)) - { throw exception; - } // load FormatManager formatManager = FormatManager.Instance; @@ -371,13 +320,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." - ); - } + 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."); // 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. @@ -402,11 +345,7 @@ 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'." - ); + throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidName, $"unknown file extension '{file.Extension}'; must be one of '.fnt', '.json', '.png', '.tbin', '.tmx', or '.xnb'."); } /// Get an error which indicates that an asset couldn't be loaded. @@ -429,16 +368,16 @@ namespace StardewModdingAPI.Framework.ContentManagers FileInfo file = this.FileLookup.GetFile(path); // try with default image extensions - if (file.Exists || !typeof(Texture2D).IsAssignableFrom(typeof(T)) || ModContentManager.LocalTilesheetExtensions.Contains(file.Extension)) - return file; - - foreach (string extension in ModContentManager.LocalTilesheetExtensions) + if (!file.Exists && typeof(Texture2D).IsAssignableFrom(typeof(T)) && !ModContentManager.LocalTilesheetExtensions.Contains(file.Extension)) { - FileInfo result = new(file.FullName + extension); - if (result.Exists) + foreach (string extension in ModContentManager.LocalTilesheetExtensions) { - file = result; - break; + FileInfo result = new(file.FullName + extension); + if (result.Exists) + { + file = result; + break; + } } } @@ -449,7 +388,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The texture to premultiply. /// Returns a premultiplied texture. /// Based on code by David Gouveia. - private static Texture2D PremultiplyTransparency(Texture2D texture) + private Texture2D PremultiplyTransparency(Texture2D texture) { // premultiply pixels Color[] data = GC.AllocateUninitializedArray(texture.Width * texture.Height); @@ -461,12 +400,7 @@ namespace StardewModdingAPI.Framework.ContentManagers 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.MaxValu