summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r--src/SMAPI/Framework/ContentPack.cs66
-rw-r--r--src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs20
-rw-r--r--src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs24
-rw-r--r--src/SMAPI/Framework/SCore.cs21
-rw-r--r--src/SMAPI/Framework/SGame.cs2
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())