From beb1acd4f823d64d125f5749b2edad06e5731407 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 18 Dec 2021 22:21:31 -0500 Subject: update Steam error message --- src/SMAPI/Framework/Logging/LogManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs index 5a291d0a..ef89c751 100644 --- a/src/SMAPI/Framework/Logging/LogManager.cs +++ b/src/SMAPI/Framework/Logging/LogManager.cs @@ -49,7 +49,7 @@ namespace StardewModdingAPI.Framework.Logging search: new Regex(@"^System\.InvalidOperationException: Steamworks is not initialized\.[\s\S]+$", RegexOptions.Compiled | RegexOptions.CultureInvariant), replacement: #if SMAPI_FOR_WINDOWS - "Oops! Steam achievements won't work because Steam isn't loaded. See 'Launch SMAPI through Steam or GOG Galaxy' in the install guide for more info: https://smapi.io/install.", + "Oops! Steam achievements won't work because Steam isn't loaded. See 'Configure your game client' in the install guide for more info: https://smapi.io/install.", #else "Oops! Steam achievements won't work because Steam isn't loaded. You can launch the game through Steam to fix that.", #endif -- cgit From 0d7d4476004d33b395d6df81386e4159d8898027 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 20 Dec 2021 22:18:09 -0500 Subject: auto-fix maps broken due to missing vanilla tilesheet --- docs/release-notes.md | 1 + src/SMAPI.sln.DotSettings | 1 + src/SMAPI/Framework/Content/TilesheetReference.cs | 15 ++++++++- src/SMAPI/Framework/ContentCoordinator.cs | 4 +-- .../ContentManagers/GameContentManager.cs | 39 +++++++++------------- 5 files changed, 33 insertions(+), 27 deletions(-) (limited to 'src/SMAPI/Framework') diff --git a/docs/release-notes.md b/docs/release-notes.md index e0c6977d..caa5cc68 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -3,6 +3,7 @@ # Release notes ## Upcoming release * For players: + * SMAPI now auto-fixes maps loaded without a required tilesheet to prevent errors. * Fixed outdated instructions in Steam error message. * Simplified [running without a terminal on Linux/macOS](https://stardewvalleywiki.com/Modding:Player_Guide/Troubleshooting#SMAPI_doesn.27t_recognize_controller_.28Steam_only.29) when needed. * Updated compatibility list. diff --git a/src/SMAPI.sln.DotSettings b/src/SMAPI.sln.DotSettings index 9a6cad37..71cd7b82 100644 --- a/src/SMAPI.sln.DotSettings +++ b/src/SMAPI.sln.DotSettings @@ -70,6 +70,7 @@ True True True + True True True \ No newline at end of file diff --git a/src/SMAPI/Framework/Content/TilesheetReference.cs b/src/SMAPI/Framework/Content/TilesheetReference.cs index 2ea38430..0919bb44 100644 --- a/src/SMAPI/Framework/Content/TilesheetReference.cs +++ b/src/SMAPI/Framework/Content/TilesheetReference.cs @@ -1,3 +1,6 @@ +using System.Numerics; +using xTile.Dimensions; + namespace StardewModdingAPI.Framework.Content { /// Basic metadata about a vanilla tilesheet. @@ -15,6 +18,12 @@ namespace StardewModdingAPI.Framework.Content /// The asset path for the tilesheet texture. public readonly string ImageSource; + /// The number of tiles in the tilesheet. + public readonly Size SheetSize; + + /// The size of each tile in pixels. + public readonly Size TileSize; + /********* ** Public methods @@ -23,11 +32,15 @@ namespace StardewModdingAPI.Framework.Content /// The tilesheet's index in the list. /// The tilesheet's unique ID in the map. /// The asset path for the tilesheet texture. - public TilesheetReference(int index, string id, string imageSource) + /// The number of tiles in the tilesheet. + /// The size of each tile in pixels. + public TilesheetReference(int index, string id, string imageSource, Size sheetSize, Size tileSize) { this.Index = index; this.Id = id; this.ImageSource = imageSource; + this.SheetSize = sheetSize; + this.TileSize = tileSize; } } } diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index b6f1669a..99091f3e 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -406,14 +406,14 @@ namespace StardewModdingAPI.Framework if (!this.VanillaTilesheets.TryGetValue(assetName, out TilesheetReference[] tilesheets)) { tilesheets = this.TryLoadVanillaAsset(assetName, out Map map) - ? map.TileSheets.Select((sheet, index) => new TilesheetReference(index, sheet.Id, sheet.ImageSource)).ToArray() + ? map.TileSheets.Select((sheet, index) => new TilesheetReference(index, sheet.Id, sheet.ImageSource, sheet.SheetSize, sheet.TileSize)).ToArray() : null; this.VanillaTilesheets[assetName] = tilesheets; this.VanillaContentManager.Unload(); } - return tilesheets ?? new TilesheetReference[0]; + return tilesheets ?? Array.Empty(); } /// Get the language enum which corresponds to a locale code (e.g. given fr-FR). diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index 7a49dd36..ab198076 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -13,6 +13,7 @@ using StardewModdingAPI.Framework.Utilities; using StardewModdingAPI.Internal; using StardewValley; using xTile; +using xTile.Tiles; namespace StardewModdingAPI.Framework.ContentManagers { @@ -308,7 +309,7 @@ namespace StardewModdingAPI.Framework.ContentManagers } // return matched asset - return this.TryValidateLoadedAsset(info, data, mod) + return this.TryFixAndValidateLoadedAsset(info, data, mod) ? new AssetDataForObject(info, data, this.AssertAndNormalizeAssetName) : null; } @@ -381,12 +382,13 @@ namespace StardewModdingAPI.Framework.ContentManagers return asset; } - /// Validate that an asset loaded by a mod is valid and won't cause issues. + /// Validate that an asset loaded by a mod is valid and won't cause issues, and fix issues if possible. /// The asset type. /// The basic asset metadata. /// The loaded asset data. /// The mod which loaded the asset. - private bool TryValidateLoadedAsset(IAssetInfo info, T data, IModMetadata mod) + /// Returns whether the asset passed validation checks (after any fixes were applied). + private bool TryFixAndValidateLoadedAsset(IAssetInfo info, T data, IModMetadata mod) { // can't load a null asset if (data == null) @@ -401,20 +403,23 @@ namespace StardewModdingAPI.Framework.ContentManagers TilesheetReference[] vanillaTilesheetRefs = this.Coordinator.GetVanillaTilesheetIds(info.AssetName); foreach (TilesheetReference vanillaSheet in vanillaTilesheetRefs) { - // skip if match - if (loadedMap.TileSheets.Count > vanillaSheet.Index && loadedMap.TileSheets[vanillaSheet.Index].Id == vanillaSheet.Id) - continue; + // add missing tilesheet + if (loadedMap.GetTileSheet(vanillaSheet.Id) == null) + { + mod.Monitor.LogOnce("SMAPI fixed maps loaded by this mod to prevent errors. See the log file for details.", LogLevel.Warn); + this.Monitor.Log($"Fixed broken map replacement: {mod.DisplayName} loaded '{info.AssetName}' without a required tilesheet (id: {vanillaSheet.Id}, source: {vanillaSheet.ImageSource})."); + + loadedMap.AddTileSheet(new TileSheet(vanillaSheet.Id, loadedMap, vanillaSheet.ImageSource, vanillaSheet.SheetSize, vanillaSheet.TileSize)); + } // handle mismatch + if (loadedMap.TileSheets.Count <= vanillaSheet.Index || loadedMap.TileSheets[vanillaSheet.Index].Id != vanillaSheet.Id) { // only show warning if not farm map // This is temporary: mods shouldn't do this for any vanilla map, but these are the ones we know will crash. Showing a warning for others instead gives modders time to update their mods, while still simplifying troubleshooting. bool isFarmMap = info.AssetNameEquals("Maps/Farm") || info.AssetNameEquals("Maps/Farm_Combat") || info.AssetNameEquals("Maps/Farm_Fishing") || info.AssetNameEquals("Maps/Farm_Foraging") || info.AssetNameEquals("Maps/Farm_FourCorners") || info.AssetNameEquals("Maps/Farm_Island") || info.AssetNameEquals("Maps/Farm_Mining"); - int loadedIndex = this.TryFindTilesheet(loadedMap, vanillaSheet.Id); - string reason = loadedIndex != -1 - ? $"mod reordered the original tilesheets, which {(isFarmMap ? "would cause a crash" : "often causes crashes")}.\nTechnical details for mod author: Expected order: {string.Join(", ", vanillaTilesheetRefs.Select(p => p.Id))}. See https://stardewvalleywiki.com/Modding:Maps#Tilesheet_order for help." - : $"mod has no tilesheet with ID '{vanillaSheet.Id}'. Map replacements must keep the original tilesheets to avoid errors or crashes."; + string reason = $"mod reordered the original tilesheets, which {(isFarmMap ? "would cause a crash" : "often causes crashes")}.\nTechnical details for mod author: Expected order: {string.Join(", ", vanillaTilesheetRefs.Select(p => p.Id))}. See https://stardewvalleywiki.com/Modding:Maps#Tilesheet_order for help."; SCore.DeprecationManager.PlaceholderWarn("3.8.2", DeprecationLevel.PendingRemoval); if (isFarmMap) @@ -429,19 +434,5 @@ namespace StardewModdingAPI.Framework.ContentManagers return true; } - - /// Find a map tilesheet by ID. - /// The map whose tilesheets to search. - /// The tilesheet ID to match. - private int TryFindTilesheet(Map map, string id) - { - for (int i = 0; i < map.TileSheets.Count; i++) - { - if (map.TileSheets[i].Id == id) - return i; - } - - return -1; - } } } -- cgit From 52f4df3f301ecde85a76e10ea9ddd607ab7a1b79 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 21 Dec 2021 20:33:08 -0500 Subject: add new game build number to the SMAPI log --- docs/release-notes.md | 1 + src/SMAPI/Constants.cs | 11 +++++++++++ src/SMAPI/Framework/Logging/LogManager.cs | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) (limited to 'src/SMAPI/Framework') diff --git a/docs/release-notes.md b/docs/release-notes.md index caa5cc68..a98cfd8e 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -4,6 +4,7 @@ ## Upcoming release * For players: * SMAPI now auto-fixes maps loaded without a required tilesheet to prevent errors. + * Added the new game build number to the SMAPI console + log. * Fixed outdated instructions in Steam error message. * Simplified [running without a terminal on Linux/macOS](https://stardewvalleywiki.com/Modding:Player_Guide/Troubleshooting#SMAPI_doesn.27t_recognize_controller_.28Steam_only.29) when needed. * Updated compatibility list. diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 5de28f84..c86acd0a 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -340,5 +340,16 @@ namespace StardewModdingAPI // if save doesn't exist yet, return the default one we expect to be created return folder; } + + /// Get a display label for the game's build number. + internal static string GetBuildVersionLabel() + { + string version = typeof(Game1).Assembly.GetName().Version?.ToString() ?? "unknown"; + + if (version.StartsWith($"{Game1.version}.")) + version = version.Substring(Game1.version.Length + 1); + + return version; + } } } diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs index ef89c751..90433c37 100644 --- a/src/SMAPI/Framework/Logging/LogManager.cs +++ b/src/SMAPI/Framework/Logging/LogManager.cs @@ -262,7 +262,7 @@ namespace StardewModdingAPI.Framework.Logging public void LogIntro(string modsPath, IDictionary customSettings) { // log platform - this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Constants.GameVersion} on {EnvironmentUtility.GetFriendlyPlatformName(Constants.Platform)}", LogLevel.Info); + this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Constants.GameVersion} (build {Constants.GetBuildVersionLabel()}) on {EnvironmentUtility.GetFriendlyPlatformName(Constants.Platform)}", LogLevel.Info); // log basic info this.Monitor.Log($"Mods go here: {modsPath}", LogLevel.Info); -- cgit From a8985e122e9ebd3cb1545971474b95d34058f896 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 15 Jan 2022 12:21:22 -0500 Subject: fix suppressed console output not suppressing newlines --- docs/release-notes.md | 1 + .../Framework/Logging/InterceptingTextWriter.cs | 12 +++++++++++- src/SMAPI/Framework/Logging/LogManager.cs | 20 ++++++++++++++++---- 3 files changed, 28 insertions(+), 5 deletions(-) (limited to 'src/SMAPI/Framework') diff --git a/docs/release-notes.md b/docs/release-notes.md index 1df0af0e..735c7c12 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -5,6 +5,7 @@ * For players: * SMAPI now auto-fixes maps loaded without a required tilesheet to prevent errors. * Added the new game build number to the SMAPI console + log. + * Fixed extra newlines shown in the console in non-developer mode. * Fixed outdated instructions in Steam error message. * Fixed uninstaller not removing `StardewModdingAPI.deps.json` file. * Fixed launch issue on macOS when using some terminals (thanks to bruce2409!). diff --git a/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs b/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs index d99f1dd2..41a3abcb 100644 --- a/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs +++ b/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs @@ -26,6 +26,10 @@ namespace StardewModdingAPI.Framework.Logging /// The event raised when a message is written to the console directly. public event Action OnMessageIntercepted; + /// Whether the text writer should ignore the next input if it's a newline. + /// This is used when log output is suppressed from the console, since Console.WriteLine writes the trailing newline as a separate call. + public bool IgnoreNextIfNewline { get; set; } + /********* ** Public methods @@ -42,12 +46,18 @@ namespace StardewModdingAPI.Framework.Logging /// public override void Write(char[] buffer, int index, int count) { + bool ignoreIfNewline = this.IgnoreNextIfNewline; + this.IgnoreNextIfNewline = false; + if (buffer.Length == 0) this.Out.Write(buffer, index, count); else if (buffer[0] == this.IgnoreChar) this.Out.Write(buffer, index + 1, count - 1); else if (this.IsEmptyOrNewline(buffer)) - this.Out.Write(buffer, index, count); + { + if (!ignoreIfNewline) + this.Out.Write(buffer, index, count); + } else this.OnMessageIntercepted?.Invoke(new string(buffer, index, count).TrimEnd('\r', '\n')); } diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs index 90433c37..acd2c617 100644 --- a/src/SMAPI/Framework/Logging/LogManager.cs +++ b/src/SMAPI/Framework/Logging/LogManager.cs @@ -22,9 +22,15 @@ namespace StardewModdingAPI.Framework.Logging /********* ** Fields *********/ + /// Whether to show trace messages in the console. + private readonly bool ShowTraceInConsole; + /// The log file to which to write messages. private readonly LogFileManager LogFile; + /// The text writer which intercepts console output. + private readonly InterceptingTextWriter ConsoleInterceptor; + /// Prefixing a low-level message with this character indicates that the console interceptor should write the string without intercepting it. (The character itself is not written.) private readonly char IgnoreChar = '\u200B'; @@ -91,10 +97,11 @@ namespace StardewModdingAPI.Framework.Logging public LogManager(string logPath, ColorSchemeConfig colorConfig, bool writeToConsole, bool isVerbose, bool isDeveloperMode, Func getScreenIdForLog) { // init construction logic + this.ShowTraceInConsole = isDeveloperMode; this.GetMonitorImpl = name => new Monitor(name, this.IgnoreChar, this.LogFile, colorConfig, isVerbose, getScreenIdForLog) { WriteToConsole = writeToConsole, - ShowTraceInConsole = isDeveloperMode, + ShowTraceInConsole = this.ShowTraceInConsole, ShowFullStampInConsole = isDeveloperMode }; @@ -104,10 +111,10 @@ namespace StardewModdingAPI.Framework.Logging this.MonitorForGame = this.GetMonitor("game"); // redirect direct console output - var output = new InterceptingTextWriter(Console.Out, this.IgnoreChar); + this.ConsoleInterceptor = new InterceptingTextWriter(Console.Out, this.IgnoreChar); if (writeToConsole) - output.OnMessageIntercepted += message => this.HandleConsoleMessage(this.MonitorForGame, message); - Console.SetOut(output); + this.ConsoleInterceptor.OnMessageIntercepted += message => this.HandleConsoleMessage(this.MonitorForGame, message); + Console.SetOut(this.ConsoleInterceptor); // enable Unicode handling on Windows // (the terminal defaults to UTF-8 on Linux/macOS) @@ -363,7 +370,10 @@ namespace StardewModdingAPI.Framework.Logging // ignore suppressed message if (level != LogLevel.Error && this.SuppressConsolePatterns.Any(p => p.IsMatch(message))) + { + this.ConsoleInterceptor.IgnoreNextIfNewline = true; return; + } // show friendly error if applicable foreach (ReplaceLogPattern entry in this.ReplaceConsolePatterns) @@ -383,6 +393,8 @@ namespace StardewModdingAPI.Framework.Logging // forward to monitor gameMonitor.Log(message, level); + if (level == LogLevel.Trace && !this.ShowTraceInConsole) + this.ConsoleInterceptor.IgnoreNextIfNewline = true; } /// Write a summary of mod warnings to the console and log. -- cgit From 8ebb9ce8d4c554b077e1e6286531c101d64c019d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 15 Jan 2022 13:27:23 -0500 Subject: fix backspaces ignored on Linux/macOS in SDV 1.5.5+ --- docs/release-notes.md | 5 +++-- src/SMAPI/Framework/Logging/InterceptingTextWriter.cs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'src/SMAPI/Framework') diff --git a/docs/release-notes.md b/docs/release-notes.md index 735c7c12..06d75b15 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -3,12 +3,13 @@ # Release notes ## Upcoming release * For players: - * SMAPI now auto-fixes maps loaded without a required tilesheet to prevent errors. + * Added auto-fix for custom maps which don't have a required tilesheet. * Added the new game build number to the SMAPI console + log. * Fixed extra newlines shown in the console in non-developer mode. + * Fixed macOS launch issue when using some terminals (thanks to bruce2409!). + * Fixed Linux/macOS terminal ignoring backspace in Stardew Valley 1.5.5+. * Fixed outdated instructions in Steam error message. * Fixed uninstaller not removing `StardewModdingAPI.deps.json` file. - * Fixed launch issue on macOS when using some terminals (thanks to bruce2409!). * Simplified [running without a terminal on Linux/macOS](https://stardewvalleywiki.com/Modding:Player_Guide/Troubleshooting#SMAPI_doesn.27t_recognize_controller_.28Steam_only.29) when needed. * Updated compatibility list. * Improved translations. Thanks to ChulkyBow (added Ukrainian)! diff --git a/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs b/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs index 41a3abcb..36f81d22 100644 --- a/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs +++ b/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs @@ -51,7 +51,7 @@ namespace StardewModdingAPI.Framework.Logging if (buffer.Length == 0) this.Out.Write(buffer, index, count); - else if (buffer[0] == this.IgnoreChar) + else if (buffer[0] == this.IgnoreChar || char.IsControl(buffer[0])) // ignore control characters like backspace this.Out.Write(buffer, index + 1, count - 1); else if (this.IsEmptyOrNewline(buffer)) { -- cgit From 1ea8d752356b313d200810235463a322a47f9425 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 16 Jan 2022 14:05:02 -0500 Subject: improve console interception logic --- .../Framework/Logging/InterceptingTextWriter.cs | 28 ++++++++++--------- src/SMAPI/Framework/Logging/LogManager.cs | 31 +++++++++------------- src/SMAPI/Framework/Monitor.cs | 2 +- 3 files changed, 30 insertions(+), 31 deletions(-) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs b/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs index 36f81d22..bad69a2a 100644 --- a/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs +++ b/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs @@ -8,15 +8,11 @@ namespace StardewModdingAPI.Framework.Logging internal class InterceptingTextWriter : TextWriter { /********* - ** Fields + ** Accessors *********/ /// Prefixing a message with this character indicates that the console interceptor should write the string without intercepting it. (The character itself is not written.) - private readonly char IgnoreChar; - + public const char IgnoreChar = '\u200B'; - /********* - ** Accessors - *********/ /// The underlying console output. public TextWriter Out { get; } @@ -36,30 +32,38 @@ namespace StardewModdingAPI.Framework.Logging *********/ /// Construct an instance. /// The underlying output writer. - /// Prefixing a message with this character indicates that the console interceptor should write the string without intercepting it. (The character itself is not written.) - public InterceptingTextWriter(TextWriter output, char ignoreChar) + public InterceptingTextWriter(TextWriter output) { this.Out = output; - this.IgnoreChar = ignoreChar; } /// public override void Write(char[] buffer, int index, int count) { + // track newline skip bool ignoreIfNewline = this.IgnoreNextIfNewline; this.IgnoreNextIfNewline = false; - if (buffer.Length == 0) + // get first character if valid + if (count == 0 || index < 0 || index >= buffer.Length) + { this.Out.Write(buffer, index, count); - else if (buffer[0] == this.IgnoreChar || char.IsControl(buffer[0])) // ignore control characters like backspace + return; + } + char firstChar = buffer[index]; + + // handle output + if (firstChar == InterceptingTextWriter.IgnoreChar) this.Out.Write(buffer, index + 1, count - 1); + else if (char.IsControl(firstChar) && firstChar is not ('\r' or '\n')) + this.Out.Write(buffer, index, count); else if (this.IsEmptyOrNewline(buffer)) { if (!ignoreIfNewline) this.Out.Write(buffer, index, count); } else - this.OnMessageIntercepted?.Invoke(new string(buffer, index, count).TrimEnd('\r', '\n')); + this.OnMessageIntercepted?.Invoke(new string(buffer, index, count)); } /// diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs index acd2c617..a8a8b6ee 100644 --- a/src/SMAPI/Framework/Logging/LogManager.cs +++ b/src/SMAPI/Framework/Logging/LogManager.cs @@ -22,9 +22,6 @@ namespace StardewModdingAPI.Framework.Logging /********* ** Fields *********/ - /// Whether to show trace messages in the console. - private readonly bool ShowTraceInConsole; - /// The log file to which to write messages. private readonly LogFileManager LogFile; @@ -32,7 +29,7 @@ namespace StardewModdingAPI.Framework.Logging private readonly InterceptingTextWriter ConsoleInterceptor; /// Prefixing a low-level message with this character indicates that the console interceptor should write the string without intercepting it. (The character itself is not written.) - private readonly char IgnoreChar = '\u200B'; + private const char IgnoreChar = InterceptingTextWriter.IgnoreChar; /// Get a named monitor instance. private readonly Func GetMonitorImpl; @@ -40,18 +37,18 @@ namespace StardewModdingAPI.Framework.Logging /// Regex patterns which match console non-error messages to suppress from the console and log. private readonly Regex[] SuppressConsolePatterns = { - new Regex(@"^TextBox\.Selected is now '(?:True|False)'\.$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^loadPreferences\(\); begin", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^savePreferences\(\); async=", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^DebugOutput:\s+(?:added cricket|dismount tile|Ping|playerPos)", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^Ignoring keys: ", RegexOptions.Compiled | RegexOptions.CultureInvariant) + new(@"^TextBox\.Selected is now '(?:True|False)'\.$", RegexOptions.Compiled | RegexOptions.CultureInvariant), + new(@"^loadPreferences\(\); begin", RegexOptions.Compiled | RegexOptions.CultureInvariant), + new(@"^savePreferences\(\); async=", RegexOptions.Compiled | RegexOptions.CultureInvariant), + new(@"^DebugOutput:\s+(?:added cricket|dismount tile|Ping|playerPos)", RegexOptions.Compiled | RegexOptions.CultureInvariant), + new(@"^Ignoring keys: ", RegexOptions.Compiled | RegexOptions.CultureInvariant) }; /// Regex patterns which match console messages to show a more friendly error for. private readonly ReplaceLogPattern[] ReplaceConsolePatterns = { // Steam not loaded - new ReplaceLogPattern( + new( search: new Regex(@"^System\.InvalidOperationException: Steamworks is not initialized\.[\s\S]+$", RegexOptions.Compiled | RegexOptions.CultureInvariant), replacement: #if SMAPI_FOR_WINDOWS @@ -63,7 +60,7 @@ namespace StardewModdingAPI.Framework.Logging ), // save file not found error - new ReplaceLogPattern( + new( search: new Regex(@"^System\.IO\.FileNotFoundException: [^\n]+\n[^:]+: '[^\n]+[/\\]Saves[/\\]([^'\r\n]+)[/\\]([^'\r\n]+)'[\s\S]+LoadGameMenu\.FindSaveGames[\s\S]+$", RegexOptions.Compiled | RegexOptions.CultureInvariant), replacement: "The game can't find the '$2' file for your '$1' save. See https://stardewvalleywiki.com/Saves#Troubleshooting for help.", logLevel: LogLevel.Error @@ -97,11 +94,10 @@ namespace StardewModdingAPI.Framework.Logging public LogManager(string logPath, ColorSchemeConfig colorConfig, bool writeToConsole, bool isVerbose, bool isDeveloperMode, Func getScreenIdForLog) { // init construction logic - this.ShowTraceInConsole = isDeveloperMode; - this.GetMonitorImpl = name => new Monitor(name, this.IgnoreChar, this.LogFile, colorConfig, isVerbose, getScreenIdForLog) + this.GetMonitorImpl = name => new Monitor(name, LogManager.IgnoreChar, this.LogFile, colorConfig, isVerbose, getScreenIdForLog) { WriteToConsole = writeToConsole, - ShowTraceInConsole = this.ShowTraceInConsole, + ShowTraceInConsole = isDeveloperMode, ShowFullStampInConsole = isDeveloperMode }; @@ -111,7 +107,7 @@ namespace StardewModdingAPI.Framework.Logging this.MonitorForGame = this.GetMonitor("game"); // redirect direct console output - this.ConsoleInterceptor = new InterceptingTextWriter(Console.Out, this.IgnoreChar); + this.ConsoleInterceptor = new InterceptingTextWriter(Console.Out); if (writeToConsole) this.ConsoleInterceptor.OnMessageIntercepted += message => this.HandleConsoleMessage(this.MonitorForGame, message); Console.SetOut(this.ConsoleInterceptor); @@ -153,7 +149,7 @@ namespace StardewModdingAPI.Framework.Logging .Add(new ReloadI18nCommand(reloadTranslations), this.Monitor); // start handling command line input - Thread inputThread = new Thread(() => + Thread inputThread = new(() => { while (true) { @@ -393,8 +389,7 @@ namespace StardewModdingAPI.Framework.Logging // forward to monitor gameMonitor.Log(message, level); - if (level == LogLevel.Trace && !this.ShowTraceInConsole) - this.ConsoleInterceptor.IgnoreNextIfNewline = true; + this.ConsoleInterceptor.IgnoreNextIfNewline = true; } /// Write a summary of mod warnings to the console and log. diff --git a/src/SMAPI/Framework/Monitor.cs b/src/SMAPI/Framework/Monitor.cs index 04e67d68..ab76e7c0 100644 --- a/src/SMAPI/Framework/Monitor.cs +++ b/src/SMAPI/Framework/Monitor.cs @@ -28,7 +28,7 @@ namespace StardewModdingAPI.Framework private static readonly int MaxLevelLength = (from level in Enum.GetValues(typeof(LogLevel)).Cast() select level.ToString().Length).Max(); /// A cache of messages that should only be logged once. - private readonly HashSet LogOnceCache = new HashSet(); + private readonly HashSet LogOnceCache = new(); /// Get the screen ID that should be logged to distinguish between players in split-screen mode, if any. private readonly Func GetScreenIdForLog; -- cgit From 230f1192056a5b49147bc45a8328b6132069f8c7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 16 Jan 2022 17:08:08 -0500 Subject: merge field rewriters to reduce rewrite iterations --- docs/release-notes.md | 1 + .../ModLoading/Framework/RewriteHelper.cs | 24 +--------- .../ModLoading/Rewriters/FieldReplaceRewriter.cs | 54 +++++++++++----------- src/SMAPI/Metadata/InstructionMetadata.cs | 9 ++-- 4 files changed, 35 insertions(+), 53 deletions(-) (limited to 'src/SMAPI/Framework') diff --git a/docs/release-notes.md b/docs/release-notes.md index 1a6f4078..56564953 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -8,6 +8,7 @@ * Added automatic save recovery when the custom farm type isn't available anymore. * Added the new game build number to the SMAPI console + log. * The installer now detects Xbox app game folders. + * Reduced mod loading time a bit. * Fixed extra newlines shown in the console in non-developer mode. * Fixed macOS launch issue when using some terminals (thanks to bruce2409!). * Fixed Linux/macOS terminal ignoring backspace in Stardew Valley 1.5.5+. diff --git a/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs b/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs index 60bbd2c7..d7cb2471 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs @@ -13,7 +13,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework ** Fields *********/ /// The comparer which heuristically compares type definitions. - private static readonly TypeReferenceComparer TypeDefinitionComparer = new TypeReferenceComparer(); + private static readonly TypeReferenceComparer TypeDefinitionComparer = new(); /********* @@ -28,28 +28,6 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework : null; } - /// Get whether the field is a reference to the expected type and field. - /// The IL instruction. - /// The full type name containing the expected field. - /// The name of the expected field. - public static bool IsFieldReferenceTo(Instruction instruction, string fullTypeName, string fieldName) - { - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); - return RewriteHelper.IsFieldReferenceTo(fieldRef, fullTypeName, fieldName); - } - - /// Get whether the field is a reference to the expected type and field. - /// The field reference to check. - /// The full type name containing the expected field. - /// The name of the expected field. - public static bool IsFieldReferenceTo(FieldReference fieldRef, string fullTypeName, string fieldName) - { - return - fieldRef != null - && fieldRef.DeclaringType.FullName == fullTypeName - && fieldRef.Name == fieldName; - } - /// Get the method reference from an instruction if it matches. /// The IL instruction. public static MethodReference AsMethodReference(Instruction instruction) diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs index 0b679e9d..857a2230 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Reflection; using Mono.Cecil; using Mono.Cecil.Cil; @@ -12,54 +13,55 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters /********* ** Fields *********/ - /// The type containing the field to which references should be rewritten. - private readonly Type Type; - - /// The field name to which references should be rewritten. - private readonly string FromFieldName; - - /// The new field to reference. - private readonly FieldInfo ToField; + /// The new fields to reference indexed by the old field/type names. + private readonly Dictionary> FieldMaps = new(); /********* ** Public methods *********/ /// Construct an instance. + public FieldReplaceRewriter() + : base(defaultPhrase: "field replacement") { } // will be overridden when a field is replaced + + /// Add a field to replace. /// The type whose field to rewrite. /// The field name to rewrite. /// The new type which will have the field. /// The new field name to reference. - public FieldReplaceRewriter(Type fromType, string fromFieldName, Type toType, string toFieldName) - : base(defaultPhrase: $"{fromType.FullName}.{fromFieldName} field") + public FieldReplaceRewriter AddField(Type fromType, string fromFieldName, Type toType, string toFieldName) { - this.Type = fromType; - this.FromFieldName = fromFieldName; - this.ToField = toType.GetField(toFieldName); - if (this.ToField == null) + // get full type name + string fromTypeName = fromType?.FullName; + if (fromTypeName == null) + throw new InvalidOperationException($"Can't replace field for invalid type reference {toType}."); + + // get target field + FieldInfo toField = toType.GetField(toFieldName); + if (toField == null) throw new InvalidOperationException($"The {toType.FullName} class doesn't have a {toFieldName} field."); - } - /// Construct an instance. - /// The type whose field to rewrite. - /// The field name to rewrite. - /// The new field name to reference. - public FieldReplaceRewriter(Type type, string fromFieldName, string toFieldName) - : this(type, fromFieldName, type, toFieldName) - { + // add mapping + if (!this.FieldMaps.TryGetValue(fromTypeName, out var fieldMap)) + this.FieldMaps[fromTypeName] = fieldMap = new(); + fieldMap[fromFieldName] = toField; + + return this; } /// public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { - // get field reference FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); - if (!RewriteHelper.IsFieldReferenceTo(fieldRef, this.Type.FullName, this.FromFieldName)) + string declaringType = fieldRef?.DeclaringType?.FullName; + + // get mapped field + if (declaringType == null || !this.FieldMaps.TryGetValue(declaringType, out var fieldMap) || !fieldMap.TryGetValue(fieldRef.Name, out FieldInfo toField)) return false; // replace with new field - instruction.Operand = module.ImportReference(this.ToField); - + this.Phrases.Add($"{fieldRef.DeclaringType.Name}.{fieldRef.Name} field"); + instruction.Operand = module.ImportReference(toField); return this.MarkRewritten(); } } diff --git a/src/SMAPI/Metadata/InstructionMetadata.cs b/src/SMAPI/Metadata/InstructionMetadata.cs index 232e54ce..367372b2 100644 --- a/src/SMAPI/Metadata/InstructionMetadata.cs +++ b/src/SMAPI/Metadata/InstructionMetadata.cs @@ -37,9 +37,10 @@ namespace StardewModdingAPI.Metadata if (rewriteMods) { // rewrite for Stardew Valley 1.5 - yield return new FieldReplaceRewriter(typeof(DecoratableLocation), "furniture", typeof(GameLocation), nameof(GameLocation.furniture)); - yield return new FieldReplaceRewriter(typeof(Farm), "resourceClumps", typeof(GameLocation), nameof(GameLocation.resourceClumps)); - yield return new FieldReplaceRewriter(typeof(MineShaft), "resourceClumps", typeof(GameLocation), nameof(GameLocation.resourceClumps)); + yield return new FieldReplaceRewriter() + .AddField(typeof(DecoratableLocation), "furniture", typeof(GameLocation), nameof(GameLocation.furniture)) + .AddField(typeof(Farm), "resourceClumps", typeof(GameLocation), nameof(GameLocation.resourceClumps)) + .AddField(typeof(MineShaft), "resourceClumps", typeof(GameLocation), nameof(GameLocation.resourceClumps)); // heuristic rewrites yield return new HeuristicFieldRewriter(this.ValidateReferencesToAssemblies); @@ -87,7 +88,7 @@ namespace StardewModdingAPI.Metadata typeof(System.IO.DirectoryInfo).FullName, typeof(System.IO.DriveInfo).FullName, typeof(System.IO.FileSystemWatcher).FullName - }, + }, InstructionHandleResult.DetectedFilesystemAccess ); -- cgit