diff --git a/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs b/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs
index 259cff0..e6ae140 100644
--- a/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs
+++ b/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs
@@ -4,6 +4,7 @@ using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using Torch.Managers.PatchManager.Transpile;
+using Torch.Utils;
using Label = System.Windows.Controls.Label;
namespace Torch.Managers.PatchManager.MSIL
@@ -13,8 +14,6 @@ namespace Torch.Managers.PatchManager.MSIL
///
public class MsilInstruction
{
- private MsilOperand _operandBacking;
-
///
/// Creates a new instruction with the given opcode.
///
@@ -97,16 +96,7 @@ namespace Torch.Managers.PatchManager.MSIL
///
/// The operand for this instruction, or null.
///
- public MsilOperand Operand
- {
- get => _operandBacking;
- set
- {
- if (_operandBacking != null && value.GetType() != _operandBacking.GetType())
- throw new ArgumentException($"Operand for {OpCode.Name} must be {_operandBacking.GetType().Name}");
- _operandBacking = value;
- }
- }
+ public MsilOperand Operand { get; }
///
/// Labels pointing to this instruction.
@@ -128,11 +118,12 @@ namespace Torch.Managers.PatchManager.MSIL
///
/// Makes a copy of the instruction with a new opcode.
///
- /// The new opcode
+ /// The new opcode
/// The copy
- public MsilInstruction CopyWith(OpCode code)
+ public MsilInstruction CopyWith(OpCode newOpcode)
{
- var result = new MsilInstruction(code) { Operand = this.Operand };
+ var result = new MsilInstruction(newOpcode);
+ Operand?.CopyTo(result.Operand);
foreach (MsilLabel x in Labels)
result.Labels.Add(x);
return result;
@@ -172,5 +163,28 @@ namespace Torch.Managers.PatchManager.MSIL
sb.Append(OpCode.Name).Append("\t").Append(Operand);
return sb.ToString();
}
+
+
+
+#pragma warning disable 169
+ [ReflectedMethod(Name = "StackChange")]
+ private static Func _stackChange;
+#pragma warning restore 169
+
+ public int StackChange()
+ {
+ int num = _stackChange.Invoke(OpCode);
+ if ((OpCode == OpCodes.Call || OpCode == OpCodes.Callvirt || OpCode == OpCodes.Newobj) &&
+ Operand is MsilOperandInline inline)
+ {
+ MethodBase op = inline.Value;
+ if (op is MethodInfo mi && mi.ReturnType != typeof(void))
+ num++;
+ num -= op.GetParameters().Length;
+ if (!op.IsStatic && OpCode != OpCodes.Newobj)
+ num--;
+ }
+ return num;
+ }
}
}
\ No newline at end of file
diff --git a/Torch/Managers/PatchManager/MSIL/MsilOperand.cs b/Torch/Managers/PatchManager/MSIL/MsilOperand.cs
index d9dbf2f..0df66c4 100644
--- a/Torch/Managers/PatchManager/MSIL/MsilOperand.cs
+++ b/Torch/Managers/PatchManager/MSIL/MsilOperand.cs
@@ -18,6 +18,8 @@ namespace Torch.Managers.PatchManager.MSIL
///
public MsilInstruction Instruction { get; }
+ internal abstract void CopyTo(MsilOperand operand);
+
internal abstract void Read(MethodContext context, BinaryReader reader);
internal abstract void Emit(LoggingIlGenerator generator);
diff --git a/Torch/Managers/PatchManager/MSIL/MsilOperandBrTarget.cs b/Torch/Managers/PatchManager/MSIL/MsilOperandBrTarget.cs
index 01e0913..ea93ce4 100644
--- a/Torch/Managers/PatchManager/MSIL/MsilOperandBrTarget.cs
+++ b/Torch/Managers/PatchManager/MSIL/MsilOperandBrTarget.cs
@@ -1,4 +1,5 @@
-using System.IO;
+using System;
+using System.IO;
using System.Reflection.Emit;
using Torch.Managers.PatchManager.Transpile;
@@ -22,8 +23,8 @@ namespace Torch.Managers.PatchManager.MSIL
{
int val = Instruction.OpCode.OperandType == OperandType.InlineBrTarget
? reader.ReadInt32()
- : reader.ReadByte();
- Target = context.LabelAt((int) reader.BaseStream.Position + val);
+ : reader.ReadSByte();
+ Target = context.LabelAt((int)reader.BaseStream.Position + val);
}
internal override void Emit(LoggingIlGenerator generator)
@@ -31,6 +32,14 @@ namespace Torch.Managers.PatchManager.MSIL
generator.Emit(Instruction.OpCode, Target.LabelFor(generator));
}
+ internal override void CopyTo(MsilOperand operand)
+ {
+ var lt = operand as MsilOperandBrTarget;
+ if (lt == null)
+ throw new ArgumentException($"Target {operand?.GetType().Name} must be of same type {GetType().Name}", nameof(operand));
+ lt.Target = Target;
+ }
+
///
public override string ToString()
{
diff --git a/Torch/Managers/PatchManager/MSIL/MsilOperandInline.cs b/Torch/Managers/PatchManager/MSIL/MsilOperandInline.cs
index e327054..b247a54 100644
--- a/Torch/Managers/PatchManager/MSIL/MsilOperandInline.cs
+++ b/Torch/Managers/PatchManager/MSIL/MsilOperandInline.cs
@@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.IO;
+using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using Torch.Managers.PatchManager.Transpile;
@@ -22,6 +23,15 @@ namespace Torch.Managers.PatchManager.MSIL
///
public T Value { get; set; }
+ internal override void CopyTo(MsilOperand operand)
+ {
+ var lt = operand as MsilOperandInline;
+ if (lt == null)
+ throw new ArgumentException($"Target {operand?.GetType().Name} must be of same type {GetType().Name}", nameof(operand));
+ lt.Value = Value;
+ ;
+ }
+
///
public override string ToString()
{
@@ -66,7 +76,7 @@ namespace Torch.Managers.PatchManager.MSIL
internal override void Read(MethodContext context, BinaryReader reader)
{
Value =
- (sbyte) reader.ReadByte();
+ (sbyte)reader.ReadByte();
}
internal override void Emit(LoggingIlGenerator generator)
@@ -209,16 +219,19 @@ namespace Torch.Managers.PatchManager.MSIL
internal override void Read(MethodContext context, BinaryReader reader)
{
- Value =
- context.Method.GetParameters()[
- Instruction.OpCode.OperandType == OperandType.ShortInlineVar
- ? reader.ReadByte()
- : reader.ReadUInt16()];
+ int paramID =
+ Instruction.OpCode.OperandType == OperandType.ShortInlineVar
+ ? reader.ReadByte()
+ : reader.ReadUInt16();
+ if (paramID == 0 && !context.Method.IsStatic)
+ throw new ArgumentException("Haven't figured out how to ldarg with the \"this\" argument");
+ Value = context.Method.GetParameters()[paramID - (context.Method.IsStatic ? 0 : 1)];
}
internal override void Emit(LoggingIlGenerator generator)
{
- generator.Emit(Instruction.OpCode, Value.Position);
+ var methodInfo = Value.Member as MethodBase;
+ generator.Emit(Instruction.OpCode, Value.Position + (methodInfo != null && methodInfo.IsStatic ? 0 : 1));
}
}
diff --git a/Torch/Managers/PatchManager/MSIL/MsilOperandSwitch.cs b/Torch/Managers/PatchManager/MSIL/MsilOperandSwitch.cs
index 66e3b2d..2f28297 100644
--- a/Torch/Managers/PatchManager/MSIL/MsilOperandSwitch.cs
+++ b/Torch/Managers/PatchManager/MSIL/MsilOperandSwitch.cs
@@ -1,4 +1,5 @@
-using System.IO;
+using System;
+using System.IO;
using System.Linq;
using System.Reflection.Emit;
using Torch.Managers.PatchManager.Transpile;
@@ -19,6 +20,20 @@ namespace Torch.Managers.PatchManager.MSIL
///
public MsilLabel[] Labels { get; set; }
+
+ internal override void CopyTo(MsilOperand operand)
+ {
+ var lt = operand as MsilOperandSwitch;
+ if (lt == null)
+ throw new ArgumentException($"Target {operand?.GetType().Name} must be of same type {GetType().Name}", nameof(operand));
+ if (Labels == null)
+ lt.Labels = null;
+ else
+ {
+ lt.Labels = new MsilLabel[Labels.Length];
+ Array.Copy(Labels, lt.Labels, Labels.Length);
+ }
+ }
internal override void Read(MethodContext context, BinaryReader reader)
{
int length = reader.ReadInt32();
diff --git a/Torch/Managers/PatchManager/Transpile/LoggingILGenerator.cs b/Torch/Managers/PatchManager/Transpile/LoggingILGenerator.cs
index e18eb30..1835ac2 100644
--- a/Torch/Managers/PatchManager/Transpile/LoggingILGenerator.cs
+++ b/Torch/Managers/PatchManager/Transpile/LoggingILGenerator.cs
@@ -52,7 +52,7 @@ namespace Torch.Managers.PatchManager.Transpile
///
public void Emit(OpCode op, LocalBuilder arg)
{
- _log?.Trace($"Emit\t{op,_opcodePadding} L:{arg.LocalIndex} {arg.LocalType}");
+ _log?.Trace($"Emit\t{op,_opcodePadding} Local:{arg.LocalIndex}/{arg.LocalType}");
Backing.Emit(op, arg);
}
diff --git a/Torch/Managers/PatchManager/Transpile/MethodContext.cs b/Torch/Managers/PatchManager/Transpile/MethodContext.cs
index 36682d6..1e69606 100644
--- a/Torch/Managers/PatchManager/Transpile/MethodContext.cs
+++ b/Torch/Managers/PatchManager/Transpile/MethodContext.cs
@@ -7,6 +7,7 @@ using System.Reflection;
using System.Reflection.Emit;
using NLog;
using Torch.Managers.PatchManager.MSIL;
+using Torch.Utils;
namespace Torch.Managers.PatchManager.Transpile
{
@@ -52,20 +53,19 @@ namespace Torch.Managers.PatchManager.Transpile
using (var reader = new BinaryReader(memory))
while (memory.Length > memory.Position)
{
- var count = 1;
+ var opcodeOffset = (int) memory.Position;
var instructionValue = (short)memory.ReadByte();
if (Prefixes.Contains(instructionValue))
{
instructionValue = (short)((instructionValue << 8) | memory.ReadByte());
- count++;
}
if (!OpCodeLookup.TryGetValue(instructionValue, out OpCode opcode))
throw new Exception($"Unknown opcode {instructionValue:X}");
- if (opcode.Size != count)
- throw new Exception($"Opcode said it was {opcode.Size} but we read {count}");
+ if (opcode.Size != memory.Position - opcodeOffset)
+ throw new Exception($"Opcode said it was {opcode.Size} but we read {memory.Position - opcodeOffset}");
var instruction = new MsilInstruction(opcode)
{
- Offset = (int)memory.Position
+ Offset = opcodeOffset
};
_instructions.Add(instruction);
instruction.Operand?.Read(this, reader);
@@ -76,22 +76,74 @@ namespace Torch.Managers.PatchManager.Transpile
{
foreach (var label in Labels)
{
- int min = 0, max = _instructions.Count - 1;
- while (min <= max)
+ int min = 0, max = _instructions.Count;
+ while (min != max)
{
- var mid = min + ((max - min) / 2);
- if (label.Key < _instructions[mid].Offset)
- max = mid - 1;
- else
+ int mid = (min + max) / 2;
+ if (_instructions[mid].Offset < label.Key)
min = mid + 1;
+ else
+ max = mid;
}
+#if DEBUG
+ if (min >= _instructions.Count || min < 0)
+ {
+ _log.Trace(
+ $"Want offset {label.Key} for {label.Value}, instruction offsets at\n {string.Join("\n", _instructions.Select(x => $"IL_{x.Offset:X4} {x}"))}");
+ }
+ MsilInstruction prevInsn = min > 0 ? _instructions[min - 1] : null;
+ if ((prevInsn == null || prevInsn.Offset >= label.Key) ||
+ _instructions[min].Offset < label.Key)
+ _log.Error($"Label {label.Value} wanted {label.Key} but instruction is at {_instructions[min].Offset}. Previous instruction is at {prevInsn?.Offset ?? -1}");
+#endif
_instructions[min]?.Labels?.Add(label.Value);
}
}
+
+ [Conditional("DEBUG")]
+ public void CheckIntegrity()
+ {
+ var entryStackCount = new Dictionary>();
+ var currentStackSize = 0;
+ foreach (MsilInstruction insn in _instructions)
+ {
+ // I don't want to deal with this, so I won't
+ if (insn.OpCode == OpCodes.Br || insn.OpCode == OpCodes.Br_S || insn.OpCode == OpCodes.Jmp ||
+ insn.OpCode == OpCodes.Leave || insn.OpCode == OpCodes.Leave_S)
+ break;
+ foreach (MsilLabel label in insn.Labels)
+ if (entryStackCount.TryGetValue(label, out Dictionary dict))
+ dict.Add(insn, currentStackSize);
+ else
+ (entryStackCount[label] = new Dictionary()).Add(insn, currentStackSize);
+
+ currentStackSize += insn.StackChange();
+
+ if (insn.Operand is MsilOperandBrTarget br)
+ if (entryStackCount.TryGetValue(br.Target, out Dictionary dict))
+ dict.Add(insn, currentStackSize);
+ else
+ (entryStackCount[br.Target] = new Dictionary()).Add(insn, currentStackSize);
+ }
+ foreach (KeyValuePair> label in entryStackCount)
+ {
+ if (label.Value.Values.Aggregate(new HashSet(), (a, b) =>
+ {
+ a.Add(b);
+ return a;
+ }).Count > 1)
+ {
+ _log.Warn($"Label {label.Key} has multiple entry stack counts");
+ foreach (KeyValuePair kv in label.Value)
+ _log.Warn($"{kv.Key.Offset:X4} {kv.Key} => {kv.Value}");
+ }
+ }
+ }
+
public string ToHumanMsil()
{
- return string.Join("\n", _instructions.Select(x => $"IL_{x.Offset:X4}: {x}"));
+ return string.Join("\n", _instructions.Select(x => $"IL_{x.Offset:X4}: {x.StackChange()} {x}"));
}
private static readonly Dictionary OpCodeLookup;
diff --git a/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs b/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs
index 3defb4a..25ea7dc 100644
--- a/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs
+++ b/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs
@@ -9,16 +9,17 @@ namespace Torch.Managers.PatchManager.Transpile
{
internal class MethodTranspiler
{
- private static readonly Logger _log = LogManager.GetCurrentClassLogger();
+ public static readonly Logger _log = LogManager.GetCurrentClassLogger();
internal static void Transpile(MethodBase baseMethod, IEnumerable transpilers, LoggingIlGenerator output, Label? retLabel)
{
var context = new MethodContext(baseMethod);
context.Read();
+ context.CheckIntegrity();
_log.Trace("Input Method:");
_log.Trace(context.ToHumanMsil);
- var methodContent = (IEnumerable) context.Instructions;
+ var methodContent = (IEnumerable)context.Instructions;
foreach (var transpiler in transpilers)
methodContent = (IEnumerable)transpiler.Invoke(null, new object[] { methodContent });
methodContent = FixBranchAndReturn(methodContent, retLabel);
@@ -35,15 +36,17 @@ namespace Torch.Managers.PatchManager.Transpile
MsilInstruction j = new MsilInstruction(OpCodes.Br).InlineTarget(new MsilLabel(retTarget.Value));
foreach (MsilLabel l in i.Labels)
j.Labels.Add(l);
+ _log.Trace($"Replacing {i} with {j}");
yield return j;
- continue;
}
- if (_opcodeReplaceRule.TryGetValue(i.OpCode, out OpCode replaceOpcode))
+ else if (_opcodeReplaceRule.TryGetValue(i.OpCode, out OpCode replaceOpcode))
{
- yield return i.CopyWith(replaceOpcode);
- continue;
+ var result = i.CopyWith(replaceOpcode);
+ _log.Trace($"Replacing {i} with {result}");
+ yield return result;
}
- yield return i;
+ else
+ yield return i;
}
}
@@ -57,7 +60,7 @@ namespace Torch.Managers.PatchManager.Transpile
if (opcode.OperandType == OperandType.ShortInlineBrTarget &&
opcode.Name.EndsWith(".s", StringComparison.OrdinalIgnoreCase))
{
- var other = (OpCode?) typeof(OpCodes).GetField(field.Name.Substring(0, field.Name.Length - 2),
+ var other = (OpCode?)typeof(OpCodes).GetField(field.Name.Substring(0, field.Name.Length - 2),
BindingFlags.Static | BindingFlags.Public)?.GetValue(null);
if (other.HasValue && other.Value.OperandType == OperandType.InlineBrTarget)
_opcodeReplaceRule.Add(opcode, other.Value);