From 4189e2f3faa9197e83aebd32fc0af93d46ee1a61 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 13 Sep 2020 11:59:17 -0400 Subject: add support for renewing uploaded JSON/log files --- docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 2135d4f2..964a0c50 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 the web UI: + * You can now renew the expiry for an uploaded JSON/log file if you need it longer. + ## 3.7.2 Released 08 September 2020 for Stardew Valley 1.4.1 or later. -- cgit 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 --- docs/release-notes.md | 6 ++++ src/SMAPI/Framework/ContentPack.cs | 66 ++++++++++++++++++++++++++++++-------- src/SMAPI/IContentPack.cs | 10 +++--- 3 files changed, 63 insertions(+), 19 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 964a0c50..bbf6e437 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -8,6 +8,12 @@ --> ## Upcoming release +* For players: + * Fixed errors on Linux/Mac due to mods with incorrect filename case. + +* For modders: + * All content pack file paths accessed through `IContentPack` are now case-insensitive. + * For the web UI: * You can now renew the expiry for an uploaded JSON/log file if you need it longer. 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)); } } } diff --git a/src/SMAPI/IContentPack.cs b/src/SMAPI/IContentPack.cs index c0479eae..9cc64dcd 100644 --- a/src/SMAPI/IContentPack.cs +++ b/src/SMAPI/IContentPack.cs @@ -25,32 +25,32 @@ namespace StardewModdingAPI ** Public methods *********/ /// Get whether a given file exists in the content pack. - /// The file path to check. + /// The relative file path within the content pack (case-insensitive). bool HasFile(string path); /// Read a JSON file from the content pack folder. /// The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types. - /// The file path relative to the content pack directory. + /// The relative file path within the content pack (case-insensitive). /// Returns the deserialized model, or null if the file doesn't exist or is empty. /// The is not relative or contains directory climbing (../). TModel ReadJsonFile(string path) where TModel : class; /// Save data to a JSON file in the content pack's folder. /// The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types. - /// The file path relative to the mod folder. + /// The relative file path within the content pack (case-insensitive). /// The arbitrary data to save. /// The is not relative or contains directory climbing (../). void WriteJsonFile(string path, TModel data) where TModel : class; /// Load content from the content pack folder (if not already cached), and return it. When loading a .png file, this must be called outside the game's draw loop. /// The expected data type. The main supported types are , , and dictionaries; other types may be supported by the game's content pipeline. - /// The local path to a content file relative to the content pack folder. + /// The relative file path within the content pack (case-insensitive). /// The is empty or contains invalid characters. /// The content asset couldn't be loaded (e.g. because it doesn't exist). T LoadAsset(string key); /// Get the underlying key in the game's content cache for an asset. This can be used to load custom map tilesheets, but should be avoided when you can use the content API instead. This does not validate whether the asset exists. - /// The the local path to a content file relative to the content pack folder. + /// The relative file path within the content pack (case-insensitive). /// The is empty or contains invalid characters. string GetActualAssetKey(string key); } -- 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 'docs') 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 'docs') 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 'docs') 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