summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI/Framework/ModLoading/AssemblyLoader.cs')
-rw-r--r--src/SMAPI/Framework/ModLoading/AssemblyLoader.cs100
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>