diff --git a/docs/release-notes.md b/docs/release-notes.md
index 23f0fdab..8bf5c77f 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -19,6 +19,7 @@ These changes have not been released yet.
* Added `Context.IsGameLaunched` field.
* Mods are now loaded much earlier in the game launch. This lets mods intercept any content asset, but the game is not fully initialised when `Entry` is called (use the `GameLaunched` event if you need to run code when the game is initialised).
* `this.Monitor.Log` now defaults to the `Trace` log level instead of `Debug`.
+ * When a mod is incompatible, the trace logs now list all detected issues instead of the first one.
* Dropped support for all deprecated APIs.
* Updated to Json.NET 12.0.1.
diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
index 878b3148..ca171ae1 100644
--- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
+++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
@@ -105,7 +105,7 @@ namespace StardewModdingAPI.Framework.ModLoading
continue;
// rewrite assembly
- bool changed = this.RewriteAssembly(mod, assembly.Definition, assumeCompatible, loggedMessages, logPrefix: " ");
+ bool changed = this.RewriteAssembly(mod, assembly.Definition, loggedMessages, logPrefix: " ");
// detect broken assembly reference
foreach (AssemblyNameReference reference in assembly.Definition.MainModule.AssemblyReferences)
@@ -114,7 +114,7 @@ namespace StardewModdingAPI.Framework.ModLoading
{
this.Monitor.LogOnce(loggedMessages, $" Broken code in {assembly.File.Name}: reference to missing assembly '{reference.FullName}'.");
if (!assumeCompatible)
- throw new IncompatibleInstructionException($"assembly reference to {reference.FullName}", $"Found a reference to missing assembly '{reference.FullName}' while loading assembly {assembly.File.Name}.");
+ throw new IncompatibleInstructionException($"Found a reference to missing assembly '{reference.FullName}' while loading assembly {assembly.File.Name}.");
mod.SetWarning(ModWarning.BrokenCodeLoaded);
break;
}
@@ -143,6 +143,10 @@ namespace StardewModdingAPI.Framework.ModLoading
this.AssemblyDefinitionResolver.Add(assembly.Definition);
}
+ // throw if incompatibilities detected
+ if (!assumeCompatible && mod.Warnings.HasFlag(ModWarning.BrokenCodeLoaded))
+ throw new IncompatibleInstructionException();
+
// last assembly loaded is the root
return lastAssembly;
}
@@ -244,12 +248,11 @@ namespace StardewModdingAPI.Framework.ModLoading
/// Rewrite the types referenced by an assembly.
/// The mod for which the assembly is being loaded.
/// The assembly to rewrite.
- /// Assume the mod is compatible, even if incompatible code is detected.
/// The messages that have already been logged for this mod.
/// A string to prefix to log messages.
/// Returns whether the assembly was modified.
/// An incompatible CIL instruction was found while rewriting the assembly.
- private bool RewriteAssembly(IModMetadata mod, AssemblyDefinition assembly, bool assumeCompatible, HashSet loggedMessages, string logPrefix)
+ private bool RewriteAssembly(IModMetadata mod, AssemblyDefinition assembly, HashSet loggedMessages, string logPrefix)
{
ModuleDefinition module = assembly.MainModule;
string filename = $"{assembly.Name.Name}.dll";
@@ -288,7 +291,7 @@ namespace StardewModdingAPI.Framework.ModLoading
foreach (IInstructionHandler handler in handlers)
{
InstructionHandleResult result = handler.Handle(module, method, this.AssemblyMap, platformChanged);
- this.ProcessInstructionHandleResult(mod, handler, result, loggedMessages, logPrefix, assumeCompatible, filename);
+ this.ProcessInstructionHandleResult(mod, handler, result, loggedMessages, logPrefix, filename);
if (result == InstructionHandleResult.Rewritten)
anyRewritten = true;
}
@@ -303,7 +306,7 @@ namespace StardewModdingAPI.Framework.ModLoading
{
Instruction instruction = instructions[offset];
InstructionHandleResult result = handler.Handle(module, cil, instruction, this.AssemblyMap, platformChanged);
- this.ProcessInstructionHandleResult(mod, handler, result, loggedMessages, logPrefix, assumeCompatible, filename);
+ this.ProcessInstructionHandleResult(mod, handler, result, loggedMessages, logPrefix, filename);
if (result == InstructionHandleResult.Rewritten)
anyRewritten = true;
}
@@ -318,10 +321,9 @@ namespace StardewModdingAPI.Framework.ModLoading
/// The instruction handler.
/// The result returned by the handler.
/// The messages already logged for the current mod.
- /// Assume the mod is compatible, even if incompatible code is detected.
/// A string to prefix to log messages.
/// The assembly filename for log messages.
- private void ProcessInstructionHandleResult(IModMetadata mod, IInstructionHandler handler, InstructionHandleResult result, HashSet loggedMessages, string logPrefix, bool assumeCompatible, string filename)
+ private void ProcessInstructionHandleResult(IModMetadata mod, IInstructionHandler handler, InstructionHandleResult result, HashSet loggedMessages, string logPrefix, string filename)
{
switch (result)
{
@@ -331,8 +333,6 @@ namespace StardewModdingAPI.Framework.ModLoading
case InstructionHandleResult.NotCompatible:
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}.");
mod.SetWarning(ModWarning.BrokenCodeLoaded);
break;
diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs
index 82c4920a..459e3210 100644
--- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs
+++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs
@@ -80,10 +80,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
// compare return types
MethodDefinition methodDef = methodReference.Resolve();
if (methodDef == null)
- {
- this.NounPhrase = $"reference to {methodReference.DeclaringType.FullName}.{methodReference.Name} (no such method)";
- return InstructionHandleResult.NotCompatible;
- }
+ return InstructionHandleResult.None; // validated by ReferenceToMissingMemberFinder
if (candidateMethods.All(method => !RewriteHelper.LooksLikeSameType(method.ReturnType, methodDef.ReturnType)))
{
diff --git a/src/SMAPI/Framework/ModLoading/IncompatibleInstructionException.cs b/src/SMAPI/Framework/ModLoading/IncompatibleInstructionException.cs
index 17ec24b1..1f9add30 100644
--- a/src/SMAPI/Framework/ModLoading/IncompatibleInstructionException.cs
+++ b/src/SMAPI/Framework/ModLoading/IncompatibleInstructionException.cs
@@ -5,31 +5,16 @@ namespace StardewModdingAPI.Framework.ModLoading
/// An exception raised when an incompatible instruction is found while loading a mod assembly.
internal class IncompatibleInstructionException : Exception
{
- /*********
- ** Accessors
- *********/
- /// A brief noun phrase which describes the incompatible instruction that was found.
- public string NounPhrase { get; }
-
-
/*********
** Public methods
*********/
/// Construct an instance.
- /// A brief noun phrase which describes the incompatible instruction that was found.
- public IncompatibleInstructionException(string nounPhrase)
- : base($"Found an incompatible CIL instruction ({nounPhrase}).")
- {
- this.NounPhrase = nounPhrase;
- }
+ public IncompatibleInstructionException()
+ : base("Found incompatible CIL instructions.") { }
/// Construct an instance.
- /// A brief noun phrase which describes the incompatible instruction that was found.
/// A message which describes the error.
- public IncompatibleInstructionException(string nounPhrase, string message)
- : base(message)
- {
- this.NounPhrase = nounPhrase;
- }
+ public IncompatibleInstructionException(string message)
+ : base(message) { }
}
}