diff options
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r-- | src/SMAPI/Framework/ContentCoordinator.cs | 10 | ||||
-rw-r--r-- | src/SMAPI/Framework/ContentManagers/ModContentManager.cs | 31 | ||||
-rw-r--r-- | src/SMAPI/Framework/ContentPack.cs | 4 | ||||
-rw-r--r-- | src/SMAPI/Framework/ModHelpers/ContentHelper.cs | 2 | ||||
-rw-r--r-- | src/SMAPI/Framework/ModHelpers/ModHelper.cs | 4 | ||||
-rw-r--r-- | src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs | 10 | ||||
-rw-r--r-- | src/SMAPI/Framework/SGame.cs | 26 | ||||
-rw-r--r-- | src/SMAPI/Framework/SGameConstructorHack.cs | 37 |
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; + } + } +} |