summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r--src/SMAPI/Framework/ModLoading/AssemblyLoader.cs46
-rw-r--r--src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs10
-rw-r--r--src/SMAPI/Framework/ModLoading/Framework/BaseTypeFinder.cs29
-rw-r--r--src/SMAPI/Framework/ModLoading/Framework/BaseTypeReferenceRewriter.cs88
-rw-r--r--src/SMAPI/Framework/ModLoading/IInstructionHandler.cs7
-rw-r--r--src/SMAPI/Framework/ModLoading/RewriteHelper.cs67
6 files changed, 222 insertions, 25 deletions
diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
index b5533335..5b5a621b 100644
--- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
+++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
@@ -285,31 +285,44 @@ namespace StardewModdingAPI.Framework.ModLoading
// find (and optionally rewrite) incompatible instructions
bool anyRewritten = false;
IInstructionHandler[] handlers = new InstructionMetadata().GetHandlers(this.ParanoidMode).ToArray();
- foreach (MethodDefinition method in this.GetMethods(module))
+ foreach (TypeDefinition type in module.GetTypes())
{
- // check method definition
+ // check type definition
foreach (IInstructionHandler handler in handlers)
{
- InstructionHandleResult result = handler.Handle(module, method, this.AssemblyMap, platformChanged);
+ InstructionHandleResult result = handler.Handle(module, type, this.AssemblyMap, platformChanged);
this.ProcessInstructionHandleResult(mod, handler, result, loggedMessages, logPrefix, filename);
if (result == InstructionHandleResult.Rewritten)
anyRewritten = true;
}
- // check CIL instructions
- ILProcessor cil = method.Body.GetILProcessor();
- var instructions = cil.Body.Instructions;
- // ReSharper disable once ForCanBeConvertedToForeach -- deliberate access by index so each handler sees replacements from previous handlers
- for (int offset = 0; offset < instructions.Count; offset++)
+ // check methods
+ foreach (MethodDefinition method in type.Methods.Where(p => p.HasBody))
{
+ // check method definition
foreach (IInstructionHandler handler in handlers)
{
- Instruction instruction = instructions[offset];
- InstructionHandleResult result = handler.Handle(module, cil, instruction, this.AssemblyMap, platformChanged);
+ InstructionHandleResult result = handler.Handle(module, method, this.AssemblyMap, platformChanged);
this.ProcessInstructionHandleResult(mod, handler, result, loggedMessages, logPrefix, filename);
if (result == InstructionHandleResult.Rewritten)
anyRewritten = true;
}
+
+ // check CIL instructions
+ ILProcessor cil = method.Body.GetILProcessor();
+ var instructions = cil.Body.Instructions;
+ // ReSharper disable once ForCanBeConvertedToForeach -- deliberate access by index so each handler sees replacements from previous handlers
+ for (int offset = 0; offset < instructions.Count; offset++)
+ {
+ foreach (IInstructionHandler handler in handlers)
+ {
+ Instruction instruction = instructions[offset];
+ InstructionHandleResult result = handler.Handle(module, cil, instruction, this.AssemblyMap, platformChanged);
+ this.ProcessInstructionHandleResult(mod, handler, result, loggedMessages, logPrefix, filename);
+ if (result == InstructionHandleResult.Rewritten)
+ anyRewritten = true;
+ }
+ }
}
}
@@ -395,18 +408,5 @@ namespace StardewModdingAPI.Framework.ModLoading
AssemblyNameReference assemblyRef = this.AssemblyMap.TargetReferences[assembly];
type.Scope = assemblyRef;
}
-
- /// <summary>Get all methods in a module.</summary>
- /// <param name="module">The module to search.</param>
- private IEnumerable<MethodDefinition> GetMethods(ModuleDefinition module)
- {
- return (
- from type in module.GetTypes()
- where type.HasMethods
- from method in type.Methods
- where method.HasBody
- select method
- );
- }
}
}
diff --git a/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs b/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs
index 10780d07..353de464 100644
--- a/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs
+++ b/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs
@@ -18,6 +18,16 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
*********/
/// <summary>Perform the predefined logic for a method if applicable.</summary>
/// <param name="module">The assembly module containing the instruction.</param>
+ /// <param name="type">The type definition to handle.</param>
+ /// <param name="assemblyMap">Metadata for mapping assemblies to the current platform.</param>
+ /// <param name="platformChanged">Whether the mod was compiled on a different platform.</param>
+ public virtual InstructionHandleResult Handle(ModuleDefinition module, TypeDefinition type, PlatformAssemblyMap assemblyMap, bool platformChanged)
+ {
+ return InstructionHandleResult.None;
+ }
+
+ /// <summary>Perform the predefined logic for a method if applicable.</summary>
+ /// <param name="module">The assembly module containing the instruction.</param>
/// <param name="method">The method definition to handle.</param>
/// <param name="assemblyMap">Metadata for mapping assemblies to the current platform.</param>
/// <param name="platformChanged">Whether the mod was compiled on a different platform.</param>
diff --git a/src/SMAPI/Framework/ModLoading/Framework/BaseTypeFinder.cs b/src/SMAPI/Framework/ModLoading/Framework/BaseTypeFinder.cs
index cfd87922..48165c4c 100644
--- a/src/SMAPI/Framework/ModLoading/Framework/BaseTypeFinder.cs
+++ b/src/SMAPI/Framework/ModLoading/Framework/BaseTypeFinder.cs
@@ -50,9 +50,38 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
/// <param name="method">The method definition.</param>
public bool IsMatch(MethodDefinition method)
{
+ // return type
if (this.IsMatch(method.ReturnType))
return true;
+ // parameters
+ foreach (ParameterDefinition parameter in method.Parameters)
+ {
+ if (this.IsMatch(parameter.ParameterType))
+ return true;
+ }
+
+ // generic parameters
+ foreach (GenericParameter parameter in method.GenericParameters)
+ {
+ if (this.IsMatch(parameter))
+ return true;
+ }
+
+ // custom attributes
+ foreach (CustomAttribute attribute in method.CustomAttributes)
+ {
+ if (this.IsMatch(attribute.AttributeType))
+ return true;
+
+ foreach (var arg in attribute.ConstructorArguments)
+ {
+ if (this.IsMatch(arg.Type))
+ return true;
+ }
+ }
+
+ // local variables
foreach (VariableDefinition variable in method.Body.Variables)
{
if (this.IsMatch(variable.VariableType))
diff --git a/src/SMAPI/Framework/ModLoading/Framework/BaseTypeReferenceRewriter.cs b/src/SMAPI/Framework/ModLoading/Framework/BaseTypeReferenceRewriter.cs
index 5f38a30b..445ac2cb 100644
--- a/src/SMAPI/Framework/ModLoading/Framework/BaseTypeReferenceRewriter.cs
+++ b/src/SMAPI/Framework/ModLoading/Framework/BaseTypeReferenceRewriter.cs
@@ -1,6 +1,8 @@
using System;
+using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
+using Mono.Collections.Generic;
namespace StardewModdingAPI.Framework.ModLoading.Framework
{
@@ -19,6 +21,20 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
*********/
/// <summary>Perform the predefined logic for a method if applicable.</summary>
/// <param name="module">The assembly module containing the instruction.</param>
+ /// <param name="type">The type definition to handle.</param>
+ /// <param name="assemblyMap">Metadata for mapping assemblies to the current platform.</param>
+ /// <param name="platformChanged">Whether the mod was compiled on a different platform.</param>
+ public override InstructionHandleResult Handle(ModuleDefinition module, TypeDefinition type, PlatformAssemblyMap assemblyMap, bool platformChanged)
+ {
+ bool rewritten = this.RewriteCustomAttributesIfNeeded(module, type.CustomAttributes);
+
+ return rewritten
+ ? InstructionHandleResult.Rewritten
+ : InstructionHandleResult.None;
+ }
+
+ /// <summary>Perform the predefined logic for a method if applicable.</summary>
+ /// <param name="module">The assembly module containing the instruction.</param>
/// <param name="method">The method definition to handle.</param>
/// <param name="assemblyMap">Metadata for mapping assemblies to the current platform.</param>
/// <param name="platformChanged">Whether the mod was compiled on a different platform.</param>
@@ -28,9 +44,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
// return type
if (this.Finder.IsMatch(method.ReturnType))
- {
rewritten |= this.RewriteIfNeeded(module, method.ReturnType, newType => method.ReturnType = newType);
- }
// parameters
foreach (ParameterDefinition parameter in method.Parameters)
@@ -47,6 +61,9 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
rewritten |= this.RewriteIfNeeded(module, parameter, newType => method.GenericParameters[i] = new GenericParameter(parameter.Name, newType));
}
+ // custom attributes
+ rewritten |= this.RewriteCustomAttributesIfNeeded(module, method.CustomAttributes);
+
// local variables
foreach (VariableDefinition variable in method.Body.Variables)
{
@@ -116,5 +133,72 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework
/// <param name="type">The type to replace if it matches.</param>
/// <param name="set">Assign the new type reference.</param>
protected abstract bool RewriteIfNeeded(ModuleDefinition module, TypeReference type, Action<TypeReference> set);
+
+ /// <summary>Rewrite custom attributes if needed.</summary>
+ /// <param name="module">The assembly module containing the attributes.</param>
+ /// <param name="attributes">The custom attributes to handle.</param>
+ private bool RewriteCustomAttributesIfNeeded(ModuleDefinition module, Collection<CustomAttribute> attributes)
+ {
+ bool rewritten = false;
+
+ for (int attrIndex = 0; attrIndex < attributes.Count; attrIndex++)
+ {
+ CustomAttribute attribute = attributes[attrIndex];
+ bool curChanged = false;
+
+ // attribute type
+ TypeReference newAttrType = null;
+ if (this.Finder.IsMatch(attribute.AttributeType))
+ {
+ rewritten |= this.RewriteIfNeeded(module, attribute.AttributeType, newType =>
+ {
+ newAttrType = newType;
+ curChanged = true;
+ });
+ }
+
+ // constructor arguments
+ TypeReference[] argTypes = new TypeReference[attribute.ConstructorArguments.Count];
+ for (int i = 0; i < argTypes.Length; i++)
+ {
+ var arg = attribute.ConstructorArguments[i];
+
+ argTypes[i] = arg.Type;
+ rewritten |= this.RewriteIfNeeded(module, arg.Type, newType =>
+ {
+ argTypes[i] = newType;
+ curChanged = true;
+ });
+ }
+
+ // swap attribute
+ if (curChanged)
+ {
+ // get constructor
+ MethodDefinition constructor = (newAttrType ?? attribute.AttributeType)
+ .Resolve()
+ .Methods
+ .Where(method => method.IsConstructor)
+ .FirstOrDefault(ctor => RewriteHelper.HasMatchingSignature(ctor, attribute.Constructor));
+ if (constructor == null)
+ throw new InvalidOperationException($"Can't rewrite attribute type '{attribute.AttributeType.FullName}' to '{newAttrType?.FullName}', no equivalent constructor found.");
+
+ // create new attribute
+ var newAttr = new CustomAttribute(module.ImportReference(constructor));
+ for (int i = 0; i < argTypes.Length; i++)
+ newAttr.ConstructorArguments.Add(new CustomAttributeArgument(argTypes[i], attribute.ConstructorArguments[i].Value));
+ foreach (var prop in attribute.Properties)
+ newAttr.Properties.Add(new CustomAttributeNamedArgument(prop.Name, prop.Argument));
+ foreach (var field in attribute.Fields)
+ newAttr.Fields.Add(new CustomAttributeNamedArgument(field.Name, field.Argument));
+
+ // swap attribute
+ attributes[attrIndex] = newAttr;
+ rewritten = true;
+ }
+ }
+
+ return rewritten;
+ }
}
}
diff --git a/src/SMAPI/Framework/ModLoading/IInstructionHandler.cs b/src/SMAPI/Framework/ModLoading/IInstructionHandler.cs
index 65b45b08..f9d320a6 100644
--- a/src/SMAPI/Framework/ModLoading/IInstructionHandler.cs
+++ b/src/SMAPI/Framework/ModLoading/IInstructionHandler.cs
@@ -18,6 +18,13 @@ namespace StardewModdingAPI.Framework.ModLoading
*********/
/// <summary>Perform the predefined logic for a method if applicable.</summary>
/// <param name="module">The assembly module containing the instruction.</param>
+ /// <param name="type">The type definition to handle.</param>
+ /// <param name="assemblyMap">Metadata for mapping assemblies to the current platform.</param>
+ /// <param name="platformChanged">Whether the mod was compiled on a different platform.</param>
+ InstructionHandleResult Handle(ModuleDefinition module, TypeDefinition type, PlatformAssemblyMap assemblyMap, bool platformChanged);
+
+ /// <summary>Perform the predefined logic for a method if applicable.</summary>
+ /// <param name="module">The assembly module containing the instruction.</param>
/// <param name="method">The method definition to handle.</param>
/// <param name="assemblyMap">Metadata for mapping assemblies to the current platform.</param>
/// <param name="platformChanged">Whether the mod was compiled on a different platform.</param>
diff --git a/src/SMAPI/Framework/ModLoading/RewriteHelper.cs b/src/SMAPI/Framework/ModLoading/RewriteHelper.cs
index d9a49cfa..553679f9 100644
--- a/src/SMAPI/Framework/ModLoading/RewriteHelper.cs
+++ b/src/SMAPI/Framework/ModLoading/RewriteHelper.cs
@@ -42,6 +42,10 @@ namespace StardewModdingAPI.Framework.ModLoading
/// <param name="reference">The type reference.</param>
public static bool IsSameType(Type type, TypeReference reference)
{
+ //
+ // duplicated by IsSameType(TypeReference, TypeReference) below
+ //
+
// same namespace & name
if (type.Namespace != reference.Namespace || type.Name != reference.Name)
return false;
@@ -66,6 +70,39 @@ namespace StardewModdingAPI.Framework.ModLoading
return true;
}
+ /// <summary>Get whether a type matches a type reference.</summary>
+ /// <param name="type">The defined type.</param>
+ /// <param name="reference">The type reference.</param>
+ public static bool IsSameType(TypeReference type, TypeReference reference)
+ {
+ //
+ // duplicated by IsSameType(Type, TypeReference) above
+ //
+
+ // same namespace & name
+ if (type.Namespace != reference.Namespace || type.Name != reference.Name)
+ return false;
+
+ // same generic parameters
+ if (type.IsGenericInstance)
+ {
+ if (!reference.IsGenericInstance)
+ return false;
+
+ TypeReference[] defGenerics = ((GenericInstanceType)type).GenericArguments.ToArray();
+ TypeReference[] refGenerics = ((GenericInstanceType)reference).GenericArguments.ToArray();
+ if (defGenerics.Length != refGenerics.Length)
+ return false;
+ for (int i = 0; i < defGenerics.Length; i++)
+ {
+ if (!RewriteHelper.IsSameType(defGenerics[i], refGenerics[i]))
+ return false;
+ }
+ }
+
+ return true;
+ }
+
/// <summary>Determine whether two type IDs look like the same type, accounting for placeholder values such as !0.</summary>
/// <param name="typeA">The type ID to compare.</param>
/// <param name="typeB">The other type ID to compare.</param>
@@ -80,6 +117,10 @@ namespace StardewModdingAPI.Framework.ModLoading
/// <param name="reference">The method reference.</param>
public static bool HasMatchingSignature(MethodInfo definition, MethodReference reference)
{
+ //
+ // duplicated by HasMatchingSignature(MethodDefinition, MethodReference) below
+ //
+
// same name
if (definition.Name != reference.Name)
return false;
@@ -97,6 +138,32 @@ namespace StardewModdingAPI.Framework.ModLoading
return true;
}
+ /// <summary>Get whether a method definition matches the signature expected by a method reference.</summary>
+ /// <param name="definition">The method definition.</param>
+ /// <param name="reference">The method reference.</param>
+ public static bool HasMatchingSignature(MethodDefinition definition, MethodReference reference)
+ {
+ //
+ // duplicated by HasMatchingSignature(MethodInfo, MethodReference) above
+ //
+
+ // same name
+ if (definition.Name != reference.Name)
+ return false;
+
+ // same arguments
+ ParameterDefinition[] definitionParameters = definition.Parameters.ToArray();
+ ParameterDefinition[] referenceParameters = reference.Parameters.ToArray();
+ if (referenceParameters.Length != definitionParameters.Length)
+ return false;
+ for (int i = 0; i < referenceParameters.Length; i++)
+ {
+ if (!RewriteHelper.IsSameType(definitionParameters[i].ParameterType, referenceParameters[i].ParameterType))
+ return false;
+ }
+ return true;
+ }
+
/// <summary>Get whether a type has a method whose signature matches the one expected by a method reference.</summary>
/// <param name="type">The type to check.</param>
/// <param name="reference">The method reference.</param>