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);