From 57bc71c7eb2e9c0145cae454424d53ca544f06e1 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 15 Sep 2020 17:34:14 -0400 Subject: make IContentPack file paths case-insensitive --- src/SMAPI/Framework/ContentPack.cs | 66 ++++++++++++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 14 deletions(-) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Framework/ContentPack.cs b/src/SMAPI/Framework/ContentPack.cs index 65abba5b..161fdbe4 100644 --- a/src/SMAPI/Framework/ContentPack.cs +++ b/src/SMAPI/Framework/ContentPack.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using StardewModdingAPI.Toolkit.Serialization; using StardewModdingAPI.Toolkit.Utilities; @@ -17,6 +18,9 @@ namespace StardewModdingAPI.Framework /// Encapsulates SMAPI's JSON file parsing. private readonly JsonHelper JsonHelper; + /// A cache of case-insensitive => exact relative paths within the content pack, for case-insensitive file lookups on Linux/Mac. + private readonly IDictionary RelativePaths = new Dictionary(StringComparer.OrdinalIgnoreCase); + /********* ** Accessors @@ -47,23 +51,29 @@ namespace StardewModdingAPI.Framework this.Content = content; this.Translation = translation; this.JsonHelper = jsonHelper; + + foreach (string path in Directory.EnumerateFiles(this.DirectoryPath, "*", SearchOption.AllDirectories)) + { + string relativePath = path.Substring(this.DirectoryPath.Length + 1); + this.RelativePaths[relativePath] = relativePath; + } } /// public bool HasFile(string path) { - this.AssertRelativePath(path, nameof(this.HasFile)); + path = PathUtilities.NormalizePath(path); - return File.Exists(Path.Combine(this.DirectoryPath, path)); + return this.GetFile(path).Exists; } /// public TModel ReadJsonFile(string path) where TModel : class { - this.AssertRelativePath(path, nameof(this.ReadJsonFile)); + path = PathUtilities.NormalizePath(path); - path = Path.Combine(this.DirectoryPath, PathUtilities.NormalizePath(path)); - return this.JsonHelper.ReadJsonFileIfExists(path, out TModel model) + FileInfo file = this.GetFile(path); + return file.Exists && this.JsonHelper.ReadJsonFileIfExists(file.FullName, out TModel model) ? model : null; } @@ -71,21 +81,30 @@ namespace StardewModdingAPI.Framework /// public void WriteJsonFile(string path, TModel data) where TModel : class { - this.AssertRelativePath(path, nameof(this.WriteJsonFile)); + path = PathUtilities.NormalizePath(path); + + FileInfo file = this.GetFile(path, out path); + this.JsonHelper.WriteJsonFile(file.FullName, data); - path = Path.Combine(this.DirectoryPath, PathUtilities.NormalizePath(path)); - this.JsonHelper.WriteJsonFile(path, data); + if (!this.RelativePaths.ContainsKey(path)) + this.RelativePaths[path] = path; } /// public T LoadAsset(string key) { + key = PathUtilities.NormalizePath(key); + + key = this.GetCaseInsensitiveRelativePath(key); return this.Content.Load(key, ContentSource.ModFolder); } /// public string GetActualAssetKey(string key) { + key = PathUtilities.NormalizePath(key); + + key = this.GetCaseInsensitiveRelativePath(key); return this.Content.GetActualAssetKey(key, ContentSource.ModFolder); } @@ -93,13 +112,32 @@ namespace StardewModdingAPI.Framework /********* ** Private methods *********/ - /// Assert that a relative path was passed it to a content pack method. - /// The path to check. - /// The name of the method which was invoked. - private void AssertRelativePath(string path, string methodName) + /// Get the real relative path from a case-insensitive path. + /// The normalized relative path. + private string GetCaseInsensitiveRelativePath(string relativePath) + { + if (!PathUtilities.IsSafeRelativePath(relativePath)) + throw new InvalidOperationException($"You must call {nameof(IContentPack)} methods with a relative path."); + + return this.RelativePaths.TryGetValue(relativePath, out string caseInsensitivePath) + ? caseInsensitivePath + : relativePath; + } + + /// Get the underlying file info. + /// The normalized file path relative to the content pack directory. + private FileInfo GetFile(string relativePath) + { + return this.GetFile(relativePath, out _); + } + + /// Get the underlying file info. + /// The normalized file path relative to the content pack directory. + /// The relative path after case-insensitive matching. + private FileInfo GetFile(string relativePath, out string actualRelativePath) { - if (!PathUtilities.IsSafeRelativePath(path)) - throw new InvalidOperationException($"You must call {nameof(IContentPack)}.{methodName} with a relative path."); + actualRelativePath = this.GetCaseInsensitiveRelativePath(relativePath); + return new FileInfo(Path.Combine(this.DirectoryPath, actualRelativePath)); } } } -- cgit From 436eb95a8617cf6df061a61b66d6cd7c1cd6a494 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 16 Sep 2020 17:26:20 -0400 Subject: fix typo in error messages --- src/SMAPI/Framework/SCore.cs | 4 ++-- src/SMAPI/Framework/SGame.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/SMAPI/Framework') diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 8be62ccf..c9477c52 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -975,7 +975,7 @@ namespace StardewModdingAPI.Framework } catch (Exception ex) { - this.LogManager.MonitorForGame.Log($"An error occured in the base update loop: {ex.GetLogSummary()}", LogLevel.Error); + this.LogManager.MonitorForGame.Log($"An error occurred in the base update loop: {ex.GetLogSummary()}", LogLevel.Error); } events.UnvalidatedUpdateTicked.RaiseEmpty(); @@ -992,7 +992,7 @@ namespace StardewModdingAPI.Framework catch (Exception ex) { // log error - this.Monitor.Log($"An error occured in the overridden update loop: {ex.GetLogSummary()}", LogLevel.Error); + this.Monitor.Log($"An error occurred in the overridden update loop: {ex.GetLogSummary()}", LogLevel.Error); // exit if irrecoverable if (!this.UpdateCrashTimer.Decrement()) diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index 6680a6c9..1c769c3f 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -162,7 +162,7 @@ namespace StardewModdingAPI.Framework catch (Exception ex) { // log error - this.Monitor.Log($"An error occured in the overridden draw loop: {ex.GetLogSummary()}", LogLevel.Error); + this.Monitor.Log($"An error occurred in the overridden draw loop: {ex.GetLogSummary()}", LogLevel.Error); // exit if irrecoverable if (!this.DrawCrashTimer.Decrement()) -- cgit From f06b4dd6102e64f929a218192002c1d2ba05ebed Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 16 Sep 2020 17:35:25 -0400 Subject: fix conflict with PyTK's map display device --- docs/release-notes.md | 1 + src/SMAPI/Framework/SCore.cs | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) (limited to 'src/SMAPI/Framework') diff --git a/docs/release-notes.md b/docs/release-notes.md index bbf6e437..05f17a2f 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -10,6 +10,7 @@ ## Upcoming release * For players: * Fixed errors on Linux/Mac due to mods with incorrect filename case. + * Fixed map rendering crash due to conflict between SMAPI and PyTK. * For modders: * All content pack file paths accessed through `IContentPack` are now case-insensitive. diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index c9477c52..e64c2801 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -43,6 +43,7 @@ using StardewModdingAPI.Toolkit.Serialization; using StardewModdingAPI.Toolkit.Utilities; using StardewModdingAPI.Utilities; using StardewValley; +using xTile.Display; using SObject = StardewValley.Object; namespace StardewModdingAPI.Framework @@ -472,8 +473,13 @@ namespace StardewModdingAPI.Framework SCore.PerformanceMonitor.PrintQueuedAlerts(); // reapply overrides - if (this.JustReturnedToTitle && !(Game1.mapDisplayDevice is SDisplayDevice)) - Game1.mapDisplayDevice = new SDisplayDevice(Game1.content, Game1.game1.GraphicsDevice); + if (this.JustReturnedToTitle) + { + if (!(Game1.mapDisplayDevice is SDisplayDevice)) + Game1.mapDisplayDevice = this.GetMapDisplayDevice(); + + this.JustReturnedToTitle = false; + } /********* ** First-tick initialization @@ -1738,6 +1744,13 @@ namespace StardewModdingAPI.Framework return translations; } + /// Get the map display device which applies SMAPI features like tile rotation to loaded maps. + /// This is separate to let mods like PyTK wrap it with their own functionality. + private IDisplayDevice GetMapDisplayDevice() + { + return new SDisplayDevice(Game1.content, Game1.game1.GraphicsDevice); + } + /// Get the absolute path to the next available log file. private string GetLogPath() { -- cgit From 5a10cf3506f56d62f6bd2dd4cc4c37587f1a5c70 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 16 Sep 2020 18:03:25 -0400 Subject: fix error when a mod rewrite adds instructions that break a short jump --- docs/release-notes.md | 1 + .../ModLoading/Framework/RecursiveRewriter.cs | 20 +++++++++++++++++- .../ModLoading/Framework/RewriteHelper.cs | 24 ++++++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) (limited to 'src/SMAPI/Framework') diff --git a/docs/release-notes.md b/docs/release-notes.md index 05f17a2f..6b8cfc3d 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -11,6 +11,7 @@ * For players: * Fixed errors on Linux/Mac due to mods with incorrect filename case. * Fixed map rendering crash due to conflict between SMAPI and PyTK. + * Fixed error in heuristically-rewritten mods in rare cases (thanks to ZaneYork!). * For modders: * All content pack file paths accessed through `IContentPack` are now case-insensitive. diff --git a/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs b/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs index ea29550a..10f68f0d 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs @@ -111,21 +111,39 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework foreach (VariableDefinition variable in method.Body.Variables) changed |= this.RewriteTypeReference(variable.VariableType, newType => variable.VariableType = newType); - // check CIL instructions + // rewrite CIL instructions ILProcessor cil = method.Body.GetILProcessor(); Collection instructions = cil.Body.Instructions; + bool addedInstructions = false; for (int i = 0; i < instructions.Count; i++) { var instruction = instructions[i]; if (instruction.OpCode.Code == Code.Nop) continue; + int oldCount = cil.Body.Instructions.Count; changed |= this.RewriteInstruction(instruction, cil, newInstruction => { changed = true; cil.Replace(instruction, newInstruction); instruction = newInstruction; }); + + if (cil.Body.Instructions.Count > oldCount) + addedInstructions = true; + } + + // special case: added instructions may cause an instruction to be out of range + // of a short jump that references it + if (addedInstructions) + { + foreach (var instruction in instructions) + { + var longJumpCode = RewriteHelper.GetEquivalentLongJumpCode(instruction.OpCode); + if (longJumpCode != null) + instruction.OpCode = longJumpCode.Value; + } + changed = true; } } } diff --git a/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs b/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs index 207b6445..1f7834ce 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs @@ -77,6 +77,30 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework }; } + /// Get the long equivalent for a short-jump op code. + /// The short-jump op code. + /// Returns the instruction, or null if it isn't a short jump. + public static OpCode? GetEquivalentLongJumpCode(OpCode shortJumpCode) + { + return shortJumpCode.Code switch + { + Code.Beq_S => OpCodes.Beq, + Code.Bge_S => OpCodes.Bge, + Code.Bge_Un_S => OpCodes.Bge_Un, + Code.Bgt_S => OpCodes.Bgt, + Code.Bgt_Un_S => OpCodes.Bgt_Un, + Code.Ble_S => OpCodes.Ble, + Code.Ble_Un_S => OpCodes.Ble_Un, + Code.Blt_S => OpCodes.Blt, + Code.Blt_Un_S => OpCodes.Blt_Un, + Code.Bne_Un_S => OpCodes.Bne_Un, + Code.Br_S => OpCodes.Br, + Code.Brfalse_S => OpCodes.Brfalse, + Code.Brtrue_S => OpCodes.Brtrue, + _ => null + }; + } + /// Get whether a type matches a type reference. /// The defined type. /// The type reference. -- cgit From 59974c18a6fb6f8f2e7e3a8c5df109e060fc5ae6 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 16 Sep 2020 21:32:45 -0400 Subject: prepare for release --- build/common.targets | 2 +- docs/release-notes.md | 12 +++++++----- src/SMAPI.Mods.ConsoleCommands/manifest.json | 4 ++-- src/SMAPI.Mods.SaveBackup/manifest.json | 4 ++-- src/SMAPI/Constants.cs | 2 +- src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs | 2 +- 6 files changed, 14 insertions(+), 12 deletions(-) (limited to 'src/SMAPI/Framework') diff --git a/build/common.targets b/build/common.targets index 53476234..7c2c5cad 100644 --- a/build/common.targets +++ b/build/common.targets @@ -4,7 +4,7 @@ - 3.7.2 + 3.7.3 SMAPI latest diff --git a/docs/release-notes.md b/docs/release-notes.md index 6b8cfc3d..86817546 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -7,14 +7,16 @@ * 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.7.3 +Released 16 September 2020 for Stardew Valley 1.4.1 or later. + * For players: - * Fixed errors on Linux/Mac due to mods with incorrect filename case. + * Fixed errors on Linux/Mac due to content packs with incorrect filename case. * Fixed map rendering crash due to conflict between SMAPI and PyTK. - * Fixed error in heuristically-rewritten mods in rare cases (thanks to ZaneYork!). + * Fixed error in heuristically-rewritten mods in rare cases (thanks to collaboration with ZaneYork!). * For modders: - * All content pack file paths accessed through `IContentPack` are now case-insensitive. + * File paths accessed through `IContentPack` are now case-insensitive (even on Linux). * For the web UI: * You can now renew the expiry for an uploaded JSON/log file if you need it longer. @@ -176,7 +178,7 @@ Released 22 March 2020 for Stardew Valley 1.4.1 or later. See [release highlight * Updated translations. Thanks to Annosz (added Hungarian)! * For modders: - * Added support for flipped and rotated map tiles (in collaboration with Platonymous). + * 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. * Mods are no longer prevented from suppressing key presses in the chatbox. diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json index f15f3c57..e4506431 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.7.2", + "Version": "3.7.3", "Description": "Adds SMAPI console commands that let you manipulate the game.", "UniqueID": "SMAPI.ConsoleCommands", "EntryDll": "ConsoleCommands.dll", - "MinimumApiVersion": "3.7.2" + "MinimumApiVersion": "3.7.3" } diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json index c458feed..34f553ba 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.7.2", + "Version": "3.7.3", "Description": "Automatically backs up all your saves once per day into its folder.", "UniqueID": "SMAPI.SaveBackup", "EntryDll": "SaveBackup.dll", - "MinimumApiVersion": "3.7.2" + "MinimumApiVersion": "3.7.3" } diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 6017a1d4..48428420 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -51,7 +51,7 @@ namespace StardewModdingAPI ** Public ****/ /// SMAPI's current semantic version. - public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("3.7.2"); + public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("3.7.3"); /// The minimum supported version of Stardew Valley. public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.4.1"); diff --git a/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs b/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs index 1f7834ce..60bbd2c7 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs @@ -79,7 +79,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework /// Get the long equivalent for a short-jump op code. /// The short-jump op code. - /// Returns the instruction, or null if it isn't a short jump. + /// Returns the new op code, or null if it isn't a short jump. public static OpCode? GetEquivalentLongJumpCode(OpCode shortJumpCode) { return shortJumpCode.Code switch -- cgit