From 822cc71619cd173a67de241843cf1679cfc1904d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 28 Jan 2021 19:51:30 -0500 Subject: [PATCH 01/20] fix error running 'install on Windows.bat' for one user --- docs/release-notes.md | 4 ++++ src/SMAPI.Installer/assets/windows-install.bat | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index dabdada1..bb5998f8 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -7,6 +7,10 @@ * Migrated to Harmony 2.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info). --> +## Upcoming release +* For players: + * Fixed error running `install on Windows.bat` in very rare cases. + ## 3.9.1 Released 25 January 2021 for Stardew Valley 1.5.4 or later. diff --git a/src/SMAPI.Installer/assets/windows-install.bat b/src/SMAPI.Installer/assets/windows-install.bat index d02dd4c6..2cc54e80 100644 --- a/src/SMAPI.Installer/assets/windows-install.bat +++ b/src/SMAPI.Installer/assets/windows-install.bat @@ -4,5 +4,5 @@ if not errorlevel 1 ( echo Oops! It looks like you're running the installer from inside a zip file. Make sure you unzip the download first. pause ) else ( - start /WAIT /B internal/windows-install.exe + start /WAIT /B ./internal/windows-install.exe ) From b2a6933efb0719b48034eff8c29b5f12beb00248 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 28 Jan 2021 21:21:18 -0500 Subject: [PATCH 02/20] fix mod type defaulted incorrectly in SMAPI toolkit --- docs/release-notes.md | 3 +++ .../Framework/ModScanning/ModScanner.cs | 15 ++++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index bb5998f8..2e99277f 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -11,6 +11,9 @@ * For players: * Fixed error running `install on Windows.bat` in very rare cases. +* For modders: + * Fixed SMAPI toolkit defaulting the mod type to SMAPI if its `manifest.json` has neither `EntryDll` nor `ContentPackFor`. This only affects external tools, since SMAPI itself validates those fields separately. + ## 3.9.1 Released 25 January 2021 for Stardew Valley 1.5.4 or later. diff --git a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs index 86a97016..fd206d9d 100644 --- a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs +++ b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs @@ -177,12 +177,17 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning } // get mod type - ModType type = ModType.Invalid; - if (manifest != null) + ModType type; { - type = !string.IsNullOrWhiteSpace(manifest.ContentPackFor?.UniqueID) - ? ModType.ContentPack - : ModType.Smapi; + bool isContentPack = !string.IsNullOrWhiteSpace(manifest?.ContentPackFor?.UniqueID); + bool isSmapi = !string.IsNullOrWhiteSpace(manifest?.EntryDll); + + if (isContentPack == isSmapi) + type = ModType.Invalid; + else if (isContentPack) + type = ModType.ContentPack; + else + type = ModType.Smapi; } // build result From 7e8f4518764a86e7d3589ae75235b1d3d4462f8b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 31 Jan 2021 15:37:00 -0500 Subject: [PATCH 03/20] add experimental 'aggressive memory optimization' flag (#757) --- docs/release-notes.md | 1 + src/SMAPI/Framework/ContentCoordinator.cs | 18 +++++++++---- .../ContentManagers/BaseContentManager.cs | 26 +++++++++++++----- .../ContentManagers/GameContentManager.cs | 5 ++-- .../ContentManagers/ModContentManager.cs | 5 ++-- src/SMAPI/Framework/Logging/LogManager.cs | 26 +++++++++++------- src/SMAPI/Framework/Models/SConfig.cs | 6 ++++- src/SMAPI/Framework/SCore.cs | 4 +-- src/SMAPI/Metadata/CoreAssetPropagator.cs | 27 ++++++++++++++++--- src/SMAPI/SMAPI.config.json | 6 +++++ 10 files changed, 92 insertions(+), 32 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 2e99277f..e54fd24b 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -9,6 +9,7 @@ ## Upcoming release * For players: + * Added _aggressive memory optimization_ option. This is experimental and disabled by default; you can enable it in `smapi-internal/config.json` if you experience `OutOfMemoryException` crashes. * Fixed error running `install on Windows.bat` in very rare cases. * For modders: diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 77dd6c72..81265fa2 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -26,6 +26,9 @@ namespace StardewModdingAPI.Framework /// An asset key prefix for assets from SMAPI mod folders. private readonly string ManagedPrefix = "SMAPI"; + /// Whether to enable more aggressive memory optimizations. + private readonly bool AggressiveMemoryOptimizations; + /// Encapsulates monitoring and logging. private readonly IMonitor Monitor; @@ -91,8 +94,10 @@ namespace StardewModdingAPI.Framework /// Simplifies access to private code. /// Encapsulates SMAPI's JSON file parsing. /// A callback to invoke the first time *any* game content manager loads an asset. - public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset) + /// Whether to enable more aggressive memory optimizations. + public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset, bool aggressiveMemoryOptimizations) { + this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations; this.Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor)); this.Reflection = reflection; this.JsonHelper = jsonHelper; @@ -108,11 +113,12 @@ namespace StardewModdingAPI.Framework monitor: monitor, reflection: reflection, onDisposing: this.OnDisposing, - onLoadingFirstAsset: onLoadingFirstAsset + onLoadingFirstAsset: onLoadingFirstAsset, + aggressiveMemoryOptimizations: aggressiveMemoryOptimizations ) ); this.VanillaContentManager = new LocalizedContentManager(serviceProvider, rootDirectory); - this.CoreAssets = new CoreAssetPropagator(this.MainContentManager.AssertAndNormalizeAssetName, reflection); + this.CoreAssets = new CoreAssetPropagator(this.MainContentManager.AssertAndNormalizeAssetName, reflection, aggressiveMemoryOptimizations); } /// Get a new content manager which handles reading files from the game content folder with support for interception. @@ -130,7 +136,8 @@ namespace StardewModdingAPI.Framework monitor: this.Monitor, reflection: this.Reflection, onDisposing: this.OnDisposing, - onLoadingFirstAsset: this.OnLoadingFirstAsset + onLoadingFirstAsset: this.OnLoadingFirstAsset, + aggressiveMemoryOptimizations: this.AggressiveMemoryOptimizations ); this.ContentManagers.Add(manager); return manager; @@ -157,7 +164,8 @@ namespace StardewModdingAPI.Framework monitor: this.Monitor, reflection: this.Reflection, jsonHelper: this.JsonHelper, - onDisposing: this.OnDisposing + onDisposing: this.OnDisposing, + aggressiveMemoryOptimizations: this.AggressiveMemoryOptimizations ); this.ContentManagers.Add(manager); return manager; diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index 92264f8c..709c624e 100644 --- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs @@ -11,6 +11,7 @@ using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Reflection; using StardewValley; +using xTile; namespace StardewModdingAPI.Framework.ContentManagers { @@ -29,6 +30,9 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Encapsulates monitoring and logging. protected readonly IMonitor Monitor; + /// Whether to enable more aggressive memory optimizations. + protected readonly bool AggressiveMemoryOptimizations; + /// Whether the content coordinator has been disposed. private bool IsDisposed; @@ -75,7 +79,8 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Simplifies access to private code. /// A callback to invoke when the content manager is being disposed. /// Whether this content manager handles managed asset keys (e.g. to load assets from a mod folder). - protected BaseContentManager(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action onDisposing, bool isNamespaced) + /// Whether to enable more aggressive memory optimizations. + protected BaseContentManager(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action onDisposing, bool isNamespaced, bool aggressiveMemoryOptimizations) : base(serviceProvider, rootDirectory, currentCulture) { // init @@ -85,6 +90,7 @@ namespace StardewModdingAPI.Framework.ContentManagers this.Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor)); this.OnDisposing = onDisposing; this.IsNamespaced = isNamespaced; + this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations; // get asset data this.LanguageCodes = this.GetKeyLocales().ToDictionary(p => p.Value, p => p.Key, StringComparer.OrdinalIgnoreCase); @@ -198,14 +204,22 @@ namespace StardewModdingAPI.Framework.ContentManagers { this.ParseCacheKey(key, out string assetName, out _); - if (removeAssets.ContainsKey(assetName)) - return true; - if (predicate(assetName, asset.GetType())) + // check if asset should be removed + bool remove = removeAssets.ContainsKey(assetName); + if (!remove && predicate(assetName, asset.GetType())) { removeAssets[assetName] = asset; - return true; + remove = true; } - return false; + + // dispose if safe + if (remove && this.AggressiveMemoryOptimizations) + { + if (asset is Map map) + map.DisposeTileSheets(Game1.mapDisplayDevice); + } + + return remove; }, dispose); return removeAssets; diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index 665c019b..f8ee575f 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -52,8 +52,9 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Simplifies access to private code. /// A callback to invoke when the content manager is being disposed. /// A callback to invoke the first time *any* game content manager loads an asset. - public GameContentManager(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action onDisposing, Action onLoadingFirstAsset) - : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: false) + /// Whether to enable more aggressive memory optimizations. + public GameContentManager(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action onDisposing, Action onLoadingFirstAsset, bool aggressiveMemoryOptimizations) + : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: false, aggressiveMemoryOptimizations: aggressiveMemoryOptimizations) { this.OnLoadingFirstAsset = onLoadingFirstAsset; } diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 1456d3c1..284c1f37 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -50,8 +50,9 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Simplifies access to private code. /// Encapsulates SMAPI's JSON file parsing. /// A callback to invoke when the content manager is being disposed. - public ModContentManager(string name, IContentManager gameContentManager, IServiceProvider serviceProvider, string modName, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onDisposing) - : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: true) + /// Whether to enable more aggressive memory optimizations. + public ModContentManager(string name, IContentManager gameContentManager, IServiceProvider serviceProvider, string modName, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onDisposing, bool aggressiveMemoryOptimizations) + : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: true, aggressiveMemoryOptimizations: aggressiveMemoryOptimizations) { this.GameContentManager = gameContentManager; this.JsonHelper = jsonHelper; diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs index ff00cff7..5f191873 100644 --- a/src/SMAPI/Framework/Logging/LogManager.cs +++ b/src/SMAPI/Framework/Logging/LogManager.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Text.RegularExpressions; using System.Threading; using StardewModdingAPI.Framework.Commands; +using StardewModdingAPI.Framework.Models; using StardewModdingAPI.Framework.ModLoading; using StardewModdingAPI.Internal.ConsoleWriting; using StardewModdingAPI.Toolkit.Framework.ModData; @@ -284,19 +285,24 @@ namespace StardewModdingAPI.Framework.Logging } /// Log details for settings that don't match the default. - /// Whether to enable full console output for developers. - /// Whether to check for newer versions of SMAPI and mods on startup. - /// Whether to rewrite mods for compatibility. - public void LogSettingsHeader(bool isDeveloperMode, bool checkForUpdates, bool rewriteMods) + /// The settings to log. + public void LogSettingsHeader(SConfig settings) { - if (isDeveloperMode) - this.Monitor.Log("You have SMAPI for developers, so the console will be much more verbose. You can disable developer mode by installing the non-developer version of SMAPI.", LogLevel.Info); - if (!checkForUpdates) - this.Monitor.Log("You configured SMAPI to not check for updates. Running an old version of SMAPI is not recommended. You can enable update checks by reinstalling SMAPI.", LogLevel.Warn); - if (!rewriteMods) - this.Monitor.Log("You configured SMAPI to not rewrite broken mods. Many older mods may fail to load. You can undo this by reinstalling SMAPI.", LogLevel.Warn); + // developer mode + if (settings.DeveloperMode) + this.Monitor.Log("You enabled developer mode, so the console will be much more verbose. You can disable it by installing the non-developer version of SMAPI.", LogLevel.Info); + + // warnings + if (!settings.CheckForUpdates) + this.Monitor.Log("You disabled update checks, so you won't be notified of new SMAPI or mod updates. Running an old version of SMAPI is not recommended. You can undo this by reinstalling SMAPI.", LogLevel.Warn); + if (settings.AggressiveMemoryOptimizations) + this.Monitor.Log("You enabled aggressive memory optimizations. This is an experimental option which may cause errors or crashes. You can undo this by reinstalling SMAPI.", LogLevel.Warn); + if (!settings.RewriteMods) + this.Monitor.Log("You disabled rewriting broken mods, so many older mods may fail to load. You can undo this by reinstalling SMAPI.", LogLevel.Info); if (!this.Monitor.WriteToConsole) this.Monitor.Log("Writing to the terminal is disabled because the --no-terminal argument was received. This usually means launching the terminal failed.", LogLevel.Warn); + + // verbose logging this.Monitor.VerboseLog("Verbose logging enabled."); } diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index dea08717..382ae41f 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -21,7 +21,8 @@ namespace StardewModdingAPI.Framework.Models [nameof(WebApiBaseUrl)] = "https://smapi.io/api/", [nameof(VerboseLogging)] = false, [nameof(LogNetworkTraffic)] = false, - [nameof(RewriteMods)] = true + [nameof(RewriteMods)] = true, + [nameof(AggressiveMemoryOptimizations)] = false }; /// The default values for , to log changes if different. @@ -60,6 +61,9 @@ namespace StardewModdingAPI.Framework.Models /// Whether SMAPI should rewrite mods for compatibility. public bool RewriteMods { get; set; } = (bool)SConfig.DefaultValues[nameof(SConfig.RewriteMods)]; + /// Whether to enable more aggressive memory optimizations. + public bool AggressiveMemoryOptimizations { get; set; } = (bool)SConfig.DefaultValues[nameof(SConfig.AggressiveMemoryOptimizations)]; + /// Whether SMAPI should log network traffic. Best combined with , which includes network metadata. public bool LogNetworkTraffic { get; set; } diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index cd094ff4..0ae69f0f 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -277,7 +277,7 @@ namespace StardewModdingAPI.Framework // log basic info this.LogManager.HandleMarkerFiles(); - this.LogManager.LogSettingsHeader(this.Settings.DeveloperMode, this.Settings.CheckForUpdates, this.Settings.RewriteMods); + this.LogManager.LogSettingsHeader(this.Settings); // set window titles this.SetWindowTitles( @@ -1149,7 +1149,7 @@ namespace StardewModdingAPI.Framework // Game1._temporaryContent initializing from SGame constructor if (this.ContentCore == null) { - this.ContentCore = new ContentCoordinator(serviceProvider, rootDirectory, Thread.CurrentThread.CurrentUICulture, this.Monitor, this.Reflection, this.Toolkit.JsonHelper, this.InitializeBeforeFirstAssetLoaded); + this.ContentCore = new ContentCoordinator(serviceProvider, rootDirectory, Thread.CurrentThread.CurrentUICulture, this.Monitor, this.Reflection, this.Toolkit.JsonHelper, this.InitializeBeforeFirstAssetLoaded, this.Settings.AggressiveMemoryOptimizations); if (this.ContentCore.Language != this.Translator.LocaleEnum) this.Translator.SetLocale(this.ContentCore.GetLocale(), this.ContentCore.Language); diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 063804e0..829aa451 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -29,6 +29,9 @@ namespace StardewModdingAPI.Metadata /********* ** Fields *********/ + /// Whether to enable more aggressive memory optimizations. + private readonly bool AggressiveMemoryOptimizations; + /// Normalizes an asset key to match the cache key and assert that it's valid. private readonly Func AssertAndNormalizeAssetName; @@ -55,10 +58,12 @@ namespace StardewModdingAPI.Metadata /// Initialize the core asset data. /// Normalizes an asset key to match the cache key and assert that it's valid. /// Simplifies access to private code. - public CoreAssetPropagator(Func assertAndNormalizeAssetName, Reflector reflection) + /// Whether to enable more aggressive memory optimizations. + public CoreAssetPropagator(Func assertAndNormalizeAssetName, Reflector reflection, bool aggressiveMemoryOptimizations) { this.AssertAndNormalizeAssetName = assertAndNormalizeAssetName; this.Reflection = reflection; + this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations; } /// Reload one of the game's core assets (if applicable). @@ -582,7 +587,7 @@ namespace StardewModdingAPI.Metadata titleMenu.aboutButton.texture = texture; titleMenu.languageButton.texture = texture; foreach (ClickableTextureComponent button in titleMenu.buttons) - button.texture = titleMenu.titleButtonsTexture; + button.texture = texture; foreach (TemporaryAnimatedSprite bird in titleMenu.birds) bird.texture = texture; @@ -785,6 +790,9 @@ namespace StardewModdingAPI.Metadata /// The location whose map to reload. private void ReloadMap(GameLocation location) { + if (this.AggressiveMemoryOptimizations) + location.map.DisposeTileSheets(Game1.mapDisplayDevice); + // reload map location.interiorDoors.Clear(); // prevent errors when doors try to update tiles which no longer exist location.reloadMap(); @@ -843,7 +851,7 @@ namespace StardewModdingAPI.Metadata // update sprite foreach (var target in characters) { - target.Npc.Sprite.spriteTexture = content.Load(target.Key); + target.Npc.Sprite.spriteTexture = this.DisposeIfNeeded(target.Npc.Sprite.spriteTexture, content.Load(target.Key)); propagated[target.Key] = true; } } @@ -881,7 +889,7 @@ namespace StardewModdingAPI.Metadata // update portrait foreach (var target in characters) { - target.Npc.Portrait = content.Load(target.Key); + target.Npc.Portrait = this.DisposeIfNeeded(target.Npc.Portrait, content.Load(target.Key)); propagated[target.Key] = true; } } @@ -1146,5 +1154,16 @@ namespace StardewModdingAPI.Metadata { return this.GetSegments(path).Length; } + + /// Dispose a texture if are enabled and it's different from the new instance. + /// The previous texture to dispose. + /// The new texture being loaded. + private Texture2D DisposeIfNeeded(Texture2D oldTexture, Texture2D newTexture) + { + if (this.AggressiveMemoryOptimizations && oldTexture != null && !oldTexture.IsDisposed && !object.ReferenceEquals(oldTexture, newTexture)) + oldTexture.Dispose(); + + return newTexture; + } } } diff --git a/src/SMAPI/SMAPI.config.json b/src/SMAPI/SMAPI.config.json index 7a710f14..6a485cbd 100644 --- a/src/SMAPI/SMAPI.config.json +++ b/src/SMAPI/SMAPI.config.json @@ -39,6 +39,12 @@ copy all the settings, or you may cause bugs due to overridden changes in future */ "RewriteMods": true, + /** + * Whether to enable more aggressive memory optimizations. + * THIS IS EXPERIMENTAL AND MAY CAUSE ERRORS OR CRASHES. + */ + "AggressiveMemoryOptimizations": true, + /** * Whether to add a section to the 'mod issues' list for mods which directly use potentially * sensitive .NET APIs like file or shell access. Note that many mods do this legitimately as From 423f2352af9d0e9815daf4ba3ba33134f587ce47 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 31 Jan 2021 22:08:03 -0500 Subject: [PATCH 04/20] rework aggressive memory optimization to minimize mod impact (#757) --- src/SMAPI/Framework/ContentCoordinator.cs | 17 ++++- .../GameContentManagerForAssetPropagation.cs | 47 ++++++++++++++ src/SMAPI/Metadata/CoreAssetPropagator.cs | 63 ++++++++++++------- 3 files changed, 103 insertions(+), 24 deletions(-) create mode 100644 src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 81265fa2..6a7385c3 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -117,8 +117,21 @@ namespace StardewModdingAPI.Framework aggressiveMemoryOptimizations: aggressiveMemoryOptimizations ) ); + var contentManagerForAssetPropagation = new GameContentManagerForAssetPropagation( + name: nameof(GameContentManagerForAssetPropagation), + serviceProvider: serviceProvider, + rootDirectory: rootDirectory, + currentCulture: currentCulture, + coordinator: this, + monitor: monitor, + reflection: reflection, + onDisposing: this.OnDisposing, + onLoadingFirstAsset: onLoadingFirstAsset, + aggressiveMemoryOptimizations: aggressiveMemoryOptimizations + ); + this.ContentManagers.Add(contentManagerForAssetPropagation); this.VanillaContentManager = new LocalizedContentManager(serviceProvider, rootDirectory); - this.CoreAssets = new CoreAssetPropagator(this.MainContentManager.AssertAndNormalizeAssetName, reflection, aggressiveMemoryOptimizations); + this.CoreAssets = new CoreAssetPropagator(this.MainContentManager, contentManagerForAssetPropagation, reflection, aggressiveMemoryOptimizations); } /// Get a new content manager which handles reading files from the game content folder with support for interception. @@ -298,7 +311,7 @@ namespace StardewModdingAPI.Framework // reload core game assets if (removedAssets.Any()) { - IDictionary propagated = this.CoreAssets.Propagate(this.MainContentManager, removedAssets.ToDictionary(p => p.Key, p => p.Value)); // use an intercepted content manager + IDictionary propagated = this.CoreAssets.Propagate(removedAssets.ToDictionary(p => p.Key, p => p.Value)); // use an intercepted content manager this.Monitor.Log($"Invalidated {removedAssets.Count} asset names ({string.Join(", ", removedAssets.Keys.OrderBy(p => p, StringComparer.OrdinalIgnoreCase))}); propagated {propagated.Count(p => p.Value)} core assets.", LogLevel.Trace); } else diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs b/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs new file mode 100644 index 00000000..cbbebf02 --- /dev/null +++ b/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs @@ -0,0 +1,47 @@ +using System; +using System.Globalization; +using Microsoft.Xna.Framework.Graphics; +using StardewModdingAPI.Framework.Reflection; +using StardewValley; + +namespace StardewModdingAPI.Framework.ContentManagers +{ + /// An extension of specifically optimized for asset propagation. + /// This avoids sharing an asset cache with or mods, so that assets can be safely disposed when the vanilla game no longer references them. + internal class GameContentManagerForAssetPropagation : GameContentManager + { + /********* + ** Fields + *********/ + /// A unique value used in to identify assets loaded through this instance. + private readonly string Tag = $"Pathoschild.SMAPI/LoadedBy:{nameof(GameContentManagerForAssetPropagation)}"; + + + /********* + ** Public methods + *********/ + /// + public GameContentManagerForAssetPropagation(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action onDisposing, Action onLoadingFirstAsset, bool aggressiveMemoryOptimizations) + : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, onLoadingFirstAsset, aggressiveMemoryOptimizations) { } + + /// + public override T Load(string assetName, LanguageCode language, bool useCache) + { + T data = base.Load(assetName, language, useCache); + + if (data is Texture2D texture) + texture.Tag = this.Tag; + + return data; + } + + /// Get whether a texture was loaded by this content manager. + /// The texture to check. + public bool IsReponsibleFor(Texture2D texture) + { + return + texture?.Tag is string tag + && tag.Contains(this.Tag); + } + } +} diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 829aa451..426fc3f6 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using Microsoft.Xna.Framework.Graphics; using Netcode; +using StardewModdingAPI.Framework.ContentManagers; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Toolkit.Utilities; using StardewValley; @@ -29,6 +30,12 @@ namespace StardewModdingAPI.Metadata /********* ** Fields *********/ + /// The main content manager through which to reload assets. + private readonly LocalizedContentManager MainContentManager; + + /// An internal content manager used only for asset propagation. See remarks on . + private readonly GameContentManagerForAssetPropagation DisposableContentManager; + /// Whether to enable more aggressive memory optimizations. private readonly bool AggressiveMemoryOptimizations; @@ -56,21 +63,24 @@ namespace StardewModdingAPI.Metadata ** Public methods *********/ /// Initialize the core asset data. - /// Normalizes an asset key to match the cache key and assert that it's valid. + /// The main content manager through which to reload assets. + /// An internal content manager used only for asset propagation. /// Simplifies access to private code. /// Whether to enable more aggressive memory optimizations. - public CoreAssetPropagator(Func assertAndNormalizeAssetName, Reflector reflection, bool aggressiveMemoryOptimizations) + public CoreAssetPropagator(LocalizedContentManager mainContent, GameContentManagerForAssetPropagation disposableContent, Reflector reflection, bool aggressiveMemoryOptimizations) { - this.AssertAndNormalizeAssetName = assertAndNormalizeAssetName; + this.MainContentManager = mainContent; + this.DisposableContentManager = disposableContent; this.Reflection = reflection; this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations; + + this.AssertAndNormalizeAssetName = disposableContent.AssertAndNormalizeAssetName; } /// Reload one of the game's core assets (if applicable). - /// The content manager through which to reload the asset. /// The asset keys and types to reload. /// Returns a lookup of asset names to whether they've been propagated. - public IDictionary Propagate(LocalizedContentManager content, IDictionary assets) + public IDictionary Propagate(IDictionary assets) { // group into optimized lists var buckets = assets.GroupBy(p => @@ -91,16 +101,16 @@ namespace StardewModdingAPI.Metadata switch (bucket.Key) { case AssetBucket.Sprite: - this.ReloadNpcSprites(content, bucket.Select(p => p.Key), propagated); + this.ReloadNpcSprites(bucket.Select(p => p.Key), propagated); break; case AssetBucket.Portrait: - this.ReloadNpcPortraits(content, bucket.Select(p => p.Key), propagated); + this.ReloadNpcPortraits(bucket.Select(p => p.Key), propagated); break; default: foreach (var entry in bucket) - propagated[entry.Key] = this.PropagateOther(content, entry.Key, entry.Value); + propagated[entry.Key] = this.PropagateOther(entry.Key, entry.Value); break; } } @@ -112,13 +122,13 @@ namespace StardewModdingAPI.Metadata ** Private methods *********/ /// Reload one of the game's core assets (if applicable). - /// The content manager through which to reload the asset. /// The asset key to reload. /// The asset type to reload. /// 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(LocalizedContentManager content, string key, Type type) + private bool PropagateOther(string key, Type type) { + var content = this.MainContentManager; key = this.AssertAndNormalizeAssetName(key); /**** @@ -830,10 +840,9 @@ namespace StardewModdingAPI.Metadata } /// Reload the sprites for matching NPCs. - /// The content manager through which to reload the asset. /// The asset keys to reload. /// The asset keys which have been propagated. - private void ReloadNpcSprites(LocalizedContentManager content, IEnumerable keys, IDictionary propagated) + private void ReloadNpcSprites(IEnumerable keys, IDictionary propagated) { // get NPCs HashSet lookup = new HashSet(keys, StringComparer.OrdinalIgnoreCase); @@ -851,16 +860,15 @@ namespace StardewModdingAPI.Metadata // update sprite foreach (var target in characters) { - target.Npc.Sprite.spriteTexture = this.DisposeIfNeeded(target.Npc.Sprite.spriteTexture, content.Load(target.Key)); + target.Npc.Sprite.spriteTexture = this.LoadAndDisposeIfNeeded(target.Npc.Sprite.spriteTexture, target.Key); propagated[target.Key] = true; } } /// Reload the portraits for matching NPCs. - /// The content manager through which to reload the asset. /// The asset key to reload. /// The asset keys which have been propagated. - private void ReloadNpcPortraits(LocalizedContentManager content, IEnumerable keys, IDictionary propagated) + private void ReloadNpcPortraits(IEnumerable keys, IDictionary propagated) { // get NPCs HashSet lookup = new HashSet(keys, StringComparer.OrdinalIgnoreCase); @@ -889,7 +897,7 @@ namespace StardewModdingAPI.Metadata // update portrait foreach (var target in characters) { - target.Npc.Portrait = this.DisposeIfNeeded(target.Npc.Portrait, content.Load(target.Key)); + target.Npc.Portrait = this.LoadAndDisposeIfNeeded(target.Npc.Portrait, target.Key); propagated[target.Key] = true; } } @@ -1155,15 +1163,26 @@ namespace StardewModdingAPI.Metadata return this.GetSegments(path).Length; } - /// Dispose a texture if are enabled and it's different from the new instance. + /// Load a texture, and dispose the old one if is enabled and it's different from the new instance. /// The previous texture to dispose. - /// The new texture being loaded. - private Texture2D DisposeIfNeeded(Texture2D oldTexture, Texture2D newTexture) + /// The asset key to load. + private Texture2D LoadAndDisposeIfNeeded(Texture2D oldTexture, string key) { - if (this.AggressiveMemoryOptimizations && oldTexture != null && !oldTexture.IsDisposed && !object.ReferenceEquals(oldTexture, newTexture)) - oldTexture.Dispose(); + // if aggressive memory optimizations are enabled, load the asset from the disposable + // content manager and dispose the old instance if needed. + if (this.AggressiveMemoryOptimizations) + { + GameContentManagerForAssetPropagation content = this.DisposableContentManager; - return newTexture; + Texture2D newTexture = content.Load(key); + if (oldTexture?.IsDisposed == false && !object.ReferenceEquals(oldTexture, newTexture) && content.IsReponsibleFor(oldTexture)) + oldTexture.Dispose(); + + return newTexture; + } + + // else just (re)load it from the main content manager + return this.MainContentManager.Load(key); } } } From e81e07594ca4863f9feb94c882b59ba7cc00a0ae Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 31 Jan 2021 22:12:36 -0500 Subject: [PATCH 05/20] extend aggressive memory optimization to a few more common textures (#757) --- src/SMAPI/Metadata/CoreAssetPropagator.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 426fc3f6..e2a28c62 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -178,14 +178,19 @@ namespace StardewModdingAPI.Metadata ** Buildings ****/ case "buildings\\houses": // Farm - reflection.GetField(typeof(Farm), nameof(Farm.houseTextures)).SetValue(content.Load(key)); - return true; + { + var field = reflection.GetField(typeof(Farm), nameof(Farm.houseTextures)); + field.SetValue( + this.LoadAndDisposeIfNeeded(field.GetValue(), key) + ); + return true; + } /**** ** Content\Characters\Farmer ****/ case "characters\\farmer\\accessories": // Game1.LoadContent - FarmerRenderer.accessoriesTexture = content.Load(key); + FarmerRenderer.accessoriesTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.accessoriesTexture, key); return true; case "characters\\farmer\\farmer_base": // Farmer @@ -195,19 +200,19 @@ namespace StardewModdingAPI.Metadata return this.ReloadPlayerSprites(key); case "characters\\farmer\\hairstyles": // Game1.LoadContent - FarmerRenderer.hairStylesTexture = content.Load(key); + FarmerRenderer.hairStylesTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.hairStylesTexture, key); return true; case "characters\\farmer\\hats": // Game1.LoadContent - FarmerRenderer.hatsTexture = content.Load(key); + FarmerRenderer.hatsTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.hatsTexture, key); return true; case "characters\\farmer\\pants": // Game1.LoadContent - FarmerRenderer.pantsTexture = content.Load(key); + FarmerRenderer.pantsTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.pantsTexture, key); return true; case "characters\\farmer\\shirts": // Game1.LoadContent - FarmerRenderer.shirtsTexture = content.Load(key); + FarmerRenderer.shirtsTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.shirtsTexture, key); return true; /**** From 54e7b5b846dfd542af3c8a904a57fc5ccc44ecb5 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 3 Feb 2021 20:24:25 -0500 Subject: [PATCH 06/20] enable aggressive memory optimizations by default (#757) The new approach should be safe, and no errors were reported so far by alpha testers. --- docs/release-notes.md | 4 ++-- src/SMAPI.Mods.ErrorHandler/ModEntry.cs | 2 +- src/SMAPI/Framework/Logging/LogManager.cs | 2 -- src/SMAPI/Framework/Models/SConfig.cs | 2 +- src/SMAPI/SMAPI.config.json | 2 +- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index e54fd24b..9cea9fa9 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -9,11 +9,11 @@ ## Upcoming release * For players: - * Added _aggressive memory optimization_ option. This is experimental and disabled by default; you can enable it in `smapi-internal/config.json` if you experience `OutOfMemoryException` crashes. + * Added more aggressive memory optimization which should eliminate many cases of `OutOfMemoryException` crashes. * Fixed error running `install on Windows.bat` in very rare cases. * For modders: - * Fixed SMAPI toolkit defaulting the mod type to SMAPI if its `manifest.json` has neither `EntryDll` nor `ContentPackFor`. This only affects external tools, since SMAPI itself validates those fields separately. + * Fixed SMAPI toolkit defaulting the mod type incorrectly if a mod's `manifest.json` has neither `EntryDll` nor `ContentPackFor`. This only affects external tools, since SMAPI itself validates those fields separately. ## 3.9.1 Released 25 January 2021 for Stardew Valley 1.5.4 or later. diff --git a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs index 2f6f1939..f543814e 100644 --- a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs +++ b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs @@ -30,7 +30,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler LogManager logManager = core.GetType().GetField("LogManager", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(core) as LogManager; if (logManager == null) { - this.Monitor.Log($"Can't access SMAPI's internal log manager. Error-handling patches won't be applied.", LogLevel.Error); + this.Monitor.Log("Can't access SMAPI's internal log manager. Error-handling patches won't be applied.", LogLevel.Error); return; } diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs index 5f191873..2c7be399 100644 --- a/src/SMAPI/Framework/Logging/LogManager.cs +++ b/src/SMAPI/Framework/Logging/LogManager.cs @@ -295,8 +295,6 @@ namespace StardewModdingAPI.Framework.Logging // warnings if (!settings.CheckForUpdates) this.Monitor.Log("You disabled update checks, so you won't be notified of new SMAPI or mod updates. Running an old version of SMAPI is not recommended. You can undo this by reinstalling SMAPI.", LogLevel.Warn); - if (settings.AggressiveMemoryOptimizations) - this.Monitor.Log("You enabled aggressive memory optimizations. This is an experimental option which may cause errors or crashes. You can undo this by reinstalling SMAPI.", LogLevel.Warn); if (!settings.RewriteMods) this.Monitor.Log("You disabled rewriting broken mods, so many older mods may fail to load. You can undo this by reinstalling SMAPI.", LogLevel.Info); if (!this.Monitor.WriteToConsole) diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index 382ae41f..4a80e34c 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -22,7 +22,7 @@ namespace StardewModdingAPI.Framework.Models [nameof(VerboseLogging)] = false, [nameof(LogNetworkTraffic)] = false, [nameof(RewriteMods)] = true, - [nameof(AggressiveMemoryOptimizations)] = false + [nameof(AggressiveMemoryOptimizations)] = true }; /// The default values for , to log changes if different. diff --git a/src/SMAPI/SMAPI.config.json b/src/SMAPI/SMAPI.config.json index 6a485cbd..a9e6f389 100644 --- a/src/SMAPI/SMAPI.config.json +++ b/src/SMAPI/SMAPI.config.json @@ -41,7 +41,7 @@ copy all the settings, or you may cause bugs due to overridden changes in future /** * Whether to enable more aggressive memory optimizations. - * THIS IS EXPERIMENTAL AND MAY CAUSE ERRORS OR CRASHES. + * You can try disabling this if you get ObjectDisposedException errors. */ "AggressiveMemoryOptimizations": true, From efec87065780426db15c51a7e68315ff488a89c0 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 6 Feb 2021 13:03:37 -0500 Subject: [PATCH 07/20] fix edge case in non-English asset cache after returning to title screen --- docs/release-notes.md | 1 + src/SMAPI/Framework/ContentCoordinator.cs | 26 +++++++++++++++++++++++ src/SMAPI/Framework/SCore.cs | 1 + 3 files changed, 28 insertions(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 9cea9fa9..9f4ea50a 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -14,6 +14,7 @@ * For modders: * Fixed SMAPI toolkit defaulting the mod type incorrectly if a mod's `manifest.json` has neither `EntryDll` nor `ContentPackFor`. This only affects external tools, since SMAPI itself validates those fields separately. + * Fixed edge case when playing in non-English where translatable assets loaded via `IAssetLoader` would no longer be applied after returning to the title screen unless manually invalidated from the cache. ## 3.9.1 Released 25 January 2021 for Stardew Valley 1.5.4 or later. diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 6a7385c3..b7c15526 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -203,6 +203,32 @@ namespace StardewModdingAPI.Framework }); } + /// Clean up when the player is returning to the title screen. + /// This is called after the player returns to the title screen, but before runs. + public void OnReturningToTitleScreen() + { + // The game clears LocalizedContentManager.localizedAssetNames after returning to the title screen. That + // causes an inconsistency in the SMAPI asset cache, which leads to an edge case where assets already + // provided by mods via IAssetLoader when playing in non-English are ignored. + // + // For example, let's say a mod provides the 'Data\mail' asset through IAssetLoader when playing in + // Portuguese. Here's the normal load process after it's loaded: + // 1. The game requests Data\mail. + // 2. SMAPI sees that it's already cached, and calls LoadRaw to bypass asset interception. + // 3. LoadRaw sees that there's a localized key mapping, and gets the mapped key. + // 4. In this case "Data\mail" is mapped to "Data\mail" since it was loaded by a mod, so it loads that + // asset. + // + // When the game clears localizedAssetNames, that process goes wrong in step 4: + // 3. LoadRaw sees that there's no localized key mapping *and* the locale is non-English, so it attempts + // to load from the localized key format. + // 4. In this case that's 'Data\mail.pt-BR', so it successfully loads that asset. + // 5. Since we've bypassed asset interception at this point, it's loaded directly from the base content + // manager without mod changes. + + this.InvalidateCache(asset => true); + } + /// Get whether this asset is mapped to a mod folder. /// The asset key. public bool IsManagedAssetKey(string key) diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 0ae69f0f..2d783eb2 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1118,6 +1118,7 @@ namespace StardewModdingAPI.Framework { // perform cleanup this.Multiplayer.CleanupOnMultiplayerExit(); + this.ContentCore.OnReturningToTitleScreen(); this.JustReturnedToTitle = true; } From 5ea871fee59e4a7ed7280d1678a76ed8019337b4 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 6 Feb 2021 19:46:34 -0500 Subject: [PATCH 08/20] update schema for Content Patcher 1.20 --- docs/release-notes.md | 3 +++ src/SMAPI.Web/wwwroot/schemas/content-patcher.json | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 9f4ea50a..cb54ee98 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -16,6 +16,9 @@ * Fixed SMAPI toolkit defaulting the mod type incorrectly if a mod's `manifest.json` has neither `EntryDll` nor `ContentPackFor`. This only affects external tools, since SMAPI itself validates those fields separately. * Fixed edge case when playing in non-English where translatable assets loaded via `IAssetLoader` would no longer be applied after returning to the title screen unless manually invalidated from the cache. +* For the web UI: + * Updated the JSON validator/schema for Content Patcher 1.20. + ## 3.9.1 Released 25 January 2021 for Stardew Valley 1.5.4 or later. diff --git a/src/SMAPI.Web/wwwroot/schemas/content-patcher.json b/src/SMAPI.Web/wwwroot/schemas/content-patcher.json index 92149f4d..21514979 100644 --- a/src/SMAPI.Web/wwwroot/schemas/content-patcher.json +++ b/src/SMAPI.Web/wwwroot/schemas/content-patcher.json @@ -11,9 +11,9 @@ "title": "Format version", "description": "The format version. You should always use the latest version to enable the latest features and avoid obsolete behavior.", "type": "string", - "const": "1.19.0", + "const": "1.20.0", "@errorMessages": { - "const": "Incorrect value '@value'. This should be set to the latest format version, currently '1.19.0'." + "const": "Incorrect value '@value'. This should be set to the latest format version, currently '1.20.0'." } }, "ConfigSchema": { From 97d3501e20c9b97dc85cfca32ccea4f55c9d61ff Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 6 Feb 2021 20:47:04 -0500 Subject: [PATCH 09/20] improve ErrorHandler's error handling if it can't access log manager --- src/SMAPI.Mods.ErrorHandler/ModEntry.cs | 27 +++++++++++++++---------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs index f543814e..e4bcc5bc 100644 --- a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs +++ b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs @@ -26,21 +26,15 @@ namespace StardewModdingAPI.Mods.ErrorHandler public override void Entry(IModHelper helper) { // get SMAPI core types - SCore core = SCore.Instance; - LogManager logManager = core.GetType().GetField("LogManager", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(core) as LogManager; - if (logManager == null) - { - this.Monitor.Log("Can't access SMAPI's internal log manager. Error-handling patches won't be applied.", LogLevel.Error); - return; - } + IMonitor monitorForGame = this.GetMonitorForGame(); // apply patches new GamePatcher(this.Monitor).Apply( - new EventErrorPatch(logManager.MonitorForGame), - new DialogueErrorPatch(logManager.MonitorForGame, this.Helper.Reflection), + new EventErrorPatch(monitorForGame), + new DialogueErrorPatch(monitorForGame, this.Helper.Reflection), new ObjectErrorPatch(), new LoadErrorPatch(this.Monitor, this.OnSaveContentRemoved), - new ScheduleErrorPatch(logManager.MonitorForGame), + new ScheduleErrorPatch(monitorForGame), new UtilityErrorPatches() ); @@ -61,7 +55,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler /// The method invoked when a save is loaded. /// The event sender. /// The event arguments. - public void OnSaveLoaded(object sender, SaveLoadedEventArgs e) + private void OnSaveLoaded(object sender, SaveLoadedEventArgs e) { // show in-game warning for removed save content if (this.IsSaveContentRemoved) @@ -70,5 +64,16 @@ namespace StardewModdingAPI.Mods.ErrorHandler Game1.addHUDMessage(new HUDMessage(this.Helper.Translation.Get("warn.invalid-content-removed"), HUDMessage.error_type)); } } + + /// Get the monitor with which to log game errors. + private IMonitor GetMonitorForGame() + { + SCore core = SCore.Instance; + LogManager logManager = core.GetType().GetField("LogManager", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(core) as LogManager; + if (logManager == null) + this.Monitor.Log("Can't access SMAPI's internal log manager. Some game errors may be reported as being from Error Handler.", LogLevel.Error); + + return logManager?.MonitorForGame ?? this.Monitor; + } } } From 67c52af72d6d0c4fddd3a07b159cb44b5c277599 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 6 Feb 2021 21:12:01 -0500 Subject: [PATCH 10/20] add early detection of disposed assets in error handler mod --- docs/release-notes.md | 3 + src/SMAPI.Mods.ErrorHandler/ModEntry.cs | 1 + .../Patches/SpriteBatchValidationPatches.cs | 63 +++++++++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchValidationPatches.cs diff --git a/docs/release-notes.md b/docs/release-notes.md index cb54ee98..9d95dacb 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -16,6 +16,9 @@ * Fixed SMAPI toolkit defaulting the mod type incorrectly if a mod's `manifest.json` has neither `EntryDll` nor `ContentPackFor`. This only affects external tools, since SMAPI itself validates those fields separately. * Fixed edge case when playing in non-English where translatable assets loaded via `IAssetLoader` would no longer be applied after returning to the title screen unless manually invalidated from the cache. +* For the ErrorHandler mod: + * Added early detection of disposed textures so the crash stack trace shows the actual code which used them. + * For the web UI: * Updated the JSON validator/schema for Content Patcher 1.20. diff --git a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs index e4bcc5bc..87f531fe 100644 --- a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs +++ b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs @@ -35,6 +35,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler new ObjectErrorPatch(), new LoadErrorPatch(this.Monitor, this.OnSaveContentRemoved), new ScheduleErrorPatch(monitorForGame), + new SpriteBatchValidationPatches(), new UtilityErrorPatches() ); diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchValidationPatches.cs b/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchValidationPatches.cs new file mode 100644 index 00000000..0211cfb1 --- /dev/null +++ b/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchValidationPatches.cs @@ -0,0 +1,63 @@ +#if HARMONY_2 +using HarmonyLib; +#else +using Harmony; +#endif +using System; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Xna.Framework.Graphics; +using StardewModdingAPI.Framework.Patching; + +namespace StardewModdingAPI.Mods.ErrorHandler.Patches +{ + /// Harmony patch for to validate textures earlier. + /// Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments. + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")] + [SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")] + internal class SpriteBatchValidationPatches : IHarmonyPatch + { + /********* + ** Accessors + *********/ + /// + public string Name => nameof(SpriteBatchValidationPatches); + + + /********* + ** Public methods + *********/ + /// +#if HARMONY_2 + public void Apply(Harmony harmony) +#else + public void Apply(HarmonyInstance harmony) +#endif + { + harmony.Patch( +#if SMAPI_FOR_WINDOWS + original: AccessTools.Method(typeof(SpriteBatch), "InternalDraw"), +#else + original: AccessTools.Method(typeof(SpriteBatch), "CheckValid", new[] { typeof(Texture2D) }), +#endif + postfix: new HarmonyMethod(this.GetType(), nameof(SpriteBatchValidationPatches.After_SpriteBatch_CheckValid)) + ); + } + + + /********* + ** Private methods + *********/ +#if SMAPI_FOR_WINDOWS + /// The method to call instead of . + /// The texture to validate. +#else + /// The method to call instead of . + /// The texture to validate. +#endif + private static void After_SpriteBatch_CheckValid(Texture2D texture) + { + if (texture?.IsDisposed == true) + throw new ObjectDisposedException("Cannot draw this texture because it's disposed."); + } + } +} From 5173ddf53571703c3895cd5f2d45891489d96ff9 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 8 Feb 2021 18:07:38 -0500 Subject: [PATCH 11/20] fix compat list error if mod has no name field due to wiki rendering issues --- docs/release-notes.md | 1 + src/SMAPI.Web/Controllers/ModsController.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 9d95dacb..7e2829d3 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -21,6 +21,7 @@ * For the web UI: * Updated the JSON validator/schema for Content Patcher 1.20. + * Fixed mod compatibility list error if a mod has no name. ## 3.9.1 Released 25 January 2021 for Stardew Valley 1.5.4 or later. diff --git a/src/SMAPI.Web/Controllers/ModsController.cs b/src/SMAPI.Web/Controllers/ModsController.cs index 24e36709..c62ed605 100644 --- a/src/SMAPI.Web/Controllers/ModsController.cs +++ b/src/SMAPI.Web/Controllers/ModsController.cs @@ -62,7 +62,7 @@ namespace StardewModdingAPI.Web.Controllers mods: this.Cache .GetWikiMods() .Select(mod => new ModModel(mod.Data)) - .OrderBy(p => Regex.Replace(p.Name.ToLower(), "[^a-z0-9]", "")), // ignore case, spaces, and special characters when sorting + .OrderBy(p => Regex.Replace((p.Name ?? "").ToLower(), "[^a-z0-9]", "")), // ignore case, spaces, and special characters when sorting lastUpdated: metadata.LastUpdated, isStale: this.Cache.IsStale(metadata.LastUpdated, this.StaleMinutes) ); From fa3305e1d8cb079fcfbccd87d2368627b4138ee4 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 8 Feb 2021 19:32:48 -0500 Subject: [PATCH 12/20] add error details when an event command fails --- docs/release-notes.md | 1 + src/SMAPI.Mods.ErrorHandler/ModEntry.cs | 3 +- .../Patches/EventPatches.cs | 67 +++++++++++++++++++ ...ntErrorPatch.cs => GameLocationPatches.cs} | 14 ++-- 4 files changed, 77 insertions(+), 8 deletions(-) create mode 100644 src/SMAPI.Mods.ErrorHandler/Patches/EventPatches.cs rename src/SMAPI.Mods.ErrorHandler/Patches/{EventErrorPatch.cs => GameLocationPatches.cs} (88%) diff --git a/docs/release-notes.md b/docs/release-notes.md index 7e2829d3..e5cac9e0 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -18,6 +18,7 @@ * For the ErrorHandler mod: * Added early detection of disposed textures so the crash stack trace shows the actual code which used them. + * Added error details when an event command fails. * For the web UI: * Updated the JSON validator/schema for Content Patcher 1.20. diff --git a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs index 87f531fe..d9426d75 100644 --- a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs +++ b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs @@ -30,8 +30,9 @@ namespace StardewModdingAPI.Mods.ErrorHandler // apply patches new GamePatcher(this.Monitor).Apply( - new EventErrorPatch(monitorForGame), new DialogueErrorPatch(monitorForGame, this.Helper.Reflection), + new EventPatches(monitorForGame), + new GameLocationPatches(monitorForGame), new ObjectErrorPatch(), new LoadErrorPatch(this.Monitor, this.OnSaveContentRemoved), new ScheduleErrorPatch(monitorForGame), diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/EventPatches.cs b/src/SMAPI.Mods.ErrorHandler/Patches/EventPatches.cs new file mode 100644 index 00000000..a15c1d32 --- /dev/null +++ b/src/SMAPI.Mods.ErrorHandler/Patches/EventPatches.cs @@ -0,0 +1,67 @@ +using System; +using System.Diagnostics.CodeAnalysis; +#if HARMONY_2 +using HarmonyLib; +#else +using Harmony; +#endif +using StardewModdingAPI.Framework.Patching; +using StardewValley; + +namespace StardewModdingAPI.Mods.ErrorHandler.Patches +{ + /// Harmony patches for which intercept errors to log more details. + /// Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments. + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")] + [SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")] + internal class EventPatches : IHarmonyPatch + { + /********* + ** Fields + *********/ + /// Writes messages to the console and log file on behalf of the game. + private static IMonitor MonitorForGame; + + + /********* + ** Accessors + *********/ + /// + public string Name => nameof(EventPatches); + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// Writes messages to the console and log file on behalf of the game. + public EventPatches(IMonitor monitorForGame) + { + EventPatches.MonitorForGame = monitorForGame; + } + + /// +#if HARMONY_2 + public void Apply(Harmony harmony) +#else + public void Apply(HarmonyInstance harmony) +#endif + { + harmony.Patch( + original: AccessTools.Method(typeof(Event), nameof(Event.LogErrorAndHalt)), + postfix: new HarmonyMethod(this.GetType(), nameof(EventPatches.After_Event_LogErrorAndHalt)) + ); + } + + + /********* + ** Private methods + *********/ + /// The method to call after . + /// The exception being logged. + private static void After_Event_LogErrorAndHalt(Exception e) + { + EventPatches.MonitorForGame.Log(e.ToString(), LogLevel.Error); + } + } +} diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/EventErrorPatch.cs b/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatches.cs similarity index 88% rename from src/SMAPI.Mods.ErrorHandler/Patches/EventErrorPatch.cs rename to src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatches.cs index fabc6cad..c10f2de7 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/EventErrorPatch.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatches.cs @@ -15,7 +15,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches /// Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments. [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")] [SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")] - internal class EventErrorPatch : IHarmonyPatch + internal class GameLocationPatches : IHarmonyPatch { /********* ** Fields @@ -28,7 +28,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches ** Accessors *********/ /// - public string Name => nameof(EventErrorPatch); + public string Name => nameof(GameLocationPatches); /********* @@ -36,9 +36,9 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches *********/ /// Construct an instance. /// Writes messages to the console and log file on behalf of the game. - public EventErrorPatch(IMonitor monitorForGame) + public GameLocationPatches(IMonitor monitorForGame) { - EventErrorPatch.MonitorForGame = monitorForGame; + GameLocationPatches.MonitorForGame = monitorForGame; } /// @@ -55,7 +55,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches { harmony.Patch( original: AccessTools.Method(typeof(GameLocation), "checkEventPrecondition"), - prefix: new HarmonyMethod(this.GetType(), nameof(EventErrorPatch.Before_GameLocation_CheckEventPrecondition)) + prefix: new HarmonyMethod(this.GetType(), nameof(GameLocationPatches.Before_GameLocation_CheckEventPrecondition)) ); } #endif @@ -89,7 +89,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches /// Returns whether to execute the original method. private static bool Before_GameLocation_CheckEventPrecondition(GameLocation __instance, ref int __result, string precondition, MethodInfo __originalMethod) { - const string key = nameof(EventErrorPatch.Before_GameLocation_CheckEventPrecondition); + const string key = nameof(GameLocationPatches.Before_GameLocation_CheckEventPrecondition); if (!PatchHelper.StartIntercept(key)) return true; @@ -101,7 +101,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches catch (TargetInvocationException ex) { __result = -1; - EventErrorPatch.MonitorForGame.Log($"Failed parsing event precondition ({precondition}):\n{ex.InnerException}", LogLevel.Error); + GameLocationPatches.MonitorForGame.Log($"Failed parsing event precondition ({precondition}):\n{ex.InnerException}", LogLevel.Error); return false; } finally From b1876dec7ac6132b03b06277ef4d3bd8900b8805 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 13 Feb 2021 16:54:57 -0500 Subject: [PATCH 13/20] fix asset propagation for map seats --- docs/release-notes.md | 3 ++- src/SMAPI/Metadata/CoreAssetPropagator.cs | 25 +++++++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index e5cac9e0..82913873 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -13,8 +13,9 @@ * Fixed error running `install on Windows.bat` in very rare cases. * For modders: - * Fixed SMAPI toolkit defaulting the mod type incorrectly if a mod's `manifest.json` has neither `EntryDll` nor `ContentPackFor`. This only affects external tools, since SMAPI itself validates those fields separately. + * Fixed asset propagation for `TileSheets/ChairTiles` not changing existing map seats. * Fixed edge case when playing in non-English where translatable assets loaded via `IAssetLoader` would no longer be applied after returning to the title screen unless manually invalidated from the cache. + * Fixed SMAPI toolkit defaulting the mod type incorrectly if a mod's `manifest.json` has neither `EntryDll` nor `ContentPackFor`. This only affects external tools, since SMAPI itself validates those fields separately. * For the ErrorHandler mod: * Added early detection of disposed textures so the crash stack trace shows the actual code which used them. diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index e2a28c62..5fb1b10d 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -452,8 +452,7 @@ namespace StardewModdingAPI.Metadata return true; case "tilesheets\\chairtiles": // Game1.LoadContent - MapSeat.mapChairTexture = content.Load(key); - return true; + return this.ReloadChairTiles(content, key); case "tilesheets\\craftables": // Game1.LoadContent Game1.bigCraftableSpriteSheet = content.Load(key); @@ -691,6 +690,28 @@ namespace StardewModdingAPI.Metadata return false; } + /// Reload map seat textures. + /// The content manager through which to reload the asset. + /// The asset key to reload. + /// Returns whether any textures were reloaded. + private bool ReloadChairTiles(LocalizedContentManager content, string key) + { + MapSeat.mapChairTexture = content.Load(key); + + foreach (var location in this.GetLocations()) + { + foreach (MapSeat seat in location.mapSeats.Where(p => p != null)) + { + string curKey = this.NormalizeAssetNameIgnoringEmpty(seat._loadedTextureFile); + + if (curKey == null || key.Equals(curKey, StringComparison.OrdinalIgnoreCase)) + seat.overlayTexture = MapSeat.mapChairTexture; + } + } + + return true; + } + /// Reload critter textures. /// The content manager through which to reload the asset. /// The asset key to reload. From 50d7e95920e1963af583f398055c6e8ad5df1033 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 19 Feb 2021 22:54:01 -0500 Subject: [PATCH 14/20] fix errors parsing the mod compatibility list on the new wiki --- docs/release-notes.md | 1 + src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 82913873..041071e0 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -22,6 +22,7 @@ * Added error details when an event command fails. * For the web UI: + * Updated for the new wiki. * Updated the JSON validator/schema for Content Patcher 1.20. * Fixed mod compatibility list error if a mod has no name. diff --git a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs index 89a22eaf..da312471 100644 --- a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs +++ b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs @@ -50,13 +50,13 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki doc.LoadHtml(html); // fetch game versions - string stableVersion = doc.DocumentNode.SelectSingleNode("div[@class='game-stable-version']")?.InnerText; - string betaVersion = doc.DocumentNode.SelectSingleNode("div[@class='game-beta-version']")?.InnerText; + string stableVersion = doc.DocumentNode.SelectSingleNode("//div[@class='game-stable-version']")?.InnerText; + string betaVersion = doc.DocumentNode.SelectSingleNode("//div[@class='game-beta-version']")?.InnerText; if (betaVersion == stableVersion) betaVersion = null; // find mod entries - HtmlNodeCollection modNodes = doc.DocumentNode.SelectNodes("table[@id='mod-list']//tr[@class='mod']"); + HtmlNodeCollection modNodes = doc.DocumentNode.SelectNodes("//table[@id='mod-list']//tr[@class='mod']"); if (modNodes == null) throw new InvalidOperationException("Can't parse wiki compatibility list, no mods found."); From 79aee3fba7a43025d84527f883ce6715d7140302 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 21 Feb 2021 13:45:10 -0500 Subject: [PATCH 15/20] reduce performance impact of new cache fix --- src/SMAPI/Framework/ContentCoordinator.cs | 27 +++++-------------- .../ContentManagers/BaseContentManager.cs | 3 +++ .../ContentManagers/GameContentManager.cs | 25 +++++++++++++++++ .../ContentManagers/IContentManager.cs | 10 ++++--- 4 files changed, 41 insertions(+), 24 deletions(-) diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index b7c15526..32195fff 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -54,7 +54,7 @@ namespace StardewModdingAPI.Framework private bool IsDisposed; /// A lock used to prevent asynchronous changes to the content manager list. - /// The game may adds content managers in asynchronous threads (e.g. when populating the load screen). + /// The game may add content managers in asynchronous threads (e.g. when populating the load screen). private readonly ReaderWriterLockSlim ContentManagerLock = new ReaderWriterLockSlim(); /// A cache of ordered tilesheet IDs used by vanilla maps. @@ -207,26 +207,11 @@ namespace StardewModdingAPI.Framework /// This is called after the player returns to the title screen, but before runs. public void OnReturningToTitleScreen() { - // The game clears LocalizedContentManager.localizedAssetNames after returning to the title screen. That - // causes an inconsistency in the SMAPI asset cache, which leads to an edge case where assets already - // provided by mods via IAssetLoader when playing in non-English are ignored. - // - // For example, let's say a mod provides the 'Data\mail' asset through IAssetLoader when playing in - // Portuguese. Here's the normal load process after it's loaded: - // 1. The game requests Data\mail. - // 2. SMAPI sees that it's already cached, and calls LoadRaw to bypass asset interception. - // 3. LoadRaw sees that there's a localized key mapping, and gets the mapped key. - // 4. In this case "Data\mail" is mapped to "Data\mail" since it was loaded by a mod, so it loads that - // asset. - // - // When the game clears localizedAssetNames, that process goes wrong in step 4: - // 3. LoadRaw sees that there's no localized key mapping *and* the locale is non-English, so it attempts - // to load from the localized key format. - // 4. In this case that's 'Data\mail.pt-BR', so it successfully loads that asset. - // 5. Since we've bypassed asset interception at this point, it's loaded directly from the base content - // manager without mod changes. - - this.InvalidateCache(asset => true); + this.ContentManagerLock.InReadLock(() => + { + foreach (IContentManager contentManager in this.ContentManagers) + contentManager.OnReturningToTitleScreen(); + }); } /// Get whether this asset is mapped to a mod folder. diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index 709c624e..b6b3a7a0 100644 --- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs @@ -133,6 +133,9 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Perform any cleanup needed when the locale changes. public virtual void OnLocaleChanged() { } + /// + public virtual void OnReturningToTitleScreen() { } + /// Normalize path separators in a file path. For asset keys, see instead. /// The file path to normalize. [Pure] diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index f8ee575f..e3d1e569 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -140,6 +140,31 @@ namespace StardewModdingAPI.Framework.ContentManagers this.Monitor.Log($"Invalidated {invalidated.Length} asset names: {string.Join(", ", invalidated)} for locale change.", LogLevel.Trace); } + /// + public override void OnReturningToTitleScreen() + { + // The game clears LocalizedContentManager.localizedAssetNames after returning to the title screen. That + // causes an inconsistency in the SMAPI asset cache, which leads to an edge case where assets already + // provided by mods via IAssetLoader when playing in non-English are ignored. + // + // For example, let's say a mod provides the 'Data\mail' asset through IAssetLoader when playing in + // Portuguese. Here's the normal load process after it's loaded: + // 1. The game requests Data\mail. + // 2. SMAPI sees that it's already cached, and calls LoadRaw to bypass asset interception. + // 3. LoadRaw sees that there's a localized key mapping, and gets the mapped key. + // 4. In this case "Data\mail" is mapped to "Data\mail" since it was loaded by a mod, so it loads that + // asset. + // + // When the game clears localizedAssetNames, that process goes wrong in step 4: + // 3. LoadRaw sees that there's no localized key mapping *and* the locale is non-English, so it attempts + // to load from the localized key format. + // 4. In this case that's 'Data\mail.pt-BR', so it successfully loads that asset. + // 5. Since we've bypassed asset interception at this point, it's loaded directly from the base content + // manager without mod changes. + if (LocalizedContentManager.CurrentLanguageCode != LocalizedContentManager.LanguageCode.en) + this.InvalidateCache((_, _) => true); + } + /// Create a new content manager for temporary use. public override LocalizedContentManager CreateTemporary() { diff --git a/src/SMAPI/Framework/ContentManagers/IContentManager.cs b/src/SMAPI/Framework/ContentManagers/IContentManager.cs index 0e7edd8f..1e222472 100644 --- a/src/SMAPI/Framework/ContentManagers/IContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/IContentManager.cs @@ -36,9 +36,6 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Whether to read/write the loaded asset to the asset cache. T Load(string assetName, LocalizedContentManager.LanguageCode language, bool useCache); - /// Perform any cleanup needed when the locale changes. - void OnLocaleChanged(); - /// Normalize path separators in a file path. For asset keys, see instead. /// The file path to normalize. [Pure] @@ -69,5 +66,12 @@ namespace StardewModdingAPI.Framework.ContentManagers /// 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 invalidated asset names and instances. IDictionary InvalidateCache(Func predicate, bool dispose = false); + + /// Perform any cleanup needed when the locale changes. + void OnLocaleChanged(); + + /// Clean up when the player is returning to the title screen. + /// This is called after the player returns to the title screen, but before runs. + void OnReturningToTitleScreen(); } } From ad80f8b078176519d49732b28a03465e6d8a6501 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 21 Feb 2021 13:50:08 -0500 Subject: [PATCH 16/20] use inheritdoc, minor cleanup --- .../ContentManagers/BaseContentManager.cs | 56 ++++++------------- .../ContentManagers/GameContentManager.cs | 27 +++------ .../GameContentManagerForAssetPropagation.cs | 2 +- .../ContentManagers/ModContentManager.cs | 21 ++----- src/SMAPI/Metadata/CoreAssetPropagator.cs | 2 +- 5 files changed, 32 insertions(+), 76 deletions(-) diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index b6b3a7a0..1a64dab8 100644 --- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs @@ -5,7 +5,6 @@ using System.Diagnostics.Contracts; using System.Globalization; using System.IO; using System.Linq; -using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.Exceptions; @@ -53,16 +52,16 @@ namespace StardewModdingAPI.Framework.ContentManagers /********* ** Accessors *********/ - /// A name for the mod manager. Not guaranteed to be unique. + /// public string Name { get; } - /// The current language as a constant. + /// public LanguageCode Language => this.GetCurrentLanguage(); - /// The absolute path to the . + /// public string FullRootDirectory => Path.Combine(Constants.ExecutionPath, this.RootDirectory); - /// Whether this content manager can be targeted by managed asset keys (e.g. to load assets from a mod folder). + /// public bool IsNamespaced { get; } @@ -97,56 +96,42 @@ namespace StardewModdingAPI.Framework.ContentManagers this.BaseDisposableReferences = reflection.GetField>(this, "disposableAssets").GetValue(); } - /// Load an asset that has been processed by the content pipeline. - /// The type of asset to load. - /// The asset path relative to the loader root directory, not including the .xnb extension. + /// public override T Load(string assetName) { return this.Load(assetName, this.Language, useCache: true); } - /// Load an asset that has been processed by the content pipeline. - /// The type of asset to load. - /// The asset path relative to the loader root directory, not including the .xnb extension. - /// The language code for which to load content. + /// public override T Load(string assetName, LanguageCode language) { return this.Load(assetName, language, useCache: true); } - /// Load an asset that has been processed by the content pipeline. - /// The type of asset to load. - /// The asset path relative to the loader root directory, not including the .xnb extension. - /// The language code for which to load content. - /// Whether to read/write the loaded asset to the asset cache. + /// public abstract T Load(string assetName, LocalizedContentManager.LanguageCode language, bool useCache); - /// Load the base asset without localization. - /// The type of asset to load. - /// The asset path relative to the loader root directory, not including the .xnb extension. + /// [Obsolete("This method is implemented for the base game and should not be used directly. To load an asset from the underlying content manager directly, use " + nameof(BaseContentManager.RawLoad) + " instead.")] public override T LoadBase(string assetName) { return this.Load(assetName, LanguageCode.en, useCache: true); } - /// Perform any cleanup needed when the locale changes. + /// public virtual void OnLocaleChanged() { } /// public virtual void OnReturningToTitleScreen() { } - /// Normalize path separators in a file path. For asset keys, see instead. - /// The file path to normalize. + /// [Pure] public string NormalizePathSeparators(string path) { return this.Cache.NormalizePathSeparators(path); } - /// Assert that the given key has a valid format and return a normalized form consistent with the underlying cache. - /// The asset key to check. - /// The asset key is empty or contains invalid characters. + /// [SuppressMessage("ReSharper", "ParameterOnlyUsedForPreconditionCheck.Local", Justification = "Parameter is only used for assertion checks by design.")] public string AssertAndNormalizeAssetName(string assetName) { @@ -163,29 +148,26 @@ namespace StardewModdingAPI.Framework.ContentManagers /**** ** Content loading ****/ - /// Get the current content locale. + /// public string GetLocale() { return this.GetLocale(this.GetCurrentLanguage()); } - /// The locale for a language. - /// The language. + /// public string GetLocale(LanguageCode language) { return this.LanguageCodeString(language); } - /// Get whether the content manager has already loaded and cached the given asset. - /// The asset path relative to the loader root directory, not including the .xnb extension. - /// The language. + /// public bool IsLoaded(string assetName, LanguageCode language) { assetName = this.Cache.NormalizeKey(assetName); return this.IsNormalizedKeyLoaded(assetName, language); } - /// Get the cached asset keys. + /// public IEnumerable GetAssetKeys() { return this.Cache.Keys @@ -196,10 +178,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /**** ** Cache invalidation ****/ - /// Purge matched assets 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 invalidated asset names and instances. + /// public IDictionary InvalidateCache(Func predicate, bool dispose = false) { IDictionary removeAssets = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -228,8 +207,7 @@ namespace StardewModdingAPI.Framework.ContentManagers return removeAssets; } - /// Dispose held resources. - /// Whether the content manager is being disposed (rather than finalized). + /// protected override void Dispose(bool isDisposing) { // ignore if disposed diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index e3d1e569..8e78faba 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -59,11 +59,7 @@ namespace StardewModdingAPI.Framework.ContentManagers this.OnLoadingFirstAsset = onLoadingFirstAsset; } - /// Load an asset that has been processed by the content pipeline. - /// The type of asset to load. - /// The asset path relative to the loader root directory, not including the .xnb extension. - /// The language code for which to load content. - /// Whether to read/write the loaded asset to the asset cache. + /// public override T Load(string assetName, LocalizedContentManager.LanguageCode language, bool useCache) { // raise first-load callback @@ -95,7 +91,7 @@ namespace StardewModdingAPI.Framework.ContentManagers if (this.AssetsBeingLoaded.Contains(assetName)) { this.Monitor.Log($"Broke loop while loading asset '{assetName}'.", LogLevel.Warn); - this.Monitor.Log($"Bypassing mod loaders for this asset. Stack trace:\n{Environment.StackTrace}", LogLevel.Trace); + this.Monitor.Log($"Bypassing mod loaders for this asset. Stack trace:\n{Environment.StackTrace}"); data = this.RawLoad(assetName, language, useCache); } else @@ -117,7 +113,7 @@ namespace StardewModdingAPI.Framework.ContentManagers return data; } - /// Perform any cleanup needed when the locale changes. + /// public override void OnLocaleChanged() { base.OnLocaleChanged(); @@ -137,7 +133,7 @@ namespace StardewModdingAPI.Framework.ContentManagers .OrderBy(p => p, StringComparer.OrdinalIgnoreCase) .ToArray(); if (invalidated.Any()) - this.Monitor.Log($"Invalidated {invalidated.Length} asset names: {string.Join(", ", invalidated)} for locale change.", LogLevel.Trace); + this.Monitor.Log($"Invalidated {invalidated.Length} asset names: {string.Join(", ", invalidated)} for locale change."); } /// @@ -165,7 +161,7 @@ namespace StardewModdingAPI.Framework.ContentManagers this.InvalidateCache((_, _) => true); } - /// Create a new content manager for temporary use. + /// public override LocalizedContentManager CreateTemporary() { return this.Coordinator.CreateGameContentManager("(temporary)"); @@ -175,9 +171,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /********* ** Private methods *********/ - /// Get whether an asset has already been loaded. - /// The normalized asset name. - /// The language to check. + /// protected override bool IsNormalizedKeyLoaded(string normalizedAssetName, LanguageCode language) { string cachedKey = null; @@ -191,12 +185,7 @@ namespace StardewModdingAPI.Framework.ContentManagers : this.Cache.ContainsKey(normalizedAssetName); } - /// Add tracking data to an asset and add it to the cache. - /// The type of asset to inject. - /// The asset path relative to the loader root directory, not including the .xnb extension. - /// The asset value. - /// The language code for which to inject the asset. - /// Whether to save the asset to the asset cache. + /// protected override void TrackAsset(string assetName, T value, LanguageCode language, bool useCache) { // handle explicit language in asset name @@ -384,7 +373,7 @@ namespace StardewModdingAPI.Framework.ContentManagers try { editor.Edit(asset); - this.Monitor.Log($"{mod.DisplayName} edited {info.AssetName}.", LogLevel.Trace); + this.Monitor.Log($"{mod.DisplayName} edited {info.AssetName}."); } catch (Exception ex) { diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs b/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs index cbbebf02..61683ce6 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs @@ -37,7 +37,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Get whether a texture was loaded by this content manager. /// The texture to check. - public bool IsReponsibleFor(Texture2D texture) + public bool IsResponsibleFor(Texture2D texture) { return texture?.Tag is string tag diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 284c1f37..9af14cb5 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -59,28 +59,19 @@ namespace StardewModdingAPI.Framework.ContentManagers this.ModName = modName; } - /// Load an asset that has been processed by the content pipeline. - /// The type of asset to load. - /// The asset path relative to the loader root directory, not including the .xnb extension. + /// public override T Load(string assetName) { return this.Load(assetName, this.DefaultLanguage, useCache: false); } - /// Load an asset that has been processed by the content pipeline. - /// The type of asset to load. - /// The asset path relative to the loader root directory, not including the .xnb extension. - /// The language code for which to load content. + /// public override T Load(string assetName, LanguageCode language) { return this.Load(assetName, language, useCache: false); } - /// Load an asset that has been processed by the content pipeline. - /// The type of asset to load. - /// The asset path relative to the loader root directory, not including the .xnb extension. - /// The language code for which to load content. - /// Whether to read/write the loaded asset to the asset cache. + /// public override T Load(string assetName, LanguageCode language, bool useCache) { assetName = this.AssertAndNormalizeAssetName(assetName); @@ -190,7 +181,7 @@ namespace StardewModdingAPI.Framework.ContentManagers return asset; } - /// Create a new content manager for temporary use. + /// public override LocalizedContentManager CreateTemporary() { throw new NotSupportedException("Can't create a temporary mod content manager."); @@ -210,9 +201,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /********* ** Private methods *********/ - /// Get whether an asset has already been loaded. - /// The normalized asset name. - /// The language to check. + /// protected override bool IsNormalizedKeyLoaded(string normalizedAssetName, LanguageCode language) { return this.Cache.ContainsKey(normalizedAssetName); diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 5fb1b10d..8b591bc1 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -1201,7 +1201,7 @@ namespace StardewModdingAPI.Metadata GameContentManagerForAssetPropagation content = this.DisposableContentManager; Texture2D newTexture = content.Load(key); - if (oldTexture?.IsDisposed == false && !object.ReferenceEquals(oldTexture, newTexture) && content.IsReponsibleFor(oldTexture)) + if (oldTexture?.IsDisposed == false && !object.ReferenceEquals(oldTexture, newTexture) && content.IsResponsibleFor(oldTexture)) oldTexture.Dispose(); return newTexture; From 8205b4bd3501360780ddbc51bea2cccfdaac7517 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 21 Feb 2021 13:54:29 -0500 Subject: [PATCH 17/20] use 'mod authors' instead of 'modders' in release notes for clarity --- docs/release-notes-archived.md | 48 ++++++++++++++++---------------- docs/release-notes.md | 50 +++++++++++++++++----------------- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/docs/release-notes-archived.md b/docs/release-notes-archived.md index 9f8de3cb..b5dd538b 100644 --- a/docs/release-notes-archived.md +++ b/docs/release-notes-archived.md @@ -19,7 +19,7 @@ Released 13 September 2019 for Stardew Valley 1.3.36. * Added log parser instructions for Android. * Fixed log parser failing in some cases due to time format localization. -* For modders: +* For mod authors: * `this.Monitor.Log` now defaults to the `Trace` log level instead of `Debug`. The change will only take effect when you recompile the mod. * Fixed 'location list changed' verbose log not correctly listing changes. * Fixed mods able to directly load (and in some cases edit) a different mod's local assets using internal asset key forwarding. @@ -42,7 +42,7 @@ Released 17 March 2019 for Stardew Valley 1.3.36. * Updated mod compatibility list. * Fixed `world_clear` console command removing chests edited to have a debris name. -* For modders: +* For mod authors: * Added support for suppressing false-positive warnings in rare cases. * For the web UI: @@ -55,7 +55,7 @@ Released 01 March 2019 for Stardew Valley 1.3.36. * For players: * Updated for Stardew Valley 1.3.36. -* For modders: +* For mod authors: * Bumped all deprecation levels to _pending removal_. * For the web UI: @@ -80,7 +80,7 @@ Released 09 January 2019 for Stardew Valley 1.3.32–33. * Added beta status filter to compatibility list. * Fixed broken ModDrop links in the compatibility list. -* For modders: +* For mod authors: * Asset changes are now propagated into the parsed save being loaded if applicable. * Added locale to context trace logs. * Fixed error loading custom map tilesheets in some cases. @@ -106,7 +106,7 @@ Released 29 December 2018 for Stardew Valley 1.3.32–33. * Minor performance improvements. * Tweaked installer to reduce antivirus false positives. -* For modders: +* For mod authors: * Added [events](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events): `GameLoop.OneSecondUpdateTicking`, `GameLoop.OneSecondUpdateTicked`, and `Specialized.LoadStageChanged`. * Added `e.IsCurrentLocation` event arg to `World` events. * You can now use `helper.Data.Read/WriteSaveData` as soon as the save is loaded (instead of once the world is initialized). @@ -133,7 +133,7 @@ Released 16 December 2018 for Stardew Valley 1.3.32. * Fixed game launch errors logged as `SMAPI` instead of `game`. * Fixed Windows installer adding unneeded Unix launcher to game folder. -* For modders: +* For mod authors: * Moved content pack methods into a new [content pack API](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Content_Packs). * Fixed invalid NPC data propagated when a mod changes NPC dispositions. * Fixed `Display.RenderedWorld` event broken in SMAPI 2.9.1. @@ -162,7 +162,7 @@ Released 07 December 2018 for Stardew Valley 1.3.32. * Fixed empty "mods with warnings" list in some cases due to hidden warnings. * Fixed Console Commands' handling of tool upgrade levels for item commands. -* For modders: +* For mod authors: * Added ModDrop update keys (see [docs](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Manifest#Update_checks)). * Added `IsLocalPlayer` to new player events. * Added `helper.CreateTemporaryContentPack` to replace the deprecated `CreateTransitionalContentPack`. @@ -225,7 +225,7 @@ Released 19 November 2018 for Stardew Valley 1.3.32. * Tweaked log parser UI (thanks to danvolchek!). * Fixed log parser instructions for Mac. -* For modders: +* For mod authors: * Added [data API](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Data) to store mod data in the save file or app data. * Added [multiplayer API](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Multiplayer) and [events](https://stardewvalleywiki.com/Modding:Modder_Guide/Apis/Events#Multiplayer_2) to send/receive messages and get connected player info. * Added [verbose logging](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Logging#Verbose_logging) feature. @@ -275,7 +275,7 @@ Released 14 August 2018 for Stardew Valley 1.3.28. * Fixed some SMAPI logs not deleted when starting a new session. * Updated compatibility list. -* For modders: +* For mod authors: * Added support for `.json` data files in the content API (including Content Patcher). * Added propagation for asset changes through the content API for... * child sprites; @@ -336,7 +336,7 @@ Released 01 August 2018 for Stardew Valley 1.3.27. * Fixed log parser mangling crossplatform paths in some cases. * Fixed `smapi.io/install` not linking to a useful page. -* For modders: +* For mod authors: * Added [input API](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Input) for reading and suppressing keyboard, controller, and mouse input. * Added code analysis in the NuGet package to flag common issues as warnings. * Replaced `LocationEvents` to support multiplayer: @@ -418,7 +418,7 @@ Released 26 March 2018 for Stardew Valley 1.2.30–1.2.33. * For the [log parser](https://smapi.io/log): * Fixed error when log text contains certain tokens. -* For modders: +* For mod authors: * Updated to Json.NET 11.0.2. * For SMAPI developers: @@ -448,7 +448,7 @@ Released 13 March 2018 for Stardew Valley ~~1.2.30~~–1.2.33. ## 2.5.2 Released 25 February 2018 for Stardew Valley 1.2.30–1.2.33. -* For modders: +* For mod authors: * Fixed issue where replacing an asset through `asset.AsImage()` or `asset.AsDictionary()` didn't take effect. * For the [log parser](https://smapi.io/log): @@ -472,7 +472,7 @@ Released 24 February 2018 for Stardew Valley 1.2.30–1.2.33. * Fixed uninstall script not reporting when done on Linux/Mac. * Updated compatibility list and enabled update checks for more mods. -* For modders: +* For mod authors: * Added support for content packs and new APIs to read them. * Added support for `ISemanticVersion` in JSON models. * Added `SpecializedEvents.UnvalidatedUpdateTick` event for specialized use cases. @@ -506,7 +506,7 @@ Released 24 January 2018 for Stardew Valley 1.2.30–1.2.33. * For the [log parser](https://smapi.io/log): * Fixed error parsing logs with zero installed mods. -* For modders: +* For mod authors: * Added `SaveEvents.BeforeCreate` and `AfterCreate` events. * Added `SButton` `IsActionButton()` and `IsUseToolButton()` extensions. * Improved JSON parse errors to provide more useful info for troubleshooting. @@ -527,7 +527,7 @@ Released 26 December 2017 for Stardew Valley 1.2.30–1.2.33. * Improved cryptic libgdiplus errors on Mac when Mono isn't installed. * Fixed mod UIs hidden when menu backgrounds are enabled. -* For modders: +* For mod authors: * **Added mod-provided APIs** to allow simple integrations between mods, even without direct assembly references. * Added `GameEvents.FirstUpdateTick` event (called once after all mods are initialized). * Added `IsSuppressed` to input events so mods can optionally avoid handling keys another mod has already handled. @@ -557,7 +557,7 @@ Released 02 December 2017 for Stardew Valley 1.2.30–1.2.33. * Fixed error when uploading very large logs. * Slightly improved the UI. -* For modders: +* For mod authors: * Added `helper.Content.NormalizeAssetName` method. * Added `SDate.DaysSinceStart` property. * Fixed input events' `e.SuppressButton(button)` method ignoring specified button. @@ -575,7 +575,7 @@ Released 01 November 2017 for Stardew Valley 1.2.30–1.2.33. * Fixed compatibility check for players with Stardew Valley 1.08. * Fixed `player_setlevel` command not setting XP too. -* For modders: +* For mod authors: * The reflection API now works with public code to simplify mod integrations. * The content API now lets you invalidated multiple assets at once. * The `InputEvents` have been improved: @@ -600,7 +600,7 @@ Released 14 October 2017 for Stardew Valley 1.2.30–1.2.33. * **Mod update checks** SMAPI now checks if your mods have updates available, and will alert you in the console with a convenient link to the mod page. This works with mods from the Chucklefish mod site, GitHub, or Nexus Mods. SMAPI 2.0 launches with - update-check support for over 250 existing mods, and more will be added as modders enable the feature. + update-check support for over 250 existing mods, and more will be added as mod authors enable the feature. * **Mod stability warnings** SMAPI now detects when a mod contains code which can destabilise your game or corrupt your save, and shows a warning @@ -610,7 +610,7 @@ Released 14 October 2017 for Stardew Valley 1.2.30–1.2.33. The console is now simpler and easier to read, some commands have been streamlined, and the colors now adjust to fit your terminal background color. -* **New features for modders** +* **New features for mod authors** SMAPI 2.0 adds several features to enable new kinds of mods (see [API documentation](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs)). @@ -688,7 +688,7 @@ For players: * Fixed errors when loading some custom maps on Linux/Mac or using XNB Loader. * Fixed errors in rare cases when a mod calculates an in-game date. -For modders: +For mod authors: * Added UTC timestamp to log file. For SMAPI developers: @@ -726,7 +726,7 @@ For players: * Fixed controller mod input broken in 1.15. * Fixed TrainerMod packaging unneeded files. -For modders: +For mod authors: * Fixed mod registry lookups by unique ID not being case-insensitive. ## 1.15 @@ -744,7 +744,7 @@ For players: * Fixed invalid `ObjectInformation.xnb` causing a flood of warnings; SMAPI now shows one error instead. * Updated mod compatibility list. -For modders: +For mod authors: * Added `SDate` utility for in-game date calculations (see [API reference](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Utilities#Dates)). * Added support for minimum dependency versions in `manifest.json` (see [API reference](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Manifest)). * Added more useful logging when loading mods. @@ -777,7 +777,7 @@ For players: * Bumped minimum game version to 1.2.30. * Updated mod compatibility list. -For modders: +For mod authors: * You can now add dependencies to `manifest.json` (see [API reference](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Manifest)). * You can now translate your mod (see [API reference](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Translation)). * You can now load unpacked `.tbin` files from your mod folder through the content API. @@ -788,7 +788,7 @@ For modders: * Fixed `smapi-crash.txt` being copied from the default log even if a different path is specified with `--log-path`. * Fixed the content API not matching XNB filenames with two dots (like `a.b.xnb`) if you don't specify the `.xnb` extension. * Fixed `debug` command output not printed to console. -* Deprecated `TimeEvents.DayOfMonthChanged`, `SeasonOfYearChanged`, and `YearOfGameChanged`. These don't do what most modders think they do and aren't very reliable, since they depend on the SMAPI/game lifecycle which can change. You should use `TimeEvents.AfterDayStarted` or `SaveEvents.BeforeSave` instead. +* Deprecated `TimeEvents.DayOfMonthChanged`, `SeasonOfYearChanged`, and `YearOfGameChanged`. These don't do what most mod authors think they do and aren't very reliable, since they depend on the SMAPI/game lifecycle which can change. You should use `TimeEvents.AfterDayStarted` or `SaveEvents.BeforeSave` instead. ## 1.13.1 Released 19 May 2017 for Stardew Valley 1.2.26–1.2.29. diff --git a/docs/release-notes.md b/docs/release-notes.md index 041071e0..1ac7cf0a 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -3,7 +3,7 @@ # Release notes @@ -12,7 +12,7 @@ * Added more aggressive memory optimization which should eliminate many cases of `OutOfMemoryException` crashes. * Fixed error running `install on Windows.bat` in very rare cases. -* For modders: +* For mod authors: * Fixed asset propagation for `TileSheets/ChairTiles` not changing existing map seats. * Fixed edge case when playing in non-English where translatable assets loaded via `IAssetLoader` would no longer be applied after returning to the title screen unless manually invalidated from the cache. * Fixed SMAPI toolkit defaulting the mod type incorrectly if a mod's `manifest.json` has neither `EntryDll` nor `ContentPackFor`. This only affects external tools, since SMAPI itself validates those fields separately. @@ -46,7 +46,7 @@ Released 22 January 2021 for Stardew Valley 1.5.4 or later. See [release highlig * Fixed compatibility for very old content packs which still load maps from `.xnb` files. These were broken by map loading changes in Stardew Valley 1.5, but SMAPI now corrects them automatically. * Fixed some broken mods incorrectly listed as XNB mods under 'skipped mods'. -* For modders: +* For mod authors: * Added new input APIs: * Added an [API for multi-key bindings](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Input#KeybindList). * Added a new [`Input.ButtonsChanged` event](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events#Input.ButtonsChanged). @@ -80,7 +80,7 @@ Released 15 January 2021 for Stardew Valley 1.5.3 or later. * Updated for Stardew Valley 1.5.3. * Fixed issue where title screen music didn't stop after loading a save. -* For modders: +* For mod authors: * Fixed `SemanticVersion` comparisons returning wrong value in rare cases. ## 3.8.3 @@ -92,7 +92,7 @@ Released 08 January 2021 for Stardew Valley 1.5.2 or later. * You can now enter console commands for a specific screen in split-screen mode by adding `screen=ID` to the command. * Typing `help` in the SMAPI console is now more helpful. -* For modders: +* For mod authors: * Simplified tilesheet order warning added in SMAPI 3.8.2. * For the Console Commands mod: @@ -109,7 +109,7 @@ Released 03 January 2021 for Stardew Valley 1.5.1 or later. * Fixed 'skipped mods' section repeating mods in some cases. * Fixed out-of-date error text. -* For modders: +* For mod authors: * Added warning when a map replacement changes the order/IDs of the original tilesheets, which may cause errors and crashes. Doing so for a farm map is blocked outright since that causes a consistent crash in Stardew Valley 1.5. * Message data from the `ModMessageReceived` event now uses the same serializer settings as the rest of SMAPI. That mainly adds support for sending crossplatform `Color`, `Point`, `Vector2`, `Rectangle`, and `SemanticVersion` fields through network messages. * When a mod is blocked by SMAPI's compatibility override list, the `TRACE` messages while loading it now say so and indicate why. @@ -121,7 +121,7 @@ Released 26 December 2020 for Stardew Valley 1.5.1 or later. * For players: * Fixed broken community center bundles for non-English saves created in Stardew Valley 1.5. Affected saves will be fixed automatically on load. -* For modders: +* For mod authors: * World events are now raised for volcano dungeon levels. * Added `apply_save_fix` command to reapply a save migration in exceptional cases. This should be used very carefully. Type `help apply_save_fix` for details. * **Deprecation notice:** the `Helper.ConsoleCommands.Trigger` method is now deprecated and should no longer be used. See [integration APIs](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Integrations) for better mod integration options. It will eventually be removed in SMAPI 4.0. @@ -137,7 +137,7 @@ Released 21 December 2020 for Stardew Valley 1.5 or later. See [release highligh * You can now run the installer from a subfolder of your game folder to auto-detect it. That simplifies installation if you have multiple copies of the game or it can't otherwise auto-detect the game path. * Clarified error when the SMAPI installer is in the `Mods` folder. -* For modders: +* For mod authors: * Added `PerScreen` utility and new `Context` fields to simplify split-screen support in mods. * Added screen ID to log when playing in split-screen mode. @@ -154,7 +154,7 @@ Released 21 November 2020 for Stardew Valley 1.4.1 or later. * Fixed error when heuristically rewriting an outdated mod in rare cases. * Fixed rare 'collection was modified' error when using `harmony summary` console command. -* For modders: +* For mod authors: * Updated TMXTile to 1.5.8 to fix exported `.tmx` files losing tile index properties. * For the Console Commands mod: @@ -164,7 +164,7 @@ Released 21 November 2020 for Stardew Valley 1.4.1 or later. ## 3.7.5 Released 16 October 2020 for Stardew Valley 1.4.1 or later. -* For modders: +* For mod authors: * Fixed changes to the town map asset not reapplying the game's community center, JojaMart, and Pam house changes. ## 3.7.4 @@ -174,7 +174,7 @@ Released 03 October 2020 for Stardew Valley 1.4.1 or later. * Improved performance on some older computers (thanks to millerscout!). * Fixed update alerts for Chucklefish forum mods broken by a recent site change. -* For modders: +* For mod authors: * Updated dependencies (including Mono.Cecil 0.11.2 → 0.11.3 and Platonymous.TMXTile 1.3.8 → 1.5.6). * Fixed asset propagation for `Data\MoviesReactions`. * Fixed error in content pack path handling when you pass a null path. @@ -193,7 +193,7 @@ Released 16 September 2020 for Stardew Valley 1.4.1 or later. * Fixed map rendering crash due to conflict between SMAPI and PyTK. * Fixed error in heuristically-rewritten mods in rare cases (thanks to collaboration with ZaneYork!). -* For modders: +* For mod authors: * File paths accessed through `IContentPack` are now case-insensitive (even on Linux). * For the web UI: @@ -205,7 +205,7 @@ Released 08 September 2020 for Stardew Valley 1.4.1 or later. * For players: * Fixed mod recipe changes not always applied in 3.7. -* For modders: +* For mod authors: * Renamed `PathUtilities.NormalizePathSeparators` to `NormalizePath`, and added normalization for more cases. ## 3.7.1 @@ -230,7 +230,7 @@ Released 07 September 2020 for Stardew Valley 1.4.1 or later. See [release highl * Removed the experimental `RewriteInParallel` option added in SMAPI 3.6 (it was already disabled by default). Unfortunately this caused intermittent and unpredictable errors when enabled. * Internal changes to prepare for upcoming game updates. -* For modders: +* For mod authors: * Added `PathUtilities` to simplify working with file/asset names. * You can now read/write `SDate` values to JSON (e.g. for `config.json`, network mod messages, etc). * Fixed asset propagation not updating title menu buttons immediately on Linux/Mac. @@ -260,7 +260,7 @@ Released 02 August 2020 for Stardew Valley 1.4.1 or later. * Fixed spawned Floor TV not functional as a TV (thanks to Platonymous!). * Fixed spawned sturgeon roe having incorrect color. -* For modders: +* For mod authors: * Updated internal dependencies. * SMAPI now ignores more file types when scanning for mod folders (`.doc`, `.docx`, `.rar`, and `.zip`). * Added current GPU to trace logs to simplify troubleshooting. @@ -293,7 +293,7 @@ Released 20 June 2020 for Stardew Valley 1.4.1 or later. See [release highlights * Updated ModDrop URLs. * Internal changes to improve performance and reliability. -* For modders: +* For mod authors: * Added [event priorities](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events#Custom_priority) (thanks to spacechase0!). * Added [update subkeys](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Update_checks#Update_subkeys). * Added [a custom build of Harmony](https://github.com/Pathoschild/Harmony#readme) to provide more useful stack traces in error logs. @@ -328,7 +328,7 @@ Released 27 April 2020 for Stardew Valley 1.4.1 or later. See [release highlight * Updated the JSON validator/schema for Content Patcher 1.13. * Fixed rare intermittent "CGI application encountered an error" errors. -* For modders: +* For mod authors: * Added map patching to the content API (via `asset.AsMap()`). * Added support for using patch helpers with arbitrary data (via `helper.Content.GetPatchHelper`). * Added `SDate` fields/methods: `SeasonIndex`, `FromDaysSinceStart`, `FromWorldDate`, `ToWorldDate`, and `ToLocaleString` (thanks to kdau!). @@ -342,7 +342,7 @@ Released 27 April 2020 for Stardew Valley 1.4.1 or later. See [release highlight ## 3.4.1 Released 24 March 2020 for Stardew Valley 1.4.1 or later. -* For modders: +* For mod authors: * Asset changes now propagate to NPCs in an event (e.g. wedding sprites). * Fixed mouse input suppression not working in SMAPI 3.4. @@ -355,7 +355,7 @@ Released 22 March 2020 for Stardew Valley 1.4.1 or later. See [release highlight * Removed invalid-location check now handled by the game. * Updated translations. Thanks to Annosz (added Hungarian)! -* For modders: +* For mod authors: * Added support for flipped and rotated map tiles (thanks to collaboration with Platonymous!). * Added support for `.tmx` maps using zlib compression (thanks to Platonymous!). * Added `this.Monitor.LogOnce` method. @@ -399,7 +399,7 @@ Released 22 February 2020 for Stardew Valley 1.4.1 or later. See [release highli * Updated the JSON validator and Content Patcher schema for `.tmx` support. * The mod compatibility page now has a sticky table header. -* For modders: +* For mod authors: * Added support for [message sending](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Integrations#Message_sending) to mods on the current computer (in addition to remote computers). * Added `ExtendImage` method to content API when editing files to resize textures. * Added `helper.Input.GetState` to get the low-level state of a button. @@ -434,7 +434,7 @@ Released 01 February 2020 for Stardew Valley 1.4.1 or later. See [release highli * Fixed extra files under `Saves` (e.g. manual backups) not being ignored. * Fixed Android issue where game files were backed up. -* For modders: +* For mod authors: * Added support for `.tmx` map files. (Thanks to [Platonymous for the underlying library](https://github.com/Platonymous/TMXTile)!) * Added special handling for `Vector2` values in `.json` files, so they work consistently crossplatform. * Reworked the order that asset editors/loaders are called between multiple mods to support some framework mods like Content Patcher and Json Assets. Note that the order is undefined and should not be depended on. @@ -483,7 +483,7 @@ Released 05 January 2019 for Stardew Valley 1.4.1 or later. See [release highlig * Fixed log parser not correctly handling content packs with no author (thanks to danvolchek!). * Fixed main sidebar link pointing to wiki instead of home page. -* For modders: +* For mod authors: * Added `World.ChestInventoryChanged` event (thanks to collaboration with wartech0!). * Added asset propagation for... * grass textures; @@ -512,7 +512,7 @@ Released 02 December 2019 for Stardew Valley 1.4 or later. * If a log can't be uploaded to Pastebin (e.g. due to rate limits), it's now uploaded to Amazon S3 instead. Logs uploaded to S3 expire after one month. * Fixed JSON validator not letting you drag & drop a file. -* For modders: +* For mod authors: * `SemanticVersion` now supports [semver 2.0](https://semver.org/) build metadata. ## 3.0 @@ -542,7 +542,7 @@ For players: * **Fixed many bugs and edge cases.** -For modders: +For mod authors: * **New event system.** SMAPI 3.0 removes the deprecated static events in favor of the new `helper.Events` API. The event engine is rewritten to make events more efficient, add events that weren't possible before, make @@ -626,7 +626,7 @@ For modders: * Added instructions for Android. * The page now detects your OS and preselects the right instructions (thanks to danvolchek!). -### For modders +### For mod authors * Breaking changes: * Mods are now loaded much earlier in the game launch. This lets mods intercept any content asset, but the game is not fully initialized when `Entry` is called; use the `GameLaunched` event if you need to run code when the game is initialized. * Removed all deprecated APIs. From 033b3856413f51e26e74498ea9fe3de291d4e93a Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 21 Feb 2021 17:33:37 -0500 Subject: [PATCH 18/20] add detailed error message when Stardew Valley.exe can't be loaded --- docs/release-notes.md | 7 +++++-- src/SMAPI/Program.cs | 31 ++++++++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 1ac7cf0a..1843b0e6 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -9,13 +9,13 @@ ## Upcoming release * For players: - * Added more aggressive memory optimization which should eliminate many cases of `OutOfMemoryException` crashes. + * Added more aggressive memory optimization which should reduce `OutOfMemoryException` errors with some mods. + * Added more detailed error when `Stardew Valley.exe` exists but can't be loaded. * Fixed error running `install on Windows.bat` in very rare cases. * For mod authors: * Fixed asset propagation for `TileSheets/ChairTiles` not changing existing map seats. * Fixed edge case when playing in non-English where translatable assets loaded via `IAssetLoader` would no longer be applied after returning to the title screen unless manually invalidated from the cache. - * Fixed SMAPI toolkit defaulting the mod type incorrectly if a mod's `manifest.json` has neither `EntryDll` nor `ContentPackFor`. This only affects external tools, since SMAPI itself validates those fields separately. * For the ErrorHandler mod: * Added early detection of disposed textures so the crash stack trace shows the actual code which used them. @@ -26,6 +26,9 @@ * Updated the JSON validator/schema for Content Patcher 1.20. * Fixed mod compatibility list error if a mod has no name. +* For SMAPI developers: + * Fixed SMAPI toolkit defaulting the mod type incorrectly if a mod's `manifest.json` has neither `EntryDll` nor `ContentPackFor`. This only affects external tools, since SMAPI itself validates those fields separately. + ## 3.9.1 Released 25 January 2021 for Stardew Valley 1.5.4 or later. diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index 23ee8453..986d2780 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -73,8 +73,22 @@ namespace StardewModdingAPI /// This must be checked *before* any references to , and this method should not reference itself to avoid errors in Mono or when the game isn't present. private static void AssertGamePresent() { - if (Type.GetType($"StardewValley.Game1, {EarlyConstants.GameAssemblyName}", throwOnError: false) == null) - Program.PrintErrorAndExit("Oops! SMAPI can't find the game. Make sure you're running StardewModdingAPI.exe in your game folder. See the readme.txt file for details."); + try + { + _ = Type.GetType($"StardewValley.Game1, {EarlyConstants.GameAssemblyName}", throwOnError: true); + } + catch (Exception ex) + { + // file doesn't exist + if (!File.Exists(Path.Combine(EarlyConstants.ExecutionPath, $"{EarlyConstants.GameAssemblyName}.exe"))) + Program.PrintErrorAndExit("Oops! SMAPI can't find the game. Make sure you're running StardewModdingAPI.exe in your game folder."); + + // can't load file + Program.PrintErrorAndExit( + message: "Oops! SMAPI couldn't load the game executable. The technical details below may have more info.", + technicalMessage: $"Technical details: {ex}" + ); + } } /// Assert that the game version is within and . @@ -130,11 +144,22 @@ namespace StardewModdingAPI /// Write an error directly to the console and exit. /// The error message to display. - private static void PrintErrorAndExit(string message) + /// An additional message to log with technical details. + private static void PrintErrorAndExit(string message, string technicalMessage = null) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(message); Console.ResetColor(); + + if (technicalMessage != null) + { + Console.WriteLine(); + Console.ForegroundColor = ConsoleColor.Gray; + Console.WriteLine(technicalMessage); + Console.ResetColor(); + Console.WriteLine(); + } + Program.PressAnyKeyToExit(showMessage: true); } From 27accf55a5e008819015a599a484c8e670cc546f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 21 Feb 2021 18:29:14 -0500 Subject: [PATCH 19/20] update ambient light when setting game time --- docs/release-notes.md | 7 +++---- .../Commands/World/SetTimeCommand.cs | 20 ++++++++++++------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 1843b0e6..09c941d6 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -12,15 +12,14 @@ * Added more aggressive memory optimization which should reduce `OutOfMemoryException` errors with some mods. * Added more detailed error when `Stardew Valley.exe` exists but can't be loaded. * Fixed error running `install on Windows.bat` in very rare cases. + * Fixed outdoor ambient lighting not updated when you reverse time using the `world_settime` command _(in Console Commands)_. * For mod authors: + * Added early detection of disposed textures so the crash stack trace shows the actual code which used them _(in Error Handler)_. + * Added error details when an event command fails _(in Error Handler)_. * Fixed asset propagation for `TileSheets/ChairTiles` not changing existing map seats. * Fixed edge case when playing in non-English where translatable assets loaded via `IAssetLoader` would no longer be applied after returning to the title screen unless manually invalidated from the cache. -* For the ErrorHandler mod: - * Added early detection of disposed textures so the crash stack trace shows the actual code which used them. - * Added error details when an event command fails. - * For the web UI: * Updated for the new wiki. * Updated the JSON validator/schema for Content Patcher 1.20. diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetTimeCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetTimeCommand.cs index 6782e38a..2d4b4565 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetTimeCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetTimeCommand.cs @@ -1,5 +1,5 @@ -using System; using System.Linq; +using Microsoft.Xna.Framework; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World @@ -45,12 +45,8 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World /// The time of day. private void SafelySetTime(int time) { - // define conversion between game time and TimeSpan - TimeSpan ToTimeSpan(int value) => new TimeSpan(0, value / 100, value % 100, 0); - int FromTimeSpan(TimeSpan span) => (span.Hours * 100) + span.Minutes; - // transition to new time - int intervals = (int)((ToTimeSpan(time) - ToTimeSpan(Game1.timeOfDay)).TotalMinutes / 10); + int intervals = Utility.CalculateMinutesBetweenTimes(Game1.timeOfDay, time) / 10; if (intervals > 0) { for (int i = 0; i < intervals; i++) @@ -60,10 +56,20 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World { for (int i = 0; i > intervals; i--) { - Game1.timeOfDay = FromTimeSpan(ToTimeSpan(Game1.timeOfDay).Subtract(TimeSpan.FromMinutes(20))); // offset 20 minutes so game updates to next interval + Game1.timeOfDay = Utility.ModifyTime(Game1.timeOfDay, -20); // offset 20 mins so game updates to next interval Game1.performTenMinuteClockUpdate(); } } + + // reset ambient light + // White is the default non-raining color. If it's raining or dark out, UpdateGameClock + // below will update it automatically. + Game1.outdoorLight = Color.White; + Game1.ambientLight = Color.White; + + // run clock update (to correct lighting, etc) + Game1.gameTimeInterval = 0; + Game1.UpdateGameClock(Game1.currentGameTime); } } } From 9c4c10d2d22cbf67ccadbd35fdf1ffced0541cc2 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 21 Feb 2021 21:58:37 -0500 Subject: [PATCH 20/20] prepare for release --- build/common.targets | 2 +- docs/release-notes.md | 14 ++++++++------ src/SMAPI.Mods.ConsoleCommands/manifest.json | 4 ++-- src/SMAPI.Mods.ErrorHandler/manifest.json | 4 ++-- src/SMAPI.Mods.SaveBackup/manifest.json | 4 ++-- src/SMAPI/Constants.cs | 2 +- 6 files changed, 16 insertions(+), 14 deletions(-) diff --git a/build/common.targets b/build/common.targets index a38f15a6..29acbb56 100644 --- a/build/common.targets +++ b/build/common.targets @@ -4,7 +4,7 @@ - 3.9.1 + 3.9.2 SMAPI latest diff --git a/docs/release-notes.md b/docs/release-notes.md index 09c941d6..fb67d8dc 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -7,21 +7,23 @@ * Migrated to Harmony 2.0 (see [_migrate to Harmony 2.0_](https://stardewvalleywiki.com/Modding:Migrate_to_Harmony_2.0) for more info). --> -## Upcoming release +## 3.9.2 +Released 21 February 2021 for Stardew Valley 1.5.4 or later. + * For players: - * Added more aggressive memory optimization which should reduce `OutOfMemoryException` errors with some mods. - * Added more detailed error when `Stardew Valley.exe` exists but can't be loaded. + * Added more aggressive memory optimization to reduce `OutOfMemoryException` errors with some mods. + * Improved error when `Stardew Valley.exe` exists but can't be loaded. * Fixed error running `install on Windows.bat` in very rare cases. - * Fixed outdoor ambient lighting not updated when you reverse time using the `world_settime` command _(in Console Commands)_. + * Fixed `world_settime` command not always updating outdoor ambient lighting _(in Console Commands)_. * For mod authors: - * Added early detection of disposed textures so the crash stack trace shows the actual code which used them _(in Error Handler)_. + * Added early detection of disposed textures so the error details are more relevant _(in Error Handler)_. * Added error details when an event command fails _(in Error Handler)_. * Fixed asset propagation for `TileSheets/ChairTiles` not changing existing map seats. * Fixed edge case when playing in non-English where translatable assets loaded via `IAssetLoader` would no longer be applied after returning to the title screen unless manually invalidated from the cache. * For the web UI: - * Updated for the new wiki. + * Updated compatibility list for the new wiki. * Updated the JSON validator/schema for Content Patcher 1.20. * Fixed mod compatibility list error if a mod has no name. diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json index 10611e08..aa3d6ceb 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.9.1", + "Version": "3.9.2", "Description": "Adds SMAPI console commands that let you manipulate the game.", "UniqueID": "SMAPI.ConsoleCommands", "EntryDll": "ConsoleCommands.dll", - "MinimumApiVersion": "3.9.1" + "MinimumApiVersion": "3.9.2" } diff --git a/src/SMAPI.Mods.ErrorHandler/manifest.json b/src/SMAPI.Mods.ErrorHandler/manifest.json index bb9942d1..b6df0f49 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.9.1", + "Version": "3.9.2", "Description": "Handles some common vanilla errors to log more useful info or avoid breaking the game.", "UniqueID": "SMAPI.ErrorHandler", "EntryDll": "ErrorHandler.dll", - "MinimumApiVersion": "3.9.1" + "MinimumApiVersion": "3.9.2" } diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json index 95ee5144..4d2003e2 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.9.1", + "Version": "3.9.2", "Description": "Automatically backs up all your saves once per day into its folder.", "UniqueID": "SMAPI.SaveBackup", "EntryDll": "SaveBackup.dll", - "MinimumApiVersion": "3.9.1" + "MinimumApiVersion": "3.9.2" } diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 57c40bbf..54fb54ab 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -54,7 +54,7 @@ namespace StardewModdingAPI ** Public ****/ /// SMAPI's current semantic version. - public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("3.9.1"); + public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("3.9.2"); /// The minimum supported version of Stardew Valley. public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.5.4");