list all detected issues in trace logs for incompatible mods
This commit is contained in:
parent
307055b028
commit
09d1c5a601
|
@ -14,6 +14,7 @@ These changes have not been released yet.
|
||||||
* Added `IContentPack.HasFile` method.
|
* Added `IContentPack.HasFile` method.
|
||||||
* Added `Context.IsGameLaunched` field.
|
* 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).
|
* 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).
|
||||||
|
* When a mod is incompatible, the trace logs now list all detected issues instead of the first one.
|
||||||
* Dropped support for all deprecated APIs.
|
* Dropped support for all deprecated APIs.
|
||||||
* Updated to Json.NET 12.0.1.
|
* Updated to Json.NET 12.0.1.
|
||||||
|
|
||||||
|
|
|
@ -105,7 +105,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// rewrite assembly
|
// 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
|
// detect broken assembly reference
|
||||||
foreach (AssemblyNameReference reference in assembly.Definition.MainModule.AssemblyReferences)
|
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}'.");
|
this.Monitor.LogOnce(loggedMessages, $" Broken code in {assembly.File.Name}: reference to missing assembly '{reference.FullName}'.");
|
||||||
if (!assumeCompatible)
|
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);
|
mod.SetWarning(ModWarning.BrokenCodeLoaded);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -143,6 +143,10 @@ namespace StardewModdingAPI.Framework.ModLoading
|
||||||
this.AssemblyDefinitionResolver.Add(assembly.Definition);
|
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
|
// last assembly loaded is the root
|
||||||
return lastAssembly;
|
return lastAssembly;
|
||||||
}
|
}
|
||||||
|
@ -244,12 +248,11 @@ namespace StardewModdingAPI.Framework.ModLoading
|
||||||
/// <summary>Rewrite the types referenced by an assembly.</summary>
|
/// <summary>Rewrite the types referenced by an assembly.</summary>
|
||||||
/// <param name="mod">The mod for which the assembly is being loaded.</param>
|
/// <param name="mod">The mod for which the assembly is being loaded.</param>
|
||||||
/// <param name="assembly">The assembly to rewrite.</param>
|
/// <param name="assembly">The assembly to rewrite.</param>
|
||||||
/// <param name="assumeCompatible">Assume the mod is compatible, even if incompatible code is detected.</param>
|
|
||||||
/// <param name="loggedMessages">The messages that have already been logged for this mod.</param>
|
/// <param name="loggedMessages">The messages that have already been logged for this mod.</param>
|
||||||
/// <param name="logPrefix">A string to prefix to log messages.</param>
|
/// <param name="logPrefix">A string to prefix to log messages.</param>
|
||||||
/// <returns>Returns whether the assembly was modified.</returns>
|
/// <returns>Returns whether the assembly was modified.</returns>
|
||||||
/// <exception cref="IncompatibleInstructionException">An incompatible CIL instruction was found while rewriting the assembly.</exception>
|
/// <exception cref="IncompatibleInstructionException">An incompatible CIL instruction was found while rewriting the assembly.</exception>
|
||||||
private bool RewriteAssembly(IModMetadata mod, AssemblyDefinition assembly, bool assumeCompatible, HashSet<string> loggedMessages, string logPrefix)
|
private bool RewriteAssembly(IModMetadata mod, AssemblyDefinition assembly, HashSet<string> loggedMessages, string logPrefix)
|
||||||
{
|
{
|
||||||
ModuleDefinition module = assembly.MainModule;
|
ModuleDefinition module = assembly.MainModule;
|
||||||
string filename = $"{assembly.Name.Name}.dll";
|
string filename = $"{assembly.Name.Name}.dll";
|
||||||
|
@ -288,7 +291,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
||||||
foreach (IInstructionHandler handler in handlers)
|
foreach (IInstructionHandler handler in handlers)
|
||||||
{
|
{
|
||||||
InstructionHandleResult result = handler.Handle(module, method, this.AssemblyMap, platformChanged);
|
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)
|
if (result == InstructionHandleResult.Rewritten)
|
||||||
anyRewritten = true;
|
anyRewritten = true;
|
||||||
}
|
}
|
||||||
|
@ -303,7 +306,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
||||||
{
|
{
|
||||||
Instruction instruction = instructions[offset];
|
Instruction instruction = instructions[offset];
|
||||||
InstructionHandleResult result = handler.Handle(module, cil, instruction, this.AssemblyMap, platformChanged);
|
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)
|
if (result == InstructionHandleResult.Rewritten)
|
||||||
anyRewritten = true;
|
anyRewritten = true;
|
||||||
}
|
}
|
||||||
|
@ -318,10 +321,9 @@ namespace StardewModdingAPI.Framework.ModLoading
|
||||||
/// <param name="handler">The instruction handler.</param>
|
/// <param name="handler">The instruction handler.</param>
|
||||||
/// <param name="result">The result returned by the handler.</param>
|
/// <param name="result">The result returned by the handler.</param>
|
||||||
/// <param name="loggedMessages">The messages already logged for the current mod.</param>
|
/// <param name="loggedMessages">The messages already logged for the current mod.</param>
|
||||||
/// <param name="assumeCompatible">Assume the mod is compatible, even if incompatible code is detected.</param>
|
|
||||||
/// <param name="logPrefix">A string to prefix to log messages.</param>
|
/// <param name="logPrefix">A string to prefix to log messages.</param>
|
||||||
/// <param name="filename">The assembly filename for log messages.</param>
|
/// <param name="filename">The assembly filename for log messages.</param>
|
||||||
private void ProcessInstructionHandleResult(IModMetadata mod, IInstructionHandler handler, InstructionHandleResult result, HashSet<string> loggedMessages, string logPrefix, bool assumeCompatible, string filename)
|
private void ProcessInstructionHandleResult(IModMetadata mod, IInstructionHandler handler, InstructionHandleResult result, HashSet<string> loggedMessages, string logPrefix, string filename)
|
||||||
{
|
{
|
||||||
switch (result)
|
switch (result)
|
||||||
{
|
{
|
||||||
|
@ -331,8 +333,6 @@ namespace StardewModdingAPI.Framework.ModLoading
|
||||||
|
|
||||||
case InstructionHandleResult.NotCompatible:
|
case InstructionHandleResult.NotCompatible:
|
||||||
this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Broken code in {filename}: {handler.NounPhrase}.");
|
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);
|
mod.SetWarning(ModWarning.BrokenCodeLoaded);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -80,10 +80,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders
|
||||||
// compare return types
|
// compare return types
|
||||||
MethodDefinition methodDef = methodReference.Resolve();
|
MethodDefinition methodDef = methodReference.Resolve();
|
||||||
if (methodDef == null)
|
if (methodDef == null)
|
||||||
{
|
return InstructionHandleResult.None; // validated by ReferenceToMissingMemberFinder
|
||||||
this.NounPhrase = $"reference to {methodReference.DeclaringType.FullName}.{methodReference.Name} (no such method)";
|
|
||||||
return InstructionHandleResult.NotCompatible;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (candidateMethods.All(method => !RewriteHelper.LooksLikeSameType(method.ReturnType, methodDef.ReturnType)))
|
if (candidateMethods.All(method => !RewriteHelper.LooksLikeSameType(method.ReturnType, methodDef.ReturnType)))
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,31 +5,16 @@ namespace StardewModdingAPI.Framework.ModLoading
|
||||||
/// <summary>An exception raised when an incompatible instruction is found while loading a mod assembly.</summary>
|
/// <summary>An exception raised when an incompatible instruction is found while loading a mod assembly.</summary>
|
||||||
internal class IncompatibleInstructionException : Exception
|
internal class IncompatibleInstructionException : Exception
|
||||||
{
|
{
|
||||||
/*********
|
|
||||||
** Accessors
|
|
||||||
*********/
|
|
||||||
/// <summary>A brief noun phrase which describes the incompatible instruction that was found.</summary>
|
|
||||||
public string NounPhrase { get; }
|
|
||||||
|
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
** Public methods
|
** Public methods
|
||||||
*********/
|
*********/
|
||||||
/// <summary>Construct an instance.</summary>
|
/// <summary>Construct an instance.</summary>
|
||||||
/// <param name="nounPhrase">A brief noun phrase which describes the incompatible instruction that was found.</param>
|
public IncompatibleInstructionException()
|
||||||
public IncompatibleInstructionException(string nounPhrase)
|
: base("Found incompatible CIL instructions.") { }
|
||||||
: base($"Found an incompatible CIL instruction ({nounPhrase}).")
|
|
||||||
{
|
|
||||||
this.NounPhrase = nounPhrase;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Construct an instance.</summary>
|
/// <summary>Construct an instance.</summary>
|
||||||
/// <param name="nounPhrase">A brief noun phrase which describes the incompatible instruction that was found.</param>
|
|
||||||
/// <param name="message">A message which describes the error.</param>
|
/// <param name="message">A message which describes the error.</param>
|
||||||
public IncompatibleInstructionException(string nounPhrase, string message)
|
public IncompatibleInstructionException(string message)
|
||||||
: base(message)
|
: base(message) { }
|
||||||
{
|
|
||||||
this.NounPhrase = nounPhrase;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue