diff options
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r-- | src/SMAPI/Framework/ContentPack.cs | 66 | ||||
-rw-r--r-- | src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs | 20 | ||||
-rw-r--r-- | src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs | 24 | ||||
-rw-r--r-- | src/SMAPI/Framework/SCore.cs | 21 | ||||
-rw-r--r-- | src/SMAPI/Framework/SGame.cs | 2 |
5 files changed, 113 insertions, 20 deletions
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 /// <summary>Encapsulates SMAPI's JSON file parsing.</summary> private readonly JsonHelper JsonHelper; + /// <summary>A cache of case-insensitive => exact relative paths within the content pack, for case-insensitive file lookups on Linux/Mac.</summary> + private readonly IDictionary<string, string> RelativePaths = new Dictionary<string, string>(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; + } } /// <inheritdoc /> 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; } /// <inheritdoc /> public TModel ReadJsonFile<TModel>(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 /// <inheritdoc /> public void WriteJsonFile<TModel>(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; } /// <inheritdoc /> public T LoadAsset<T>(string key) { + key = PathUtilities.NormalizePath(key); + + key = this.GetCaseInsensitiveRelativePath(key); return this.Content.Load<T>(key, ContentSource.ModFolder); } /// <inheritdoc /> 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 *********/ - /// <summary>Assert that a relative path was passed it to a content pack method.</summary> - /// <param name="path">The path to check.</param> - /// <param name="methodName">The name of the method which was invoked.</param> - private void AssertRelativePath(string path, string methodName) + /// <summary>Get the real relative path from a case-insensitive path.</summary> + /// <param name="relativePath">The normalized relative path.</param> + 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; + } + + /// <summary>Get the underlying file info.</summary> + /// <param name="relativePath">The normalized file path relative to the content pack directory.</param> + private FileInfo GetFile(string relativePath) + { + return this.GetFile(relativePath, out _); + } + + /// <summary>Get the underlying file info.</summary> + /// <param name="relativePath">The normalized file path relative to the content pack directory.</param> + /// <param name="actualRelativePath">The relative path after case-insensitive matching.</param> + 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/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<Instruction> 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..60bbd2c7 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 }; } + /// <summary>Get the long equivalent for a short-jump op code.</summary> + /// <param name="shortJumpCode">The short-jump op code.</param> + /// <returns>Returns the new op code, or <c>null</c> if it isn't a short jump.</returns> + 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 + }; + } + /// <summary>Get whether a type matches a type reference.</summary> /// <param name="type">The defined type.</param> /// <param name="reference">The type reference.</param> diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 8be62ccf..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 @@ -975,7 +981,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 +998,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()) @@ -1738,6 +1744,13 @@ namespace StardewModdingAPI.Framework return translations; } + /// <summary>Get the map display device which applies SMAPI features like tile rotation to loaded maps.</summary> + /// <remarks>This is separate to let mods like PyTK wrap it with their own functionality.</remarks> + private IDisplayDevice GetMapDisplayDevice() + { + return new SDisplayDevice(Game1.content, Game1.game1.GraphicsDevice); + } + /// <summary>Get the absolute path to the next available log file.</summary> private string GetLogPath() { 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()) |