summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r--src/SMAPI/Framework/ContentCoordinator.cs10
-rw-r--r--src/SMAPI/Framework/ContentManagers/ModContentManager.cs31
-rw-r--r--src/SMAPI/Framework/ContentPack.cs4
-rw-r--r--src/SMAPI/Framework/ModHelpers/ContentHelper.cs2
-rw-r--r--src/SMAPI/Framework/ModHelpers/ModHelper.cs4
-rw-r--r--src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs10
-rw-r--r--src/SMAPI/Framework/SGame.cs26
-rw-r--r--src/SMAPI/Framework/SGameConstructorHack.cs37
8 files changed, 100 insertions, 24 deletions
diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs
index d9b2109a..9eb7b5f9 100644
--- a/src/SMAPI/Framework/ContentCoordinator.cs
+++ b/src/SMAPI/Framework/ContentCoordinator.cs
@@ -9,6 +9,7 @@ using StardewModdingAPI.Framework.Content;
using StardewModdingAPI.Framework.ContentManagers;
using StardewModdingAPI.Framework.Reflection;
using StardewModdingAPI.Metadata;
+using StardewModdingAPI.Toolkit.Serialisation;
using StardewModdingAPI.Toolkit.Utilities;
using StardewValley;
@@ -32,6 +33,9 @@ namespace StardewModdingAPI.Framework
/// <summary>Simplifies access to private code.</summary>
private readonly Reflector Reflection;
+ /// <summary>Encapsulates SMAPI's JSON file parsing.</summary>
+ private readonly JsonHelper JsonHelper;
+
/// <summary>The loaded content managers (including the <see cref="MainContentManager"/>).</summary>
private readonly IList<IContentManager> ContentManagers = new List<IContentManager>();
@@ -67,10 +71,12 @@ namespace StardewModdingAPI.Framework
/// <param name="currentCulture">The current culture for which to localise content.</param>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="reflection">Simplifies access to private code.</param>
- public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection)
+ /// <param name="jsonHelper">Encapsulates SMAPI's JSON file parsing.</param>
+ public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper)
{
this.Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor));
this.Reflection = reflection;
+ this.JsonHelper = jsonHelper;
this.FullRootDirectory = Path.Combine(Constants.ExecutionPath, rootDirectory);
this.ContentManagers.Add(
this.MainContentManager = new GameContentManager("Game1.content", serviceProvider, rootDirectory, currentCulture, this, monitor, reflection, this.OnDisposing)
@@ -92,7 +98,7 @@ namespace StardewModdingAPI.Framework
/// <param name="rootDirectory">The root directory to search for content (or <c>null</c> for the default).</param>
public ModContentManager CreateModContentManager(string name, string rootDirectory)
{
- ModContentManager manager = new ModContentManager(name, this.MainContentManager.ServiceProvider, rootDirectory, this.MainContentManager.CurrentCulture, this, this.Monitor, this.Reflection, this.OnDisposing);
+ ModContentManager manager = new ModContentManager(name, this.MainContentManager.ServiceProvider, rootDirectory, this.MainContentManager.CurrentCulture, this, this.Monitor, this.Reflection, this.JsonHelper, this.OnDisposing);
this.ContentManagers.Add(manager);
return manager;
}
diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
index 80bf37e9..24ce69ea 100644
--- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
@@ -5,6 +5,7 @@ using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Framework.Exceptions;
using StardewModdingAPI.Framework.Reflection;
+using StardewModdingAPI.Toolkit.Serialisation;
using StardewValley;
namespace StardewModdingAPI.Framework.ContentManagers
@@ -13,6 +14,13 @@ namespace StardewModdingAPI.Framework.ContentManagers
internal class ModContentManager : BaseContentManager
{
/*********
+ ** Properties
+ *********/
+ /// <summary>Encapsulates SMAPI's JSON file parsing.</summary>
+ private readonly JsonHelper JsonHelper;
+
+
+ /*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
@@ -23,9 +31,13 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <param name="coordinator">The central coordinator which manages content managers.</param>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="reflection">Simplifies access to private code.</param>
+ /// <param name="jsonHelper">Encapsulates SMAPI's JSON file parsing.</param>
/// <param name="onDisposing">A callback to invoke when the content manager is being disposed.</param>
- public ModContentManager(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action<BaseContentManager> onDisposing)
- : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isModFolder: true) { }
+ public ModContentManager(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action<BaseContentManager> onDisposing)
+ : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isModFolder: true)
+ {
+ this.JsonHelper = jsonHelper;
+ }
/// <summary>Load an asset that has been processed by the content pipeline.</summary>
/// <typeparam name="T">The type of asset to load.</typeparam>
@@ -95,9 +107,14 @@ namespace StardewModdingAPI.Framework.ContentManagers
case ".xnb":
return base.Load<T>(relativePath, language);
- // unpacked map
- case ".tbin":
- throw GetContentError($"can't read unpacked map file directly from the underlying content manager. It must be loaded through the mod's {typeof(IModHelper)}.{nameof(IModHelper.Content)} helper.");
+ // unpacked data
+ case ".json":
+ {
+ if (!this.JsonHelper.ReadJsonFileIfExists(file.FullName, out T data))
+ throw GetContentError("the JSON file is invalid."); // should never happen since we check for file existence above
+
+ return data;
+ }
// unpacked image
case ".png":
@@ -114,6 +131,10 @@ namespace StardewModdingAPI.Framework.ContentManagers
return (T)(object)texture;
}
+ // unpacked map
+ case ".tbin":
+ throw GetContentError($"can't read unpacked map file directly from the underlying content manager. It must be loaded through the mod's {typeof(IModHelper)}.{nameof(IModHelper.Content)} helper.");
+
default:
throw GetContentError($"unknown file extension '{file.Extension}'; must be one of '.png', '.tbin', or '.xnb'.");
}
diff --git a/src/SMAPI/Framework/ContentPack.cs b/src/SMAPI/Framework/ContentPack.cs
index 4a4adb90..62d8b80d 100644
--- a/src/SMAPI/Framework/ContentPack.cs
+++ b/src/SMAPI/Framework/ContentPack.cs
@@ -54,7 +54,9 @@ namespace StardewModdingAPI.Framework
public TModel ReadJsonFile<TModel>(string path) where TModel : class
{
path = Path.Combine(this.DirectoryPath, PathUtilities.NormalisePathSeparators(path));
- return this.JsonHelper.ReadJsonFile<TModel>(path);
+ return this.JsonHelper.ReadJsonFileIfExists(path, out TModel model)
+ ? model
+ : null;
}
/// <summary>Load content from the content pack folder (if not already cached), and return it. When loading a <c>.png</c> file, this must be called outside the game's draw loop.</summary>
diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs
index 671dc21e..a8b24a13 100644
--- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs
+++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs
@@ -259,7 +259,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
// get seasonal name (if applicable)
string seasonalImageSource = null;
- if (Game1.currentSeason != null)
+ if (Context.IsSaveLoaded && Game1.currentSeason != null)
{
string filename = Path.GetFileName(imageSource) ?? throw new InvalidOperationException($"The '{imageSource}' tilesheet couldn't be loaded: filename is unexpectedly null.");
bool hasSeasonalPrefix =
diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs
index d9498e83..0ba258b4 100644
--- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs
+++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs
@@ -138,7 +138,9 @@ namespace StardewModdingAPI.Framework.ModHelpers
where TModel : class
{
path = Path.Combine(this.DirectoryPath, PathUtilities.NormalisePathSeparators(path));
- return this.JsonHelper.ReadJsonFile<TModel>(path);
+ return this.JsonHelper.ReadJsonFileIfExists(path, out TModel data)
+ ? data
+ : null;
}
/// <summary>Save to a JSON file.</summary>
diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs
index cf5a3175..3a26660f 100644
--- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs
+++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs
@@ -70,7 +70,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
// method reference
MethodReference methodReference = RewriteHelper.AsMethodReference(instruction);
- if (methodReference != null && this.ShouldValidate(methodReference.DeclaringType))
+ if (methodReference != null && !this.IsUnsupported(methodReference) && this.ShouldValidate(methodReference.DeclaringType))
{
// get potential targets
MethodDefinition[] candidateMethods = methodReference.DeclaringType.Resolve()?.Methods.Where(found => found.Name == methodReference.Name).ToArray();
@@ -106,6 +106,14 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
return type != null && this.ValidateReferencesToAssemblies.Contains(type.Scope.Name);
}
+ /// <summary>Get whether a method reference is a special case that's not currently supported (e.g. array methods).</summary>
+ /// <param name="method">The method reference.</param>
+ private bool IsUnsupported(MethodReference method)
+ {
+ return
+ method.DeclaringType.Name.Contains("["); // array methods
+ }
+
/// <summary>Get a shorter type name for display.</summary>
/// <param name="type">The type reference.</param>
private string GetFriendlyTypeName(TypeReference type)
diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs
index 05fedc3d..83e8c9a7 100644
--- a/src/SMAPI/Framework/SGame.cs
+++ b/src/SMAPI/Framework/SGame.cs
@@ -17,6 +17,7 @@ using StardewModdingAPI.Framework.Input;
using StardewModdingAPI.Framework.Reflection;
using StardewModdingAPI.Framework.StateTracking;
using StardewModdingAPI.Framework.Utilities;
+using StardewModdingAPI.Toolkit.Serialisation;
using StardewValley;
using StardewValley.BellsAndWhistles;
using StardewValley.Buildings;
@@ -37,16 +38,6 @@ namespace StardewModdingAPI.Framework
** Properties
*********/
/****
- ** Constructor hack
- ****/
- /// <summary>A static instance of <see cref="Monitor"/> to use while <see cref="Game1"/> is initialising, which happens before the <see cref="SGame"/> constructor runs.</summary>
- internal static IMonitor MonitorDuringInitialisation;
-
- /// <summary>A static instance of <see cref="Reflection"/> to use while <see cref="Game1"/> is initialising, which happens before the <see cref="SGame"/> constructor runs.</summary>
- internal static Reflector ReflectorDuringInitialisation;
-
-
- /****
** SMAPI state
****/
/// <summary>Encapsulates monitoring and logging.</summary>
@@ -83,6 +74,9 @@ namespace StardewModdingAPI.Framework
/// <summary>Simplifies access to private game code.</summary>
private readonly Reflector Reflection;
+ /// <summary>Encapsulates SMAPI's JSON file parsing.</summary>
+ private readonly JsonHelper JsonHelper;
+
/****
** Game state
****/
@@ -105,6 +99,9 @@ namespace StardewModdingAPI.Framework
/*********
** Accessors
*********/
+ /// <summary>Static state to use while <see cref="Game1"/> is initialising, which happens before the <see cref="SGame"/> constructor runs.</summary>
+ internal static SGameConstructorHack ConstructorHack { get; set; }
+
/// <summary>SMAPI's content manager.</summary>
public ContentCoordinator ContentCore { get; private set; }
@@ -132,10 +129,13 @@ namespace StardewModdingAPI.Framework
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="reflection">Simplifies access to private game code.</param>
/// <param name="eventManager">Manages SMAPI events for mods.</param>
+ /// <param name="jsonHelper">Encapsulates SMAPI's JSON file parsing.</param>
/// <param name="onGameInitialised">A callback to invoke after the game finishes initialising.</param>
/// <param name="onGameExiting">A callback to invoke when the game exits.</param>
- internal SGame(IMonitor monitor, Reflector reflection, EventManager eventManager, Action onGameInitialised, Action onGameExiting)
+ internal SGame(IMonitor monitor, Reflector reflection, EventManager eventManager, JsonHelper jsonHelper, Action onGameInitialised, Action onGameExiting)
{
+ SGame.ConstructorHack = null;
+
// check expectations
if (this.ContentCore == null)
throw new InvalidOperationException($"The game didn't initialise its first content manager before SMAPI's {nameof(SGame)} constructor. This indicates an incompatible lifecycle change.");
@@ -147,6 +147,7 @@ namespace StardewModdingAPI.Framework
this.Monitor = monitor;
this.Events = eventManager;
this.Reflection = reflection;
+ this.JsonHelper = jsonHelper;
this.OnGameInitialised = onGameInitialised;
this.OnGameExiting = onGameExiting;
Game1.input = new SInputState();
@@ -191,8 +192,7 @@ namespace StardewModdingAPI.Framework
// NOTE: this method is called before the SGame constructor runs. Don't depend on anything being initialised at this point.
if (this.ContentCore == null)
{
- this.ContentCore = new ContentCoordinator(serviceProvider, rootDirectory, Thread.CurrentThread.CurrentUICulture, SGame.MonitorDuringInitialisation, SGame.ReflectorDuringInitialisation);
- SGame.MonitorDuringInitialisation = null;
+ this.ContentCore = new ContentCoordinator(serviceProvider, rootDirectory, Thread.CurrentThread.CurrentUICulture, SGame.ConstructorHack.Monitor, SGame.ConstructorHack.Reflection, SGame.ConstructorHack.JsonHelper);
this.NextContentManagerIsMain = true;
return this.ContentCore.CreateGameContentManager("Game1._temporaryContent");
}
diff --git a/src/SMAPI/Framework/SGameConstructorHack.cs b/src/SMAPI/Framework/SGameConstructorHack.cs
new file mode 100644
index 00000000..494bab99
--- /dev/null
+++ b/src/SMAPI/Framework/SGameConstructorHack.cs
@@ -0,0 +1,37 @@
+using StardewModdingAPI.Framework.Reflection;
+using StardewModdingAPI.Toolkit.Serialisation;
+using StardewValley;
+
+namespace StardewModdingAPI.Framework
+{
+ /// <summary>The static state to use while <see cref="Game1"/> is initialising, which happens before the <see cref="SGame"/> constructor runs.</summary>
+ internal class SGameConstructorHack
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <summary>Encapsulates monitoring and logging.</summary>
+ public IMonitor Monitor { get; }
+
+ /// <summary>Simplifies access to private game code.</summary>
+ public Reflector Reflection { get; }
+
+ /// <summary>Encapsulates SMAPI's JSON file parsing.</summary>
+ public JsonHelper JsonHelper { get; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="monitor">Encapsulates monitoring and logging.</param>
+ /// <param name="reflection">Simplifies access to private game code.</param>
+ /// <param name="jsonHelper">Encapsulates SMAPI's JSON file parsing.</param>
+ public SGameConstructorHack(IMonitor monitor, Reflector reflection, JsonHelper jsonHelper)
+ {
+ this.Monitor = monitor;
+ this.Reflection = reflection;
+ this.JsonHelper = jsonHelper;
+ }
+ }
+}