summaryrefslogtreecommitdiff
path: root/src/StardewModdingAPI
diff options
context:
space:
mode:
Diffstat (limited to 'src/StardewModdingAPI')
-rw-r--r--src/StardewModdingAPI/Constants.cs9
-rw-r--r--src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs100
-rw-r--r--src/StardewModdingAPI/StardewModdingAPI.csproj1
3 files changed, 75 insertions, 35 deletions
diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs
index b9074b6e..4f2f00a1 100644
--- a/src/StardewModdingAPI/Constants.cs
+++ b/src/StardewModdingAPI/Constants.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -112,6 +113,12 @@ namespace StardewModdingAPI
return new PlatformAssemblyMap(targetPlatform, removeAssemblyReferences, targetAssemblies);
}
+ /// <summary>Get method rewriters which fix incompatible method calls in mod assemblies.</summary>
+ internal static IEnumerable<IMethodRewriter> GetMethodRewriters()
+ {
+ yield break;
+ }
+
/*********
** Private field
@@ -123,4 +130,4 @@ namespace StardewModdingAPI
return $"{prefix}_{Game1.uniqueIDForThisGame}";
}
}
-} \ No newline at end of file
+}
diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs
index 43f6aa11..1e97bdcb 100644
--- a/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs
+++ b/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs
@@ -2,6 +2,8 @@
using System.Linq;
using System.Reflection;
using Mono.Cecil;
+using Mono.Cecil.Cil;
+using Mono.Cecil.Rocks;
using StardewModdingAPI.AssemblyRewriters;
namespace StardewModdingAPI.Framework.AssemblyRewriting
@@ -18,9 +20,6 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting
/// <summary>A type => assembly lookup for types which should be rewritten.</summary>
private readonly IDictionary<string, Assembly> TypeAssemblies;
- /// <summary>An assembly => reference cache.</summary>
- private readonly IDictionary<Assembly, AssemblyNameReference> AssemblyNameReferences;
-
/// <summary>Encapsulates monitoring and logging.</summary>
private readonly IMonitor Monitor;
@@ -37,24 +36,18 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting
this.AssemblyMap = assemblyMap;
this.Monitor = monitor;
- // cache assembly metadata
- this.AssemblyNameReferences = assemblyMap.Targets.ToDictionary(assembly => assembly, assembly => AssemblyNameReference.Parse(assembly.FullName));
-
// collect type => assembly lookup
this.TypeAssemblies = new Dictionary<string, Assembly>();
foreach (Assembly assembly in assemblyMap.Targets)
{
- foreach (Module assemblyModule in assembly.Modules)
+ ModuleDefinition module = this.AssemblyMap.TargetModules[assembly];
+ foreach (TypeDefinition type in module.GetTypes())
{
- ModuleDefinition module = ModuleDefinition.ReadModule(assemblyModule.FullyQualifiedName);
- foreach (TypeDefinition type in module.GetTypes())
- {
- if (!type.IsPublic)
- continue; // no need to rewrite
- if (type.Namespace.Contains("<"))
- continue; // ignore assembly metadata
- this.TypeAssemblies[type.FullName] = assembly;
- }
+ if (!type.IsPublic)
+ continue; // no need to rewrite
+ if (type.Namespace.Contains("<"))
+ continue; // ignore assembly metadata
+ this.TypeAssemblies[type.FullName] = assembly;
}
}
}
@@ -64,13 +57,12 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting
public void RewriteAssembly(AssemblyDefinition assembly)
{
ModuleDefinition module = assembly.Modules.Single(); // technically an assembly can have multiple modules, but none of the build tools (including MSBuild) support it; simplify by assuming one module
- bool shouldRewrite = false;
// remove old assembly references
+ bool shouldRewrite = false;
for (int i = 0; i < module.AssemblyReferences.Count; i++)
{
- bool shouldRemove = this.AssemblyMap.RemoveNames.Any(name => module.AssemblyReferences[i].Name == name);
- if (shouldRemove)
+ if (this.AssemblyMap.RemoveNames.Any(name => module.AssemblyReferences[i].Name == name))
{
this.Monitor.Log($"removing reference to {module.AssemblyReferences[i]}", LogLevel.Trace);
shouldRewrite = true;
@@ -78,25 +70,52 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting
i--;
}
}
+ if (!shouldRewrite)
+ return;
- // replace references
- if (shouldRewrite)
+ // add target assembly references
+ foreach (AssemblyNameReference target in this.AssemblyMap.TargetReferences.Values)
{
- // add target assembly references
- foreach (AssemblyNameReference target in this.AssemblyNameReferences.Values)
- {
- this.Monitor.Log($" adding reference to {target}", LogLevel.Trace);
- module.AssemblyReferences.Add(target);
- }
+ this.Monitor.Log($" adding reference to {target}", LogLevel.Trace);
+ module.AssemblyReferences.Add(target);
+ }
- // rewrite type scopes to use target assemblies
- IEnumerable<TypeReference> typeReferences = module.GetTypeReferences().OrderBy(p => p.FullName);
- string lastTypeLogged = null;
- foreach (TypeReference type in typeReferences)
+ // rewrite type scopes to use target assemblies
+ IEnumerable<TypeReference> typeReferences = module.GetTypeReferences().OrderBy(p => p.FullName);
+ string lastTypeLogged = null;
+ foreach (TypeReference type in typeReferences)
+ {
+ this.ChangeTypeScope(type, shouldLog: type.FullName != lastTypeLogged);
+ lastTypeLogged = type.FullName;
+ }
+
+ // rewrite incompatible methods
+ IMethodRewriter[] methodRewriters = Constants.GetMethodRewriters().ToArray();
+ foreach (MethodDefinition method in this.GetMethods(module))
+ {
+ // skip methods with no rewritable method
+ bool hasMethodToRewrite = method.Body.Instructions.Any(op => (op.OpCode == OpCodes.Call || op.OpCode == OpCodes.Callvirt) && methodRewriters.Any(rewriter => rewriter.ShouldRewrite((MethodReference)op.Operand)));
+ if (!hasMethodToRewrite)
+ continue;
+
+ // rewrite method references
+ method.Body.SimplifyMacros();
+ ILProcessor cil = method.Body.GetILProcessor();
+ Instruction[] instructions = cil.Body.Instructions.ToArray();
+ foreach (Instruction op in instructions)
{
- this.ChangeTypeScope(type, shouldLog: type.FullName != lastTypeLogged);
- lastTypeLogged = type.FullName;
+ if (op.OpCode == OpCodes.Call || op.OpCode == OpCodes.Callvirt)
+ {
+ IMethodRewriter rewriter = methodRewriters.FirstOrDefault(p => p.ShouldRewrite((MethodReference)op.Operand));
+ if (rewriter != null)
+ {
+ MethodReference methodRef = (MethodReference)op.Operand;
+ this.Monitor.Log($"rewriting method reference {methodRef.DeclaringType.FullName}.{methodRef.Name}");
+ rewriter.Rewrite(module, cil, op, methodRef, this.AssemblyMap);
+ }
+ }
}
+ method.Body.OptimizeMacros();
}
}
@@ -119,10 +138,23 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting
return;
// replace scope
- AssemblyNameReference assemblyRef = this.AssemblyNameReferences[assembly];
+ AssemblyNameReference assemblyRef = this.AssemblyMap.TargetReferences[assembly];
if (shouldLog)
this.Monitor.Log($"redirecting {type.FullName} from {type.Scope.Name} to {assemblyRef.Name}", LogLevel.Trace);
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/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj
index 6f3bcb4b..f57c1b6f 100644
--- a/src/StardewModdingAPI/StardewModdingAPI.csproj
+++ b/src/StardewModdingAPI/StardewModdingAPI.csproj
@@ -252,5 +252,6 @@
<Copy SourceFiles="$(TargetDir)\$(TargetName).xml" DestinationFolder="$(GamePath)" />
<Copy SourceFiles="$(TargetDir)\Newtonsoft.Json.dll" DestinationFolder="$(GamePath)" />
<Copy SourceFiles="$(TargetDir)\Mono.Cecil.dll" DestinationFolder="$(GamePath)" />
+ <Copy SourceFiles="$(TargetDir)\Mono.Cecil.Rocks.dll" DestinationFolder="$(GamePath)" />
</Target>
</Project> \ No newline at end of file