diff options
author | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2020-05-19 20:57:50 -0400 |
---|---|---|
committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2020-05-19 20:57:50 -0400 |
commit | 1838842bbc2db2d1049c193b8650bd101ba4858f (patch) | |
tree | 650c97b1d00091c53869323307705e78b3766275 /src/SMAPI/Framework/ModLoading/AssemblyLoader.cs | |
parent | f96dde00f98a913557617f716673f1af355cc6b5 (diff) | |
download | SMAPI-1838842bbc2db2d1049c193b8650bd101ba4858f.tar.gz SMAPI-1838842bbc2db2d1049c193b8650bd101ba4858f.tar.bz2 SMAPI-1838842bbc2db2d1049c193b8650bd101ba4858f.zip |
rewrite assembly rewriting, merge Harmony rewriters (#711)
This reduces duplication, decouples it from the assembly loader, and makes it more flexible to handle Harmony rewriting.
Diffstat (limited to 'src/SMAPI/Framework/ModLoading/AssemblyLoader.cs')
-rw-r--r-- | src/SMAPI/Framework/ModLoading/AssemblyLoader.cs | 100 |
1 files changed, 46 insertions, 54 deletions
diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs index b95a45b5..d9b4af1b 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs @@ -4,8 +4,8 @@ using System.IO; using System.Linq; using System.Reflection; using Mono.Cecil; -using Mono.Cecil.Cil; using StardewModdingAPI.Framework.Exceptions; +using StardewModdingAPI.Framework.ModLoading.Framework; using StardewModdingAPI.Metadata; using StardewModdingAPI.Toolkit.Framework.ModData; using StardewModdingAPI.Toolkit.Utilities; @@ -283,54 +283,32 @@ namespace StardewModdingAPI.Framework.ModLoading this.ChangeTypeScope(type); } - // find (and optionally rewrite) incompatible instructions - bool anyRewritten = false; - IInstructionHandler[] handlers = new InstructionMetadata().GetHandlers(this.ParanoidMode).ToArray(); - foreach (TypeDefinition type in module.GetTypes()) - { - // check type definition - foreach (IInstructionHandler handler in handlers) + // find or rewrite code + IInstructionHandler[] handlers = new InstructionMetadata().GetHandlers(this.ParanoidMode, platformChanged).ToArray(); + RecursiveRewriter rewriter = new RecursiveRewriter( + module: module, + rewriteType: (type, replaceWith) => { - InstructionHandleResult result = handler.Handle(module, type, this.AssemblyMap, platformChanged); - this.ProcessInstructionHandleResult(mod, handler, result, loggedMessages, logPrefix, filename); - if (result == InstructionHandleResult.Rewritten) - anyRewritten = true; - } - - // check methods - foreach (MethodDefinition method in type.Methods.Where(p => p.HasBody)) + bool rewritten = false; + foreach (IInstructionHandler handler in handlers) + rewritten |= handler.Handle(module, type, replaceWith); + return rewritten; + }, + rewriteInstruction: (instruction, cil, replaceWith) => { - // check method definition + bool rewritten = false; foreach (IInstructionHandler handler in handlers) - { - 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++) - { - Instruction instruction = instructions[offset]; - if (instruction.OpCode.Code == Code.Nop) - continue; - - foreach (IInstructionHandler handler in handlers) - { - InstructionHandleResult result = handler.Handle(module, cil, instruction, this.AssemblyMap, platformChanged); - this.ProcessInstructionHandleResult(mod, handler, result, loggedMessages, logPrefix, filename); - if (result == InstructionHandleResult.Rewritten) - { - instruction = instructions[offset]; - anyRewritten = true; - } - } - } + rewritten |= handler.Handle(module, cil, instruction, replaceWith); + return rewritten; } + ); + bool anyRewritten = rewriter.RewriteModule(); + + // handle rewrite flags + foreach (IInstructionHandler handler in handlers) + { + foreach (var flag in handler.Flags) + this.ProcessInstructionHandleResult(mod, handler, flag, loggedMessages, logPrefix, filename); } return platformChanged || anyRewritten; @@ -345,49 +323,52 @@ namespace StardewModdingAPI.Framework.ModLoading /// <param name="filename">The assembly filename for log messages.</param> private void ProcessInstructionHandleResult(IModMetadata mod, IInstructionHandler handler, InstructionHandleResult result, HashSet<string> loggedMessages, string logPrefix, string filename) { + // get message template + // ($phrase is replaced with the noun phrase or messages) + string template = null; switch (result) { case InstructionHandleResult.Rewritten: - this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Rewrote {filename} to fix {handler.NounPhrase}..."); + template = $"{logPrefix}Rewrote {filename} to fix $phrase..."; break; case InstructionHandleResult.NotCompatible: - this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Broken code in {filename}: {handler.NounPhrase}."); + template = $"{logPrefix}Broken code in {filename}: $phrase."; mod.SetWarning(ModWarning.BrokenCodeLoaded); break; case InstructionHandleResult.DetectedGamePatch: - this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Detected game patcher ({handler.NounPhrase}) in assembly {filename}."); + template = $"{logPrefix}Detected game patcher ($phrase) in assembly {filename}."; mod.SetWarning(ModWarning.PatchesGame); break; case InstructionHandleResult.DetectedSaveSerializer: - this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Detected possible save serializer change ({handler.NounPhrase}) in assembly {filename}."); + template = $"{logPrefix}Detected possible save serializer change ($phrase) in assembly {filename}."; mod.SetWarning(ModWarning.ChangesSaveSerializer); break; case InstructionHandleResult.DetectedUnvalidatedUpdateTick: - this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Detected reference to {handler.NounPhrase} in assembly {filename}."); + template = $"{logPrefix}Detected reference to $phrase in assembly {filename}."; mod.SetWarning(ModWarning.UsesUnvalidatedUpdateTick); break; case InstructionHandleResult.DetectedDynamic: - this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Detected 'dynamic' keyword ({handler.NounPhrase}) in assembly {filename}."); + template = $"{logPrefix}Detected 'dynamic' keyword ($phrase) in assembly {filename}."; mod.SetWarning(ModWarning.UsesDynamic); break; case InstructionHandleResult.DetectedConsoleAccess: - this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Detected direct console access ({handler.NounPhrase}) in assembly {filename}."); + template = $"{logPrefix}Detected direct console access ($phrase) in assembly {filename}."; mod.SetWarning(ModWarning.AccessesConsole); break; case InstructionHandleResult.DetectedFilesystemAccess: - this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Detected filesystem access ({handler.NounPhrase}) in assembly {filename}."); + template = $"{logPrefix}Detected filesystem access ($phrase) in assembly {filename}."; mod.SetWarning(ModWarning.AccessesFilesystem); break; case InstructionHandleResult.DetectedShellAccess: - this.Monitor.LogOnce(loggedMessages, $"{logPrefix}Detected shell or process access ({handler.NounPhrase}) in assembly {filename}."); + template = $"{logPrefix}Detected shell or process access ($phrase) in assembly {filename}."; mod.SetWarning(ModWarning.AccessesShell); break; @@ -397,6 +378,17 @@ namespace StardewModdingAPI.Framework.ModLoading default: throw new NotSupportedException($"Unrecognized instruction handler result '{result}'."); } + if (template == null) + return; + + // format messages + if (handler.Phrases.Any()) + { + foreach (string message in handler.Phrases) + this.Monitor.LogOnce(template.Replace("$phrase", message)); + } + else + this.Monitor.LogOnce(template.Replace("$phrase", handler.DefaultPhrase ?? handler.GetType().Name)); } /// <summary>Get the correct reference to use for compatibility with the current platform.</summary> |