From beffadc919b02b5b7221388e0d4a1571466fa402 Mon Sep 17 00:00:00 2001 From: LoganDark Date: Mon, 3 Oct 2022 10:51:46 -0700 Subject: [PATCH 1/3] Methods for getting indices and making loads and stores Adds these extension methods on CodeInstruction: - LocalIndex, which returns the operand of any ldloc/ldloca/stloc, even the short forms or the fixed forms (i.e. ldloc.s or ldloc.1) - ArgIndex, which does the same but for ldarg/ldarga/starg Adds these static helper methods to CodeInstruction: - LoadLocal, which allows you to produce an optimal load from an index, optionally loading the address - StoreLocal, which allows you to produce an optional store to an index - LoadArg, which allows you to produce an optimal load from an argument, optionally loading the address - StoreArg, which allows you to produce an optimal store to an argument All of the methods are annotated with doc comments and include links to their counterparts via `` references. --- Harmony/Public/CodeInstruction.cs | 74 +++++++++++++++++++++++++++++++ Harmony/Tools/Extensions.cs | 34 ++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/Harmony/Public/CodeInstruction.cs b/Harmony/Public/CodeInstruction.cs index 7e782d86..bdb87eea 100644 --- a/Harmony/Public/CodeInstruction.cs +++ b/Harmony/Public/CodeInstruction.cs @@ -229,6 +229,80 @@ public static CodeInstruction StoreField(Type type, string name) return new CodeInstruction(field.IsStatic ? OpCodes.Stsfld : OpCodes.Stfld, field); } + // --- LOCALS + + /// Creates a CodeInstruction loading a local with the given index, using the shorter forms when possible + /// The index where the local is stored + /// Use address of local + /// + /// + public static CodeInstruction LoadLocal(int index, bool useAddress = false) + { + if (useAddress) + { + if (index < 256) return new CodeInstruction(OpCodes.Ldloca_S, Convert.ToByte(index)); + else return new CodeInstruction(OpCodes.Ldloca, index); + } + else + { + if (index == 0) return new CodeInstruction(OpCodes.Ldloc_0); + else if (index == 1) return new CodeInstruction(OpCodes.Ldloc_1); + else if (index == 2) return new CodeInstruction(OpCodes.Ldloc_2); + else if (index == 3) return new CodeInstruction(OpCodes.Ldloc_3); + else if (index < 256) return new CodeInstruction(OpCodes.Ldloc_S, Convert.ToByte(index)); + else return new CodeInstruction(OpCodes.Ldloc, index); + } + } + + /// Creates a CodeInstruction storing to a local with the given index, using the shorter forms when possible + /// The index where the local is stored + /// + /// + public static CodeInstruction StoreLocal(int index) + { + if (index == 0) return new CodeInstruction(OpCodes.Stloc_0); + else if (index == 1) return new CodeInstruction(OpCodes.Stloc_1); + else if (index == 2) return new CodeInstruction(OpCodes.Stloc_2); + else if (index == 3) return new CodeInstruction(OpCodes.Stloc_3); + else if (index < 256) return new CodeInstruction(OpCodes.Stloc_S, Convert.ToByte(index)); + else return new CodeInstruction(OpCodes.Stloc, index); + } + + // --- ARGUMENTS + + /// Creates a CodeInstruction loading an argument with the given index, using the shorter forms when possible + /// The index of the argument + /// Use address of argument + /// + /// + public static CodeInstruction LoadArg(int index, bool useAddress = false) + { + if (useAddress) + { + if (index < 256) return new CodeInstruction(OpCodes.Ldarga_S, Convert.ToByte(index)); + else return new CodeInstruction(OpCodes.Ldarga, index); + } + else + { + if (index == 0) return new CodeInstruction(OpCodes.Ldarg_0); + else if (index == 1) return new CodeInstruction(OpCodes.Ldarg_1); + else if (index == 2) return new CodeInstruction(OpCodes.Ldarg_2); + else if (index == 3) return new CodeInstruction(OpCodes.Ldarg_3); + else if (index < 256) return new CodeInstruction(OpCodes.Ldarg_S, Convert.ToByte(index)); + else return new CodeInstruction(OpCodes.Ldarg, index); + } + } + + /// Creates a CodeInstruction storing to an argument with the given index, using the shorter forms when possible + /// The index of the argument + /// + /// + public static CodeInstruction StoreArg(int index) + { + if (index < 256) return new CodeInstruction(OpCodes.Starg_S, Convert.ToByte(index)); + else return new CodeInstruction(OpCodes.Starg, index); + } + // --- TOSTRING /// Returns a string representation of the code instruction diff --git a/Harmony/Tools/Extensions.cs b/Harmony/Tools/Extensions.cs index cc092c19..bda6acd0 100644 --- a/Harmony/Tools/Extensions.cs +++ b/Harmony/Tools/Extensions.cs @@ -480,6 +480,40 @@ public static bool StoresField(this CodeInstruction code, FieldInfo field) return code.opcode == stfldCode && Equals(code.operand, field); } + /// Returns the index targeted by this ldloc, ldloca, or stloc + /// The + /// The index it targets + /// + /// + public static int LocalIndex(this CodeInstruction code) + { + if (code.opcode == OpCodes.Ldloc_0 || code.opcode == OpCodes.Stloc_0) return 0; + else if (code.opcode == OpCodes.Ldloc_1 || code.opcode == OpCodes.Stloc_1) return 1; + else if (code.opcode == OpCodes.Ldloc_2 || code.opcode == OpCodes.Stloc_2) return 2; + else if (code.opcode == OpCodes.Ldloc_3 || code.opcode == OpCodes.Stloc_3) return 3; + else if (code.opcode == OpCodes.Ldloc_S || code.opcode == OpCodes.Ldloc) return Convert.ToInt32(code.operand); + else if (code.opcode == OpCodes.Stloc_S || code.opcode == OpCodes.Stloc) return Convert.ToInt32(code.operand); + else if (code.opcode == OpCodes.Ldloca_S || code.opcode == OpCodes.Ldloca) return Convert.ToInt32(code.operand); + else throw new ArgumentException("Instruction is not a load or store", "code"); + } + + /// Returns the index targeted by this ldarg, ldarga, or starg + /// The + /// The index it targets + /// + /// + public static int ArgIndex(this CodeInstruction code) + { + if (code.opcode == OpCodes.Ldarg_0) return 0; + else if (code.opcode == OpCodes.Ldarg_1) return 1; + else if (code.opcode == OpCodes.Ldarg_2) return 2; + else if (code.opcode == OpCodes.Ldarg_3) return 3; + else if (code.opcode == OpCodes.Ldarg_S || code.opcode == OpCodes.Ldarg) return Convert.ToInt32(code.operand); + else if (code.opcode == OpCodes.Starg_S || code.opcode == OpCodes.Starg) return Convert.ToInt32(code.operand); + else if (code.opcode == OpCodes.Ldarga_S || code.opcode == OpCodes.Ldarga) return Convert.ToInt32(code.operand); + else throw new ArgumentException("Instruction is not a load or store", "code"); + } + /// Adds labels to the code instruction and return it /// The /// One or several to add From b951db5fff14d861cf7e5ea8a6eb83b7471ae66c Mon Sep 17 00:00:00 2001 From: LoganDark Date: Mon, 3 Oct 2022 11:18:21 -0700 Subject: [PATCH 2/3] CodeMatch helpers for matching loads and stores This adds four new static methods to CodeMatch: - LoadsLocal which matches any local load (ldloc family) - StoresLocal which matches any local store (stloc family) - LoadsArgument which matches any argument load (ldarg family) - StoresArgument which matches any argument store (starg family) Matching specific indices can be done with the predicate and checking the instruction LocalIndex or ArgIndex --- Harmony/Tools/CodeMatch.cs | 100 +++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/Harmony/Tools/CodeMatch.cs b/Harmony/Tools/CodeMatch.cs index 364a6932..e15d8974 100644 --- a/Harmony/Tools/CodeMatch.cs +++ b/Harmony/Tools/CodeMatch.cs @@ -125,6 +125,106 @@ internal bool Matches(List codes, CodeInstruction instruction) return true; } + /// Creates a code match for local loads + /// Whether to match for address loads + /// An optional name + /// + public static CodeMatch LoadsLocal(bool useAddress = false, string name = null) + { + CodeMatch match = new CodeMatch(null, null, name); + + if (useAddress) + { + match.opcodes.AddRange(new[] + { + OpCodes.Ldloca_S, + OpCodes.Ldloca + }); + } + else + { + match.opcodes.AddRange(new[] + { + OpCodes.Ldloc_0, + OpCodes.Ldloc_1, + OpCodes.Ldloc_2, + OpCodes.Ldloc_3, + OpCodes.Ldloc_S, + OpCodes.Ldloc + }); + } + + return match; + } + + /// Creates a code match for local stores + /// An optional name + /// + public static CodeMatch StoresLocal(string name = null) + { + CodeMatch match = new CodeMatch(null, null, name); + + match.opcodes.AddRange(new[] + { + OpCodes.Stloc_0, + OpCodes.Stloc_1, + OpCodes.Stloc_2, + OpCodes.Stloc_3, + OpCodes.Stloc_S, + OpCodes.Stloc + }); + + return match; + } + + /// Creates a code match for argument loads + /// Whether to match for address loads + /// An optional name + /// + public static CodeMatch LoadsArgument(bool useAddress = false, string name = null) + { + CodeMatch match = new CodeMatch(null, null, name); + + if (useAddress) + { + match.opcodes.AddRange(new[] + { + OpCodes.Ldarga_S, + OpCodes.Ldarga + }); + } + else + { + match.opcodes.AddRange(new[] + { + OpCodes.Ldarg_0, + OpCodes.Ldarg_1, + OpCodes.Ldarg_2, + OpCodes.Ldarg_3, + OpCodes.Ldarg_S, + OpCodes.Ldarg + }); + } + + return match; + } + + /// Creates a code match for argument stores + /// An optional name + /// + public static CodeMatch StoresArgument(string name = null) + { + CodeMatch match = new CodeMatch(null, null, name); + + match.opcodes.AddRange(new[] + { + OpCodes.Starg_S, + OpCodes.Starg + }); + + return match; + } + /// Returns a string that represents the match /// A string representation /// From 679408ead0bc0e542163f93c9945f290abca8152 Mon Sep 17 00:00:00 2001 From: LoganDark Date: Tue, 4 Oct 2022 05:17:55 -0700 Subject: [PATCH 3/3] Use the full "Argument" term in method names I'm having trouble getting feedback on this one but I think this is what we want? --- Harmony/Public/CodeInstruction.cs | 8 ++++---- Harmony/Tools/Extensions.cs | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Harmony/Public/CodeInstruction.cs b/Harmony/Public/CodeInstruction.cs index bdb87eea..6791235e 100644 --- a/Harmony/Public/CodeInstruction.cs +++ b/Harmony/Public/CodeInstruction.cs @@ -274,8 +274,8 @@ public static CodeInstruction StoreLocal(int index) /// The index of the argument /// Use address of argument /// - /// - public static CodeInstruction LoadArg(int index, bool useAddress = false) + /// + public static CodeInstruction LoadArgument(int index, bool useAddress = false) { if (useAddress) { @@ -296,8 +296,8 @@ public static CodeInstruction LoadArg(int index, bool useAddress = false) /// Creates a CodeInstruction storing to an argument with the given index, using the shorter forms when possible /// The index of the argument /// - /// - public static CodeInstruction StoreArg(int index) + /// + public static CodeInstruction StoreArgument(int index) { if (index < 256) return new CodeInstruction(OpCodes.Starg_S, Convert.ToByte(index)); else return new CodeInstruction(OpCodes.Starg, index); diff --git a/Harmony/Tools/Extensions.cs b/Harmony/Tools/Extensions.cs index bda6acd0..ecdb41ac 100644 --- a/Harmony/Tools/Extensions.cs +++ b/Harmony/Tools/Extensions.cs @@ -500,9 +500,9 @@ public static int LocalIndex(this CodeInstruction code) /// Returns the index targeted by this ldarg, ldarga, or starg /// The /// The index it targets - /// - /// - public static int ArgIndex(this CodeInstruction code) + /// + /// + public static int ArgumentIndex(this CodeInstruction code) { if (code.opcode == OpCodes.Ldarg_0) return 0; else if (code.opcode == OpCodes.Ldarg_1) return 1;