From fdb74df8a4c899b81009c7e04659be9007545788 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 6 Jun 2022 21:28:57 -0400 Subject: [PATCH 01/27] simplify repeated hash set creation --- src/SMAPI/Framework/Models/SConfig.cs | 7 +++---- src/SMAPI/Framework/SCore.cs | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index 1c7cd3ed..316f7ac3 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -76,7 +76,7 @@ namespace StardewModdingAPI.Framework.Models public ColorSchemeConfig ConsoleColors { get; } /// The mod IDs SMAPI should ignore when performing update checks or validating update keys. - public string[] SuppressUpdateChecks { get; } + public HashSet SuppressUpdateChecks { get; } /******** @@ -110,7 +110,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.SuppressUpdateChecks = suppressUpdateChecks ?? Array.Empty(); + this.SuppressUpdateChecks = new HashSet(suppressUpdateChecks ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); } /// Override the value of . @@ -132,8 +132,7 @@ namespace StardewModdingAPI.Framework.Models custom[name] = value; } - HashSet curSuppressUpdateChecks = new(this.SuppressUpdateChecks, StringComparer.OrdinalIgnoreCase); - if (SConfig.DefaultSuppressUpdateChecks.Count != curSuppressUpdateChecks.Count || SConfig.DefaultSuppressUpdateChecks.Any(p => !curSuppressUpdateChecks.Contains(p))) + if (!this.SuppressUpdateChecks.SetEquals(SConfig.DefaultSuppressUpdateChecks)) custom[nameof(this.SuppressUpdateChecks)] = "[" + string.Join(", ", this.SuppressUpdateChecks) + "]"; return custom; diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 731731d4..fa217f20 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1507,7 +1507,7 @@ namespace StardewModdingAPI.Framework { try { - HashSet suppressUpdateChecks = new HashSet(this.Settings.SuppressUpdateChecks, StringComparer.OrdinalIgnoreCase); + HashSet suppressUpdateChecks = this.Settings.SuppressUpdateChecks; // prepare search model List searchMods = new List(); @@ -1608,7 +1608,7 @@ namespace StardewModdingAPI.Framework using (AssemblyLoader modAssemblyLoader = new(Constants.Platform, this.Monitor, this.Settings.ParanoidWarnings, this.Settings.RewriteMods)) { // init - HashSet suppressUpdateChecks = new HashSet(this.Settings.SuppressUpdateChecks, StringComparer.OrdinalIgnoreCase); + HashSet suppressUpdateChecks = this.Settings.SuppressUpdateChecks; IInterfaceProxyFactory proxyFactory = this.Settings.UsePintail ? new InterfaceProxyFactory() : new OriginalInterfaceProxyFactory(); From 8713914a1af3b5ac7e4a2cdce7e084055ac9cd33 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 8 Jun 2022 23:33:09 -0400 Subject: [PATCH 02/27] avoid NPC pathfinding rebuild if reachable locations didn't change --- docs/release-notes.md | 5 +++++ src/SMAPI/Framework/ContentCoordinator.cs | 6 +++--- src/SMAPI/Metadata/CoreAssetPropagator.cs | 26 +++++++++++------------ 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index d8cfa350..b7605bf6 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,11 +1,16 @@ ← [README](README.md) # Release notes +## Upcoming release +* For mod authors: + * Fixed map edits which change warps sometimes rebuilding the NPC pathfinding cache unnecessarily, which could cause a noticeable delay for players. + ## 3.14.7 Released 01 June 2022 for Stardew Valley 1.5.6 or later. * For players: * Optimized reflection cache to reduce frame skips for some players. + * For mod authors: * Removed `runtimeconfig.json` setting which impacted hot reload support. diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index cfeb35c8..69a39ac7 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -465,7 +465,7 @@ namespace StardewModdingAPI.Framework assets: invalidatedAssets.ToDictionary(p => p.Key, p => p.Value), ignoreWorld: Context.IsWorldFullyUnloaded, out IDictionary propagated, - out bool updatedNpcWarps + out bool updatedWarpRoutes ); // log summary @@ -481,8 +481,8 @@ namespace StardewModdingAPI.Framework ? $"Propagated {propagatedKeys.Length} core assets ({FormatKeyList(propagatedKeys)})." : "Propagated 0 core assets." ); - if (updatedNpcWarps) - report.AppendLine("Updated NPC pathfinding cache."); + if (updatedWarpRoutes) + report.AppendLine("Updated NPC warp route cache."); } this.Monitor.Log(report.ToString().TrimEnd()); } diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 8ed6b591..b783b2b9 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -85,8 +85,8 @@ namespace StardewModdingAPI.Metadata /// The asset keys and types to reload. /// Whether the in-game world is fully unloaded (e.g. on the title screen), so there's no need to propagate changes into the world. /// A lookup of asset names to whether they've been propagated. - /// Whether the NPC pathfinding cache was reloaded. - public void Propagate(IDictionary assets, bool ignoreWorld, out IDictionary propagatedAssets, out bool updatedNpcWarps) + /// Whether the NPC pathfinding warp route cache was reloaded. + public void Propagate(IDictionary assets, bool ignoreWorld, out IDictionary propagatedAssets, out bool changedWarpRoutes) { // get base name lookup propagatedAssets = assets @@ -107,7 +107,7 @@ namespace StardewModdingAPI.Metadata }); // reload assets - updatedNpcWarps = false; + changedWarpRoutes = false; foreach (var bucket in buckets) { switch (bucket.Key) @@ -126,10 +126,10 @@ namespace StardewModdingAPI.Metadata foreach (var entry in bucket) { bool changed = false; - bool curChangedMapWarps = false; + bool curChangedMapRoutes = false; try { - changed = this.PropagateOther(entry.Key, entry.Value, ignoreWorld, out curChangedMapWarps); + changed = this.PropagateOther(entry.Key, entry.Value, ignoreWorld, out curChangedMapRoutes); } catch (Exception ex) { @@ -137,14 +137,14 @@ namespace StardewModdingAPI.Metadata } propagatedAssets[entry.Key] = changed; - updatedNpcWarps = updatedNpcWarps || curChangedMapWarps; + changedWarpRoutes = changedWarpRoutes || curChangedMapRoutes; } break; } } - // reload NPC pathfinding cache if any map changed - if (updatedNpcWarps) + // reload NPC pathfinding cache if any map routes changed + if (changedWarpRoutes) NPC.populateRoutesFromLocationToLocationList(); } @@ -156,14 +156,14 @@ namespace StardewModdingAPI.Metadata /// The asset name to reload. /// The asset type to reload. /// Whether the in-game world is fully unloaded (e.g. on the title screen), so there's no need to propagate changes into the world. - /// Whether any map warps were changed as part of this propagation. + /// Whether the locations reachable by warps from this location changed as part of this propagation. /// Returns whether an asset was loaded. The return value may be true or false, or a non-null value for true. [SuppressMessage("ReSharper", "StringLiteralTypo", Justification = "These deliberately match the asset names.")] - private bool PropagateOther(IAssetName assetName, Type type, bool ignoreWorld, out bool changedWarps) + private bool PropagateOther(IAssetName assetName, Type type, bool ignoreWorld, out bool changedWarpRoutes) { var content = this.MainContentManager; string key = assetName.BaseName; - changedWarps = false; + changedWarpRoutes = false; /**** ** Special case: current map tilesheet @@ -197,7 +197,7 @@ namespace StardewModdingAPI.Metadata static ISet GetWarpSet(GameLocation location) { return new HashSet( - location.warps.Select(p => $"{p.X} {p.Y} {p.TargetName} {p.TargetX} {p.TargetY}") + location.warps.Select(p => p.TargetName) ); } @@ -205,7 +205,7 @@ namespace StardewModdingAPI.Metadata this.UpdateMap(info); var newWarps = GetWarpSet(location); - changedWarps = changedWarps || oldWarps.Count != newWarps.Count || oldWarps.Any(p => !newWarps.Contains(p)); + changedWarpRoutes = changedWarpRoutes || oldWarps.Count != newWarps.Count || oldWarps.Any(p => !newWarps.Contains(p)); anyChanged = true; } } From cb6fcb0450da28607e1a7307f5638cccbd6ce9f7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 6 Jun 2022 21:46:21 -0400 Subject: [PATCH 03/27] rework VerboseLogging option to allow enabling for specific mods --- docs/release-notes.md | 1 + src/SMAPI/Framework/Logging/LogManager.cs | 19 ++++++++++--------- src/SMAPI/Framework/Models/SConfig.cs | 17 ++++++++++------- src/SMAPI/Framework/SCore.cs | 8 ++++---- src/SMAPI/SMAPI.config.json | 12 ++++++++++-- 5 files changed, 35 insertions(+), 22 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index b7605bf6..e1aa47ab 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -4,6 +4,7 @@ ## Upcoming release * For mod authors: * Fixed map edits which change warps sometimes rebuilding the NPC pathfinding cache unnecessarily, which could cause a noticeable delay for players. + * In `smapi-internal/config.json`, you can now enable verbose logging for specific mods (instead of all or nothing). ## 3.14.7 Released 01 June 2022 for Stardew Valley 1.5.6 or later. diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs index ed5b6959..d811ed5c 100644 --- a/src/SMAPI/Framework/Logging/LogManager.cs +++ b/src/SMAPI/Framework/Logging/LogManager.cs @@ -31,8 +31,8 @@ namespace StardewModdingAPI.Framework.Logging /// Prefixing a low-level message with this character indicates that the console interceptor should write the string without intercepting it. (The character itself is not written.) private const char IgnoreChar = InterceptingTextWriter.IgnoreChar; - /// Get a named monitor instance. - private readonly Func GetMonitorImpl; + /// Create a monitor instance given the ID and name. + private readonly Func GetMonitorImpl; /// Regex patterns which match console non-error messages to suppress from the console and log. private readonly Regex[] SuppressConsolePatterns = @@ -88,23 +88,23 @@ namespace StardewModdingAPI.Framework.Logging /// The log file path to write. /// The colors to use for text written to the SMAPI console. /// Whether to output log messages to the console. - /// Whether verbose logging is enabled. This enables more detailed diagnostic messages than are normally needed. + /// The log contexts for which to enable verbose logging, which may show a lot more information to simplify troubleshooting. /// Whether to enable full console output for developers. /// Get the screen ID that should be logged to distinguish between players in split-screen mode, if any. - public LogManager(string logPath, ColorSchemeConfig colorConfig, bool writeToConsole, bool isVerbose, bool isDeveloperMode, Func getScreenIdForLog) + public LogManager(string logPath, ColorSchemeConfig colorConfig, bool writeToConsole, HashSet verboseLogging, bool isDeveloperMode, Func getScreenIdForLog) { // init log file this.LogFile = new LogFileManager(logPath); // init monitor - this.GetMonitorImpl = name => new Monitor(name, LogManager.IgnoreChar, this.LogFile, colorConfig, isVerbose, getScreenIdForLog) + this.GetMonitorImpl = (id, name) => new Monitor(name, LogManager.IgnoreChar, this.LogFile, colorConfig, verboseLogging.Contains("*") || verboseLogging.Contains(id), getScreenIdForLog) { WriteToConsole = writeToConsole, ShowTraceInConsole = isDeveloperMode, ShowFullStampInConsole = isDeveloperMode }; - this.Monitor = this.GetMonitor("SMAPI"); - this.MonitorForGame = this.GetMonitor("game"); + this.Monitor = this.GetMonitor("SMAPI", "SMAPI"); + this.MonitorForGame = this.GetMonitor("game", "game"); // redirect direct console output this.ConsoleInterceptor = new InterceptingTextWriter( @@ -124,10 +124,11 @@ namespace StardewModdingAPI.Framework.Logging } /// Get a monitor instance derived from SMAPI's current settings. + /// The unique ID for the mod context. /// The name of the module which will log messages with this instance. - public Monitor GetMonitor(string name) + public Monitor GetMonitor(string id, string name) { - return this.GetMonitorImpl(name); + return this.GetMonitorImpl(id, name); } /// Set the title of the SMAPI console window. diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index 316f7ac3..240af002 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -20,7 +20,6 @@ namespace StardewModdingAPI.Framework.Models [nameof(UseBetaChannel)] = Constants.ApiVersion.IsPrerelease(), [nameof(GitHubProjectName)] = "Pathoschild/SMAPI", [nameof(WebApiBaseUrl)] = "https://smapi.io/api/", - [nameof(VerboseLogging)] = false, [nameof(LogNetworkTraffic)] = false, [nameof(RewriteMods)] = true, [nameof(UsePintail)] = true, @@ -57,8 +56,9 @@ namespace StardewModdingAPI.Framework.Models /// The base URL for SMAPI's web API, used to perform update checks. public string WebApiBaseUrl { get; } - /// Whether SMAPI should log more information about the game context. - public bool VerboseLogging { get; } + /// The log contexts for which to enable verbose logging, which may show a lot more information to simplify troubleshooting. + /// The possible values are "*" (everything is verbose), "SMAPI", (SMAPI itself), or mod IDs. + public HashSet VerboseLogging { get; } /// Whether SMAPI should rewrite mods for compatibility. public bool RewriteMods { get; } @@ -89,14 +89,14 @@ namespace StardewModdingAPI.Framework.Models /// Whether to show beta versions as valid updates. /// SMAPI's GitHub project name, used to perform update checks. /// The base URL for SMAPI's web API, used to perform update checks. - /// Whether SMAPI should log more information about the game context. + /// 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 the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself. /// >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. /// 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, bool? verboseLogging, bool? rewriteMods, bool? usePintail, 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? usePintail, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, ColorSchemeConfig consoleColors, string[]? suppressUpdateChecks) { this.DeveloperMode = developerMode; this.CheckForUpdates = checkForUpdates ?? (bool)SConfig.DefaultValues[nameof(this.CheckForUpdates)]; @@ -104,7 +104,7 @@ namespace StardewModdingAPI.Framework.Models this.UseBetaChannel = useBetaChannel ?? (bool)SConfig.DefaultValues[nameof(this.UseBetaChannel)]; this.GitHubProjectName = gitHubProjectName; this.WebApiBaseUrl = webApiBaseUrl; - this.VerboseLogging = verboseLogging ?? (bool)SConfig.DefaultValues[nameof(this.VerboseLogging)]; + this.VerboseLogging = new HashSet(verboseLogging ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); this.RewriteMods = rewriteMods ?? (bool)SConfig.DefaultValues[nameof(this.RewriteMods)]; this.UsePintail = usePintail ?? (bool)SConfig.DefaultValues[nameof(this.UsePintail)]; this.UseCaseInsensitivePaths = useCaseInsensitivePaths ?? (bool)SConfig.DefaultValues[nameof(this.UseCaseInsensitivePaths)]; @@ -133,7 +133,10 @@ namespace StardewModdingAPI.Framework.Models } if (!this.SuppressUpdateChecks.SetEquals(SConfig.DefaultSuppressUpdateChecks)) - custom[nameof(this.SuppressUpdateChecks)] = "[" + string.Join(", ", this.SuppressUpdateChecks) + "]"; + custom[nameof(this.SuppressUpdateChecks)] = $"[{string.Join(", ", this.SuppressUpdateChecks)}]"; + + if (this.VerboseLogging.Any()) + custom[nameof(this.VerboseLogging)] = $"[{string.Join(", ", this.VerboseLogging)}]"; return custom; } diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index fa217f20..4f4212dc 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -194,7 +194,7 @@ namespace StardewModdingAPI.Framework if (developerMode.HasValue) this.Settings.OverrideDeveloperMode(developerMode.Value); - this.LogManager = new LogManager(logPath: logPath, colorConfig: this.Settings.ConsoleColors, writeToConsole: writeToConsole, isVerbose: this.Settings.VerboseLogging, isDeveloperMode: this.Settings.DeveloperMode, getScreenIdForLog: this.GetScreenIdForLog); + this.LogManager = new LogManager(logPath: logPath, colorConfig: this.Settings.ConsoleColors, writeToConsole: writeToConsole, verboseLogging: this.Settings.VerboseLogging, isDeveloperMode: this.Settings.DeveloperMode, getScreenIdForLog: this.GetScreenIdForLog); this.CommandManager = new CommandManager(this.Monitor); this.EventManager = new EventManager(this.ModRegistry); SCore.DeprecationManager = new DeprecationManager(this.Monitor, this.ModRegistry); @@ -1827,7 +1827,7 @@ namespace StardewModdingAPI.Framework // load as content pack if (mod.IsContentPack) { - IMonitor monitor = this.LogManager.GetMonitor(mod.DisplayName); + IMonitor monitor = this.LogManager.GetMonitor(manifest.UniqueID, mod.DisplayName); IFileLookup fileLookup = this.GetFileLookup(mod.DirectoryPath); GameContentHelper gameContentHelper = new(this.ContentCore, mod, mod.DisplayName, monitor, this.Reflection); IModContentHelper modContentHelper = new ModContentHelper(this.ContentCore, mod.DirectoryPath, mod, mod.DisplayName, gameContentHelper.GetUnderlyingContentManager(), this.Reflection); @@ -1902,7 +1902,7 @@ namespace StardewModdingAPI.Framework } // init mod helpers - IMonitor monitor = this.LogManager.GetMonitor(mod.DisplayName); + IMonitor monitor = this.LogManager.GetMonitor(manifest.UniqueID, mod.DisplayName); TranslationHelper translationHelper = new(mod, contentCore.GetLocale(), contentCore.Language); IModHelper modHelper; { @@ -1965,7 +1965,7 @@ namespace StardewModdingAPI.Framework ); // create mod helpers - IMonitor packMonitor = this.LogManager.GetMonitor(packManifest.Name); + IMonitor packMonitor = this.LogManager.GetMonitor(packManifest.UniqueID, packManifest.Name); GameContentHelper gameContentHelper = new(contentCore, fakeMod, packManifest.Name, packMonitor, this.Reflection); IModContentHelper packContentHelper = new ModContentHelper(contentCore, packDirPath, fakeMod, packManifest.Name, gameContentHelper.GetUnderlyingContentManager(), this.Reflection); TranslationHelper packTranslationHelper = new(fakeMod, contentCore.GetLocale(), contentCore.Language); diff --git a/src/SMAPI/SMAPI.config.json b/src/SMAPI/SMAPI.config.json index 8324f45b..a6ec42d3 100644 --- a/src/SMAPI/SMAPI.config.json +++ b/src/SMAPI/SMAPI.config.json @@ -16,9 +16,17 @@ copy all the settings, or you may cause bugs due to overridden changes in future */ { /** - * Whether SMAPI should log more information about the game context. + * The logs for which to enable verbose logging, which may show a lot more information to + * simplify troubleshooting. + * + * The possible values are: + * - "*" for everything (not recommended); + * - "SMAPI" for messages from SMAPI itself; + * - mod IDs from their manifest.json files. + * + * For example: [ "SMAPI", "Pathoschild.ContentPatcher" ] */ - "VerboseLogging": false, + "VerboseLogging": [], /** * Whether SMAPI should check for newer versions of SMAPI and mods when you load the game. If new From a546fd113f431bd8888da50aad087213193c937e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 25 May 2022 18:02:48 -0400 Subject: [PATCH 04/27] add experimental image load rewrite --- docs/release-notes.md | 4 ++ src/SMAPI/Framework/ContentCoordinator.cs | 10 ++++- .../ContentManagers/ModContentManager.cs | 41 ++++++++++++++++--- src/SMAPI/Framework/Models/SConfig.cs | 8 +++- src/SMAPI/Framework/SCore.cs | 3 +- src/SMAPI/SMAPI.config.json | 5 +++ src/SMAPI/SMAPI.csproj | 1 + 7 files changed, 63 insertions(+), 9 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index e1aa47ab..a1b5222e 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,6 +2,10 @@ # Release notes ## Upcoming release +* For players: + * Added experimental image load rewrite (disabled by default). + _If you have many content mods installed, enabling `UseExperimentalImageLoading` in `smapi-internal/config.json` may reduce load times or stutters when they load many image files at once._ + * For mod authors: * Fixed map edits which change warps sometimes rebuilding the NPC pathfinding cache unnecessarily, which could cause a noticeable delay for players. * In `smapi-internal/config.json`, you can now enable verbose logging for specific mods (instead of all or nothing). diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 69a39ac7..3ad112cd 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -32,6 +32,9 @@ namespace StardewModdingAPI.Framework /// An asset key prefix for assets from SMAPI mod folders. private readonly string ManagedPrefix = "SMAPI"; + /// Whether to use a newer approach when loading image files from mod folder which may be faster. + private readonly bool UseExperimentalImageLoading; + /// Get a file lookup for the given directory. private readonly Func GetFileLookup; @@ -130,7 +133,8 @@ 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. - public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset, Action onAssetLoaded, Func getFileLookup, Action> onAssetsInvalidated, Func requestAssetOperations) + /// Whether to use a newer approach when loading image files from mod folder which may be faster. + 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 useExperimentalImageLoading) { this.GetFileLookup = getFileLookup; this.Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor)); @@ -141,6 +145,7 @@ namespace StardewModdingAPI.Framework this.OnAssetsInvalidated = onAssetsInvalidated; this.RequestAssetOperations = requestAssetOperations; this.FullRootDirectory = Path.Combine(Constants.GamePath, rootDirectory); + this.UseExperimentalImageLoading = useExperimentalImageLoading; this.ContentManagers.Add( this.MainContentManager = new GameContentManager( name: "Game1.content", @@ -219,7 +224,8 @@ namespace StardewModdingAPI.Framework reflection: this.Reflection, jsonHelper: this.JsonHelper, onDisposing: this.OnDisposing, - fileLookup: this.GetFileLookup(rootDirectory) + fileLookup: this.GetFileLookup(rootDirectory), + useExperimentalImageLoading: this.UseExperimentalImageLoading ); this.ContentManagers.Add(manager); return manager; 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). diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index 240af002..f12da0a7 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -23,6 +23,7 @@ namespace StardewModdingAPI.Framework.Models [nameof(LogNetworkTraffic)] = false, [nameof(RewriteMods)] = true, [nameof(UsePintail)] = true, + [nameof(UseExperimentalImageLoading)] = false, [nameof(UseCaseInsensitivePaths)] = Constants.Platform is Platform.Android or Platform.Linux }; @@ -66,6 +67,9 @@ namespace StardewModdingAPI.Framework.Models /// Whether to use the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself. public bool UsePintail { get; } + /// Whether to use a newer approach when loading image files from mod folder which may be faster. + public bool UseExperimentalImageLoading { get; } + /// Whether to make SMAPI file APIs case-insensitive, even on Linux. public bool UseCaseInsensitivePaths { get; } @@ -92,11 +96,12 @@ namespace StardewModdingAPI.Framework.Models /// 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 the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself. + /// Whether to use a newer approach when loading image files from mod folder which may be faster. /// >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. /// The mod IDs SMAPI should ignore when performing update checks or validating update keys. - public SConfig(bool developerMode, bool? checkForUpdates, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, string[]? verboseLogging, bool? rewriteMods, bool? usePintail, bool? 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? usePintail, bool? useExperimentalImageLoading, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, ColorSchemeConfig consoleColors, string[]? suppressUpdateChecks) { this.DeveloperMode = developerMode; this.CheckForUpdates = checkForUpdates ?? (bool)SConfig.DefaultValues[nameof(this.CheckForUpdates)]; @@ -107,6 +112,7 @@ namespace StardewModdingAPI.Framework.Models this.VerboseLogging = new HashSet(verboseLogging ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); this.RewriteMods = rewriteMods ?? (bool)SConfig.DefaultValues[nameof(this.RewriteMods)]; this.UsePintail = usePintail ?? (bool)SConfig.DefaultValues[nameof(this.UsePintail)]; + this.UseExperimentalImageLoading = useExperimentalImageLoading ?? (bool)SConfig.DefaultValues[nameof(this.UseExperimentalImageLoading)]; 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 4f4212dc..242776b3 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1301,7 +1301,8 @@ namespace StardewModdingAPI.Framework onAssetLoaded: this.OnAssetLoaded, onAssetsInvalidated: this.OnAssetsInvalidated, getFileLookup: this.GetFileLookup, - requestAssetOperations: this.RequestAssetOperations + requestAssetOperations: this.RequestAssetOperations, + useExperimentalImageLoading: this.Settings.UseExperimentalImageLoading ); 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 a6ec42d3..8e710435 100644 --- a/src/SMAPI/SMAPI.config.json +++ b/src/SMAPI/SMAPI.config.json @@ -60,6 +60,11 @@ copy all the settings, or you may cause bugs due to overridden changes in future */ "UsePintail": true, + /** + * Whether to use a newer approach when loading image files from mod folder which may be faster. + */ + "UseExperimentalImageLoading": false, + /** * 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 diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index a0ca54cc..91e4c668 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -41,6 +41,7 @@ + From 4708385f696d2e47d24e795f210752f4b0224bff Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 25 May 2022 22:46:51 -0400 Subject: [PATCH 05/27] add IRawTextureData asset type --- docs/release-notes.md | 3 + .../Framework/Content/AssetDataForImage.cs | 179 ++++++++++++------ src/SMAPI/Framework/Content/RawTextureData.cs | 10 + .../ContentManagers/GameContentManager.cs | 4 + .../ContentManagers/ModContentManager.cs | 23 ++- src/SMAPI/IAssetDataForImage.cs | 10 + src/SMAPI/IContentHelper.cs | 2 +- src/SMAPI/IContentPack.cs | 2 +- src/SMAPI/IModContentHelper.cs | 2 +- src/SMAPI/IRawTextureData.cs | 17 ++ 10 files changed, 188 insertions(+), 64 deletions(-) create mode 100644 src/SMAPI/Framework/Content/RawTextureData.cs create mode 100644 src/SMAPI/IRawTextureData.cs diff --git a/docs/release-notes.md b/docs/release-notes.md index a1b5222e..b22f4de9 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -5,6 +5,9 @@ * For players: * Added experimental image load rewrite (disabled by default). _If you have many content mods installed, enabling `UseExperimentalImageLoading` in `smapi-internal/config.json` may reduce load times or stutters when they load many image files at once._ +* For mod authors: + * Added specialized `IRawTextureData` asset type. + _When you're only loading a mod file to patch it into an asset, you can now load it using `helper.ModContent.Load(path)`. This reads the image data from disk without initializing a `Texture2D` instance through the GPU. You can then pass this to SMAPI APIs that accept `Texture2D` instances._ * For mod authors: * Fixed map edits which change warps sometimes rebuilding the NPC pathfinding cache unnecessarily, which could cause a noticeable delay for players. diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index 97729c95..3393b22f 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using StardewValley; @@ -28,74 +29,62 @@ namespace StardewModdingAPI.Framework.Content public AssetDataForImage(string? locale, IAssetName assetName, Texture2D data, Func getNormalizedPath, Action onDataReplaced) : base(locale, assetName, data, getNormalizedPath, onDataReplaced) { } + /// + 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 + if (source == null) + throw new ArgumentNullException(nameof(source), "Can't patch from null source data."); + + // get the pixels for the source area + Color[] sourceData; + { + int areaX = sourceArea.Value.X; + int areaY = sourceArea.Value.Y; + int areaWidth = sourceArea.Value.Width; + int areaHeight = sourceArea.Value.Height; + + if (areaX == 0 && areaY == 0 && areaWidth == source.Width && areaHeight == source.Height) + sourceData = source.Data; + else + { + sourceData = new Color[areaWidth * areaHeight]; + int i = 0; + for (int y = areaY, maxY = areaY + areaHeight - 1; 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]; + } + } + } + } + + // 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) { - // get 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."); - Texture2D target = this.Data; - - // get areas - sourceArea ??= new Rectangle(0, 0, source.Width, source.Height); - targetArea ??= new Rectangle(0, 0, Math.Min(sourceArea.Value.Width, target.Width), Math.Min(sourceArea.Value.Height, target.Height)); - - // validate if (!source.Bounds.Contains(sourceArea.Value)) throw new ArgumentOutOfRangeException(nameof(sourceArea), "The source area is outside the bounds of the source texture."); - if (!target.Bounds.Contains(targetArea.Value)) - throw new ArgumentOutOfRangeException(nameof(targetArea), "The target area is outside the bounds of the target texture."); - if (sourceArea.Value.Size != targetArea.Value.Size) - throw new InvalidOperationException("The source and target areas must be the same size."); // get source data int pixelCount = sourceArea.Value.Width * sourceArea.Value.Height; Color[] sourceData = GC.AllocateUninitializedArray(pixelCount); source.GetData(0, sourceArea, sourceData, 0, pixelCount); - // merge data in overlay mode - if (patchMode == PatchMode.Overlay) - { - // get target data - Color[] targetData = GC.AllocateUninitializedArray(pixelCount); - target.GetData(0, targetArea, targetData, 0, pixelCount); - - // merge pixels - for (int i = 0; i < sourceData.Length; i++) - { - Color above = sourceData[i]; - Color below = targetData[i]; - - // shortcut transparency - if (above.A < MinOpacity) - { - sourceData[i] = below; - continue; - } - if (below.A < MinOpacity) - { - sourceData[i] = above; - continue; - } - - // merge pixels - // 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/. - // Note: don't use named arguments here since they're different between - // Linux/macOS and Windows. - float alphaBelow = 1 - (above.A / 255f); - sourceData[i] = new Color( - (int)(above.R + (below.R * alphaBelow)), // r - (int)(above.G + (below.G * alphaBelow)), // g - (int)(above.B + (below.B * alphaBelow)), // b - Math.Max(above.A, below.A) // a - ); - } - } - - // patch target texture - target.SetData(0, targetArea, sourceData, 0, pixelCount); + // apply + this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode); } /// @@ -110,5 +99,85 @@ namespace StardewModdingAPI.Framework.Content this.PatchImage(original); return true; } + + + /********* + ** Private methods + *********/ + /// Get the bounds for an image patch. + /// The source area to set if needed. + /// The target area to set if needed. + /// The width of the full source image. + /// The height of the full source image. + private void GetPatchBounds([NotNull] ref Rectangle? sourceArea, [NotNull] ref Rectangle? targetArea, int sourceWidth, int sourceHeight) + { + sourceArea ??= new Rectangle(0, 0, sourceWidth, sourceHeight); + targetArea ??= new Rectangle(0, 0, Math.Min(sourceArea.Value.Width, this.Data.Width), Math.Min(sourceArea.Value.Height, this.Data.Height)); + } + + /// 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 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. + /// 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) + { + // get texture + Texture2D target = this.Data; + int pixelCount = sourceArea.Width * sourceArea.Height; + + // validate + if (sourceArea.X < 0 || sourceArea.Y < 0 || sourceArea.Right > sourceWidth || sourceArea.Bottom > sourceHeight) + throw new ArgumentOutOfRangeException(nameof(sourceArea), "The source area is outside the bounds of the source texture."); + if (!target.Bounds.Contains(targetArea)) + throw new ArgumentOutOfRangeException(nameof(targetArea), "The target area is outside the bounds of the target texture."); + if (sourceArea.Size != targetArea.Size) + throw new InvalidOperationException("The source and target areas must be the same size."); + + // merge data + if (patchMode == PatchMode.Overlay) + { + // get target data + Color[] mergedData = GC.AllocateUninitializedArray(pixelCount); + target.GetData(0, targetArea, mergedData, 0, pixelCount); + + // merge pixels + for (int i = 0; i < pixelCount; i++) + { + Color above = sourceData[i]; + Color below = mergedData[i]; + + // shortcut transparency + if (above.A < MinOpacity) + continue; + if (below.A < MinOpacity) + mergedData[i] = 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[i] = 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); + } + else + target.SetData(0, targetArea, sourceData, 0, pixelCount); + } } } diff --git a/src/SMAPI/Framework/Content/RawTextureData.cs b/src/SMAPI/Framework/Content/RawTextureData.cs new file mode 100644 index 00000000..4a0835b0 --- /dev/null +++ b/src/SMAPI/Framework/Content/RawTextureData.cs @@ -0,0 +1,10 @@ +using Microsoft.Xna.Framework; + +namespace StardewModdingAPI.Framework.Content +{ + /// The raw data for an image read from the filesystem. + /// The image width. + /// The image height. + /// The loaded image data. + internal record RawTextureData(int Width, int Height, Color[] Data) : IRawTextureData; +} 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 { diff --git a/src/SMAPI/IAssetDataForImage.cs b/src/SMAPI/IAssetDataForImage.cs index 6f8a4719..3e5b833d 100644 --- a/src/SMAPI/IAssetDataForImage.cs +++ b/src/SMAPI/IAssetDataForImage.cs @@ -10,6 +10,16 @@ namespace StardewModdingAPI /********* ** Public methods *********/ + /// Overwrite part of the image. + /// The image to patch into the 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. + /// One of the arguments is null. + /// The is outside the bounds of the spritesheet. + /// The content being read isn't an image. + void PatchImage(IRawTextureData source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace); + /// Overwrite part of the image. /// The image to patch into the content. /// The part of the to copy (or null to take the whole texture). This must be within the bounds of the texture. diff --git a/src/SMAPI/IContentHelper.cs b/src/SMAPI/IContentHelper.cs index 2cd0c1fc..7637edf0 100644 --- a/src/SMAPI/IContentHelper.cs +++ b/src/SMAPI/IContentHelper.cs @@ -35,7 +35,7 @@ namespace StardewModdingAPI ** Public methods *********/ /// Load content from the game folder or mod folder (if not already cached), and return it. When loading a .png file, this must be called outside the game's draw loop. - /// The expected data type. The main supported types are , , dictionaries, and lists; other types may be supported by the game's content pipeline. + /// The expected data type. The main supported types are , , (for mod content only), and data structures; other types may be supported by the game's content pipeline. /// The asset key to fetch (if the is ), or the local path to a content file relative to the mod folder. /// Where to search for a matching content asset. /// The is empty or contains invalid characters. diff --git a/src/SMAPI/IContentPack.cs b/src/SMAPI/IContentPack.cs index 1215fe0b..73b1a860 100644 --- a/src/SMAPI/IContentPack.cs +++ b/src/SMAPI/IContentPack.cs @@ -48,7 +48,7 @@ namespace StardewModdingAPI where TModel : class; /// Load content from the content pack folder (if not already cached), and return it. When loading a .png file, this must be called outside the game's draw loop. - /// The expected data type. The main supported types are , , and dictionaries; other types may be supported by the game's content pipeline. + /// The expected data type. The main supported types are , , , and data structures; other types may be supported by the game's content pipeline. /// The relative file path within the content pack (case-insensitive). /// The is empty or contains invalid characters. /// The content asset couldn't be loaded (e.g. because it doesn't exist). diff --git a/src/SMAPI/IModContentHelper.cs b/src/SMAPI/IModContentHelper.cs index f1f6ce94..1e2d82a8 100644 --- a/src/SMAPI/IModContentHelper.cs +++ b/src/SMAPI/IModContentHelper.cs @@ -12,7 +12,7 @@ namespace StardewModdingAPI ** Public methods *********/ /// Load content from the mod folder and return it. When loading a .png file, this must be called outside the game's draw loop. - /// The expected data type. The main supported types are , , dictionaries, and lists; other types may be supported by the game's content pipeline. + /// The expected data type. The main supported types are , , , and data structures; other types may be supported by the game's content pipeline. /// The local path to a content file relative to the mod folder. /// The is empty or contains invalid characters. /// The content asset couldn't be loaded (e.g. because it doesn't exist). diff --git a/src/SMAPI/IRawTextureData.cs b/src/SMAPI/IRawTextureData.cs new file mode 100644 index 00000000..a4da52f3 --- /dev/null +++ b/src/SMAPI/IRawTextureData.cs @@ -0,0 +1,17 @@ +using Microsoft.Xna.Framework; + +namespace StardewModdingAPI +{ + /// The raw data for an image read from the filesystem. + public interface IRawTextureData + { + /// The image width. + int Width { get; } + + /// The image height. + int Height { get; } + + /// The loaded image data. + Color[] Data { get; } + } +} From 769475166ab3b92cd3763bb86e364a8b2c7d914f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 26 May 2022 00:51:11 -0400 Subject: [PATCH 06/27] enable raw image loading by default, rename setting --- docs/release-notes.md | 7 +++---- src/SMAPI/Framework/ContentCoordinator.cs | 12 ++++++------ .../Framework/ContentManagers/ModContentManager.cs | 12 ++++++------ src/SMAPI/Framework/Models/SConfig.cs | 12 ++++++------ src/SMAPI/Framework/SCore.cs | 2 +- src/SMAPI/SMAPI.config.json | 5 +++-- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index b22f4de9..7fff656a 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -3,11 +3,10 @@ # Release notes ## Upcoming release * For players: - * Added experimental image load rewrite (disabled by default). - _If you have many content mods installed, enabling `UseExperimentalImageLoading` in `smapi-internal/config.json` may reduce load times or stutters when they load many image files at once._ + * Optimized mod image file loading. * For mod authors: - * Added specialized `IRawTextureData` asset type. - _When you're only loading a mod file to patch it into an asset, you can now load it using `helper.ModContent.Load(path)`. This reads the image data from disk without initializing a `Texture2D` instance through the GPU. You can then pass this to SMAPI APIs that accept `Texture2D` instances._ + * Added a new `IRawTextureData` asset type. + _You can now load image files through `helper.ModContent` as `IRawTextureData` instead of `Texture2D`. This provides the image size and raw pixel data, which you can pass into other SMAPI APIs like `asset.AsImage().PatchImage`. This is much more efficient when you don't need a full `Texture2D` instance, since it bypasses the GPU operations needed to create one._ * For mod authors: * Fixed map edits which change warps sometimes rebuilding the NPC pathfinding cache unnecessarily, which could cause a noticeable delay for players. diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 3ad112cd..3e09ac62 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -32,8 +32,8 @@ namespace StardewModdingAPI.Framework /// An asset key prefix for assets from SMAPI mod folders. private readonly string ManagedPrefix = "SMAPI"; - /// 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; /// Get a file lookup for the given directory. private readonly Func GetFileLookup; @@ -133,8 +133,8 @@ 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 a newer approach when loading image files from mod folder which may be faster. - 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 useExperimentalImageLoading) + /// 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) { this.GetFileLookup = getFileLookup; this.Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor)); @@ -145,7 +145,7 @@ namespace StardewModdingAPI.Framework this.OnAssetsInvalidated = onAssetsInvalidated; this.RequestAssetOperations = requestAssetOperations; this.FullRootDirectory = Path.Combine(Constants.GamePath, rootDirectory); - this.UseExperimentalImageLoading = useExperimentalImageLoading; + this.UseRawImageLoading = useRawImageLoading; this.ContentManagers.Add( this.MainContentManager = new GameContentManager( name: "Game1.content", @@ -225,7 +225,7 @@ namespace StardewModdingAPI.Framework jsonHelper: this.JsonHelper, onDisposing: this.OnDisposing, fileLookup: this.GetFileLookup(rootDirectory), - useExperimentalImageLoading: this.UseExperimentalImageLoading + useRawImageLoading: this.UseRawImageLoading ); this.ContentManagers.Add(manager); return manager; 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); diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index f12da0a7..6edaa818 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -23,7 +23,7 @@ namespace StardewModdingAPI.Framework.Models [nameof(LogNetworkTraffic)] = false, [nameof(RewriteMods)] = true, [nameof(UsePintail)] = true, - [nameof(UseExperimentalImageLoading)] = false, + [nameof(UseRawImageLoading)] = true, [nameof(UseCaseInsensitivePaths)] = Constants.Platform is Platform.Android or Platform.Linux }; @@ -67,8 +67,8 @@ namespace StardewModdingAPI.Framework.Models /// Whether to use the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself. public bool UsePintail { get; } - /// Whether to use a newer approach when loading image files from mod folder which may be faster. - public bool UseExperimentalImageLoading { get; } + /// Whether to use raw image data when possible, instead of initializing an XNA Texture2D instance through the GPU. + public bool UseRawImageLoading { get; } /// Whether to make SMAPI file APIs case-insensitive, even on Linux. public bool UseCaseInsensitivePaths { get; } @@ -96,12 +96,12 @@ namespace StardewModdingAPI.Framework.Models /// 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 the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself. - /// Whether to use a newer approach when loading image files from mod folder which may be faster. + /// 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. /// The mod IDs SMAPI should ignore when performing update checks or validating update keys. - public SConfig(bool developerMode, bool? checkForUpdates, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, string[]? verboseLogging, bool? rewriteMods, bool? usePintail, bool? useExperimentalImageLoading, 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? usePintail, bool? useRawImageLoading, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, ColorSchemeConfig consoleColors, string[]? suppressUpdateChecks) { this.DeveloperMode = developerMode; this.CheckForUpdates = checkForUpdates ?? (bool)SConfig.DefaultValues[nameof(this.CheckForUpdates)]; @@ -112,7 +112,7 @@ namespace StardewModdingAPI.Framework.Models this.VerboseLogging = new HashSet(verboseLogging ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); this.RewriteMods = rewriteMods ?? (bool)SConfig.DefaultValues[nameof(this.RewriteMods)]; this.UsePintail = usePintail ?? (bool)SConfig.DefaultValues[nameof(this.UsePintail)]; - this.UseExperimentalImageLoading = useExperimentalImageLoading ?? (bool)SConfig.DefaultValues[nameof(this.UseExperimentalImageLoading)]; + 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 242776b3..f018acad 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1302,7 +1302,7 @@ namespace StardewModdingAPI.Framework onAssetsInvalidated: this.OnAssetsInvalidated, getFileLookup: this.GetFileLookup, requestAssetOperations: this.RequestAssetOperations, - useExperimentalImageLoading: this.Settings.UseExperimentalImageLoading + useRawImageLoading: this.Settings.UseRawImageLoading ); 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 8e710435..97e8e00c 100644 --- a/src/SMAPI/SMAPI.config.json +++ b/src/SMAPI/SMAPI.config.json @@ -61,9 +61,10 @@ copy all the settings, or you may cause bugs due to overridden changes in future "UsePintail": true, /** - * Whether to use a newer approach when loading image files from mod folder which may be faster. + * Whether to use raw image data when possible, instead of initializing an XNA Texture2D + * instance through the GPU. */ - "UseExperimentalImageLoading": false, + "UseRawImageLoading": true, /** * Whether to add a section to the 'mod issues' list for mods which directly use potentially From db578c389e35ee026ed4ea12dfdcef99f8bc3b28 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 26 May 2022 00:57:13 -0400 Subject: [PATCH 07/27] drop support for pre-Pintail proxying --- docs/release-notes.md | 1 + src/SMAPI.Tests/Core/InterfaceProxyTests.cs | 7 +- src/SMAPI/Framework/Models/SConfig.cs | 6 - .../OriginalInterfaceProxyBuilder.cs | 118 ------------------ .../OriginalInterfaceProxyFactory.cs | 57 --------- src/SMAPI/Framework/SCore.cs | 4 +- 6 files changed, 4 insertions(+), 189 deletions(-) delete mode 100644 src/SMAPI/Framework/Reflection/OriginalInterfaceProxyBuilder.cs delete mode 100644 src/SMAPI/Framework/Reflection/OriginalInterfaceProxyFactory.cs diff --git a/docs/release-notes.md b/docs/release-notes.md index 7fff656a..ddf97fe8 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -7,6 +7,7 @@ * For mod authors: * Added a new `IRawTextureData` asset type. _You can now load image files through `helper.ModContent` as `IRawTextureData` instead of `Texture2D`. This provides the image size and raw pixel data, which you can pass into other SMAPI APIs like `asset.AsImage().PatchImage`. This is much more efficient when you don't need a full `Texture2D` instance, since it bypasses the GPU operations needed to create one._ + * Removed transitional `UsePintail` option added in 3.14.0 (now always enabled). * For mod authors: * Fixed map edits which change warps sometimes rebuilding the NPC pathfinding cache unnecessarily, which could cause a noticeable delay for players. diff --git a/src/SMAPI.Tests/Core/InterfaceProxyTests.cs b/src/SMAPI.Tests/Core/InterfaceProxyTests.cs index 6be97526..d14c116f 100644 --- a/src/SMAPI.Tests/Core/InterfaceProxyTests.cs +++ b/src/SMAPI.Tests/Core/InterfaceProxyTests.cs @@ -29,11 +29,8 @@ namespace SMAPI.Tests.Core /// The random number generator with which to create sample values. private readonly Random Random = new(); - /// Sample user inputs for season names. - private static readonly IInterfaceProxyFactory[] ProxyFactories = { - new InterfaceProxyFactory(), - new OriginalInterfaceProxyFactory() - }; + /// The proxy factory to use in unit tests. + private static readonly IInterfaceProxyFactory[] ProxyFactories = { new InterfaceProxyFactory() }; /********* diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index 6edaa818..baef6144 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(UsePintail)] = true, [nameof(UseRawImageLoading)] = true, [nameof(UseCaseInsensitivePaths)] = Constants.Platform is Platform.Android or Platform.Linux }; @@ -64,9 +63,6 @@ namespace StardewModdingAPI.Framework.Models /// Whether SMAPI should rewrite mods for compatibility. public bool RewriteMods { get; } - /// Whether to use the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself. - public bool UsePintail { get; } - /// Whether to use raw image data when possible, instead of initializing an XNA Texture2D instance through the GPU. public bool UseRawImageLoading { get; } @@ -95,7 +91,6 @@ 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 the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself. /// 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. @@ -111,7 +106,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.UsePintail = usePintail ?? (bool)SConfig.DefaultValues[nameof(this.UsePintail)]; 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)]; diff --git a/src/SMAPI/Framework/Reflection/OriginalInterfaceProxyBuilder.cs b/src/SMAPI/Framework/Reflection/OriginalInterfaceProxyBuilder.cs deleted file mode 100644 index 9576f768..00000000 --- a/src/SMAPI/Framework/Reflection/OriginalInterfaceProxyBuilder.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; - -namespace StardewModdingAPI.Framework.Reflection -{ - /// Generates a proxy class to access a mod API through an arbitrary interface. - internal class OriginalInterfaceProxyBuilder - { - /********* - ** Fields - *********/ - /// The target class type. - private readonly Type TargetType; - - /// The generated proxy type. - private readonly Type ProxyType; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The type name to generate. - /// The CLR module in which to create proxy classes. - /// The interface type to implement. - /// The target type. - public OriginalInterfaceProxyBuilder(string name, ModuleBuilder moduleBuilder, Type interfaceType, Type targetType) - { - // validate - if (name == null) - throw new ArgumentNullException(nameof(name)); - if (targetType == null) - throw new ArgumentNullException(nameof(targetType)); - - // define proxy type - TypeBuilder proxyBuilder = moduleBuilder.DefineType(name, TypeAttributes.Public | TypeAttributes.Class); - proxyBuilder.AddInterfaceImplementation(interfaceType); - - // create field to store target instance - FieldBuilder targetField = proxyBuilder.DefineField("__Target", targetType, FieldAttributes.Private); - - // create constructor which accepts target instance and sets field - { - ConstructorBuilder constructor = proxyBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard | CallingConventions.HasThis, new[] { targetType }); - ILGenerator il = constructor.GetILGenerator(); - - il.Emit(OpCodes.Ldarg_0); // this - // ReSharper disable once AssignNullToNotNullAttribute -- never null - il.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes)!); // call base constructor - il.Emit(OpCodes.Ldarg_0); // this - il.Emit(OpCodes.Ldarg_1); // load argument - il.Emit(OpCodes.Stfld, targetField); // set field to loaded argument - il.Emit(OpCodes.Ret); - } - - // proxy methods - foreach (MethodInfo proxyMethod in interfaceType.GetMethods()) - { - var targetMethod = targetType.GetMethod(proxyMethod.Name, proxyMethod.GetParameters().Select(a => a.ParameterType).ToArray()); - if (targetMethod == null) - throw new InvalidOperationException($"The {interfaceType.FullName} interface defines method {proxyMethod.Name} which doesn't exist in the API."); - - this.ProxyMethod(proxyBuilder, targetMethod, targetField); - } - - // save info - this.TargetType = targetType; - this.ProxyType = proxyBuilder.CreateType()!; - } - - /// Create an instance of the proxy for a target instance. - /// The target instance. - public object CreateInstance(object targetInstance) - { - ConstructorInfo? constructor = this.ProxyType.GetConstructor(new[] { this.TargetType }); - if (constructor == null) - throw new InvalidOperationException($"Couldn't find the constructor for generated proxy type '{this.ProxyType.Name}'."); // should never happen - return constructor.Invoke(new[] { targetInstance }); - } - - - /********* - ** Private methods - *********/ - /// Define a method which proxies access to a method on the target. - /// The proxy type being generated. - /// The target method. - /// The proxy field containing the API instance. - private void ProxyMethod(TypeBuilder proxyBuilder, MethodInfo target, FieldBuilder instanceField) - { - Type[] argTypes = target.GetParameters().Select(a => a.ParameterType).ToArray(); - - // create method - MethodBuilder methodBuilder = proxyBuilder.DefineMethod(target.Name, MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual); - methodBuilder.SetParameters(argTypes); - methodBuilder.SetReturnType(target.ReturnType); - - // create method body - { - ILGenerator il = methodBuilder.GetILGenerator(); - - // load target instance - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, instanceField); - - // invoke target method on instance - for (int i = 0; i < argTypes.Length; i++) - il.Emit(OpCodes.Ldarg, i + 1); - il.Emit(OpCodes.Call, target); - - // return result - il.Emit(OpCodes.Ret); - } - } - } -} diff --git a/src/SMAPI/Framework/Reflection/OriginalInterfaceProxyFactory.cs b/src/SMAPI/Framework/Reflection/OriginalInterfaceProxyFactory.cs deleted file mode 100644 index d6966978..00000000 --- a/src/SMAPI/Framework/Reflection/OriginalInterfaceProxyFactory.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Reflection.Emit; - -namespace StardewModdingAPI.Framework.Reflection -{ - /// - internal class OriginalInterfaceProxyFactory : IInterfaceProxyFactory - { - /********* - ** Fields - *********/ - /// The CLR module in which to create proxy classes. - private readonly ModuleBuilder ModuleBuilder; - - /// The generated proxy types. - private readonly IDictionary Builders = new Dictionary(); - - - /********* - ** Public methods - *********/ - /// Construct an instance. - public OriginalInterfaceProxyFactory() - { - AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName($"StardewModdingAPI.Proxies, Version={this.GetType().Assembly.GetName().Version}, Culture=neutral"), AssemblyBuilderAccess.Run); - this.ModuleBuilder = assemblyBuilder.DefineDynamicModule("StardewModdingAPI.Proxies"); - } - - /// - public TInterface CreateProxy(object instance, string sourceModID, string targetModID) - where TInterface : class - { - lock (this.Builders) - { - // validate - if (instance == null) - throw new InvalidOperationException("Can't proxy access to a null API."); - if (!typeof(TInterface).IsInterface) - throw new InvalidOperationException("The proxy type must be an interface, not a class."); - - // get proxy type - Type targetType = instance.GetType(); - string proxyTypeName = $"StardewModdingAPI.Proxies.From<{sourceModID}_{typeof(TInterface).FullName}>_To<{targetModID}_{targetType.FullName}>"; - if (!this.Builders.TryGetValue(proxyTypeName, out OriginalInterfaceProxyBuilder? builder)) - { - builder = new OriginalInterfaceProxyBuilder(proxyTypeName, this.ModuleBuilder, typeof(TInterface), targetType); - this.Builders[proxyTypeName] = builder; - } - - // create instance - return (TInterface)builder.CreateInstance(instance); - } - } - } -} diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index f018acad..fa3f8778 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1610,9 +1610,7 @@ namespace StardewModdingAPI.Framework { // init HashSet suppressUpdateChecks = this.Settings.SuppressUpdateChecks; - IInterfaceProxyFactory proxyFactory = this.Settings.UsePintail - ? new InterfaceProxyFactory() - : new OriginalInterfaceProxyFactory(); + IInterfaceProxyFactory proxyFactory = new InterfaceProxyFactory(); // load mods foreach (IModMetadata mod in mods) From b6a8dcdd46dbc2875b24e0f77049c61a5cf398d9 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 29 Mar 2022 18:59:05 -0400 Subject: [PATCH 08/27] update to Harmony 2.2.1 --- build/0Harmony.dll | Bin 167424 -> 238592 bytes build/0Harmony.xml | 594 +++++++++++++++++- docs/release-notes.md | 2 + .../TemporaryHacks/MiniMonoModHotfix.cs | 25 - src/SMAPI/SMAPI.csproj | 2 +- 5 files changed, 570 insertions(+), 53 deletions(-) diff --git a/build/0Harmony.dll b/build/0Harmony.dll index 91d36ea25de3ab3e6d9db9fccb11d7f9fe5ede6e..492255be297ccadecefc747c15558e8ce06dd3af 100644 GIT binary patch literal 238592 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ξ z9^jthpOK{Q0I+rOFM-WlegmAe>UrR|#=H#tdF~&9+a|pYJZ;infJW*60mHt306SLF z-?G@~Gw~~TeJh6m*ZW2QPg^+#D7F*=&s|;$OqLuETs7$=;K{UTj0g@ zzXP6D^9SHXCmsUcU;8KEqqTnp{yX(|;A-DLfwNBjH}HflSuIE{Lc01d)r9w)NE-MV z#$WhIa(d-ZggI_TKCq%=9Pp{|WZ;{P6~LzDbAjC(Nr&%cEZs=*fqdPHMUdC6@G<%s z*D|hW3@}C*H!}7!ZfD%dxQlT&nXMB|LNyg_GUtxTc@m&a-<5bQ+V?JX6V;N&PVn*ptI{0sCjd?Crwjvs2GA5| z8oA)l0-EA%BMW=?BOm+*pow3}7!AH1h+k|r#sYhc@xTj>0?01{nz%z;1Qcd5 z&@fAYrdbA@U`_!Rn8yJN%@cq{W;t-8IUQJR&Hzp_X97#iN?@ru2Uup#15P#<0H>IX zfK$y=fXA6jfXAE5fG3zf;54%uSZ>w=Pc-X+(@j6{By$yThS>N|z~yEV=rj9(Rpw@3wb>7>F}DJ1%?p5a=0(7Ib31T_xdZ4oF9EJJF9WVJcLE#C z%YlvNmB7{JRlp{57jTVv4REb_EpVNAJ+Rr_4Qw%Q1h$$t0Z%n=2A*c#3OwE116*(3 z4m`uW19+yn7kC!#0HDRqJAvnzcLCeXeZURo-N1l(FR@p7mBj)44bIm7!-R6_PsQDDI$9x(XGoJy*&F6r<<_o}$=8M3D`4TW`z5+~{ zuLAqb*MXbNH-MY*Z8Uy4+&m2IH{SuCXC47=HQxoEZ@ve-z&-IYx6CQP-R5z?8_W}c zH=5el-!W$bZ#FA|x0rK)x0>^S-!&Hi_n3=-x0$B^Z#S0!zh^E3-eLNH z-#4p)d(B$l56pVt4^2PtN9HQvon|BO$7U1oE^{sLCuTEnpV_nTqh&&*EXgJuNybF&-xkl6!#*o*@oF*gDam`UKHW*_hu z=4RkQvmf}FxfS@hc>(a3=0(6K%myVfFGN$ z0zWZd2mZr+1Nf==Ch(u;Vc=2o9pJyrBS6FRu3-ohh#UN#_kdo{`@n3^-+)6r9|Chc z9|4DYJ^>E%dl9$QwFEfbS_Yh9 z`G7O6YG9>R3!G!s1Ls+O-~wwEaFNvrJjH4PF0s}Ems!m~pVbPiwoU`qTI+%J)|o)R zbvAI7)dp;|0>CCK2wZE0fz4JYu+@qHPqVs#>#ZK(nN}QlwzU!1W+j0Es}C5oHUq;} zKd{r<3XE760K2V=fIZfBVBFdP+-O|_Oj?%#`>dV7&DQ0>e(Or$R_iL@1=cR$MbXjue5FjUS;h8?y_zNUSr(>yw=(ayx#gDaJO|Q z@J8z{;7!&(;LXlxrf)^or|tQUZfS}y_*S}y?~w_X80VZ91`(s~{El=TMi zY3ohkGuC0?bJjb+7px<|7p-@JFIn#aU$Nc?zH0pq_`3BW@D1xD;G5Pbz{A$3z;~>p zz$4aYz;~@LfbUsf0^hd`4@Riv0e)y%z>lnK;3rlN@Kb9T@Tipw{LIP&eqrSUzqCdJ z4ewZ>$2%Tqc?*Eq-XdU*w-`9gTLR4WmI3p;Q-Jy2E<1J3kT11r6?z&YM};5@G% zxWKy#xX9ZGJjL4tT;g2|T;^>C`n;{cYVT>lTJL&bz4uI@-+MN2mA4Jp=nVjyyg}ev zZy4C@?F6=ZBf!(V-N5zU9^jeYIPh%mMqrya2@H7qfI;tOVA$Ia?DTF0M!XjQyS*0y zd%W9$aqkY`M(-uSr1vslpLZv4v-fgfzxPVuR_|573%t947kRG%Zuedb+~K_*c!_s6 z@G|d>z@6TkfR}r3243mC6?m0*4{(?FcHlMMJAl`E_X4l?{t&p^dnfQl?_I!~y!(JR zd+!F`>b)1Z$9q5UcJF@R9o`3ld%X_zeK0N(3;5_rG& zDd2wZ)4&J4&v=$#rmM!u)%UV1f#1)b1N>3;Jm8PB7XW{fy$JZz>{EdEWG}(_R!dGL z@YI|+z%z1I0nf@=06ZsW5pY9JBd|SZ34T4a10in^T?l!Dh$7?-B8HGRh+c%eK_n3J z29ZL@SvXNUO+-XOY!x1yW~6Xlu@C106=uD8y1CoD%Y4{;*8ITqdd7N=_bl+N_T22b z!!s;vbXG&w`mD~Z-mI&$nyhxK*SgZ$W8G)HYJFr4^B(Wr;=R#(i}xk()!DaaPZ+Xn z$VWqroM|~v=KMWp^U$k@-Zu2^q3;jE;rc^~I}mN#PL@gwJtY#Mpq$RCe< zcjVtkemQbh{*wIm{0s6g&A%%D!TjImzm)%%{EzZSjG8m5W>jL-j!}C@y*%puQ3a#d zjlO#H4WnNlT{ULan3gfYF{B%cOfIJwNHlq<>EuQF4084JG%LJY4e2l9JMj(j}$g(s=35ON+{; zl}#_ZsH|f0{K?xU@0|SbWY3hFrhIS8Yg68yGJI;q)MZnXQ}391*VOx`9+>+1eh_6jlSN?l#YAOCEPG-0t%^WCO!g15{+&YNto9AZ6eP`;!gXDQT{`N}d zdv{?kG*gz~I!J%-niT`X_2KWmWX8Rm>)=$B_cYMyp!J|LaE^T@=q%9LpmRWNpba8V z1jI!k`XNCt9Hk6ak$J>IOwYJ)oEvFXEtH{95ovPy)XxoWx!96sQlh z3A7os1=KGlit|8QMX@*^bb**JE(BeK6ZdVP?VyW6JH*N2E4ZUd2D%;eJ!3e^-T)l}y$N~? zbQttD=pE3XKu17-2E7aV3+O%2UqSDK@C$R|Z=nAJeF*wH2xtGeA>kLFfc^pc6!cHf zQP96YpMgFHeF6G6=u40={J2Ts7beI9$^uy+FDM%{1e60B3K|9)4$1|M0Of&3g7QJ5 zK%+rpKx0AUK;uCZKn0*eP!VV%s2DT}R01jmm4POMrhuk`jsqPJIsr5dR1P{3G#zvj zXa=YPG!rxnR0*05ngf~(ng^N>S^!!IS_C>7bP8xOXbEU3Xc=fZ$OozdRfB3k`28MH z2dW3H0Qo^HL90LwphnPYP!nhkXf0?Rs2S7(Y6YDNIt_F>Xg%l*(3zmKKxc!_0kwfP zfC8X)P!JRXg+U#lPEZ#p0y-De4T^$#Krv7p)C<}ON`R7}6sQlh3A7os1=J5Z5406@ zKIj6_g`kT-+d$hv7lU?yz5==gbSdaE&{sh_L03+PtRcMTk9U7kI_c--Df&Fos#r{H+D|fkaPgm~g$~{B5XDIhf<({eBmC9YI z+_T}P_svoAIZD1t$s5)4d+K>eJ>OE#&-sb{MT5$PvvXq({z~!$^h@bp$Znj2s^=0@ zzHb?x6n>fM>!tTsD|wmcD9g({GTaon$$yq|&*o>A*s7ivDEWT%d{8}K!IRQ|#Y6cK z;#K8-Rk>eR&o|WbP38ZlavxUi!|a|Q-cjy%l>3NsA5rdimHS=geowjIV>eD#mHU0= z{+oJ!sGc7w|BsaW6ZQPmvv>jJ|EPL?rk=T3R6kiFkDoZHR?jKwd6`$%-!8AL&t1xW zje0((o-e59i|YB3dcLBbuX-<=MD_TpcYG=3<8|eJUAaF~;l99=!hPYD?di#u^=@TL zIeyTW`$e{LXDk04CC^jxJS8uIo8DKcp7Yexr=InA&Vn|X209)z8?+MpT`RH2)FARa zMaD&*a^q>wbmNF;2557R^oXC>(^FKd`P+R zOzw2N>r(F{cyG^$=|*bA`<~lJWP4I0Rv}!uu`q9y$Den^@aG+u<NN% zIoQ47(E?z_Al>}(Vu%mxB=CT9fUWq%bVwU-b-=fW3oLLk6Go(^Q`s!dCYX< zlQHGS=&>WRM&cbK^Dp&Yk#(*0r?Iz!W@XP9myP$V0{zfRj9caT&bWK67su_l{ygpl z>x!&WPjdWZ&w1msk?)O2zudTbLaFBt&?6ICAv=Qh$;Nx8qrM-&^Jk!Ey+jWbOh>-T zjl%^fYvC$Sw{@>Ir|^8wlF{cOuT9=J%=0{-f>6hvAw_3S$3 zGqOG(@(=L!sJm65deq-4P`NQ{(nr~ld1j2OH)f1m1-jI`V$!AF1*0$Z-j#K!cLwSs zKB>`zIv+B-E)(yamHp(%ji{SvkMXSIRr45!-G1X97S#&D`D9$MP2Jy__AMjc6YJhmyENVUZ4!?AE85cS8B zsYG8e6^X}^PN=29wzir`vNsy&uZ{+iN%5K(ZeTne-GCoy(B~?Aj)shIr zlD&~=I6-mO1QIA=Dx8qj$$>JnEu&^i=gP21dY+v$+MCmn5^iyNYfU&AOhkIA)wt0} zmYJNLTIFn&(W<^^x2$WKHJ2*6*r| zgrgy+tp|-Z*N!%iIbSf<7s#kU>67_FCB+M6#Awu)Oon^fqY;_;L0@OTt#S@k$ht5! zckPy7*r`YMsEa3h0x8{TxPDolnF*N;4+{22SE%Wa1$rVuS;@_P9UYM^6o2-h@duYK zl3}SXDVKG9(P&0%v%DghiYF*-cHz}4CUkOGJ`B?U)e_}G3~?3nsVwEQl`^n zRAV*a_P$QMgY_>*uL$b?z)p;4yKY!Ov6?nR<8!sJD6=`G5G_zSpQ=OYP9Db`NsE)d zSU+b~=?M;r($yhDWef*9YUKhR4p7_G z(RdQ-ovL06GjlPxbT%iz9f3J_iTts3fmkOeIY$P|$j0E(xptDwpvzDnaJtK&N$H@n zVbqK&n{6Q#aX@JXmChMdI#){3YRn)*?oyr>Np&@KbR@%adL1+cJAukYlo?-0nkek3 z2?wKrL^yr!VV?ncmQv(nHgk59OJ{8ilYAloZPpmy6kZ+PB2^a$btZCcWym<(a2x{G zPAU=$rWyk6;V4(!9QnG;HXBqrcTnj9DMf~v$*4H$YAoj=MlMv1)68%n0=H+Hur88F zDpSgilaRekM$rmJPhg~QRY=kaRjh%PU|qNeb)|h>Mjd5^G;Ufr&Ww^MJJQt?Y>(dl zmbh|I=OshHAM0?5WO-b&Q=yk&%D5!WUGctX2pz?qpIx3*FKRYPmpYc1dcQvgm8W>F zIJclN5R3HoMXATwc~{o3E#XKgt2B~ru^;vwW>7~tqQ2@#=fgJlH&n+%Qa9>av|TEm z%DpmH5K0Y#fmRs&PziE+u~Q*QJek6b;}+@nOBIiZs3w(e$W;RF+wiT9_w-`iaho=v z^J_U`tqUjnqC9#K4#|dG)2gbeW|&TjZ47jWYr^5)>Ub|ttB9jp$6fOoGT8@I!il9J z>iwN;3`FBW#W$xMz9AGybkYcEjrCH;#0*Cky1%BnD;(@TUq?n}+x(TBQZvT0?V#@3 z*0iZZdp3l^(QuD;HK*Jz4yzJTwn?K*vLQrCwn~Scq;jhy-M--UDh(VJFJnAoT?G$H z>A$SWD4`5ZB;%3><3vy)gD-awb?}u9^?*bec5|rJA4|=djin93qW#_L1k`a|AQEkg z$xd-TmIag-ycIS$xMSGt6ec6TGJK{KMzl%~<}f(ji(R8gdLUnEW>}hat&)K43@)=n zN-1R-7AyRvO_$N9rB`O|SSq_E+o)4JyZnXEGr4zeZT(z9AqlAhHnj`S>2$!u5bdu&i*o2gs?QC(jwh{tLyRB7`7 ztY4X8%GHzzg%jaWnh+U;%1cscOm3k_5VI-xwSD0}dUAcP3wMC!Q4@>yH}?i&aMo^N zods(F4k#bV=s?x+XcS8@DWt9vh@~RfL5s&$U|EM98btEfZp1D_B-O8GK$5LY#$!qg zt1z}P3199Aw7v$9Ht1>&rzjnoHSuh(6g_R|nXYB2ck(?)pAy+U_DT>WIaUtD}~g>Tx{0Lq9rb$-1(uVRrB%WCLL;~lMZ`Y^6QXt zZqq^CE)Hvl(!OQ>> zheA39sn8)CeELP@Dw7gHczVOeWG9SwScbY~NlQsyT83Iq8f&)HL$d`orm>xEE0Eb* zNz4|g5}55;5g=rSdsVpq)IhWkCUGROVYaA=up$X0`r&AwJ@_D(S&$#{#>nQ!Lgdfb8g*yUrkN*qWSX{)y%J}S~RP!cG29b`STYp z^etFaH``ZP=UZ4aXMSz<+&Og%>*mx})-0-;KX3NJ>bY|l`sOU0>uMIarj~N0siniw z)H;&X%{r*tB{j7U7i?RnSyI}{ArrTKIJ)iSc5z~MI9B^IIXg$KI(d$!TnFK{5sDpfw|3D6=?^dvoP{-J8L2Z^lzM z0k}c)twP`c2V6#OSxDMMErJo^*twt26(IAY626tN)4oEhOhk?mojUi~g=j&& zz;EhHq)qXd+~B=*5$5-Js4p5`N_!`fO>{WZ(~AuXoWHR3S`$uT5uRKsdN^2ptG`C9 z!Om3&j;g?R$#YI{b)u%WsAtSJ@F<=}w0vL-MU`x`{lI)A-? zwXdPAv9@JJQ%&0%-#TBT@UL!e@vW||RnF%AWGdWK;cpTUx74RTt)wXSZnBa@)^ z^y=C*E&is}(z~{~wV{Ps-@5wdHs88+zV+;Mb-remS2Cd6v;#`tanQ^lJ><+F`Kr#a zl2%1+F3|xa+;PET(2j;M)+O%r?IdY}aguC}X$Mku95J_}Gv)G$x*g$-I(9-c2&_^^ z$Y%UWA1b@uQmu}&Rh;V7d7Zzu0S&RPw$6XL;PzI@NTTYD18grB*FfwZ_ zq1px`l>$pRXS3}*f^+f+ht4C1ka^TW{Orvc(<06oi)F^Nyp?5K9OfuvU}VNvLKy=i zGsY4QJ%@5Yp~1+|ETPa~WN4OfXoThB%(aUH&M6KYx;Pv{7DorcQ7OF11fVCSL!6%c z?Wj`7QHkZ$ohYqZ#5KVw$(qn|3ZR+@oGgGNnx{ozRH*q{1V)J#Xb~6|Eu<%x9Gq6> z?vir=SzHcbv$C_XH)(I34sojFER)4$q0P#YLuOZSt9B5h^2!`%O{eI0#Q?8cguG?8 zlt24%vo=#GbOPuk;?VetqmBWQ>=B!jBn^bI4IWhso zDa`|!Qaj`;bq+XEICwzY$N(G{dd5}6R>kAFiB>^vc41JIj%X-A?xbe8Y!FaQz^A#d zT?NO3wm7K7s=NZmew%2e!;#Kd7%bWW$0>mbJ2-8fG)19T(32lN^lqW{OeyDK04hzK z8pq^^8#iq*c{&2I{uFFaDxcKrqPn{yg4$wc^Rp@u4s=tf_9!0Y&5SH4ng*$N0VvHj(E_u8s+$szVF#{FP<~J=JtX0x=ps4& zA37bq#&JU(r@5gp)7;RCX>MqsG&gibn%loH-HlA@M{Jw*x-2*t67sy%Uo1GUzTo{h27=CZ65OQT$|JmM8kgB}@>noY@A0SFwnCjw$M z#^fe!b)@2biJCwvAVLh`C|Gqi)evdNu!;w9?4);9X#pR+W zOOy(e`5}V|-mJYvDzT|K5fn)fh4hC6Za&b1T-AMv1Wa}uEC)KnA+!$a9bSudFTS(!h(XW?XA;Foe4&baU5P}{BP$+bD5dhKsDzvb_V|6$j3QIRPwk=77H_@g# z*DL~X29wRMY#XI&r8 z4sdm1KvWJ53!= zdfSY<8HO$sEs01EJ*g)ti-7f-=Hv3}p78bdh7pWL zW|pGL0U+d_O4?z9k|Do?iLqInE~ZmvrH@pal)OKgE<&#_dKP-OgU;rX@9=PF_WZ zgpW=`&ZRVNV*84k=x|tcemmDRDL83xGIUp+BBO4VkD4V~Fw@vFxu>oIxNbiQ+wl;dr7KhtC~olGluinNHAG}RD#_&=!CEAZt_49PMX3=tI-jJ>c8mezR)H| z7{nbSmkd#HL!>H!09`II;!~pP0)M)P%OzvG1f+IwtB2G!WkXh+06e~2;3tMOU+qL+p!;)f-qjrFGtvjSm5gi$$@3@@bPnS{GRuUAKT+XpRzpoi)2ER`+b+D%R4SHcJU&m7q8h|vmo;{>YDg-$I zQ%@?Hyse5p!(Vss)l~r_l|J>5j$YSi1%e9A@0X1lqsnA0%+?x>44ct8k|C*iIba8w zbrf(=ITkhH7TAPZ+p8|bE$8MlZAcCjgF`FJXPs*W$8z_Qjs-}Ftu=QnM}T`&DnZ#Kq*HB? zY??HYtQ5M#&*|B>t*fx+?PgKC%~6JVe$e@F3_&fSal|}@ZJ`YncG2}J#}$#n#Y^Y3 zwb4ZEnlkOU&gzI&gUbo^uhvbX3iiD;Ohm68QyL=d>|vXP*>$ikB4~JMTvDP57B=yR zUx8?vkaAv2%Pu(q$dJJ}4&pEhRXqBT#(tq~>9B-)#TtBu2z!IwwADZ>IJXQ<$bJdF zXU!}`weq}=omx+_gOcPgK-oq4)0cKw(3aBu7=2Ogh3f4V>|kOF4vW*eV3xrkPIPm_ zRackNOdVm*S3q_7M1EN!BxJQ=}SK z%4LeZ{FMEZ2C&{MsG~zUj-oblwVL%imO+JN5hsCTk{2zwuUdbUIjX4~!S z>`bsG#LR-O1Kp$R-ldL2Z3sKTi7MDOYy`26!roD3TO0HaqEK>p&|HBYThWRQ@MHyS zNBO;~N;#gWXo+i?NWYIHn?vo5%Xxh{(wI*}{Twb(? z;{FZR)gf%Zen{h%l!$^kp(7z^}=!w8Q9xQomDs@CU z`_Kso^>%%0>ZHqyU%W7>>a->9_+feuU;s$9>xk2C)H-6>jriT`!qLDM=8}%Tt^{bf z5U%kJ^q{XyV2pOTyr4U^zI4K~R%Wf~$aLboFu`Zxc6V3f)e$ITjsvD5?Gd^z?Dz)k zh|N{~7?HFBVTN=wTMs;GNv;0mb=~B$SNfM%jwa}m_fwNtOYNCBNoDTKC{vu-(3!4x$EpZvUaCEH4#;i`< zZ4V231j-=xFqBT&H6>4XY9&V}7759!{7Q$qKggwR=3`niDX12u1I^LD0(yoHJK~#Z z+mSWOnm{s1+Yf#4A?<(#HEl%EUSpS_^(Ip9647`kKapHrJPNCyU1WaC+6~7#6|0QQ z1}VYGBYCvOpqj}hr$f9XGgR{_$zpS#Q{h7bJ`|E%Z$#j*P&FS;l4T$|Yi5p*o0+2{ zWi7?}TeE!boY^3Bq=MCVe=JBZX(v}uGqq&ow)S==0--QX)T9QnKIn$w6-f;y&)C!A zusAG5ucJtmnq2$pG#nT2bMaz{?N_Q7+q#ny4smQ5IyaJc1+Ak=%k7rmUq}hI6kEbc z^&KQm>0)vEaHAUo!7ax*krcPHXLC5*t?C>H_I6DZua31)&Oj8MgAn>4*@Y+dF)S6j z0E+EJyPf_?|Xyl&uuOu(mi;^dmCb=kmG_4hy`&_oE{akD@z*=>C<7ZZ4&`?r} zB^>w$EPG`ZaJNAn?}mesXhn5IIvsI=?bUS1&?iCG>ReAOZtV_PBasBUalwB zI_y%Fz&y4y$)aao|B402)7LA+ETuduLMlah-GT$vK|#Q(N+se^R&sQ86L$oXm^oqa zl0(hj#9|GSOtO&jg$aj~pk)x$cR0{PVdIH@<={fF;t{>{yD}UYijJT1eh0Ji)(W#Q zXM5QEUpJ@Lc-9Y}P3exp*{^2-el_)3g>R{6CRBqC4zGx@9RjR9px*aUDE!c%@ z?uzu_6DWJt!EVw3Y7i&T*}I#g2t)^W=#4PGXjuq7L7N0>&y!jDT%X5X(spvUlFoDc zdU_?t8wMQ|8B4ubk_=(ay}k%5{PKWJ&8V4TY1!CPs(wFjMROu_*2))3_;O6Ti0{Xw zi};F6x`=Pdq>K2XOuC5g%A||PMQz zeUeBD3o@wm2z+Qm2(uw4f-M5M*-0BoIDDbad)`K3Uv*`)C9@$f zWGD%i(RfJ=21&SEYEMC*apIjZIp=bmrehMHfG`vmwya z9tt?0Dq>Jm-buf>gkk$rMp*{T3#2h{nFa@pG0EeH1TYvS@7gC*Cnz$w`c#pkYMWAS z?LtyP>a4;Zr>-#d(Re$V=rU~`QQmo|(Dr+pXA5rmRM5IE!<&^ZEltv6#o{o2`f#kt z`(L7m^|9pjI#qe0E?pVJB&5FK6ue?_<`Yp5!;20Wv_HPsa=Aj-;FBr&8(ek8mFsqs zPIL}UsV|Z}>K0Kabi1W#>6S#GIYLdi-U91d{(72-xPMEHQgwS`so-{!w!wL>TNH66 zg${|mAgUA6Qat}kC-1{+s&lePJsWcd7mF{f@C-Ci>WqW*(7^%monAn>qx&YdaYuLKXa?oaBk*0Z-sU5D zb6+oQU%I7@v=gFk1vz1rRA$H#(-sq6Ef29BvEE;3>Pt0s@cFIF4`}a&*5?92-UqPHTTj&P3>GtbKK4=9kDxp)HnF|OdJufb{r>`V;FXhTNlHT z$lYGIn^rfQ9f7iY96{L5TgFe5*&CBs7xg&0$krM-iKD#^-cw0Lphl$D3*mC5;>Zjm zS52JPR)IvgB@ShsS_u;XoMR^_hqMylU>gBX+8n|IJ(~v#=_Gkl${fV9xwT7`%_Z#N zfyDl7tm|>a619WPyu7tzYd(6V|x2_G5Dl1iYM63hZxMkxQ;>BAV!z^P>A>%R+q3f*@p_?`b`~dlE?Yy+Sn797ZCwl z5u5~WLXystXEO5gAFq)_bG%1g{gQ5^DT7L9`s#^RRa9*{G0b`L2j^tqWLKnvw)M^k z<9GDg@=L|>vjAHhy})-Ne0>lj75A2jXzq^ma+kuDU1@hv4`K=K@A4B@c)5t|z*Szo zfT$+(1Wul5*b`TL`H5S;dU|b$#H6e$!Cp-Xk?QG{YaR3$a^RjOag?f@mBT|5Z2!|v`{;Og;Oq_=prySW$3$Us!?!f+nx&=5ID8-Yqj zd$_Vc%X>MB$C;7tq;&MCq*xdYB1JnXf9Ng6we$U0EP<$#;*)kPsIgautv2GZRb-2Z zvA4#+%Q|DdLOy5&x+n<4L0|Drh>kE;x$unRf?&c?2&i8^=2S^QFXGITb{@L%kjcth zLkUsG`>%AHNbNknr{T9fI4aq;^rbqwZkh~nup1o4?{D#sZQ&(}2rZ@U-yXqEDGVp)Fcp0qGlAN2 zu{*rI%jZbwdavX1Co@b~mmg}v6;R$nr>*xsmpFyg+Ine+fvh~|px8(E=Cp2<4!%l9 z-y`rv9xVGg2sU`=r~rX6D6oyeipWti%1O3>VmH$*7pXpBNn;m~T(z_Zi0!J3Lvzx8 znW7+;78ICAN7`W%t_8rLXZsrdL7U@{)JTvvHag9#H5pbYxc?%v)S3)S{U#6k^VmWxYl_^#jHAzn@mex~nVkGGkl!Q8naolP!I0Ee1 z@d=D95Z3P4L5d{!u02i&`HvL!uw^li3B)*0}4R*55dbqI&Q93 z$0@yem&*}ym<(C3oucb^`0R^<$dsOKofV$_0Z4`GKB4}hQhZs0PeRhxZA2f8eg1Z8ECu6m#gKlT$&m+=sOR{x{ zoWR1P3k_@{;hX_qKndC>5v`a9*QC%NLG%XxDvIw!Cj_18*b51@ktY?e{tVEcBy~W) z#w6j8UC}$*w$rI{GeuR>gyQlJ^f(qfsaKz;6B&axh z)ZsQ}l9wOKgN$cvHX&;~4>fgE(N+lO#YevpPP4ZDl~yXxK#9(>oUN1C^A6JQq}q|} zOzQxS3CxQaeiy4dd7`F+>6dADFXcr$;OY7~8p@dV(UWb!xtX7Y^2I(dG^!31=-U!O zCi}I!N99DWjAJ1>O`^;9R4sHS=(srqA96Xej6ErPRRxu}DFSVzn_Zuvas|YDkL68s zFy`Wf?Qbh?TY{r7>>y!kkn8^>j0UMh)vwpc?v39Rqx}e8%VK{{w4vd@oUu^}1`M%vn8g|^AN=}D2=9A`n&mcZ`E7^Dm1 zNqrV9KMR1HPJ*~5!Q(^;Uoy(JhqKo?xRiY@iD^BF4*|6c8wH#u_Xk7>h3PvQT+PR? zlu}2qYbB&?Y?7ldyIct6a^Zz^O;<(N-5HYttt&~Yd$dbrcWReRjXjqH?{T?0p+Zof z@|WwivU3Dsb?EI#C_>umKB;46NMWL*fEhv<(at^kB)*l_Z$_@DTksu2EN+sx2~X>; z=2Sm9;LECkPQlqF$B1<5Lp$3}vqPzJ%3n?A17?{NS!$#Jamx~mCzd(ace&qUkCU!i z(PAnfZ_IM9|^zzmo`WG)Z|}U7NIX>q>>pN9ja?B?FDYrlNF3 zVmLEb9!gtC7cN6`dF)}%3kVuyd}~j7+BX2Cl6E<{a$Rm&y)FU0#pUFBprdt{41?C? zk}1$eqf3ZVRrg^C_qb%3^W^1Fm&~pkmke>W+3b=A^kpJait5%(OPp5cv>Z)hjLXYK zN&eK5MUklVB26Ldg*VD+0?OQ0#t0AL15ZACQx}htay!NO*i%K;>ek+VuduKG*s4}< z+*ed5`g>Dx9R2rp^}EBtMwJKF$QbTf@=p}=r4IcYm4X&=Qk;})bGiqF=47M}T^9{> zCIvceBq&*Z?N7S2)@Z`_%(QMt50M_2Y3*HM-bIt*4o(h62?EN>g5IOm6HoJ+rx)XB z#{P1WJ<|k>m(Fc#qji#Kj06+$WV|EA_NEUrM1K-uNR&F$96&!{Zrf0NOO&ti z(sT_K--07JC~p3oNb+*wmlUvodj3mG+b6-?*QnRiG{S#mMGhBaP2Oun4tVk><>L~{ z^J46W+rgUqd)nh}fn)ExLeXsLIxTVeaG~_La>-S9roFlfG$hmu-G{Wx$J^5D!kzg2 zZ)~X#@U}bp8v80G)Th#9JeAP#jGda3XI0C3xhlu6E9YqI^rBD*x>J`Hh8J&X5-1OO zWSRaad*oQw{V6SmQU-(mrJwY}DqC^1#4~=Nil(i!MB%QMXPs$ce6o|~<_+73S_*JQ z4LA!z-`6Jz>6%_%TS;cDezg?a3mC}F7^(PjQnmqfE%h;VWP%E&Ztil29WOa`$+3nT z8a&GBN!^Q|vTVevjZ(I6$&^E2GIb?OW1Y8%Nz3u?b)iqdTGJc9@(r|=tLA#L1d?>= zfw$!_{@lA_@Ca;iwy}^7EK2zog=9kVhZ}`5bmbP5Dumv%QDt$Qa*GvpBlmP@$fy5x z9co+WrPMyI!0>aqdYx4CEHT(YafNMBa{;ySjDmB2hK85bA(c;DI<#Sk?y6UgTxV%+ zEJEqt4C`3?*E8^;7A*rg4w83@_Df=_5O?Ms>ui6(qVEt7XTXBD+W_Hd0;~OKQ{Hk-}0`THcWb>^N$3 zlNB-lB?0;K&3v&`PXo$9Mwnu?WviTQ7h(>=9miM*UqEnE<9t6!A9%>iKJ1dp(wB1A z9bB#&feURQCr1hwk3k{OaK`pIT~NhBhZDz@B)S`m$(fyUyN3n^u3y@_q#-5dT}wLh z=M^HhC>4Y3@X3pE%K6hLUI8xu#LoiGd_=<$M5P_cA`Nl1E zS;`$z*nFy0=Of6%aqV!`Z?8CECYWN8c3avO!g-D<<;(Oapmt4s#Iv z_79nv^b;HSg4{M5(MC1+vrkBZrlw;gByeAYF68hHS$;cyqf6!jPx&oz6J4^P%c<0U zY??cgT6yQe2U9=?YJh@rT)ZFWI9t#dl#<^^H+Yq!11A#t&pFUYq0i!5;>r{6R9r$v zrR>Thq7?5P%iTb1fe6fQ`0Ga4IfkmCjgpY78>!LhmL!}%DMl+9C=Jq4u!iK*^-Z}N zq`CpMfoZ#STk1-78DIJrg6s`q^vM6XL9C3K|1~Mq_vLtATJaxZyNASo2&UBY|D0dy zx&M#JW#sw4q~OlCJLux2v+=d2G>=5)?+Ec#1B?LNgvH7$*2y0`%6?79G-zZ(N)A_a zL~|_A3tK9MJ*iaJx;U7&1few9sN2h>1FKFoUrAouxn!KM2Vw$xRTQlyd2`l(SbosYEbqMKgAE+wmkf|Lzxla1K`)s`}*}7g#6! zy&JvV-{8Z?Q20WT{%|ZhKYA!121+*@GcXO%@KS(tJ)C~1l{CdbVeyS0M+Uo&6uoxS z+hIDfA4-j-fo(oYA?6R-;?`9vh3b_Uiqef1>c(##_3_{RS`)`~$}g3lBG>4x=YCZ| z1H`zBh3{|G1~tdz!)cdD*@<*G?UKr>3{koPb&^6?UT7;^ZC8*B73gwN9GISDNb-gU z@!cS$SM8kw_m&v9kzP??*kk>pe*nam;w`-MNtfPjVL+`yI03XMB4Wg$g!mv3KWu|N zFMHdZ9h5oppp{X4uN1yi3<`}~3~<421y?Gjfs^SUD?|@cqZHaX!AbSDC3yYH=k04#*6LEN+t&U%qgs^kcATP`YuIF2fYNaapI;EBtyNT-X_n9U)DA#$lU;z*|(?-AKP z>MYBP~i%`pVWQ=XedMFUsS|;&d?=Z#fxx5u;_U zXNs9{$$W}jDN`;FLS>hVCCIxdaQkc(l-I3d4p8K*7Of~tgAmrqNLl1JaNfcwgN!3` z9SO?gImv!E^-5hs!ZR6ZRiGRRw5#xJ#XB!R&Sd_mTp_rW@7M~|4)x3+>Q0XnkynAb z#dndAQyqw0a#P)qMC40f*>`D3i?V^~cOg8ziH0&#D5AQi_MrhT+ktursRog^iNjD& zpk5$`$sSAZ7Ne_Iem#z|;k*XZU_^mp;Lyj}5L)7}P$*S&9Cr}f*{ZQK$ zwV-HjMY`CE!uX@!h)bw=E7gzF186i+U!;*5MOf;uq%o*nNMBHmrno1lR;oT$n*R@J zq8$GGo za7CL#&|63a42&)6t5F(Kj+Ad*v}z3xk}|2OZqDy!pcw5=TdA^qdNY-r zR3t0^T$0H8gDOUj+u*x}OGSp0QsFr@7?`UuM8(*8sDvhz#D`~#SdV-_>Va`$Ev9_t zQtBzyJ46oEJPjgIsrwO?VqnctuaZiH-Wh~Tc%~suGMa=3lXV&8EVJ9DsktIqHZn$< zP)7G8BgT@S9xBwEsMSO^NzoEb)VF2tphymUsdb(mP zjSW(HQch+C+Ikt8@^RD*jeA{AX_%8KAnS-U02$y~agtQFhpzG7LDC-BrcOMq^2PYU zas^{!#$;E1+$qT%Wt8oNdXylQHVxOId^9zav7-$su_V16{x=;X^&09bPxcH-jdCGo zq`NCJYFticwo$sk(M_3c6XZPV+Ly~7yHB$Lq7BT9a+gp_dPyeZ>E27DI@8o^K)%~y z=A;{dVo??MrvS7Wm8%mqPpwNUL@KKuzf#L%w-ItVF2yi8S5S`^wLavf9VyVvK%<#@ z6fNa+-gP~uzr7J@k=7#3dnR*2iKOaI7I{F;14bEn?>b7zhkARV)SRuq^F|3NS9Kr z$+>`PF?~GBdL`XVGh@0LO@36H)Mf)`+7`@*)T*=y_+Q>Ew(VSvipuP+?%}8#RjZ&S z$`YiBM6QX$upFgTMY4g4BFX-=jwAn0WKXUfsRK(tUfq@duQ?G#$9|6(KlnRD0Zk~h znwFi1)C5f+m;!SAs8BLF#1feU_mnbNO~|R=85n8}n_Y`FFDAcX(xRp{wXjO5%g>Wj z#(z=*(#m)5uZW@*FbUS7Gn1jP9x2zrFsQ-`xE_P3QiyF&X$v5IywPkL;wqMLQE@(&mN(hi_iwlG?o<`X~`$-I=SzpejqD(H^$cm-yFIxE1$gD*O z${m(}ZKMa>%Su0&)0t6a3N+41i%E;80-HEwr^#jaG;tREWKEW{d>V2&9a}n6(W$6h zvr)sSt8JI1PjaN{sNmA{8EBkjj$l1CJ3UyBWAdS0B*$m4(JZrqj1;GLybMJ@TAF)q zlT74IB0yp!EV^8>y5JUsP0QGxzO)k9l$VWt?6COy;VsYTXJnFw;5?<(<*>$3-@+udIOgV*nZn? zCgYD@RhnU{R6x>-f>APLl+v!6-dU#Cx!YW(AghvgaP-7TV_`E=rv*d$!dZs+Z&}3B zi+5=$Wo;VK!b?uAV%sC@QOA8qI$fdD*p~K@>@Y2eOsnWjyOp~2vHWOur`DipwBt~K+WBxocC(~$w^{2f|;a`gbJ5HWdBW{9IuHJ$w{WY>HTc( zI0Pq;y^lCO>ODD_oXN-tc{%oca6A(C+wg2VrX^~Fv|z3$FnT9q{tT41Y5P&6C7zt+ zjkDfJ%h0NIo>Lqc<%~_A4eC@=`>j#E )AeI6k)@$+nRePk$Q`_JntoFS)M>L+< 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 literal 167424 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�{z8s=_GKVOm1x`sM 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

