diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/SMAPI/Framework/IModMetadata.cs | 7 | ||||
-rw-r--r-- | src/SMAPI/Framework/ModLoading/AssemblyLoader.cs | 16 | ||||
-rw-r--r-- | src/SMAPI/Framework/ModLoading/ModMetadata.cs | 11 | ||||
-rw-r--r-- | src/SMAPI/Framework/ModLoading/ModWarning.cs | 31 | ||||
-rw-r--r-- | src/SMAPI/Program.cs | 53 | ||||
-rw-r--r-- | src/SMAPI/StardewModdingAPI.csproj | 1 |
6 files changed, 102 insertions, 17 deletions
diff --git a/src/SMAPI/Framework/IModMetadata.cs b/src/SMAPI/Framework/IModMetadata.cs index b7972fe1..c0d6408d 100644 --- a/src/SMAPI/Framework/IModMetadata.cs +++ b/src/SMAPI/Framework/IModMetadata.cs @@ -24,6 +24,9 @@ namespace StardewModdingAPI.Framework /// <summary>The metadata resolution status.</summary> ModMetadataStatus Status { get; } + /// <summary>Indicates non-error issues with the mod.</summary> + ModWarning Warnings { get; } + /// <summary>The reason the metadata is invalid, if any.</summary> string Error { get; } @@ -52,6 +55,10 @@ namespace StardewModdingAPI.Framework /// <returns>Return the instance for chaining.</returns> IModMetadata SetStatus(ModMetadataStatus status, string error = null); + /// <summary>Set a warning flag for the mod.</summary> + /// <param name="warning">The warning to set.</param> + IModMetadata SetWarning(ModWarning warning); + /// <summary>Set the mod instance.</summary> /// <param name="mod">The mod instance to set.</param> IModMetadata SetMod(IMod mod); diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs index 6c0e1c14..2fb2aba7 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs @@ -285,33 +285,27 @@ namespace StardewModdingAPI.Framework.ModLoading this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Broken code in {filename}: {handler.NounPhrase}."); if (!assumeCompatible) throw new IncompatibleInstructionException(handler.NounPhrase, $"Found an incompatible CIL instruction ({handler.NounPhrase}) while loading assembly {filename}."); - this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Found broken code ({handler.NounPhrase}) while loading assembly {filename}, but SMAPI is configured to allow it anyway. The mod may crash or behave unexpectedly.", LogLevel.Warn); + mod.SetWarning(ModWarning.BrokenCodeLoaded); break; case InstructionHandleResult.DetectedGamePatch: this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Detected game patcher ({handler.NounPhrase}) in assembly {filename}."); - this.Monitor.LogOnce(loggedMessages, $"{logPrefix}{mod.DisplayName} patches the game, which may impact game stability. If you encounter problems, try removing this mod first.", LogLevel.Warn); + mod.SetWarning(ModWarning.PatchesGame); break; case InstructionHandleResult.DetectedSaveSerialiser: this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Detected possible save serialiser change ({handler.NounPhrase}) in assembly {filename}."); - this.Monitor.LogOnce(loggedMessages, $"{logPrefix}{mod.DisplayName} seems to change the save serialiser. It may change your saves in such a way that they won't work without this mod in the future.", LogLevel.Warn); + mod.SetWarning(ModWarning.ChangesSaveSerialiser); break; case InstructionHandleResult.DetectedUnvalidatedUpdateTick: this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Detected reference to {handler.NounPhrase} in assembly {filename}."); - this.Monitor.LogOnce(loggedMessages, $"{logPrefix}{mod.DisplayName} uses a specialised SMAPI event that may crash the game or corrupt your save file. If you encounter problems, try removing this mod first.", LogLevel.Warn); + mod.SetWarning(ModWarning.UsesUnvalidatedUpdateTick); break; case InstructionHandleResult.DetectedDynamic: this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Detected 'dynamic' keyword ({handler.NounPhrase}) in assembly {filename}."); - this.Monitor.LogOnce(loggedMessages, $"{logPrefix}{mod.DisplayName} uses the 'dynamic' keyword, which isn't compatible with Stardew Valley on Linux or Mac.", -#if SMAPI_FOR_WINDOWS - this.IsDeveloperMode ? LogLevel.Warn : LogLevel.Debug -#else - LogLevel.Warn -#endif - ); + mod.SetWarning(ModWarning.UsesDynamic); break; case InstructionHandleResult.None: diff --git a/src/SMAPI/Framework/ModLoading/ModMetadata.cs b/src/SMAPI/Framework/ModLoading/ModMetadata.cs index d3a33e7a..e4ec7e3b 100644 --- a/src/SMAPI/Framework/ModLoading/ModMetadata.cs +++ b/src/SMAPI/Framework/ModLoading/ModMetadata.cs @@ -25,6 +25,9 @@ namespace StardewModdingAPI.Framework.ModLoading /// <summary>The metadata resolution status.</summary> public ModMetadataStatus Status { get; private set; } + /// <summary>Indicates non-error issues with the mod.</summary> + public ModWarning Warnings { get; private set; } + /// <summary>The reason the metadata is invalid, if any.</summary> public string Error { get; private set; } @@ -71,6 +74,14 @@ namespace StardewModdingAPI.Framework.ModLoading return this; } + /// <summary>Set a warning flag for the mod.</summary> + /// <param name="warning">The warning to set.</param> + public IModMetadata SetWarning(ModWarning warning) + { + this.Warnings |= warning; + return this; + } + /// <summary>Set the mod instance.</summary> /// <param name="mod">The mod instance to set.</param> public IModMetadata SetMod(IMod mod) diff --git a/src/SMAPI/Framework/ModLoading/ModWarning.cs b/src/SMAPI/Framework/ModLoading/ModWarning.cs new file mode 100644 index 00000000..0e4b2570 --- /dev/null +++ b/src/SMAPI/Framework/ModLoading/ModWarning.cs @@ -0,0 +1,31 @@ +using System; +using StardewModdingAPI.Events; + +namespace StardewModdingAPI.Framework.ModLoading +{ + /// <summary>Indicates a detected non-error mod issue.</summary> + [Flags] + internal enum ModWarning + { + /// <summary>No issues detected.</summary> + None = 0, + + /// <summary>SMAPI detected incompatible code in the mod, but was configured to load it anyway.</summary> + BrokenCodeLoaded = 1, + + /// <summary>The mod affects the save serializer in a way that may make saves unloadable without the mod.</summary> + ChangesSaveSerialiser = 2, + + /// <summary>The mod patches the game in a way that may impact stability.</summary> + PatchesGame = 4, + + /// <summary>The mod uses the <c>dynamic</c> keyword which won't work on Linux/Mac.</summary> + UsesDynamic = 8, + + /// <summary>The mod references <see cref="SpecialisedEvents.UnvalidatedUpdateTick"/> which may impact stability.</summary> + UsesUnvalidatedUpdateTick = 16, + + /// <summary>The mod has no update keys set.</summary> + NoUpdateKeys = 32 + } +} diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index f410abc1..e50c4ec9 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -742,7 +742,7 @@ namespace StardewModdingAPI // show warning for missing update key if (metadata.HasManifest() && !metadata.HasUpdateKeys()) - this.Monitor.Log($" {metadata.DisplayName} has no {nameof(IManifest.UpdateKeys)} in its manifest. You may not see update alerts for this mod.", LogLevel.Warn); + metadata.SetWarning(ModWarning.NoUpdateKeys); // validate status if (metadata.Status == ModMetadataStatus.Failed) @@ -791,7 +791,7 @@ namespace StardewModdingAPI // show warnings if (metadata.HasManifest() && !metadata.HasUpdateKeys() && !this.AllowMissingUpdateKeys.Contains(metadata.Manifest.UniqueID)) - this.Monitor.Log($" {metadata.DisplayName} has no {nameof(IManifest.UpdateKeys)} in its manifest. You may not see update alerts for this mod.", LogLevel.Warn); + metadata.SetWarning(ModWarning.NoUpdateKeys); // validate status if (metadata.Status == ModMetadataStatus.Failed) @@ -831,6 +831,10 @@ namespace StardewModdingAPI // initialise mod try { + // get mod instance + if (!this.TryLoadModEntry(modAssembly, error => TrackSkip(metadata, error), out Mod mod)) + continue; + // get content packs if (!contentPacksByModID.TryGetValue(manifest.UniqueID, out IContentPack[] contentPacks)) contentPacks = new IContentPack[0]; @@ -858,10 +862,6 @@ namespace StardewModdingAPI modHelper = new ModHelper(manifest.UniqueID, metadata.DirectoryPath, jsonHelper, contentHelper, commandHelper, modRegistryHelper, reflectionHelper, multiplayerHelper, translationHelper, contentPacks, CreateTransitionalContentPack, this.DeprecationManager); } - // get mod instance - if (!this.TryLoadModEntry(modAssembly, error => TrackSkip(metadata, error), out Mod mod)) - continue; - // init mod mod.ModManifest = manifest; mod.Helper = modHelper; @@ -931,6 +931,28 @@ namespace StardewModdingAPI this.Monitor.Newline(); } + // log warnings + { + IModMetadata[] modsWithWarnings = this.ModRegistry.GetAll().Where(p => p.Warnings != ModWarning.None).ToArray(); + if (modsWithWarnings.Any()) + { + this.Monitor.Log($"Found issues with {modsWithWarnings.Length} mods:", LogLevel.Warn); + foreach (IModMetadata metadata in modsWithWarnings) + { + string[] warnings = this.GetWarningText(metadata.Warnings).ToArray(); + if (warnings.Length == 1) + this.Monitor.Log($" {metadata.DisplayName}: {warnings[0]}", LogLevel.Warn); + else + { + this.Monitor.Log($" {metadata.DisplayName}:", LogLevel.Warn); + foreach (string warning in warnings) + this.Monitor.Log(" - " + warning, LogLevel.Warn); + } + } + this.Monitor.Newline(); + } + } + // initialise translations this.ReloadTranslations(loadedMods); @@ -1020,6 +1042,25 @@ namespace StardewModdingAPI this.ModRegistry.AreAllModsInitialised = true; } + /// <summary>Get the warning text for a mod warning bit mask.</summary> + /// <param name="mask">The mod warning bit mask.</param> + private IEnumerable<string> GetWarningText(ModWarning mask) + { + if (mask.HasFlag(ModWarning.BrokenCodeLoaded)) + yield return "has broken code, but SMAPI is configured to allow it anyway. The mod may crash or behave unexpectedly."; + if (mask.HasFlag(ModWarning.ChangesSaveSerialiser)) + yield return "accesses the save serialiser and may break your saves."; + if (mask.HasFlag(ModWarning.PatchesGame)) + yield return "patches the game. This may cause errors or bugs in-game. If you have issues, try removing this mod first."; + if (mask.HasFlag(ModWarning.UsesUnvalidatedUpdateTick)) + yield return "bypasses normal SMAPI event protections. This may cause errors or save corruption. If you have issues, try removing this mod first."; + if (mask.HasFlag(ModWarning.UsesDynamic)) + yield return "uses the 'dynamic' keyword. This won't work on Linux/Mac."; + if (mask.HasFlag(ModWarning.NoUpdateKeys)) + yield return "has no update keys in its manifest. SMAPI won't show update alerts for this mod."; + + } + /// <summary>Load a mod's entry class.</summary> /// <param name="modAssembly">The mod assembly.</param> /// <param name="onError">A callback invoked when loading fails.</param> diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj index 01d5aaf1..19c1e6fe 100644 --- a/src/SMAPI/StardewModdingAPI.csproj +++ b/src/SMAPI/StardewModdingAPI.csproj @@ -113,6 +113,7 @@ <Compile Include="Framework\ModLoading\IInstructionHandler.cs" /> <Compile Include="Framework\ModLoading\IncompatibleInstructionException.cs" /> <Compile Include="Framework\ModLoading\InstructionHandleResult.cs" /> + <Compile Include="Framework\ModLoading\ModWarning.cs" /> <Compile Include="Framework\ModLoading\PlatformAssemblyMap.cs" /> <Compile Include="Framework\ModLoading\RewriteHelper.cs" /> <Compile Include="Framework\ModLoading\Rewriters\FieldReplaceRewriter.cs" /> |