public DynamicMethod RecompileMethod(Cecil.MethodDefinition methodDef) { Debug.Trace("Recompiling method: {0}", methodDef.FullName); var declaringType = FindType(methodDef.DeclaringType); var returnType = FindType(methodDef.ReturnType); Type[] paramTypes = methodDef.Parameters.Select((paramDef) => { return(FindType(paramDef.ParameterType)); }).ToArray(); if (!methodDef.IsStatic) { paramTypes = new Type[] { declaringType }.Concat(paramTypes).ToArray(); } var dynMethod = new DynamicMethod(methodDef.Name, returnType, paramTypes, true); ILGenerator il = dynMethod.GetILGenerator(); dynMethod.InitLocals = methodDef.Body.InitLocals; foreach (var variable in methodDef.Body.Variables) { var localType = FindType(variable.VariableType); Debug.Trace("Declaring local (cecil type: {0}) of type (runtime type: {1})", variable.VariableType, localType); il.DeclareLocal(localType); } var labels = new Dictionary <Cecil.Cil.Instruction, Label>(); foreach (var inst in methodDef.Body.Instructions) { if (inst.Operand != null && inst.Operand.GetType() == typeof(Cecil.Cil.Instruction)) { var opinst = (Cecil.Cil.Instruction)(inst.Operand); labels[opinst] = il.DefineLabel(); } } foreach (var inst in Instrument.IterateInstructions(methodDef)) { Debug.Trace("Emitting: {0}", inst); Label label; if (labels.TryGetValue(inst, out label)) { il.MarkLabel(label); } var ilop = FindOpcode(inst.OpCode); if (inst.Operand != null) { var operand = inst.Operand; var operandType = operand.GetType(); // Dynamic dispatch implementation: // We have to run different processing code depending on the type of instruction // operand. Visitor pattern cannot be implemented here, because we don't actually // own the classes that are the operands (and some of them are primitive or system // types). // Therefore, dynamic dispatcher is used. Method for each operand type is implemented // in this class, and reflection is used to find correct method to call. // In newer .net versions we would be able to do EmitInstruction(il, ilop, (dynamic)operand), // but the .net version we are targeting (because of Unity compatibility) does not // have `dynamic`. if (operandType == typeof(Cecil.Cil.Instruction)) { //branch location var operandInst = (Cecil.Cil.Instruction)operand; il.Emit(ilop, labels[operandInst]); } else if (primitiveOperandTypes.Contains(operandType)) { //if operand is primitive, call il.Emit directly Reflection.MethodInfo method; if (!EmitPrimitiveCache.TryGetValue(operandType, out method)) { method = typeof(ILGenerator).GetMethod("Emit", new Type[] { typeof(OpCode), operandType }); EmitPrimitiveCache[operandType] = method; } if (method == null) { throw new Exception(String.Format("Emit method for primitive type {0} not found.", operandType.Name)); } method.Invoke(il, new object[] { ilop, operand }); } else { //or else, call our EmitInstruction Reflection.MethodInfo method; if (!EmitInstructionCache.TryGetValue(operandType, out method)) { method = GetType().GetMethod("EmitInstruction", bindingAttr: bflags_all_instance, binder: null, modifiers: null, types: new Type[] { typeof(ILGenerator), typeof(OpCode), operandType }); EmitInstructionCache[operandType] = method; } if (method == null) { throw new Exception(String.Format("Don't know what to do with operand {0}", operandType.Name)); } method.Invoke(this, new object[] { il, ilop, operand }); } } else { il.Emit(ilop); } } return(dynMethod); }
public void HotPatch(string assemblyFilename) { Debug.Trace("Started hotpatching {0}", assemblyFilename); var newAssembly = Cecil.AssemblyDefinition.ReadAssembly(assemblyFilename); foreach (var method in IterateMethods(newAssembly)) { Debug.Trace($"Searching for {method.FullName}"); LocalMethod localMethod; if (!localMethods.TryGetValue(method.FullName, out localMethod)) { Debug.Trace($"Did not find loaded method {method.FullName}. New method or a bug."); continue; } string newBody = ConcatBody(method); int newBodyHash = newBody.GetHashCode(); if (localMethod.BodyHashCode == newBody.GetHashCode()) { Debug.Trace($"Found {method.FullName}, but hash code didn't change."); continue; } string oldBody = localMethod.BodyString; Debug.Log($"<i>Trying to hotpatch {method.FullName}...</i>"); if (localMethod.Method == null) { Debug.Log($"Can't hotpatch {method.FullName} - local method not found,"); continue; } Debug.Trace("----------"); Debug.Trace("Hotswapping {0}", method.FullName); Debug.Trace("Method body hashcode was: {0}, is: {1}", localMethod.BodyHashCode, newBodyHash); Debug.Trace("Method body was:"); Debug.Trace(oldBody); Debug.Trace("Method body is:"); Debug.Trace(newBody); try { var dynmethod = recompiler.RecompileMethod(method); if (dynmethod != null) { SwapMethod(localMethod.Method, dynmethod); localMethod.CurrentSwap = dynmethod; localMethod.BodyString = newBody; localMethod.BodyHashCode = newBodyHash; } } catch (Exception e) { Debug.LogWarningFormat("Failed to patch {0}. <i>See full stacktrace in Temp/hotpatch.log.</i>\nError is: {1}", method.FullName, e.Message); Debug.Trace(e.StackTrace); } finally { Debug.Trace("----------"); } } }