add logic to detect incompatible mod instructions & reject mod load (#247)

This commit is contained in:
Jesse Plamondon-Willard 2017-03-12 01:31:15 -05:00
parent b0fab4a076
commit 6d2d90b768
5 changed files with 59 additions and 0 deletions

View File

@ -135,6 +135,14 @@ namespace StardewModdingAPI
return new PlatformAssemblyMap(targetPlatform, removeAssemblyReferences, targetAssemblies); return new PlatformAssemblyMap(targetPlatform, removeAssemblyReferences, targetAssemblies);
} }
/// <summary>Get finders which match incompatible CIL instructions in mod assemblies.</summary>
internal static IEnumerable<IInstructionFinder> GetIncompatibilityFinders()
{
return new IInstructionFinder[]
{
};
}
/// <summary>Get rewriters which fix incompatible CIL instructions in mod assemblies.</summary> /// <summary>Get rewriters which fix incompatible CIL instructions in mod assemblies.</summary>
internal static IEnumerable<IInstructionRewriter> GetRewriters() internal static IEnumerable<IInstructionRewriter> GetRewriters()
{ {

View File

@ -55,6 +55,7 @@ namespace StardewModdingAPI.Framework
/// <summary>Preprocess and load an assembly.</summary> /// <summary>Preprocess and load an assembly.</summary>
/// <param name="assemblyPath">The assembly file path.</param> /// <param name="assemblyPath">The assembly file path.</param>
/// <returns>Returns the rewrite metadata for the preprocessed assembly.</returns> /// <returns>Returns the rewrite metadata for the preprocessed assembly.</returns>
/// <exception cref="IncompatibleInstructionException">An incompatible CIL instruction was found while rewriting the assembly.</exception>
public Assembly Load(string assemblyPath) public Assembly Load(string assemblyPath)
{ {
// get referenced local assemblies // get referenced local assemblies
@ -159,6 +160,7 @@ namespace StardewModdingAPI.Framework
/// <summary>Rewrite the types referenced by an assembly.</summary> /// <summary>Rewrite the types referenced by an assembly.</summary>
/// <param name="assembly">The assembly to rewrite.</param> /// <param name="assembly">The assembly to rewrite.</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>
private bool RewriteAssembly(AssemblyDefinition assembly) private bool RewriteAssembly(AssemblyDefinition assembly)
{ {
ModuleDefinition module = assembly.MainModule; ModuleDefinition module = assembly.MainModule;
@ -189,6 +191,22 @@ namespace StardewModdingAPI.Framework
this.ChangeTypeScope(type); this.ChangeTypeScope(type);
} }
// throw exception if assembly contains incompatible instructions can't be rewritten
{
IInstructionFinder[] finders = Constants.GetIncompatibilityFinders().ToArray();
foreach (MethodDefinition method in this.GetMethods(module))
{
foreach (Instruction instruction in method.Body.Instructions)
{
foreach (IInstructionFinder finder in finders)
{
if (finder.IsMatch(instruction, platformChanged))
throw new IncompatibleInstructionException(finder.NounPhrase, $"Found an incompatible CIL instruction ({finder.NounPhrase}) while loading assembly {assembly.Name.Name}.");
}
}
}
}
// rewrite incompatible instructions // rewrite incompatible instructions
bool anyRewritten = false; bool anyRewritten = false;
IInstructionRewriter[] rewriters = Constants.GetRewriters().ToArray(); IInstructionRewriter[] rewriters = Constants.GetRewriters().ToArray();

View File

@ -0,0 +1,27 @@
using System;
namespace StardewModdingAPI.Framework
{
/// <summary>An exception raised when an incompatible instruction is found while loading a mod assembly.</summary>
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
*********/
/// <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>
public IncompatibleInstructionException(string nounPhrase, string message)
: base(message)
{
this.NounPhrase = nounPhrase;
}
}
}

View File

@ -453,6 +453,11 @@ namespace StardewModdingAPI
{ {
modAssembly = modAssemblyLoader.Load(assemblyPath); modAssembly = modAssemblyLoader.Load(assemblyPath);
} }
catch (IncompatibleInstructionException ex)
{
this.Monitor.Log($"{skippedPrefix} because it's not compatible with the latest version of the game (detected {ex.NounPhrase}). Please check for a newer version of the mod (you have v{manifest.Version}).", LogLevel.Error);
continue;
}
catch (Exception ex) catch (Exception ex)
{ {
this.Monitor.Log($"{skippedPrefix} because its DLL '{manifest.EntryDll}' couldn't be loaded.\n{ex.GetLogSummary()}", LogLevel.Error); this.Monitor.Log($"{skippedPrefix} because its DLL '{manifest.EntryDll}' couldn't be loaded.\n{ex.GetLogSummary()}", LogLevel.Error);

View File

@ -154,6 +154,7 @@
<Compile Include="Framework\Content\ContentEventHelper.cs" /> <Compile Include="Framework\Content\ContentEventHelper.cs" />
<Compile Include="Framework\Content\ContentEventHelperForDictionary.cs" /> <Compile Include="Framework\Content\ContentEventHelperForDictionary.cs" />
<Compile Include="Framework\Content\ContentEventHelperForImage.cs" /> <Compile Include="Framework\Content\ContentEventHelperForImage.cs" />
<Compile Include="Framework\IncompatibleInstructionException.cs" />
<Compile Include="Framework\Logging\ConsoleInterceptionManager.cs" /> <Compile Include="Framework\Logging\ConsoleInterceptionManager.cs" />
<Compile Include="Framework\Logging\InterceptingTextWriter.cs" /> <Compile Include="Framework\Logging\InterceptingTextWriter.cs" />
<Compile Include="Framework\CommandHelper.cs" /> <Compile Include="Framework\CommandHelper.cs" />