summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/SMAPI/Framework/IModMetadata.cs7
-rw-r--r--src/SMAPI/Framework/ModLoading/AssemblyLoader.cs16
-rw-r--r--src/SMAPI/Framework/ModLoading/ModMetadata.cs11
-rw-r--r--src/SMAPI/Framework/ModLoading/ModWarning.cs31
-rw-r--r--src/SMAPI/Program.cs53
-rw-r--r--src/SMAPI/StardewModdingAPI.csproj1
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" />