From 4df199985558caee3275fdd65762bdc3e1d040c6 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 26 Nov 2016 16:10:41 -0500 Subject: move cache struct into its own file (#166) --- .../Framework/AssemblyRewriting/CachePaths.cs | 33 ++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/StardewModdingAPI/Framework/AssemblyRewriting/CachePaths.cs (limited to 'src/StardewModdingAPI/Framework/AssemblyRewriting') diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/CachePaths.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/CachePaths.cs new file mode 100644 index 00000000..17c4d188 --- /dev/null +++ b/src/StardewModdingAPI/Framework/AssemblyRewriting/CachePaths.cs @@ -0,0 +1,33 @@ +namespace StardewModdingAPI.Framework.AssemblyRewriting +{ + /// Contains the paths for an assembly's cached data. + internal struct CachePaths + { + /********* + ** Accessors + *********/ + /// The directory path which contains the assembly. + public string Directory { get; } + + /// The file path of the assembly file. + public string Assembly { get; } + + /// The file path containing the MD5 hash for the assembly. + public string Hash { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The directory path which contains the assembly. + /// The file path of the assembly file. + /// The file path containing the MD5 hash for the assembly. + public CachePaths(string directory, string assembly, string hash) + { + this.Directory = directory; + this.Assembly = assembly; + this.Hash = hash; + } + } +} \ No newline at end of file -- cgit From b06aed66c47d093585600ca0f7ee1e247507e6b8 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 26 Nov 2016 16:12:21 -0500 Subject: rewrite type references in mod assemblies to match target platform (#166) --- .../AssemblyRewriting/AssemblyTypeRewriter.cs | 318 +++++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs (limited to 'src/StardewModdingAPI/Framework/AssemblyRewriting') diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs new file mode 100644 index 00000000..93003a64 --- /dev/null +++ b/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs @@ -0,0 +1,318 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Mono.Cecil; +using Mono.Cecil.Cil; +using CallSite = Mono.Cecil.CallSite; + +namespace StardewModdingAPI.Framework.AssemblyRewriting +{ + /// Rewrites type references. + internal class AssemblyTypeRewriter + { + /********* + ** Properties + *********/ + /// The assemblies to target. Equivalent types will be rewritten to use these assemblies. + private readonly Assembly[] TargetAssemblies; + + /// >The short assembly names to remove as assembly reference, and replace with the . + private readonly string[] RemoveAssemblyNames; + + /// A type => assembly lookup for types which should be rewritten. + private readonly IDictionary TypeAssemblies; + + /// An assembly => reference cache. + private readonly IDictionary AssemblyNameReferences; + + /// An assembly => module cache. + private readonly IDictionary AssemblyModules; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The assembly filenames to target. Equivalent types will be rewritten to use these assemblies. + /// The short assembly names to remove as assembly reference, and replace with the . + public AssemblyTypeRewriter(Assembly[] targetAssemblies, string[] removeAssemblyNames) + { + // save config + this.TargetAssemblies = targetAssemblies; + this.RemoveAssemblyNames = removeAssemblyNames; + + // cache assembly metadata + this.AssemblyNameReferences = targetAssemblies.ToDictionary(assembly => assembly, assembly => AssemblyNameReference.Parse(assembly.FullName)); + this.AssemblyModules = targetAssemblies.ToDictionary(assembly => assembly, assembly => ModuleDefinition.ReadModule(assembly.Modules.Single().FullyQualifiedName)); // technically an assembly can contain multiple modules, but none of the build tools (including MSBuild itself) support it + + // collect type => assembly lookup + this.TypeAssemblies = new Dictionary(); + foreach (Assembly assembly in targetAssemblies) + { + ModuleDefinition module = this.AssemblyModules[assembly]; + foreach (TypeDefinition type in module.GetTypes()) + { + if (!type.IsPublic) + continue; // no need to rewrite + if (type.Namespace.Contains("<")) + continue; // ignore C++ stuff + this.TypeAssemblies[type.FullName] = assembly; + } + } + } + + /// Rewrite the types referenced by an assembly. + /// The assembly to rewrite. + public void RewriteAssembly(AssemblyDefinition assembly) + { + foreach (ModuleDefinition module in assembly.Modules) + { + // rewrite assembly references + bool shouldRewriteTypes = false; + for (int i = 0; i < module.AssemblyReferences.Count; i++) + { + bool shouldRemove = this.RemoveAssemblyNames.Any(name => module.AssemblyReferences[i].Name == name) || this.TargetAssemblies.Any(a => module.AssemblyReferences[i].Name == a.GetName().Name); + if (shouldRemove) + { + shouldRewriteTypes = true; + module.AssemblyReferences.RemoveAt(i); + i--; + } + } + foreach (AssemblyNameReference target in this.AssemblyNameReferences.Values) + { + module.AssemblyReferences.Add(target); + shouldRewriteTypes = true; + } + + // rewrite references + if (shouldRewriteTypes) + { + // rewrite types + foreach (TypeDefinition type in module.GetTypes()) + this.RewriteReferences(type, module); + + // rewrite type references + TypeReference[] refs = (TypeReference[])module.GetTypeReferences(); + for (int i = 0; i < refs.Length; ++i) + refs[i] = this.GetTypeReference(refs[i], module); + } + } + } + + + /********* + ** Private methods + *********/ + /// Rewrite the references for a code object. + /// The type to rewrite. + /// The module being rewritten. + private void RewriteReferences(TypeDefinition type, ModuleDefinition module) + { + // rewrite base type + type.BaseType = this.GetTypeReference(type.BaseType, module); + + // rewrite interfaces + for (int i = 0; i < type.Interfaces.Count; i++) + type.Interfaces[i] = this.GetTypeReference(type.Interfaces[i], module); + + // rewrite events + foreach (EventDefinition @event in type.Events) + { + this.RewriteReferences(@event.AddMethod, module); + this.RewriteReferences(@event.RemoveMethod, module); + this.RewriteReferences(@event.InvokeMethod, module); + } + + // rewrite properties + foreach (PropertyDefinition property in type.Properties) + { + this.RewriteReferences(property.GetMethod, module); + this.RewriteReferences(property.SetMethod, module); + } + + // rewrite methods + foreach (MethodDefinition method in type.Methods) + this.RewriteReferences(method, module); + + // rewrite fields + foreach (FieldDefinition field in type.Fields) + this.RewriteReferences(field, module); + + // rewrite nested types + foreach (TypeDefinition nestedType in type.NestedTypes) + this.RewriteReferences(nestedType, module); + + // rewrite generic parameters + foreach (GenericParameter parameter in type.GenericParameters) + this.RewriteReferences(parameter, module); + + module.Import(type); + } + + /// Rewrite the references for a code object. + /// The method to rewrite. + /// The module being rewritten. + private void RewriteReferences(MethodReference method, ModuleDefinition module) + { + // parameter types + if (method.HasParameters) + { + foreach (ParameterDefinition parameter in method.Parameters) + parameter.ParameterType = this.GetTypeReference(parameter.ParameterType, module); + } + + // return type + method.MethodReturnType.ReturnType = this.GetTypeReference(method.MethodReturnType.ReturnType, module); + + module.Import(method); + } + + /// Rewrite the references for a code object. + /// The method to rewrite. + /// The module being rewritten. + private void RewriteReferences(MethodDefinition method, ModuleDefinition module) + { + if (method == null) + return; + + this.RewriteReferences((MethodReference)method, module); + + // overrides + foreach (MethodReference @override in method.Overrides) + this.RewriteReferences(@override, module); + + // body + if (method.HasBody) + { + // this + if (method.Body.ThisParameter != null) + method.Body.ThisParameter.ParameterType = this.GetTypeReference(method.Body.ThisParameter.ParameterType, module); + + // variables + if (method.Body.HasVariables) + { + foreach (VariableDefinition variable in method.Body.Variables) + variable.VariableType = this.GetTypeReference(variable.VariableType, module); + } + + // instructions + foreach (Instruction instruction in method.Body.Instructions) + { + object operand = instruction.Operand; + + // type + { + TypeReference type = operand as TypeReference; + if (type != null) + { + instruction.Operand = this.GetTypeReference(type, module); + continue; + } + } + + // method + { + MethodReference methodRef = operand as MethodReference; + if (methodRef != null) + { + this.RewriteReferences(methodRef, module); + continue; + } + } + + // field + { + FieldReference field = operand as FieldReference; + if (field != null) + { + this.RewriteReferences(field, module); + continue; + } + } + + // variable + { + VariableDefinition variable = operand as VariableDefinition; + if (variable != null) + { + variable.VariableType = this.GetTypeReference(variable.VariableType, module); + continue; + } + } + + // parameter + { + ParameterDefinition parameter = operand as ParameterDefinition; + if (parameter != null) + { + parameter.ParameterType = this.GetTypeReference(parameter.ParameterType, module); + continue; + } + } + + // call site + { + CallSite call = operand as CallSite; + if (call != null) + { + foreach (ParameterDefinition parameter in call.Parameters) + parameter.ParameterType = this.GetTypeReference(parameter.ParameterType, module); + call.ReturnType = this.GetTypeReference(call.ReturnType, module); + } + } + } + } + + module.Import(method); + } + + /// Rewrite the references for a code object. + /// The generic parameter to rewrite. + /// The module being rewritten. + private void RewriteReferences(GenericParameter parameter, ModuleDefinition module) + { + // constraints + for (int i = 0; i < parameter.Constraints.Count; i++) + parameter.Constraints[i] = this.GetTypeReference(parameter.Constraints[i], module); + + // generic parameters + foreach (GenericParameter genericParam in parameter.GenericParameters) + this.RewriteReferences(genericParam, module); + } + + /// Rewrite the references for a code object. + /// The field to rewrite. + /// The module being rewritten. + private void RewriteReferences(FieldReference field, ModuleDefinition module) + { + field.DeclaringType = this.GetTypeReference(field.DeclaringType, module); + field.FieldType = this.GetTypeReference(field.FieldType, module); + module.Import(field); + } + + /// Get the correct reference to use for compatibility with the current platform. + /// The type reference to rewrite. + /// The module being rewritten. + private TypeReference GetTypeReference(TypeReference type, ModuleDefinition module) + { + // check skip conditions + if (type == null) + return null; + if (type.FullName.StartsWith("System.")) + return type; + + // get assembly + Assembly assembly; + if (!this.TypeAssemblies.TryGetValue(type.FullName, out assembly)) + return type; + + // replace type + AssemblyNameReference newAssembly = this.AssemblyNameReferences[assembly]; + ModuleDefinition newModule = this.AssemblyModules[assembly]; + type = new TypeReference(type.Namespace, type.Name, newModule, newAssembly); + + return module.Import(type); + } + } +} -- cgit From 2154b6de95bc97e3412b0800d3e2809bd2a1e544 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 26 Nov 2016 16:14:10 -0500 Subject: use simpler, non-broken approach for rewriting mod type references (#166) --- .../AssemblyRewriting/AssemblyTypeRewriter.cs | 255 ++------------------- 1 file changed, 24 insertions(+), 231 deletions(-) (limited to 'src/StardewModdingAPI/Framework/AssemblyRewriting') diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs index 93003a64..7a339266 100644 --- a/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs +++ b/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs @@ -2,8 +2,6 @@ using System.Linq; using System.Reflection; using Mono.Cecil; -using Mono.Cecil.Cil; -using CallSite = Mono.Cecil.CallSite; namespace StardewModdingAPI.Framework.AssemblyRewriting { @@ -25,9 +23,6 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting /// An assembly => reference cache. private readonly IDictionary AssemblyNameReferences; - /// An assembly => module cache. - private readonly IDictionary AssemblyModules; - /********* ** Public methods @@ -43,20 +38,22 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting // cache assembly metadata this.AssemblyNameReferences = targetAssemblies.ToDictionary(assembly => assembly, assembly => AssemblyNameReference.Parse(assembly.FullName)); - this.AssemblyModules = targetAssemblies.ToDictionary(assembly => assembly, assembly => ModuleDefinition.ReadModule(assembly.Modules.Single().FullyQualifiedName)); // technically an assembly can contain multiple modules, but none of the build tools (including MSBuild itself) support it // collect type => assembly lookup this.TypeAssemblies = new Dictionary(); foreach (Assembly assembly in targetAssemblies) { - ModuleDefinition module = this.AssemblyModules[assembly]; - foreach (TypeDefinition type in module.GetTypes()) + foreach (Module assemblyModule in assembly.Modules) { - if (!type.IsPublic) - continue; // no need to rewrite - if (type.Namespace.Contains("<")) - continue; // ignore C++ stuff - this.TypeAssemblies[type.FullName] = assembly; + 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; + } } } } @@ -67,36 +64,25 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting { foreach (ModuleDefinition module in assembly.Modules) { - // rewrite assembly references - bool shouldRewriteTypes = false; + // remove old assembly references for (int i = 0; i < module.AssemblyReferences.Count; i++) { bool shouldRemove = this.RemoveAssemblyNames.Any(name => module.AssemblyReferences[i].Name == name) || this.TargetAssemblies.Any(a => module.AssemblyReferences[i].Name == a.GetName().Name); if (shouldRemove) { - shouldRewriteTypes = true; module.AssemblyReferences.RemoveAt(i); i--; } } + + // add target assembly references foreach (AssemblyNameReference target in this.AssemblyNameReferences.Values) - { module.AssemblyReferences.Add(target); - shouldRewriteTypes = true; - } - - // rewrite references - if (shouldRewriteTypes) - { - // rewrite types - foreach (TypeDefinition type in module.GetTypes()) - this.RewriteReferences(type, module); - // rewrite type references - TypeReference[] refs = (TypeReference[])module.GetTypeReferences(); - for (int i = 0; i < refs.Length; ++i) - refs[i] = this.GetTypeReference(refs[i], module); - } + // rewrite type scopes to use target assemblies + TypeReference[] refs = (TypeReference[])module.GetTypeReferences(); + foreach (TypeReference type in refs) + this.ChangeTypeScope(type); } } @@ -104,215 +90,22 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting /********* ** Private methods *********/ - /// Rewrite the references for a code object. - /// The type to rewrite. - /// The module being rewritten. - private void RewriteReferences(TypeDefinition type, ModuleDefinition module) - { - // rewrite base type - type.BaseType = this.GetTypeReference(type.BaseType, module); - - // rewrite interfaces - for (int i = 0; i < type.Interfaces.Count; i++) - type.Interfaces[i] = this.GetTypeReference(type.Interfaces[i], module); - - // rewrite events - foreach (EventDefinition @event in type.Events) - { - this.RewriteReferences(@event.AddMethod, module); - this.RewriteReferences(@event.RemoveMethod, module); - this.RewriteReferences(@event.InvokeMethod, module); - } - - // rewrite properties - foreach (PropertyDefinition property in type.Properties) - { - this.RewriteReferences(property.GetMethod, module); - this.RewriteReferences(property.SetMethod, module); - } - - // rewrite methods - foreach (MethodDefinition method in type.Methods) - this.RewriteReferences(method, module); - - // rewrite fields - foreach (FieldDefinition field in type.Fields) - this.RewriteReferences(field, module); - - // rewrite nested types - foreach (TypeDefinition nestedType in type.NestedTypes) - this.RewriteReferences(nestedType, module); - - // rewrite generic parameters - foreach (GenericParameter parameter in type.GenericParameters) - this.RewriteReferences(parameter, module); - - module.Import(type); - } - - /// Rewrite the references for a code object. - /// The method to rewrite. - /// The module being rewritten. - private void RewriteReferences(MethodReference method, ModuleDefinition module) - { - // parameter types - if (method.HasParameters) - { - foreach (ParameterDefinition parameter in method.Parameters) - parameter.ParameterType = this.GetTypeReference(parameter.ParameterType, module); - } - - // return type - method.MethodReturnType.ReturnType = this.GetTypeReference(method.MethodReturnType.ReturnType, module); - - module.Import(method); - } - - /// Rewrite the references for a code object. - /// The method to rewrite. - /// The module being rewritten. - private void RewriteReferences(MethodDefinition method, ModuleDefinition module) - { - if (method == null) - return; - - this.RewriteReferences((MethodReference)method, module); - - // overrides - foreach (MethodReference @override in method.Overrides) - this.RewriteReferences(@override, module); - - // body - if (method.HasBody) - { - // this - if (method.Body.ThisParameter != null) - method.Body.ThisParameter.ParameterType = this.GetTypeReference(method.Body.ThisParameter.ParameterType, module); - - // variables - if (method.Body.HasVariables) - { - foreach (VariableDefinition variable in method.Body.Variables) - variable.VariableType = this.GetTypeReference(variable.VariableType, module); - } - - // instructions - foreach (Instruction instruction in method.Body.Instructions) - { - object operand = instruction.Operand; - - // type - { - TypeReference type = operand as TypeReference; - if (type != null) - { - instruction.Operand = this.GetTypeReference(type, module); - continue; - } - } - - // method - { - MethodReference methodRef = operand as MethodReference; - if (methodRef != null) - { - this.RewriteReferences(methodRef, module); - continue; - } - } - - // field - { - FieldReference field = operand as FieldReference; - if (field != null) - { - this.RewriteReferences(field, module); - continue; - } - } - - // variable - { - VariableDefinition variable = operand as VariableDefinition; - if (variable != null) - { - variable.VariableType = this.GetTypeReference(variable.VariableType, module); - continue; - } - } - - // parameter - { - ParameterDefinition parameter = operand as ParameterDefinition; - if (parameter != null) - { - parameter.ParameterType = this.GetTypeReference(parameter.ParameterType, module); - continue; - } - } - - // call site - { - CallSite call = operand as CallSite; - if (call != null) - { - foreach (ParameterDefinition parameter in call.Parameters) - parameter.ParameterType = this.GetTypeReference(parameter.ParameterType, module); - call.ReturnType = this.GetTypeReference(call.ReturnType, module); - } - } - } - } - - module.Import(method); - } - - /// Rewrite the references for a code object. - /// The generic parameter to rewrite. - /// The module being rewritten. - private void RewriteReferences(GenericParameter parameter, ModuleDefinition module) - { - // constraints - for (int i = 0; i < parameter.Constraints.Count; i++) - parameter.Constraints[i] = this.GetTypeReference(parameter.Constraints[i], module); - - // generic parameters - foreach (GenericParameter genericParam in parameter.GenericParameters) - this.RewriteReferences(genericParam, module); - } - - /// Rewrite the references for a code object. - /// The field to rewrite. - /// The module being rewritten. - private void RewriteReferences(FieldReference field, ModuleDefinition module) - { - field.DeclaringType = this.GetTypeReference(field.DeclaringType, module); - field.FieldType = this.GetTypeReference(field.FieldType, module); - module.Import(field); - } - /// Get the correct reference to use for compatibility with the current platform. /// The type reference to rewrite. - /// The module being rewritten. - private TypeReference GetTypeReference(TypeReference type, ModuleDefinition module) + private void ChangeTypeScope(TypeReference type) { // check skip conditions - if (type == null) - return null; - if (type.FullName.StartsWith("System.")) - return type; + if (type == null || type.FullName.StartsWith("System.")) + return; // get assembly Assembly assembly; if (!this.TypeAssemblies.TryGetValue(type.FullName, out assembly)) - return type; - - // replace type - AssemblyNameReference newAssembly = this.AssemblyNameReferences[assembly]; - ModuleDefinition newModule = this.AssemblyModules[assembly]; - type = new TypeReference(type.Namespace, type.Name, newModule, newAssembly); + return; - return module.Import(type); + // replace scope + AssemblyNameReference assemblyRef = this.AssemblyNameReferences[assembly]; + type.Scope = assemblyRef; } } } -- cgit From 8917fb6697b5eae0c14bcf2437aef7e9a8d51abb Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 27 Nov 2016 12:08:00 -0500 Subject: only rewrite assemblies if needed (#166) --- .../AssemblyRewriting/AssemblyTypeRewriter.cs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) (limited to 'src/StardewModdingAPI/Framework/AssemblyRewriting') diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs index 7a339266..2bfb43e4 100644 --- a/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs +++ b/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs @@ -65,24 +65,29 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting foreach (ModuleDefinition module in assembly.Modules) { // remove old assembly references + bool shouldRewrite = false; for (int i = 0; i < module.AssemblyReferences.Count; i++) { - bool shouldRemove = this.RemoveAssemblyNames.Any(name => module.AssemblyReferences[i].Name == name) || this.TargetAssemblies.Any(a => module.AssemblyReferences[i].Name == a.GetName().Name); + bool shouldRemove = this.RemoveAssemblyNames.Any(name => module.AssemblyReferences[i].Name == name); if (shouldRemove) { + shouldRewrite = true; module.AssemblyReferences.RemoveAt(i); i--; } } - // add target assembly references - foreach (AssemblyNameReference target in this.AssemblyNameReferences.Values) - module.AssemblyReferences.Add(target); + // replace references + if (shouldRewrite) + { + // add target assembly references + foreach (AssemblyNameReference target in this.AssemblyNameReferences.Values) + module.AssemblyReferences.Add(target); - // rewrite type scopes to use target assemblies - TypeReference[] refs = (TypeReference[])module.GetTypeReferences(); - foreach (TypeReference type in refs) - this.ChangeTypeScope(type); + // rewrite type scopes to use target assemblies + foreach (TypeReference type in module.GetTypeReferences()) + this.ChangeTypeScope(type); + } } } -- cgit From 0d94c628bbc1d1ab098e0a90ee5758ee69694887 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 27 Nov 2016 12:27:44 -0500 Subject: add trace logs when rewriting an assembly (#166) --- .../AssemblyRewriting/AssemblyTypeRewriter.cs | 58 ++++++++++++++-------- 1 file changed, 37 insertions(+), 21 deletions(-) (limited to 'src/StardewModdingAPI/Framework/AssemblyRewriting') diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs index 2bfb43e4..7081df15 100644 --- a/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs +++ b/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs @@ -23,6 +23,9 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting /// An assembly => reference cache. private readonly IDictionary AssemblyNameReferences; + /// Encapsulates monitoring and logging. + private readonly IMonitor Monitor; + /********* ** Public methods @@ -30,11 +33,13 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting /// Construct an instance. /// The assembly filenames to target. Equivalent types will be rewritten to use these assemblies. /// The short assembly names to remove as assembly reference, and replace with the . - public AssemblyTypeRewriter(Assembly[] targetAssemblies, string[] removeAssemblyNames) + /// Encapsulates monitoring and logging. + public AssemblyTypeRewriter(Assembly[] targetAssemblies, string[] removeAssemblyNames, IMonitor monitor) { // save config this.TargetAssemblies = targetAssemblies; this.RemoveAssemblyNames = removeAssemblyNames; + this.Monitor = monitor; // cache assembly metadata this.AssemblyNameReferences = targetAssemblies.ToDictionary(assembly => assembly, assembly => AssemblyNameReference.Parse(assembly.FullName)); @@ -62,31 +67,39 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting /// The assembly to rewrite. public void RewriteAssembly(AssemblyDefinition assembly) { - foreach (ModuleDefinition module in assembly.Modules) + 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 + for (int i = 0; i < module.AssemblyReferences.Count; i++) { - // remove old assembly references - bool shouldRewrite = false; - for (int i = 0; i < module.AssemblyReferences.Count; i++) + bool shouldRemove = this.RemoveAssemblyNames.Any(name => module.AssemblyReferences[i].Name == name); + if (shouldRemove) { - bool shouldRemove = this.RemoveAssemblyNames.Any(name => module.AssemblyReferences[i].Name == name); - if (shouldRemove) - { - shouldRewrite = true; - module.AssemblyReferences.RemoveAt(i); - i--; - } + this.Monitor.Log($"removing reference to {module.AssemblyReferences[i]}", LogLevel.Trace); + shouldRewrite = true; + module.AssemblyReferences.RemoveAt(i); + i--; } + } - // replace references - if (shouldRewrite) + // replace references + if (shouldRewrite) + { + // add target assembly references + foreach (AssemblyNameReference target in this.AssemblyNameReferences.Values) { - // add target assembly references - foreach (AssemblyNameReference target in this.AssemblyNameReferences.Values) - module.AssemblyReferences.Add(target); + this.Monitor.Log($" adding reference to {target}", LogLevel.Trace); + module.AssemblyReferences.Add(target); + } - // rewrite type scopes to use target assemblies - foreach (TypeReference type in module.GetTypeReferences()) - this.ChangeTypeScope(type); + // rewrite type scopes to use target assemblies + IEnumerable 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; } } } @@ -97,7 +110,8 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting *********/ /// Get the correct reference to use for compatibility with the current platform. /// The type reference to rewrite. - private void ChangeTypeScope(TypeReference type) + /// Whether to log a message. + private void ChangeTypeScope(TypeReference type, bool shouldLog) { // check skip conditions if (type == null || type.FullName.StartsWith("System.")) @@ -110,6 +124,8 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting // replace scope AssemblyNameReference assemblyRef = this.AssemblyNameReferences[assembly]; + if (shouldLog) + this.Monitor.Log($"redirecting {type.FullName} from {type.Scope.Name} to {assemblyRef.Name}", LogLevel.Trace); type.Scope = assemblyRef; } } -- cgit From f7b8879011873fa8f7a3d5dd7db27254bfc90469 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 27 Nov 2016 15:56:47 -0500 Subject: supplement assembly resolution for Mono (#166) --- .../AssemblyRewriting/AssemblyTypeRewriter.cs | 21 +++++-------- .../AssemblyRewriting/PlatformAssemblyMap.cs | 35 ++++++++++++++++++++++ 2 files changed, 43 insertions(+), 13 deletions(-) create mode 100644 src/StardewModdingAPI/Framework/AssemblyRewriting/PlatformAssemblyMap.cs (limited to 'src/StardewModdingAPI/Framework/AssemblyRewriting') diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs index 7081df15..66c36c03 100644 --- a/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs +++ b/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs @@ -11,11 +11,8 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting /********* ** Properties *********/ - /// The assemblies to target. Equivalent types will be rewritten to use these assemblies. - private readonly Assembly[] TargetAssemblies; - - /// >The short assembly names to remove as assembly reference, and replace with the . - private readonly string[] RemoveAssemblyNames; + /// Metadata for mapping assemblies to the current . + private readonly PlatformAssemblyMap AssemblyMap; /// A type => assembly lookup for types which should be rewritten. private readonly IDictionary TypeAssemblies; @@ -31,22 +28,20 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting ** Public methods *********/ /// Construct an instance. - /// The assembly filenames to target. Equivalent types will be rewritten to use these assemblies. - /// The short assembly names to remove as assembly reference, and replace with the . + /// Metadata for mapping assemblies to the current . /// Encapsulates monitoring and logging. - public AssemblyTypeRewriter(Assembly[] targetAssemblies, string[] removeAssemblyNames, IMonitor monitor) + public AssemblyTypeRewriter(PlatformAssemblyMap assemblyMap, IMonitor monitor) { // save config - this.TargetAssemblies = targetAssemblies; - this.RemoveAssemblyNames = removeAssemblyNames; + this.AssemblyMap = assemblyMap; this.Monitor = monitor; // cache assembly metadata - this.AssemblyNameReferences = targetAssemblies.ToDictionary(assembly => assembly, assembly => AssemblyNameReference.Parse(assembly.FullName)); + this.AssemblyNameReferences = assemblyMap.Targets.ToDictionary(assembly => assembly, assembly => AssemblyNameReference.Parse(assembly.FullName)); // collect type => assembly lookup this.TypeAssemblies = new Dictionary(); - foreach (Assembly assembly in targetAssemblies) + foreach (Assembly assembly in assemblyMap.Targets) { foreach (Module assemblyModule in assembly.Modules) { @@ -73,7 +68,7 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting // remove old assembly references for (int i = 0; i < module.AssemblyReferences.Count; i++) { - bool shouldRemove = this.RemoveAssemblyNames.Any(name => module.AssemblyReferences[i].Name == name); + bool shouldRemove = this.AssemblyMap.RemoveNames.Any(name => module.AssemblyReferences[i].Name == name); if (shouldRemove) { this.Monitor.Log($"removing reference to {module.AssemblyReferences[i]}", LogLevel.Trace); diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/PlatformAssemblyMap.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/PlatformAssemblyMap.cs new file mode 100644 index 00000000..3e6cec8a --- /dev/null +++ b/src/StardewModdingAPI/Framework/AssemblyRewriting/PlatformAssemblyMap.cs @@ -0,0 +1,35 @@ +using System.Reflection; + +namespace StardewModdingAPI.Framework.AssemblyRewriting +{ + /// Metadata for mapping assemblies to the current . + internal class PlatformAssemblyMap + { + /********* + ** Accessors + *********/ + /// The target game platform. + public readonly Platform TargetPlatform; + + /// The short assembly names to remove as assembly reference, and replace with the . These should be short names (like "Stardew Valley"). + public readonly string[] RemoveNames; + + /// The assembly filenames to target. Equivalent types should be rewritten to use these assemblies. + public readonly Assembly[] Targets; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The target game platform. + /// The assembly short names to remove (like Stardew Valley). + /// The assemblies to target. + public PlatformAssemblyMap(Platform targetPlatform, string[] removeAssemblyNames, Assembly[] targetAssemblies) + { + this.TargetPlatform = targetPlatform; + this.RemoveNames = removeAssemblyNames; + this.Targets = targetAssemblies; + } + } +} \ No newline at end of file -- cgit From 5470e95bf59e5e3bae249ead6909163390fb2af3 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 29 Nov 2016 14:02:59 -0500 Subject: add separate project to support upcoming IL rewriting (#166) --- .../AssemblyRewriting/AssemblyTypeRewriter.cs | 1 + .../AssemblyRewriting/PlatformAssemblyMap.cs | 35 ---------------------- 2 files changed, 1 insertion(+), 35 deletions(-) delete mode 100644 src/StardewModdingAPI/Framework/AssemblyRewriting/PlatformAssemblyMap.cs (limited to 'src/StardewModdingAPI/Framework/AssemblyRewriting') diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs index 66c36c03..43f6aa11 100644 --- a/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs +++ b/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Reflection; using Mono.Cecil; +using StardewModdingAPI.AssemblyRewriters; namespace StardewModdingAPI.Framework.AssemblyRewriting { diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/PlatformAssemblyMap.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/PlatformAssemblyMap.cs deleted file mode 100644 index 3e6cec8a..00000000 --- a/src/StardewModdingAPI/Framework/AssemblyRewriting/PlatformAssemblyMap.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; - -namespace StardewModdingAPI.Framework.AssemblyRewriting -{ - /// Metadata for mapping assemblies to the current . - internal class PlatformAssemblyMap - { - /********* - ** Accessors - *********/ - /// The target game platform. - public readonly Platform TargetPlatform; - - /// The short assembly names to remove as assembly reference, and replace with the . These should be short names (like "Stardew Valley"). - public readonly string[] RemoveNames; - - /// The assembly filenames to target. Equivalent types should be rewritten to use these assemblies. - public readonly Assembly[] Targets; - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The target game platform. - /// The assembly short names to remove (like Stardew Valley). - /// The assemblies to target. - public PlatformAssemblyMap(Platform targetPlatform, string[] removeAssemblyNames, Assembly[] targetAssemblies) - { - this.TargetPlatform = targetPlatform; - this.RemoveNames = removeAssemblyNames; - this.Targets = targetAssemblies; - } - } -} \ No newline at end of file -- cgit From cc4d3c1cf8755467acc4c72d25bf1e03fd8c4cba Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 29 Nov 2016 14:03:14 -0500 Subject: add framework for rewriting incompatible methods (#166) --- .../AssemblyRewriting/AssemblyTypeRewriter.cs | 100 ++++++++++++++------- 1 file changed, 66 insertions(+), 34 deletions(-) (limited to 'src/StardewModdingAPI/Framework/AssemblyRewriting') 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 /// A type => assembly lookup for types which should be rewritten. private readonly IDictionary TypeAssemblies; - /// An assembly => reference cache. - private readonly IDictionary AssemblyNameReferences; - /// Encapsulates monitoring and logging. 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(); 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 typeReferences = module.GetTypeReferences().OrderBy(p => p.FullName); - string lastTypeLogged = null; - foreach (TypeReference type in typeReferences) + // rewrite type scopes to use target assemblies + IEnumerable 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; } + + /// Get all methods in a module. + /// The module to search. + private IEnumerable GetMethods(ModuleDefinition module) + { + return ( + from type in module.GetTypes() + where type.HasMethods + from method in type.Methods + where method.HasBody + select method + ); + } } } -- cgit