diff --git a/NLog.config b/NLog.config index 984f095..270fe34 100644 --- a/NLog.config +++ b/NLog.config @@ -6,10 +6,12 @@ + + \ No newline at end of file diff --git a/Torch/Managers/KeenLogManager.cs b/Torch/Managers/KeenLogManager.cs new file mode 100644 index 0000000..aefeb92 --- /dev/null +++ b/Torch/Managers/KeenLogManager.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NLog; +using Torch.API; +using Torch.Managers.PatchManager; +using Torch.Utils; +using VRage.Utils; + +namespace Torch.Managers +{ + public class KeenLogManager : Manager + { + private static readonly Logger _log = LogManager.GetLogger("Keen"); + +#pragma warning disable 649 + [Dependency] + private PatchManager.PatchManager _patchManager; + + [ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.Log), Parameters = new[] { typeof(MyLogSeverity), typeof(StringBuilder) })] + private static MethodInfo _logStringBuilder; + + [ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.Log), Parameters = new[] { typeof(MyLogSeverity), typeof(string), typeof(object[]) })] + private static MethodInfo _logFormatted; + + [ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.WriteLine), Parameters = new[] { typeof(string) })] + private static MethodInfo _logWriteLine; + + [ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.AppendToClosedLog), Parameters = new[] { typeof(string) })] + private static MethodInfo _logAppendToClosedLog; + + [ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.WriteLine), Parameters = new[] { typeof(string), typeof(LoggingOptions) })] + private static MethodInfo _logWriteLineOptions; + + [ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.WriteLine), Parameters = new[] { typeof(Exception) })] + private static MethodInfo _logWriteLineException; + + [ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.AppendToClosedLog), Parameters = new[] { typeof(Exception) })] + private static MethodInfo _logAppendToClosedLogException; + + [ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.WriteLineAndConsole), Parameters = new[] { typeof(string) })] + private static MethodInfo _logWriteLineAndConsole; +#pragma warning restore 649 + + private PatchContext _context; + + public KeenLogManager(ITorchBase torchInstance) : base(torchInstance) + { + } + + public override void Attach() + { + _context = _patchManager.AcquireContext(); + + _context.GetPattern(_logStringBuilder).Prefixes.Add(Method(nameof(PrefixLogStringBuilder))); + _context.GetPattern(_logFormatted).Prefixes.Add(Method(nameof(PrefixLogFormatted))); + + _context.GetPattern(_logWriteLine).Prefixes.Add(Method(nameof(PrefixWriteLine))); + _context.GetPattern(_logAppendToClosedLog).Prefixes.Add(Method(nameof(PrefixAppendToClosedLog))); + _context.GetPattern(_logWriteLineAndConsole).Prefixes.Add(Method(nameof(PrefixWriteLine))); + + _context.GetPattern(_logWriteLineException).Prefixes.Add(Method(nameof(PrefixWriteLineException))); + _context.GetPattern(_logAppendToClosedLogException).Prefixes.Add(Method(nameof(PrefixAppendToClosedLogException))); + + _context.GetPattern(_logWriteLineOptions).Prefixes.Add(Method(nameof(PrefixWriteLineOptions))); + + _patchManager.Commit(); + } + + public override void Detach() + { + _patchManager.FreeContext(_context); + } + + private static MethodInfo Method(string name) + { + return typeof(KeenLogManager).GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); + } + + [ReflectedMethod(Name = "GetThreadId")] + private static Func _getThreadId; + + [ReflectedMethod(Name = "GetIdentByThread")] + private static Func _getIndentByThread; + + private static readonly ThreadLocal _tmpStringBuilder = new ThreadLocal(() => new StringBuilder(32)); + + private static StringBuilder PrepareLog(MyLog log) + { + return _tmpStringBuilder.Value.Clear().Append(' ', _getIndentByThread(log, _getThreadId(log)) * 3); + } + + private static bool PrefixWriteLine(MyLog __instance, string msg) + { + _log.Info(PrepareLog(__instance).Append(msg)); + return false; + } + private static bool PrefixAppendToClosedLog(MyLog __instance, string text) + { + _log.Info(PrepareLog(__instance).Append(text)); + return false; + } + private static bool PrefixWriteLineOptions(MyLog __instance, string message, LoggingOptions option) + { + if (__instance.LogFlag(option)) + _log.Info(PrepareLog(__instance).Append(message)); + return false; + } + + private static bool PrefixAppendToClosedLogException(Exception e) + { + _log.Info(e); + return false; + } + + private static bool PrefixWriteLineException(Exception ex) + { + _log.Info(ex); + return false; + } + + private static bool PrefixLogFormatted(MyLog __instance, MyLogSeverity severity, string format, object[] args) + { + _log.Log(LogLevelFor(severity), PrepareLog(__instance).AppendFormat(format, args)); + return false; + } + + private static bool PrefixLogStringBuilder(MyLog __instance, MyLogSeverity severity, StringBuilder builder) + { + _log.Log(LogLevelFor(severity), PrepareLog(__instance).Append(builder)); + return false; + } + + private static LogLevel LogLevelFor(MyLogSeverity severity) + { + switch (severity) + { + case MyLogSeverity.Debug: + return LogLevel.Debug; + case MyLogSeverity.Info: + return LogLevel.Info; + case MyLogSeverity.Warning: + return LogLevel.Warn; + case MyLogSeverity.Error: + return LogLevel.Error; + case MyLogSeverity.Critical: + return LogLevel.Fatal; + default: + return LogLevel.Info; + } + } + } +} diff --git a/Torch/Managers/PatchManager/DecoratedMethod.cs b/Torch/Managers/PatchManager/DecoratedMethod.cs index 025b61a..5892de8 100644 --- a/Torch/Managers/PatchManager/DecoratedMethod.cs +++ b/Torch/Managers/PatchManager/DecoratedMethod.cs @@ -34,18 +34,21 @@ namespace Torch.Managers.PatchManager if (Prefixes.Count == 0 && Suffixes.Count == 0 && Transpilers.Count == 0) return; + _log.Debug($"Begin patching {_method.DeclaringType?.FullName}#{_method.Name}({string.Join(", ", _method.GetParameters().Select(x => x.ParameterType.Name))})"); var patch = ComposePatchedMethod(); _revertAddress = AssemblyMemory.GetMethodBodyStart(_method); var newAddress = AssemblyMemory.GetMethodBodyStart(patch); _revertData = AssemblyMemory.WriteJump(_revertAddress, newAddress); _pinnedPatch = GCHandle.Alloc(patch); + _log.Debug($"Done patching {_method.DeclaringType?.FullName}#{_method.Name}({string.Join(", ", _method.GetParameters().Select(x => x.ParameterType.Name))})"); } internal void Revert() { if (_pinnedPatch.HasValue) { + _log.Debug($"Revert {_method.DeclaringType?.FullName}#{_method.Name}({string.Join(", ", _method.GetParameters().Select(x => x.ParameterType.Name))})"); AssemblyMemory.WriteMemory(_revertAddress, _revertData); _revertData = null; _pinnedPatch.Value.Free(); @@ -113,29 +116,30 @@ namespace Torch.Managers.PatchManager var specialVariables = new Dictionary(); - Label? labelAfterOriginalContent = Suffixes.Count > 0 ? target.DefineLabel() : (Label?)null; - Label? labelAfterOriginalReturn = Prefixes.Any(x => x.ReturnType == typeof(bool)) ? target.DefineLabel() : (Label?)null; + Label labelAfterOriginalContent = target.DefineLabel(); + Label labelAfterOriginalReturn = target.DefineLabel(); - var returnType = _method is MethodInfo meth ? meth.ReturnType : typeof(void); - var resultVariable = returnType != typeof(void) && (labelAfterOriginalReturn.HasValue || // If we jump past main content we need local to store return val - Prefixes.Concat(Suffixes).SelectMany(x => x.GetParameters()).Any(x => x.Name == RESULT_PARAMETER)) - ? target.DeclareLocal(returnType) - : null; + Type returnType = _method is MethodInfo meth ? meth.ReturnType : typeof(void); + LocalBuilder resultVariable = null; + if (returnType != typeof(void)) + { + if (Prefixes.Concat(Suffixes).SelectMany(x => x.GetParameters()).Any(x => x.Name == RESULT_PARAMETER)) + resultVariable = target.DeclareLocal(returnType); + else if (Prefixes.Any(x => x.ReturnType == typeof(bool))) + resultVariable = target.DeclareLocal(returnType); + } resultVariable?.SetToDefault(target); if (resultVariable != null) specialVariables.Add(RESULT_PARAMETER, resultVariable); target.EmitComment("Prefixes Begin"); - foreach (var prefix in Prefixes) + foreach (MethodInfo prefix in Prefixes) { EmitMonkeyCall(target, prefix, specialVariables); if (prefix.ReturnType == typeof(bool)) - { - Debug.Assert(labelAfterOriginalReturn.HasValue); - target.Emit(OpCodes.Brfalse, labelAfterOriginalReturn.Value); - } + target.Emit(OpCodes.Brfalse, labelAfterOriginalReturn); else if (prefix.ReturnType != typeof(void)) throw new Exception( $"Prefixes must return void or bool. {prefix.DeclaringType?.FullName}.{prefix.Name} returns {prefix.ReturnType}"); @@ -145,30 +149,23 @@ namespace Torch.Managers.PatchManager target.EmitComment("Original Begin"); MethodTranspiler.Transpile(_method, Transpilers, target, labelAfterOriginalContent); target.EmitComment("Original End"); - if (labelAfterOriginalContent.HasValue) - { - target.MarkLabel(labelAfterOriginalContent.Value); - if (resultVariable != null) - target.Emit(OpCodes.Stloc, resultVariable); - } - if (labelAfterOriginalReturn.HasValue) - target.MarkLabel(labelAfterOriginalReturn.Value); + + target.MarkLabel(labelAfterOriginalContent); + if (resultVariable != null) + target.Emit(OpCodes.Stloc, resultVariable); + target.MarkLabel(labelAfterOriginalReturn); target.EmitComment("Suffixes Begin"); - foreach (var suffix in Suffixes) + foreach (MethodInfo suffix in Suffixes) { EmitMonkeyCall(target, suffix, specialVariables); if (suffix.ReturnType != typeof(void)) throw new Exception($"Suffixes must return void. {suffix.DeclaringType?.FullName}.{suffix.Name} returns {suffix.ReturnType}"); } target.EmitComment("Suffixes End"); - - if (labelAfterOriginalContent.HasValue || labelAfterOriginalReturn.HasValue) - { - if (resultVariable != null) - target.Emit(OpCodes.Ldloc, resultVariable); - target.Emit(OpCodes.Ret); - } + if (resultVariable != null) + target.Emit(OpCodes.Ldloc, resultVariable); + target.Emit(OpCodes.Ret); } private void EmitMonkeyCall(LoggingIlGenerator target, MethodInfo patch, diff --git a/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs b/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs index 9ac0359..259cff0 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 Label = System.Windows.Controls.Label; namespace Torch.Managers.PatchManager.MSIL { @@ -69,7 +70,7 @@ namespace Torch.Managers.PatchManager.MSIL break; case OperandType.ShortInlineI: Operand = OpCode == OpCodes.Ldc_I4_S - ? (MsilOperand) new MsilOperandInline.MsilOperandInt8(this) + ? (MsilOperand)new MsilOperandInline.MsilOperandInt8(this) : new MsilOperandInline.MsilOperandUInt8(this); break; case OperandType.ShortInlineR: @@ -120,10 +121,23 @@ namespace Torch.Managers.PatchManager.MSIL /// This instruction public MsilInstruction InlineValue(T o) { - ((MsilOperandInline) Operand).Value = o; + ((MsilOperandInline)Operand).Value = o; return this; } + /// + /// Makes a copy of the instruction with a new opcode. + /// + /// The new opcode + /// The copy + public MsilInstruction CopyWith(OpCode code) + { + var result = new MsilInstruction(code) { Operand = this.Operand }; + foreach (MsilLabel x in Labels) + result.Labels.Add(x); + return result; + } + /// /// Sets the inline branch target for this instruction. /// @@ -131,7 +145,7 @@ namespace Torch.Managers.PatchManager.MSIL /// This instruction public MsilInstruction InlineTarget(MsilLabel label) { - ((MsilOperandBrTarget) Operand).Target = label; + ((MsilOperandBrTarget)Operand).Target = label; return this; } diff --git a/Torch/Managers/PatchManager/Transpile/MethodContext.cs b/Torch/Managers/PatchManager/Transpile/MethodContext.cs index 8c5be0d..36682d6 100644 --- a/Torch/Managers/PatchManager/Transpile/MethodContext.cs +++ b/Torch/Managers/PatchManager/Transpile/MethodContext.cs @@ -34,8 +34,8 @@ namespace Torch.Managers.PatchManager.Transpile public MethodContext(MethodBase method) { Method = method; - _msilBytes = Method.GetMethodBody().GetILAsByteArray(); - TokenResolver = new NormalTokenResolver(method); + _msilBytes = Method.GetMethodBody().GetILAsByteArray(); + TokenResolver = new NormalTokenResolver(method); } public void Read() @@ -56,7 +56,7 @@ namespace Torch.Managers.PatchManager.Transpile var instructionValue = (short)memory.ReadByte(); if (Prefixes.Contains(instructionValue)) { - instructionValue = (short) ((instructionValue << 8) | memory.ReadByte()); + instructionValue = (short)((instructionValue << 8) | memory.ReadByte()); count++; } if (!OpCodeLookup.TryGetValue(instructionValue, out OpCode opcode)) @@ -65,7 +65,7 @@ namespace Torch.Managers.PatchManager.Transpile throw new Exception($"Opcode said it was {opcode.Size} but we read {count}"); var instruction = new MsilInstruction(opcode) { - Offset = (int) memory.Position + Offset = (int)memory.Position }; _instructions.Add(instruction); instruction.Operand?.Read(this, reader); @@ -106,9 +106,9 @@ namespace Torch.Managers.PatchManager.Transpile var opcode = (OpCode)field.GetValue(null); if (opcode.OpCodeType != OpCodeType.Nternal) OpCodeLookup.Add(opcode.Value, opcode); - if ((ushort) opcode.Value > 0xFF) + if ((ushort)opcode.Value > 0xFF) { - Prefixes.Add((short) ((ushort) opcode.Value >> 8)); + Prefixes.Add((short)((ushort)opcode.Value >> 8)); } } } diff --git a/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs b/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs index d9a37eb..3defb4a 100644 --- a/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs +++ b/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs @@ -28,18 +28,19 @@ namespace Torch.Managers.PatchManager.Transpile private static IEnumerable FixBranchAndReturn(IEnumerable insn, Label? retTarget) { - foreach (var i in insn) + foreach (MsilInstruction i in insn) { if (retTarget.HasValue && i.OpCode == OpCodes.Ret) { - var j = new MsilInstruction(OpCodes.Br); - ((MsilOperandBrTarget)j.Operand).Target = new MsilLabel(retTarget.Value); + MsilInstruction j = new MsilInstruction(OpCodes.Br).InlineTarget(new MsilLabel(retTarget.Value)); + foreach (MsilLabel l in i.Labels) + j.Labels.Add(l); yield return j; continue; } if (_opcodeReplaceRule.TryGetValue(i.OpCode, out OpCode replaceOpcode)) { - yield return new MsilInstruction(replaceOpcode) { Operand = i.Operand }; + yield return i.CopyWith(replaceOpcode); continue; } yield return i; @@ -62,6 +63,7 @@ namespace Torch.Managers.PatchManager.Transpile _opcodeReplaceRule.Add(opcode, other.Value); } } + _opcodeReplaceRule[OpCodes.Leave_S] = OpCodes.Leave; } } } diff --git a/Torch/Torch.csproj b/Torch/Torch.csproj index aed1db0..db958da 100644 --- a/Torch/Torch.csproj +++ b/Torch/Torch.csproj @@ -159,6 +159,7 @@ + diff --git a/Torch/TorchBase.cs b/Torch/TorchBase.cs index f167e26..00cccaa 100644 --- a/Torch/TorchBase.cs +++ b/Torch/TorchBase.cs @@ -24,6 +24,7 @@ using Torch.API.Session; using Torch.Commands; using Torch.Managers; using Torch.Managers.ChatManager; +using Torch.Managers.PatchManager; using Torch.Utils; using Torch.Session; using VRage.Collections; @@ -119,6 +120,8 @@ namespace Torch sessionManager.AddFactory((x) => new EntityManager(this)); Managers.AddManager(sessionManager); + Managers.AddManager(new PatchManager(this)); + Managers.AddManager(new KeenLogManager(this)); Managers.AddManager(new FilesystemManager(this)); Managers.AddManager(new UpdateManager(this)); Managers.AddManager(Plugins);