to 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 X`C*T5M8Pm+-_mmZub=<#DDZy);+(dU diff --git a/build/0Harmony.xml b/build/0Harmony.xml index ba2f340e..8499d20b 100644 --- a/build/0Harmony.xml +++ b/build/0Harmony.xml @@ -265,6 +265,9 @@

This is a static constructor + + This targets the MoveNext method of the enumerator result + Specifies the type of argument @@ -475,6 +478,13 @@ An array of argument types to target overloads An array of + + + An annotation that specifies a method, property or constructor to patch + The full name of the declaring class/type + The name of the method, property or constructor to patch + The + Annotation to define the original method for delegate injection @@ -796,6 +806,13 @@ The lambda expression using the method + + + Returns an instruction to call the specified closure + The delegate type to emit + The closure that defines the method to call + A that calls the closure as a method + Creates a CodeInstruction loading a field (LD[S]FLD[A]) @@ -980,6 +997,11 @@ For normal frames, frame.GetMethod() is returned. For frames containing patched methods, the replacement method is returned or null if no method can be found + + Gets the original method from the stackframe and uses original if method is a dynamic replacement + The + The original method from that stackframe + Gets Harmony version for all active Harmony instances [out] The current Harmony version @@ -1210,7 +1232,7 @@ - Patch serialization + Patch serialization @@ -1241,27 +1263,27 @@ - Serializable patch information + Serializable patch information - Prefixes as an array of + Prefixes as an array of - Postfixes as an array of + Postfixes as an array of - Transpilers as an array of + Transpilers as an array of - Finalizers as an array of + Finalizers as an array of - Returns if any of the patches wants debugging turned on + Returns if any of the patches wants debugging turned on @@ -1339,35 +1361,35 @@ - A serializable patch + A serializable patch - Zero-based index + Zero-based index - The owner (Harmony ID) + The owner (Harmony ID) - The priority, see + The priority, see - Keep this patch before the patches indicated in the list of Harmony IDs + Keep this patch before the patches indicated in the list of Harmony IDs - Keep this patch after the patches indicated in the list of Harmony IDs + Keep this patch after the patches indicated in the list of Harmony IDs - A flag that will log the replacement method via every time this patch is used to build the replacement, even in the future + A flag that will log the replacement method via every time this patch is used to build the replacement, even in the future - The method of the static patch method + The method of the static patch method @@ -1760,6 +1782,12 @@ The name of the field A field or null when type/name is null or when the field cannot be found + + + Gets the reflection information for a directly declared field + The member in the form TypeFullName:MemberName, where TypeFullName matches the form recognized by Type.GetType like Some.Namespace.Type. + A field or null when the field cannot be found + Gets the reflection information for a field by searching the type and all its super types @@ -1767,6 +1795,12 @@ The name of the field (case sensitive) A field or null when type/name is null or when the field cannot be found + + + Gets the reflection information for a field by searching the type and all its super types + The member in the form TypeFullName:MemberName, where TypeFullName matches the form recognized by Type.GetType like Some.Namespace.Type. + A field or null when the field cannot be found + Gets the reflection information for a field @@ -1781,6 +1815,12 @@ The name of the property (case sensitive) A property or null when type/name is null or when the property cannot be found + + + Gets the reflection information for a directly declared property + The member in the form TypeFullName:MemberName, where TypeFullName matches the form recognized by Type.GetType like Some.Namespace.Type. + A property or null when the property cannot be found + Gets the reflection information for the getter method of a directly declared property @@ -1788,6 +1828,12 @@ The name of the property (case sensitive) A method or null when type/name is null or when the property cannot be found + + + Gets the reflection information for the getter method of a directly declared property + The member in the form TypeFullName:MemberName, where TypeFullName matches the form recognized by Type.GetType like Some.Namespace.Type. + A method or null when the property cannot be found + Gets the reflection information for the setter method of a directly declared property @@ -1795,6 +1841,12 @@ The name of the property (case sensitive) A method or null when type/name is null or when the property cannot be found + + + Gets the reflection information for the Setter method of a directly declared property + The member in the form TypeFullName:MemberName, where TypeFullName matches the form recognized by Type.GetType like Some.Namespace.Type. + A method or null when the property cannot be found + Gets the reflection information for a property by searching the type and all its super types @@ -1802,6 +1854,12 @@ The name A property or null when type/name is null or when the property cannot be found + + + Gets the reflection information for a property by searching the type and all its super types + The member in the form TypeFullName:MemberName, where TypeFullName matches the form recognized by Type.GetType like Some.Namespace.Type. + A property or null when the property cannot be found + Gets the reflection information for the getter method of a property by searching the type and all its super types @@ -1809,6 +1867,12 @@ The name A method or null when type/name is null or when the property cannot be found + + + Gets the reflection information for the getter method of a property by searching the type and all its super types + The member in the form TypeFullName:MemberName, where TypeFullName matches the form recognized by Type.GetType like Some.Namespace.Type. + A method or null when type/name is null or when the property cannot be found + Gets the reflection information for the setter method of a property by searching the type and all its super types @@ -1816,6 +1880,12 @@ The name A method or null when type/name is null or when the property cannot be found + + + Gets the reflection information for the setter method of a property by searching the type and all its super types + The member in the form TypeFullName:MemberName, where TypeFullName matches the form recognized by Type.GetType like Some.Namespace.Type. + A method or null when type/name is null or when the property cannot be found + Gets the reflection information for a directly declared method @@ -1825,6 +1895,14 @@ Optional list of types that define the generic version of the method A method or null when type/name is null or when the method cannot be found + + + Gets the reflection information for a directly declared method + The member in the form TypeFullName:MemberName, where TypeFullName matches the form recognized by Type.GetType like Some.Namespace.Type. + Optional parameters to target a specific overload of the method + Optional list of types that define the generic version of the method + A method or null when the method cannot be found + Gets the reflection information for a method by searching the type and all its super types @@ -1837,12 +1915,17 @@ Gets the reflection information for a method by searching the type and all its super types - The target method in the form TypeFullName:MethodName, where the type name matches a form recognized by Type.GetType like Some.Namespace.Type. + The member in the form TypeFullName:MemberName, where TypeFullName matches the form recognized by Type.GetType like Some.Namespace.Type. Optional parameters to target a specific overload of the method Optional list of types that define the generic version of the method - A method or null when type/name is null or when the method cannot be found + A method or null when the method cannot be found + + Gets the method of an enumerator method + Enumerator method that creates the enumerator + The internal method of the enumerator or null if no valid enumerator is detected + Gets the names of all method that are declared in a type The declaring class/type @@ -2109,6 +2192,12 @@ + + Creates a field reference delegate for an instance field of a class or static field (NOT an instance field of a struct) + type of the field + The member in the form TypeFullName:MemberName, where TypeFullName matches the form recognized by Type.GetType like Some.Namespace.Type. + A readable/assignable delegate with T=object + Creates a field reference delegate for an instance field of a class or static field (NOT an instance field of a struct) @@ -2281,6 +2370,13 @@ The name of the field A readable/assignable reference to the field + + + Creates a static field reference + The type of the field + The member in the form TypeFullName:MemberName, where TypeFullName matches the form recognized by Type.GetType like Some.Namespace.Type. + A readable/assignable reference to the field + Creates a static field reference @@ -2336,6 +2432,34 @@ + + + Creates a delegate to a given method + The delegate Type + The method in the form TypeFullName:MemberName, where TypeFullName matches the form recognized by Type.GetType like Some.Namespace.Type. + + Only applies for instance methods. If null (default), returned delegate is an open (a.k.a. unbound) instance delegate + where an instance is supplied as the first argument to the delegate invocation; else, delegate is a closed (a.k.a. bound) + instance delegate where the delegate invocation always applies to the given . + + + Only applies for instance methods. If true (default) and is virtual, invocation of the delegate + calls the instance method virtually (the instance type's most-derived/overriden implementation of the method is called); + else, invocation of the delegate calls the exact specified (this is useful for calling base class methods) + Note: if false and is an interface method, an ArgumentException is thrown. + + A delegate of given to given + + + Delegate invocation is more performant and more convenient to use than + at a one-time setup cost. + + + Works for both type of static and instance methods, both open and closed (a.k.a. unbound and bound) instance methods, + and both class and struct methods. + + + Creates a delegate for a given delegate definition, attributed with [] @@ -2508,6 +2632,412 @@ The objects The hash code + + + A CodeInstruction match + + + The name of the match + + + The matched opcodes + + + The matched operands + + + The jumps from the match + + + The jumps to the match + + + The match predicate + + + Creates a code match + The optional opcode + The optional operand + The optional name + + + + Creates a code match + The CodeInstruction + An optional name + + + + Creates a code match + The predicate + An optional name + + + + Returns a string that represents the match + A string representation + + + + A CodeInstruction matcher + + + The current position + The index or -1 if out of bounds + + + + Gets the number of code instructions in this matcher + The count + + + + Checks whether the position of this CodeMatcher is within bounds + True if this CodeMatcher is valid + + + + Checks whether the position of this CodeMatcher is outside its bounds + True if this CodeMatcher is invalid + + + + Gets the remaining code instructions + The remaining count + + + + Gets the opcode at the current position + The opcode + + + + Gets the operand at the current position + The operand + + + + Gets the labels at the current position + The labels + + + + Gets the exception blocks at the current position + The blocks + + + + Creates an empty code matcher + + + Creates a code matcher from an enumeration of instructions + The instructions (transpiler argument) + An optional IL generator + + + + Makes a clone of this instruction matcher + A copy of this matcher + + + + Gets instructions at the current position + The instruction + + + + Gets instructions at the current position with offset + The offset + The instruction + + + + Gets all instructions + A list of instructions + + + + Gets all instructions as an enumeration + A list of instructions + + + + Gets some instructions counting from current position + Number of instructions + A list of instructions + + + + Gets all instructions within a range + The start index + The end index + A list of instructions + + + + Gets all instructions within a range (relative to current position) + The start offset + The end offset + A list of instructions + + + + Gets a list of all distinct labels + The instructions (transpiler argument) + A list of Labels + + + + Reports a failure + The method involved + The logger + True if current position is invalid and error was logged + + + + Throw an InvalidOperationException if current state is invalid (position out of bounds / last match failed) + Explanation of where/why the exception was thrown that will be added to the exception message + The same code matcher + + + + Throw an InvalidOperationException if current state is invalid (position out of bounds / last match failed), + or if the matches do not match at current position + Explanation of where/why the exception was thrown that will be added to the exception message + Some code matches + The same code matcher + + + + Throw an InvalidOperationException if current state is invalid (position out of bounds / last match failed), + or if the matches do not match at any point between current position and the end + Explanation of where/why the exception was thrown that will be added to the exception message + Some code matches + The same code matcher + + + + Throw an InvalidOperationException if current state is invalid (position out of bounds / last match failed), + or if the matches do not match at any point between current position and the start + Explanation of where/why the exception was thrown that will be added to the exception message + Some code matches + The same code matcher + + + + Throw an InvalidOperationException if current state is invalid (position out of bounds / last match failed), + or if the check function returns false + Explanation of where/why the exception was thrown that will be added to the exception message + Function that checks validity of current state. If it returns false, an exception is thrown + The same code matcher + + + + Sets an instruction at current position + The instruction to set + The same code matcher + + + + Sets instruction at current position and advances + The instruction + The same code matcher + + + + Sets opcode and operand at current position + The opcode + The operand + The same code matcher + + + + Sets opcode and operand at current position and advances + The opcode + The operand + The same code matcher + + + + Sets opcode at current position and advances + The opcode + The same code matcher + + + + Sets operand at current position and advances + The operand + The same code matcher + + + + Creates a label at current position + [out] The label + The same code matcher + + + + Creates a label at a position + The position + [out] The new label + The same code matcher + + + + Creates a label at a position + The offset + [out] The new label + The same code matcher + + + + Adds an enumeration of labels to current position + The labels + The same code matcher + + + + Adds an enumeration of labels at a position + The position + The labels + The same code matcher + + + + Sets jump to + Branch instruction + Destination for the jump + [out] The created label + The same code matcher + + + + Inserts some instructions + The instructions + The same code matcher + + + + Inserts an enumeration of instructions + The instructions + The same code matcher + + + + Inserts a branch + The branch opcode + Branch destination + The same code matcher + + + + Inserts some instructions and advances the position + The instructions + The same code matcher + + + + Inserts an enumeration of instructions and advances the position + The instructions + The same code matcher + + + + Inserts a branch and advances the position + The branch opcode + Branch destination + The same code matcher + + + + Removes current instruction + The same code matcher + + + + Removes some instruction from current position by count + Number of instructions + The same code matcher + + + + Removes the instructions in a range + The start + The end + The same code matcher + + + + Removes the instructions in a offset range + The start offset + The end offset + The same code matcher + + + + Advances the current position + The offset + The same code matcher + + + + Moves the current position to the start + The same code matcher + + + + Moves the current position to the end + The same code matcher + + + + Searches forward with a predicate and advances position + The predicate + The same code matcher + + + + Searches backwards with a predicate and reverses position + The predicate + The same code matcher + + + + Matches forward and advances position to beginning of matching sequence + Some code matches + The same code matcher + + + + Matches forward and advances position to ending of matching sequence + Some code matches + The same code matcher + + + + Matches backwards and reverses position to beginning of matching sequence + Some code matches + The same code matcher + + + + Matches backwards and reverses position to ending of matching sequence + Some code matches + The same code matcher + + + + Repeats a match action until boundaries are met + The match action + An optional action that is executed when no match is found + The same code matcher + + + + Gets a match by its name + The match name + An instruction + General extensions for common cases @@ -2574,6 +3104,11 @@ Extensions for + + Returns if an is initialized and valid + The + + Shortcut for testing whether the operand is equal to a non-null value The @@ -2715,15 +3250,15 @@ A list of - Moves all labels from the code instruction to a different one + Moves all labels from the code instruction to another one The to move the labels from - The to move the labels to + The other to move the labels to The code instruction labels were moved from (now empty) - Moves all labels from a different code instruction to the current one - The to move the labels from - The to move the labels to + Moves all labels from another code instruction to the current one + The to move the labels to + The other to move the labels from The code instruction that received the labels @@ -2744,15 +3279,15 @@ A list of - Moves all ExceptionBlocks from the code instruction to a different one + Moves all ExceptionBlocks from the code instruction to another one The to move the ExceptionBlocks from - The to move the ExceptionBlocks to + The other to move the ExceptionBlocks to The code instruction blocks were moved from (now empty) - Moves all ExceptionBlocks from a different code instruction to the current one - The to move the ExceptionBlocks from - The to move the ExceptionBlocks to + Moves all ExceptionBlocks from another code instruction to the current one + The to move the ExceptionBlocks to + The other to move the ExceptionBlocks from The code instruction that received the blocks @@ -2859,6 +3394,11 @@ Log a string directly to disk. Slower method that prevents missing information in case of a crash The string to log. + + + Log a string directly to disk if Harmony.DEBUG is true. Slower method that prevents missing information in case of a crash + The string to log. + Resets and deletes the log diff --git a/docs/release-notes.md b/docs/release-notes.md index ddf97fe8..0781eb40 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -7,6 +7,8 @@ * For mod authors: * Added a new `IRawTextureData` asset type. _You can now load image files through `helper.ModContent` as `IRawTextureData` instead of `Texture2D`. This provides the image size and raw pixel data, which you can pass into other SMAPI APIs like `asset.AsImage().PatchImage`. This is much more efficient when you don't need a full `Texture2D` instance, since it bypasses the GPU operations needed to create one._ + * Updated to Harmony 2.2.1 (see what's new in [2.2.0](https://github.com/pardeike/Harmony/releases/tag/v2.2.0.0) and [2.2.1](https://github.com/pardeike/Harmony/releases/tag/v2.2.1.0)). + * Updated dependencies (MonoMod.Common 21.6.21.1 → 22.3.5.1). * Removed transitional `UsePintail` option added in 3.14.0 (now always enabled). * For mod authors: diff --git a/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs b/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs index b5fc1f57..1fcda077 100644 --- a/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs +++ b/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs @@ -27,15 +27,6 @@ namespace MonoMod.Utils private static readonly object[] _NoArgs = Array.Empty(); private static readonly object?[] _CacheGetterArgs = { /* MemberListType.All */ 0, /* name apparently always null? */ null }; - private static readonly Type? t_RuntimeModule = - typeof(Module).Assembly - .GetType("System.Reflection.RuntimeModule"); - - private static readonly PropertyInfo? p_RuntimeModule_RuntimeType = - typeof(Module).Assembly - .GetType("System.Reflection.RuntimeModule") - ?.GetProperty("RuntimeType", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - private static readonly Type? t_RuntimeType = typeof(Type).Assembly .GetType("System.RuntimeType"); @@ -109,22 +100,6 @@ namespace MonoMod.Utils } } - public static Type? GetModuleType(this Module? module) - { - // Sadly we can't blindly resolve type 0x02000001 as the runtime throws ArgumentException. - - if (module == null || t_RuntimeModule == null || !t_RuntimeModule.IsInstanceOfType(module)) - return null; - - // .NET - if (p_RuntimeModule_RuntimeType != null) - return (Type?)p_RuntimeModule_RuntimeType.GetValue(module, _NoArgs); - - // The hotfix doesn't apply to Mono anyway, thus that's not copied over. - - return null; - } - public static Type? GetRealDeclaringType(this MemberInfo member) { return member.DeclaringType ?? member.Module.GetModuleType(); diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index 91e4c668..5bc12429 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -23,7 +23,7 @@ - + From 5ffa260e02fc8a6eae40c1fd798da2a4877262c0 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 26 May 2022 01:41:49 -0400 Subject: [PATCH 09/27] add validation error when loading XNB file as IRawTextureData --- 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 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) From 2134cef74fc7549387defc639789ebc0bcb5b5db Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 27 May 2022 20:47:50 -0400 Subject: [PATCH 10/27] link to SMAPI 3.15 release highlights --- docs/release-notes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 0781eb40..db229ecf 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,6 +2,8 @@ # Release notes ## Upcoming release +See [release highlights](https://www.patreon.com/posts/66986798). + * For players: * Optimized mod image file loading. * For mod authors: From 4f6965eef3c7690dcfc1e1946362a84f68f40767 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 28 May 2022 15:07:19 -0400 Subject: [PATCH 11/27] encapsulate loading the raw image data for mod patching --- .../ContentManagers/ModContentManager.cs | 53 +++++++++++++------ 1 file changed, 36 insertions(+), 17 deletions(-) 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); + this.LoadRawImageData(file, out int width, out int height, out Color[] pixels, asRawData); - // 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 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. From 9d21e0bbecb64243cf98e6c9a10ed5aa4ceed2bf Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 28 May 2022 19:17:33 -0400 Subject: [PATCH 12/27] simplify pixel conversion loop --- src/SMAPI/Framework/ContentManagers/ModContentManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 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: [PATCH 13/27] 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. --- .../ContentManagers/ModContentManager.cs | 239 ++++++++++++++---- 1 file changed, 183 insertions(+), 56 deletions(-) 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; } From 87c7095e8d7bfc1c1819e7d17cfea3d07c65dfea Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 30 May 2022 01:22:50 -0400 Subject: [PATCH 14/27] apply style conventions --- .../ContentManagers/ModContentManager.cs | 155 +++++------------- 1 file changed, 37 insertions(+), 118 deletions(-) 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.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; } @@ -479,10 +413,7 @@ 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) { @@ -505,23 +436,13 @@ namespace StardewModdingAPI.Framework.ContentManagers // 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) { @@ -531,12 +452,11 @@ namespace StardewModdingAPI.Framework.ContentManagers tilesheet.ImageSource = assetName.Name; } } - catch (SContentLoadException) - { - throw; - } catch (Exception ex) { + if (ex is SContentLoadException) + throw; + throw new SContentLoadException(ContentLoadErrorType.InvalidData, $"{errorPrefix} The tilesheet couldn't be loaded.", ex); } } @@ -580,11 +500,10 @@ namespace StardewModdingAPI.Framework.ContentManagers } // get from game assets - AssetName contentKey = this.Coordinator.ParseAssetName(ModContentManager.GetContentKeyForTilesheetImageSource(relativePath), allowLocales: false); + AssetName contentKey = this.Coordinator.ParseAssetName(this.GetContentKeyForTilesheetImageSource(relativePath), allowLocales: false); try { - // no need to bypass cache here, since we're not storing the asset - this.GameContentManager.LoadLocalized(contentKey, this.GameContentManager.Language, useCache: true); + this.GameContentManager.LoadLocalized(contentKey, this.GameContentManager.Language, useCache: true); // no need to bypass cache here, since we're not storing the asset assetName = contentKey; return true; } @@ -622,7 +541,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Get the asset key for a tilesheet in the game's Maps content folder. /// The tilesheet image source. - private static string GetContentKeyForTilesheetImageSource(string relativePath) + private string GetContentKeyForTilesheetImageSource(string relativePath) { string key = relativePath; string topFolder = PathUtilities.GetSegments(key, limit: 2)[0]; From ba7f5701def491f66b3e14aa5990eba521dfcf7c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 30 May 2022 01:22:50 -0400 Subject: [PATCH 15/27] simplify asset type validaiton --- .../ContentManagers/ModContentManager.cs | 67 +++++-------------- 1 file changed, 15 insertions(+), 52 deletions(-) diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index d4a30f71..1f38b76b 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -162,57 +162,14 @@ 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. /// The file to load. private T LoadFont(IAssetName assetName, FileInfo file) { - // validate - if (!this.ValidateType(assetName, file, out var exception)) - throw exception; + this.AssertValidType(assetName, file, typeof(XmlSource)); - // load string source = File.ReadAllText(file.FullName); return (T)(object)new XmlSource(source); } @@ -235,10 +192,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The file to load. private T LoadImageFile(IAssetName assetName, FileInfo file) { - // validate type - if (!this.ValidateType(assetName, file, out var exception)) - throw exception; - + this.AssertValidType(assetName, file, typeof(Texture2D), typeof(IRawTextureData)); bool asRawData = typeof(T).IsAssignableTo(typeof(IRawTextureData)); // load @@ -302,11 +256,8 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The file to load. private T LoadMapFile(IAssetName assetName, FileInfo file) { - // validate - if (!this.ValidateType(assetName, file, out var exception)) - throw exception; + this.AssertValidType(assetName, file, typeof(Map)); - // load FormatManager formatManager = FormatManager.Instance; Map map = formatManager.LoadMap(file.FullName); map.assetPath = assetName.Name; @@ -348,6 +299,18 @@ namespace StardewModdingAPI.Framework.ContentManagers throw this.GetLoadError(assetName, ContentLoadErrorType.InvalidName, $"unknown file extension '{file.Extension}'; must be one of '.fnt', '.json', '.png', '.tbin', '.tmx', or '.xnb'."); } + /// Assert that the asset type is compatible with one of the allowed types. + /// The actual asset type. + /// The asset name relative to the loader root directory. + /// The file being loaded. + /// The allowed asset types. + /// The is not compatible with any of the . + 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))}'."); + } + /// Get 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. From 565677c18ca4e12a2d8635b4ea2db49255d28142 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 30 May 2022 01:22:51 -0400 Subject: [PATCH 16/27] use IRawTextureData instead of intermediate tuple --- .../ContentManagers/ModContentManager.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 1f38b76b..160e3c19 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -1,11 +1,11 @@ using System; 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,7 +19,6 @@ using StardewValley; using xTile; using xTile.Format; using xTile.Tiles; -using Color = Microsoft.Xna.Framework.Color; namespace StardewModdingAPI.Framework.ContentManagers { @@ -198,14 +197,14 @@ namespace StardewModdingAPI.Framework.ContentManagers // load if (asRawData || this.UseRawImageLoading) { - (Size size, Color[] pixels) = this.LoadRawImageData(file, asRawData); + IRawTextureData raw = this.LoadRawImageData(file, asRawData); if (asRawData) - return (T)(object)new RawTextureData(size.Width, size.Height, pixels); + return (T)raw; else { - Texture2D texture = new(Game1.graphics.GraphicsDevice, size.Width, size.Height); - texture.SetData(pixels); + Texture2D texture = new(Game1.graphics.GraphicsDevice, raw.Width, raw.Height); + texture.SetData(raw.Data); return (T)(object)texture; } } @@ -224,17 +223,18 @@ 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 (Size Size, Color[] Data) LoadRawImageData(FileInfo file, bool forRawData) + private IRawTextureData LoadRawImageData(FileInfo file, bool forRawData) { - Size size; - // load raw data + int width; + int height; SKPMColor[] rawPixels; { using FileStream stream = File.OpenRead(file.FullName); using SKBitmap bitmap = SKBitmap.Decode(stream); rawPixels = SKPMColor.PreMultiply(bitmap.Pixels); - size = new(bitmap.Width, bitmap.Height); + width = bitmap.Width; + height = bitmap.Height; } // convert to XNA pixel format @@ -247,7 +247,7 @@ namespace StardewModdingAPI.Framework.ContentManagers : new Color(r: pixel.Red, g: pixel.Green, b: pixel.Blue, alpha: pixel.Alpha); } - return (size, pixels); + return new RawTextureData(width, height, pixels); } /// Load an unpacked image file (.tbin or .tmx). From 43e9e2cfe2c6938121d9a5e106acfc944a1821c6 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 30 May 2022 01:30:43 -0400 Subject: [PATCH 17/27] fix accidental case-sensitivity change --- src/SMAPI/Framework/ContentManagers/ModContentManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 160e3c19..8e2d58a6 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -331,7 +331,7 @@ 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, StringComparer.OrdinalIgnoreCase)) { foreach (string extension in ModContentManager.LocalTilesheetExtensions) { From 03897776e08cb0703c9763e3b5dcc81d85f277f9 Mon Sep 17 00:00:00 2001 From: Ameisen <14104310+ameisen@users.noreply.github.com> Date: Wed, 1 Jun 2022 19:39:47 -0500 Subject: [PATCH 18/27] Cleaning up and optimizing `ContentCache.cs` --- src/SMAPI/Framework/Content/ContentCache.cs | 31 +++++++++++++-------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/SMAPI/Framework/Content/ContentCache.cs b/src/SMAPI/Framework/Content/ContentCache.cs index 736ee5da..959d4fb3 100644 --- a/src/SMAPI/Framework/Content/ContentCache.cs +++ b/src/SMAPI/Framework/Content/ContentCache.cs @@ -14,7 +14,7 @@ namespace StardewModdingAPI.Framework.Content ** Fields *********/ /// The underlying asset cache. - private readonly IDictionary Cache; + private readonly Dictionary Cache; /********* @@ -29,7 +29,7 @@ namespace StardewModdingAPI.Framework.Content } /// The current cache keys. - public IEnumerable Keys => this.Cache.Keys; + public Dictionary.KeyCollection Keys => this.Cache.Keys; /********* @@ -89,33 +89,40 @@ namespace StardewModdingAPI.Framework.Content /// Returns the removed key (if any). public bool Remove(string key, bool dispose) { - // get entry - if (!this.Cache.TryGetValue(key, out object? value)) + // remove and get entry + if (!this.Cache.Remove(key, out object? value)) return false; // dispose & remove entry if (dispose && value is IDisposable disposable) disposable.Dispose(); - return this.Cache.Remove(key); + return true; } - /// Purge matched assets from the cache. + /// Purge assets matching from the cache. /// Matches the asset keys to invalidate. - /// Whether to dispose invalidated assets. This should only be true when they're being invalidated as part of a dispose, to avoid crashing the game. - /// Returns the removed keys (if any). + /// Whether to dispose invalidated assets. This should only be when they're being invalidated as part of a , to avoid crashing the game. + /// Returns any removed keys. public IEnumerable Remove(Func predicate, bool dispose) { List removed = new List(); - foreach (string key in this.Cache.Keys.ToArray()) + foreach ((string key, object value) in this.Cache) { - if (predicate(key, this.Cache[key])) + if (predicate(key, value)) { - this.Remove(key, dispose); removed.Add(key); } } - return removed; + + foreach (string key in removed) + { + this.Remove(key, dispose); + } + + // If `removed` is empty, return an empty `Enumerable` instead so that `removed` + // can be quickly collected in Gen0 instead of potentially living longer. + return removed.Count == 0 ? Enumerable.Empty() : removed; } } } From 62328e438487a55cb84ee09ef966092572b2252e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 2 Jun 2022 01:28:04 -0400 Subject: [PATCH 19/27] tweak new code, update release notes --- docs/release-notes.md | 1 + src/SMAPI/Framework/Content/ContentCache.cs | 12 ++++-------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index db229ecf..66447b3d 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -6,6 +6,7 @@ See [release highlights](https://www.patreon.com/posts/66986798). * For players: * Optimized mod image file loading. + * Minor optimizations (thanks to Michael Kuklinski / Ameisen!). * For mod authors: * Added a new `IRawTextureData` asset type. _You can now load image files through `helper.ModContent` as `IRawTextureData` instead of `Texture2D`. This provides the image size and raw pixel data, which you can pass into other SMAPI APIs like `asset.AsImage().PatchImage`. This is much more efficient when you don't need a full `Texture2D` instance, since it bypasses the GPU operations needed to create one._ diff --git a/src/SMAPI/Framework/Content/ContentCache.cs b/src/SMAPI/Framework/Content/ContentCache.cs index 959d4fb3..bf42812b 100644 --- a/src/SMAPI/Framework/Content/ContentCache.cs +++ b/src/SMAPI/Framework/Content/ContentCache.cs @@ -106,23 +106,19 @@ namespace StardewModdingAPI.Framework.Content /// Returns any removed keys. public IEnumerable Remove(Func predicate, bool dispose) { - List removed = new List(); + List removed = new(); foreach ((string key, object value) in this.Cache) { if (predicate(key, value)) - { removed.Add(key); - } } foreach (string key in removed) - { this.Remove(key, dispose); - } - // If `removed` is empty, return an empty `Enumerable` instead so that `removed` - // can be quickly collected in Gen0 instead of potentially living longer. - return removed.Count == 0 ? Enumerable.Empty() : removed; + return removed.Count == 0 + ? Enumerable.Empty() // let GC collect the list in gen0 instead of potentially living longer + : removed; } } } From b6d15ec57fb0aa420db81f310057e869eb3f0c53 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 7 Jun 2022 20:41:41 -0400 Subject: [PATCH 20/27] update unit test & web dependencies --- .../SMAPI.ModBuildConfig.Analyzer.Tests.csproj | 6 +++--- src/SMAPI.Tests/SMAPI.Tests.csproj | 8 ++++---- src/SMAPI.Toolkit/SMAPI.Toolkit.csproj | 2 +- src/SMAPI.Web/SMAPI.Web.csproj | 14 +++++++------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj b/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj index 264932e4..3be9c225 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj @@ -6,9 +6,9 @@ - - - + + + diff --git a/src/SMAPI.Tests/SMAPI.Tests.csproj b/src/SMAPI.Tests/SMAPI.Tests.csproj index 67997b30..f09abbb1 100644 --- a/src/SMAPI.Tests/SMAPI.Tests.csproj +++ b/src/SMAPI.Tests/SMAPI.Tests.csproj @@ -14,11 +14,11 @@ - - - + + + - + diff --git a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj index ec27bf79..2400b76d 100644 --- a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj +++ b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/SMAPI.Web/SMAPI.Web.csproj b/src/SMAPI.Web/SMAPI.Web.csproj index f1400e62..4c2569e1 100644 --- a/src/SMAPI.Web/SMAPI.Web.csproj +++ b/src/SMAPI.Web/SMAPI.Web.csproj @@ -15,14 +15,14 @@ - - + + - - - - - + + + + + From 0bb9fc42933fc01c349a685e061a753cecb49518 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 7 Jun 2022 21:27:11 -0400 Subject: [PATCH 21/27] update Newtonsoft.Json --- docs/release-notes.md | 13 ++++++------- .../SMAPI.ModBuildConfig.csproj | 2 +- src/SMAPI.Tests/SMAPI.Tests.csproj | 2 +- src/SMAPI.Toolkit/SMAPI.Toolkit.csproj | 2 +- src/SMAPI/SMAPI.csproj | 2 +- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 66447b3d..83dcd53d 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -7,16 +7,15 @@ See [release highlights](https://www.patreon.com/posts/66986798). * For players: * Optimized mod image file loading. * Minor optimizations (thanks to Michael Kuklinski / Ameisen!). -* For mod authors: - * Added a new `IRawTextureData` asset type. - _You can now load image files through `helper.ModContent` as `IRawTextureData` instead of `Texture2D`. This provides the image size and raw pixel data, which you can pass into other SMAPI APIs like `asset.AsImage().PatchImage`. This is much more efficient when you don't need a full `Texture2D` instance, since it bypasses the GPU operations needed to create one._ - * Updated to Harmony 2.2.1 (see what's new in [2.2.0](https://github.com/pardeike/Harmony/releases/tag/v2.2.0.0) and [2.2.1](https://github.com/pardeike/Harmony/releases/tag/v2.2.1.0)). - * Updated dependencies (MonoMod.Common 21.6.21.1 → 22.3.5.1). - * Removed transitional `UsePintail` option added in 3.14.0 (now always enabled). * For mod authors: - * Fixed map edits which change warps sometimes rebuilding the NPC pathfinding cache unnecessarily, which could cause a noticeable delay for players. + * Added a [new `IRawTextureData` asset type](https://stardewvalleywiki.com/Modding:Migrate_to_SMAPI_4.0#Raw_texture_data), so mods can avoid creating full `Texture2D` instances in many cases. * In `smapi-internal/config.json`, you can now enable verbose logging for specific mods (instead of all or nothing). + * Updated dependencies: + * Harmony 2.2.1 (see changes in [2.2.0](https://github.com/pardeike/Harmony/releases/tag/v2.2.0.0) and [2.2.1](https://github.com/pardeike/Harmony/releases/tag/v2.2.1.0)); + * Newtonsoft.Json 13.0.1 (see [changes](https://github.com/JamesNK/Newtonsoft.Json/releases/tag/13.0.1)). + * Removed transitional `UsePintail` option added in 3.14.0 (now always enabled). + * Fixed map edits which change warps sometimes rebuilding the NPC pathfinding cache unnecessarily, which could cause a noticeable delay for players. ## 3.14.7 Released 01 June 2022 for Stardew Valley 1.5.6 or later. diff --git a/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj b/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj index c5790186..e25da168 100644 --- a/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj +++ b/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj @@ -24,7 +24,7 @@ - + - 3.14.7 + 3.15.0 SMAPI latest $(AssemblySearchPaths);{GAC} diff --git a/docs/release-notes.md b/docs/release-notes.md index 14658340..496d016a 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,8 +1,8 @@ ← [README](README.md) # Release notes -## Upcoming release -See [release highlights](https://www.patreon.com/posts/66986798). +## 3.15.0 +Released 17 June 2022 for Stardew Valley 1.5.6 or later. See [release highlights](https://www.patreon.com/posts/67877219). * For players: * Optimized mod image file loading. @@ -10,7 +10,7 @@ See [release highlights](https://www.patreon.com/posts/66986798). * Updated compatibility list. * For mod authors: - * Added a [new `IRawTextureData` asset type](https://stardewvalleywiki.com/Modding:Migrate_to_SMAPI_4.0#Raw_texture_data), so mods can avoid creating full `Texture2D` instances in many cases. + * Added an [`IRawTextureData` asset type](https://stardewvalleywiki.com/Modding:Migrate_to_SMAPI_4.0#Raw_texture_data), to avoid creating full `Texture2D` instances in many cases. * In `smapi-internal/config.json`, you can now enable verbose logging for specific mods (instead of all or nothing). * Updated dependencies: * Harmony 2.2.1 (see changes in [2.2.0](https://github.com/pardeike/Harmony/releases/tag/v2.2.0.0) and [2.2.1](https://github.com/pardeike/Harmony/releases/tag/v2.2.1.0)); diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json index 564e480e..300de9d2 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.14.7", + "Version": "3.15.0", "Description": "Adds SMAPI console commands that let you manipulate the game.", "UniqueID": "SMAPI.ConsoleCommands", "EntryDll": "ConsoleCommands.dll", - "MinimumApiVersion": "3.14.7" + "MinimumApiVersion": "3.15.0" } diff --git a/src/SMAPI.Mods.ErrorHandler/manifest.json b/src/SMAPI.Mods.ErrorHandler/manifest.json index 39d22b5f..15a1e0f3 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.14.7", + "Version": "3.15.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.14.7" + "MinimumApiVersion": "3.15.0" } diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json index 8eaf2475..1a11742c 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.14.7", + "Version": "3.15.0", "Description": "Automatically backs up all your saves once per day into its folder.", "UniqueID": "SMAPI.SaveBackup", "EntryDll": "SaveBackup.dll", - "MinimumApiVersion": "3.14.7" + "MinimumApiVersion": "3.15.0" } diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index c63324e3..db88563e 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -50,7 +50,7 @@ namespace StardewModdingAPI internal static int? LogScreenId { get; set; } /// SMAPI's current raw semantic version. - internal static string RawApiVersion = "3.14.7"; + internal static string RawApiVersion = "3.15.0"; } /// Contains SMAPI's constants and assumptions.

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