From 8bf3ef118a822afd1c7d7f80f6cf6eaeed346167 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 26 Mar 2017 19:17:48 -0400 Subject: add support for rewriting method definitions (#254) --- .../Finders/EventFinder.cs | 19 +++++++++--- .../Finders/FieldFinder.cs | 17 ++++++++-- .../Finders/MethodFinder.cs | 17 ++++++++-- .../Finders/TypeFinder.cs | 36 ++++++++++++++++++++-- .../IInstructionRewriter.cs | 9 ++++++ .../Rewriters/FieldReplaceRewriter.cs | 2 +- .../Rewriters/FieldToPropertyRewriter.cs | 2 +- .../Rewriters/MethodParentRewriter.cs | 12 ++++++++ src/StardewModdingAPI/Framework/AssemblyLoader.cs | 20 ++++++++++++ 9 files changed, 119 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/EventFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/EventFinder.cs index bcceee32..c0051469 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/EventFinder.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/EventFinder.cs @@ -37,6 +37,18 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders this.NounPhrase = nounPhrase ?? $"{fullTypeName}.{eventName} event"; } + /// Rewrite a method definition for compatibility. + /// The module being rewritten. + /// The method definition to rewrite. + /// Metadata for mapping assemblies to the current platform. + /// Whether the mod was compiled on a different platform. + /// Returns whether the instruction was rewritten. + /// The CIL instruction is not compatible, and can't be rewritten. + public virtual bool Rewrite(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged) + { + return false; + } + /// Rewrite a CIL instruction for compatibility. /// The module being rewritten. /// The CIL rewriter. @@ -45,9 +57,9 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders /// Whether the mod was compiled on a different platform. /// Returns whether the instruction was rewritten. /// The CIL instruction is not compatible, and can't be rewritten. - public bool Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) + public virtual bool Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) { - if (!this.IsMatch(instruction, platformChanged)) + if (!this.IsMatch(instruction)) return false; throw new IncompatibleInstructionException(this.NounPhrase); @@ -59,8 +71,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders *********/ /// Get whether a CIL instruction matches. /// The IL instruction. - /// Whether the mod was compiled on a different platform. - protected bool IsMatch(Instruction instruction, bool platformChanged) + protected bool IsMatch(Instruction instruction) { MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); return diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/FieldFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/FieldFinder.cs index cdfc3bd5..b44883e9 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/FieldFinder.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/FieldFinder.cs @@ -37,6 +37,18 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders this.NounPhrase = nounPhrase ?? $"{fullTypeName}.{fieldName} field"; } + /// Rewrite a method definition for compatibility. + /// The module being rewritten. + /// The method definition to rewrite. + /// Metadata for mapping assemblies to the current platform. + /// Whether the mod was compiled on a different platform. + /// Returns whether the instruction was rewritten. + /// The CIL instruction is not compatible, and can't be rewritten. + public virtual bool Rewrite(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged) + { + return false; + } + /// Rewrite a CIL instruction for compatibility. /// The module being rewritten. /// The CIL rewriter. @@ -47,7 +59,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders /// The CIL instruction is not compatible, and can't be rewritten. public virtual bool Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) { - if (!this.IsMatch(instruction, platformChanged)) + if (!this.IsMatch(instruction)) return false; throw new IncompatibleInstructionException(this.NounPhrase); @@ -59,8 +71,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders *********/ /// Get whether a CIL instruction matches. /// The IL instruction. - /// Whether the mod was compiled on a different platform. - protected bool IsMatch(Instruction instruction, bool platformChanged) + protected bool IsMatch(Instruction instruction) { FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); return diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/MethodFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/MethodFinder.cs index 2efcbb0f..19dda58a 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/MethodFinder.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/MethodFinder.cs @@ -37,6 +37,18 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders this.NounPhrase = nounPhrase ?? $"{fullTypeName}.{methodName} method"; } + /// Rewrite a method definition for compatibility. + /// The module being rewritten. + /// The method definition to rewrite. + /// Metadata for mapping assemblies to the current platform. + /// Whether the mod was compiled on a different platform. + /// Returns whether the instruction was rewritten. + /// The CIL instruction is not compatible, and can't be rewritten. + public virtual bool Rewrite(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged) + { + return false; + } + /// Rewrite a CIL instruction for compatibility. /// The module being rewritten. /// The CIL rewriter. @@ -47,7 +59,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders /// The CIL instruction is not compatible, and can't be rewritten. public bool Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) { - if (!this.IsMatch(instruction, platformChanged)) + if (!this.IsMatch(instruction)) return false; throw new IncompatibleInstructionException(this.NounPhrase); @@ -59,8 +71,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders *********/ /// Get whether a CIL instruction matches. /// The IL instruction. - /// Whether the mod was compiled on a different platform. - protected bool IsMatch(Instruction instruction, bool platformChanged) + protected bool IsMatch(Instruction instruction) { MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); return diff --git a/src/StardewModdingAPI.AssemblyRewriters/Finders/TypeFinder.cs b/src/StardewModdingAPI.AssemblyRewriters/Finders/TypeFinder.cs index 96cbb229..0e4d6824 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Finders/TypeFinder.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Finders/TypeFinder.cs @@ -33,6 +33,21 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders this.NounPhrase = nounPhrase ?? $"{fullTypeName} type"; } + /// Rewrite a method definition for compatibility. + /// The module being rewritten. + /// The method definition to rewrite. + /// Metadata for mapping assemblies to the current platform. + /// Whether the mod was compiled on a different platform. + /// Returns whether the instruction was rewritten. + /// The CIL instruction is not compatible, and can't be rewritten. + public virtual bool Rewrite(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged) + { + if (!this.IsMatch(method)) + return false; + + throw new IncompatibleInstructionException(this.NounPhrase); + } + /// Rewrite a CIL instruction for compatibility. /// The module being rewritten. /// The CIL rewriter. @@ -43,7 +58,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders /// The CIL instruction is not compatible, and can't be rewritten. public virtual bool Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) { - if (!this.IsMatch(instruction, platformChanged)) + if (!this.IsMatch(instruction)) return false; throw new IncompatibleInstructionException(this.NounPhrase); @@ -53,10 +68,25 @@ namespace StardewModdingAPI.AssemblyRewriters.Finders /********* ** Protected methods *********/ + /// Get whether a CIL instruction matches. + /// The method deifnition. + protected bool IsMatch(MethodDefinition method) + { + if (method.ReturnType.FullName == this.FullTypeName) + return true; + + foreach (VariableDefinition variable in method.Body.Variables) + { + if (variable.VariableType.FullName == this.FullTypeName) + return true; + } + + return false; + } + /// Get whether a CIL instruction matches. /// The IL instruction. - /// Whether the mod was compiled on a different platform. - protected bool IsMatch(Instruction instruction, bool platformChanged) + protected bool IsMatch(Instruction instruction) { string fullName = this.FullTypeName; diff --git a/src/StardewModdingAPI.AssemblyRewriters/IInstructionRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/IInstructionRewriter.cs index 3a7b1365..2f16b23d 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/IInstructionRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/IInstructionRewriter.cs @@ -16,6 +16,15 @@ namespace StardewModdingAPI.AssemblyRewriters /********* ** Methods *********/ + /// Rewrite a method definition for compatibility. + /// The module being rewritten. + /// The method definition to rewrite. + /// Metadata for mapping assemblies to the current platform. + /// Whether the mod was compiled on a different platform. + /// Returns whether the instruction was rewritten. + /// The CIL instruction is not compatible, and can't be rewritten. + bool Rewrite(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged); + /// Rewrite a CIL instruction for compatibility. /// The module being rewritten. /// The CIL rewriter. diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldReplaceRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldReplaceRewriter.cs index 95663c49..73844073 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldReplaceRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldReplaceRewriter.cs @@ -42,7 +42,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters /// The CIL instruction is not compatible, and can't be rewritten. public override bool Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) { - if (!this.IsMatch(instruction, platformChanged)) + if (!this.IsMatch(instruction)) return false; FieldReference newRef = module.Import(this.ToField); diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldToPropertyRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldToPropertyRewriter.cs index a25f3fef..3f57042d 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldToPropertyRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/FieldToPropertyRewriter.cs @@ -42,7 +42,7 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters /// The CIL instruction is not compatible, and can't be rewritten. public override bool Rewrite(ModuleDefinition module, ILProcessor cil, Instruction instruction, PlatformAssemblyMap assemblyMap, bool platformChanged) { - if (!this.IsMatch(instruction, platformChanged)) + if (!this.IsMatch(instruction)) return false; string methodPrefix = instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Ldfld ? "get" : "set"; diff --git a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/MethodParentRewriter.cs b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/MethodParentRewriter.cs index 3ec8c704..035ef211 100644 --- a/src/StardewModdingAPI.AssemblyRewriters/Rewriters/MethodParentRewriter.cs +++ b/src/StardewModdingAPI.AssemblyRewriters/Rewriters/MethodParentRewriter.cs @@ -43,6 +43,18 @@ namespace StardewModdingAPI.AssemblyRewriters.Rewriters this.OnlyIfPlatformChanged = onlyIfPlatformChanged; } + /// Rewrite a method definition for compatibility. + /// The module being rewritten. + /// The method definition to rewrite. + /// Metadata for mapping assemblies to the current platform. + /// Whether the mod was compiled on a different platform. + /// Returns whether the instruction was rewritten. + /// The CIL instruction is not compatible, and can't be rewritten. + public bool Rewrite(ModuleDefinition module, MethodDefinition method, PlatformAssemblyMap assemblyMap, bool platformChanged) + { + return false; + } + /// Rewrite a CIL instruction for compatibility. /// The module being rewritten. /// The CIL rewriter. diff --git a/src/StardewModdingAPI/Framework/AssemblyLoader.cs b/src/StardewModdingAPI/Framework/AssemblyLoader.cs index 5d00c525..f6fe89f5 100644 --- a/src/StardewModdingAPI/Framework/AssemblyLoader.cs +++ b/src/StardewModdingAPI/Framework/AssemblyLoader.cs @@ -198,6 +198,26 @@ namespace StardewModdingAPI.Framework IInstructionRewriter[] rewriters = Constants.GetRewriters().ToArray(); foreach (MethodDefinition method in this.GetMethods(module)) { + // check method definition + foreach (IInstructionRewriter rewriter in rewriters) + { + try + { + if (rewriter.Rewrite(module, method, this.AssemblyMap, platformChanged)) + { + this.LogOnce(this.Monitor, loggedMessages, $"Rewrote {assembly.Name.Name} to fix {rewriter.NounPhrase}..."); + anyRewritten = true; + } + } + catch (IncompatibleInstructionException) + { + if (!assumeCompatible) + throw new IncompatibleInstructionException(rewriter.NounPhrase, $"Found an incompatible CIL instruction ({rewriter.NounPhrase}) while loading assembly {assembly.Name.Name}."); + this.LogOnce(this.Monitor, loggedMessages, $"Found an incompatible CIL instruction ({rewriter.NounPhrase}) while loading assembly {assembly.Name.Name}, but SMAPI is configured to allow it anyway. The mod may crash or behave unexpectedly.", LogLevel.Warn); + } + } + + // check CIL instructions ILProcessor cil = method.Body.GetILProcessor(); foreach (Instruction instruction in cil.Body.Instructions.ToArray()) { -- cgit