public static void RedirectConstructorFromBase(CecilContext stardewContext, Type asmType, Type[] test, string type, string method, Type[] parameters) { var types = test.Select(n => stardewContext.GetTypeReference(n)).ToArray(); var typeDef = stardewContext.GetTypeDefinition(asmType.Namespace + "." + asmType.Name); var typeDefBase = stardewContext.GetTypeDefinition(asmType.BaseType.Namespace + "." + asmType.BaseType.Name); var newConstructorReference = stardewContext.GetConstructorReference(typeDef); var oldConstructorReference = stardewContext.GetConstructorReference(typeDefBase); var concreteFromConstructor = MakeHostInstanceGeneric(oldConstructorReference, types); var concreteToConstructor = MakeHostInstanceGeneric(newConstructorReference, types); ILProcessor ilProcessor = stardewContext.GetMethodIlProcessor(type, method); var instructions = ilProcessor.Body.Instructions.Where(n => n.OpCode == OpCodes.Newobj && n.Operand is MethodReference && ((MethodReference)n.Operand).FullName == concreteFromConstructor.FullName).ToList(); foreach (var instruction in instructions) { ilProcessor.Replace(instruction, ilProcessor.Create(OpCodes.Newobj, concreteToConstructor)); } }
private static void InjectReturnableMethod <TParam, TThis, TInput, TLocal, TUseOutput, TMethodOutputBind>(CecilContext stardewContext, ILProcessor ilProcessor, Instruction target, MethodReference method, bool isExit = false) { ilProcessor.Body.SimplifyMacros(); // Add UseReturnVal variable if (ilProcessor.Body.Variables.All(n => n.Name != "UseReturnVal")) { var boolType = stardewContext.GetTypeReference(typeof(bool)); ilProcessor.Body.Variables.Add(new VariableDefinition("UseReturnVal", boolType)); } var useOutputVariable = ilProcessor.Body.Variables.First(n => n.Name == "UseReturnVal"); if (ilProcessor.Body.Variables.All(n => n.Name != "ReturnContainer-" + method.ReturnType.Name)) { ilProcessor.Body.Variables.Add(new VariableDefinition("ReturnContainer-" + method.ReturnType.Name, method.ReturnType)); } if (isExit) { if (ilProcessor.Body.Variables.All(n => n.Name != "OldReturnContainer")) { ilProcessor.Body.Variables.Add(new VariableDefinition("OldReturnContainer", method.ReturnType)); } } var containerVariable = ilProcessor.Body.Variables.First(n => n.Name == "ReturnContainer-" + method.ReturnType.Name); var oldContainerVariable = ilProcessor.Body.Variables.FirstOrDefault(n => n.Name == "OldReturnContainer"); List <Instruction> instructions = new List <Instruction>(); if (isExit) { instructions.Add(ilProcessor.Create(OpCodes.Stloc, oldContainerVariable)); } if (method.HasParameters) { foreach (var parameter in method.Parameters) { var paramLdInstruction = GetInstructionForParameter <TParam, TThis, TInput, TLocal, TUseOutput, TMethodOutputBind>(stardewContext, ilProcessor, parameter); if (paramLdInstruction == null) { throw new Exception($"Error parsing parameter setup on {parameter.Name}"); } instructions.Add(paramLdInstruction); } } instructions.Add(ilProcessor.Create(OpCodes.Call, method)); instructions.Add(ilProcessor.Create(OpCodes.Stloc, containerVariable)); instructions.Add(ilProcessor.Create(OpCodes.Ldloc, useOutputVariable)); if (!isExit) { instructions.Add(ilProcessor.Create(OpCodes.Brfalse, target)); instructions.Add(ilProcessor.Create(OpCodes.Ldloc, containerVariable)); instructions.Add(ilProcessor.Create(OpCodes.Br, ilProcessor.Body.Instructions.Last())); } else { var brToRet = ilProcessor.Create(OpCodes.Br, target); var loadOld = ilProcessor.Create(OpCodes.Ldloc, oldContainerVariable); var loadNew = ilProcessor.Create(OpCodes.Ldloc, containerVariable); instructions.Add(ilProcessor.Create(OpCodes.Brfalse, loadOld)); instructions.Add(loadNew); instructions.Add(brToRet); instructions.Add(loadOld); } foreach (var inst in instructions) { ilProcessor.InsertBefore(target, inst); } ilProcessor.Body.OptimizeMacros(); }
public static void InjectGlobalRoutePreMethod(CecilContext stardewContext, string injecteeType, string injecteeMethod, int index) { var fieldDefinition = stardewContext.GetFieldDefinition("Farmhand.Events.GlobalRouteManager", "IsEnabled"); var methodIsListenedTo = stardewContext.GetMethodDefinition("Farmhand.Events.GlobalRouteManager", "IsBeingPreListenedTo"); var methodDefinition = stardewContext.GetMethodDefinition("Farmhand.Events.GlobalRouteManager", "GlobalRoutePreInvoke"); var ilProcessor = stardewContext.GetMethodIlProcessor(injecteeType, injecteeMethod); if (ilProcessor == null || methodDefinition == null || fieldDefinition == null) { return; } var method = ilProcessor.Body.Method; var hasThis = method.HasThis; var argIndex = 0; var first = ilProcessor.Body.Instructions.First(); var last = ilProcessor.Body.Instructions.Last(); var objectType = stardewContext.GetTypeReference(typeof(object)); var voidType = stardewContext.GetTypeReference(typeof(void)); var newInstructions = new List <Instruction>(); newInstructions.Add(ilProcessor.Create(OpCodes.Ldsfld, fieldDefinition)); newInstructions.Add(ilProcessor.Create(OpCodes.Brfalse, first)); newInstructions.Add(ilProcessor.Create(OpCodes.Ldc_I4, index)); newInstructions.Add(ilProcessor.Create(OpCodes.Call, methodIsListenedTo)); newInstructions.Add(ilProcessor.Create(OpCodes.Brfalse, first)); newInstructions.Add(ilProcessor.Create(OpCodes.Ldc_I4, index)); newInstructions.Add(ilProcessor.Create(OpCodes.Ldstr, injecteeType)); newInstructions.Add(ilProcessor.Create(OpCodes.Ldstr, injecteeMethod)); var outputVar = new VariableDefinition("GlobalRouteOutput", objectType); ilProcessor.Body.Variables.Add(outputVar); newInstructions.Add(ilProcessor.Create(OpCodes.Ldloca, outputVar)); newInstructions.Add(ilProcessor.Create(OpCodes.Ldc_I4, method.Parameters.Count + (hasThis ? 1 : 0))); newInstructions.Add(ilProcessor.Create(OpCodes.Newarr, objectType)); if (hasThis) { newInstructions.Add(ilProcessor.Create(OpCodes.Dup)); newInstructions.Add(ilProcessor.Create(OpCodes.Ldc_I4, argIndex++)); newInstructions.Add(ilProcessor.Create(OpCodes.Ldarg, ilProcessor.Body.ThisParameter)); newInstructions.Add(ilProcessor.Create(OpCodes.Stelem_Ref)); } foreach (var param in method.Parameters) { newInstructions.Add(ilProcessor.Create(OpCodes.Dup)); newInstructions.Add(ilProcessor.Create(OpCodes.Ldc_I4, argIndex++)); newInstructions.Add(ilProcessor.Create(OpCodes.Ldarg, param)); if (param.ParameterType.IsPrimitive) { newInstructions.Add(ilProcessor.Create(OpCodes.Box, param.ParameterType)); } newInstructions.Add(ilProcessor.Create(OpCodes.Stelem_Ref)); } newInstructions.Add(ilProcessor.Create(OpCodes.Call, methodDefinition)); newInstructions.Add(ilProcessor.Create(OpCodes.Brfalse, first)); if (method.ReturnType != null && method.ReturnType.FullName != voidType.FullName) { newInstructions.Add(ilProcessor.Create(OpCodes.Ldloc, outputVar)); if (method.ReturnType.IsPrimitive || method.ReturnType.IsGenericParameter) { newInstructions.Add(ilProcessor.Create(OpCodes.Unbox_Any, method.ReturnType)); } else { newInstructions.Add(ilProcessor.Create(OpCodes.Castclass, method.ReturnType)); } newInstructions.Add(ilProcessor.Create(OpCodes.Br, last)); } ilProcessor.Body.SimplifyMacros(); if (newInstructions.Any()) { var previousInstruction = newInstructions.First(); ilProcessor.InsertBefore(first, previousInstruction); for (var i = 1; i < newInstructions.Count; ++i) { ilProcessor.InsertAfter(previousInstruction, newInstructions[i]); previousInstruction = newInstructions[i]; } } ilProcessor.Body.OptimizeMacros(); }
public static void InjectGlobalRoutePostMethod(CecilContext stardewContext, string injecteeType, string injecteeMethod, int index) { var ilProcessor = stardewContext.GetMethodIlProcessor(injecteeType, injecteeMethod); if (ilProcessor == null) { return; } var objectType = stardewContext.GetTypeReference(typeof(object)); var voidType = stardewContext.GetTypeReference(typeof(void)); var boolType = stardewContext.GetTypeReference(typeof(bool)); var method = ilProcessor.Body.Method; var hasThis = method.HasThis; var returnsValue = method.ReturnType != null && method.ReturnType.FullName != voidType.FullName; VariableDefinition outputVar = null; var isEnabledField = stardewContext.GetFieldDefinition("Farmhand.Events.GlobalRouteManager", "IsEnabled"); var methodIsListenedTo = stardewContext.GetMethodDefinition("Farmhand.Events.GlobalRouteManager", "IsBeingPostListenedTo"); MethodDefinition methodDefinition; methodDefinition = stardewContext.GetMethodDefinition("Farmhand.Events.GlobalRouteManager", "GlobalRoutePostInvoke", n => n.Parameters.Count == (returnsValue ? 5 : 4)); if (methodDefinition == null || isEnabledField == null) { return; } var retInstructions = ilProcessor.Body.Instructions.Where(n => n.OpCode == OpCodes.Ret).ToArray(); foreach (var ret in retInstructions) { var newInstructions = new List <Instruction>(); newInstructions.Add(ilProcessor.PushFieldToStack(isEnabledField)); newInstructions.Add(ilProcessor.BranchIfFalse(ret)); newInstructions.Add(ilProcessor.PushInt32ToStack(index)); newInstructions.Add(ilProcessor.Call(methodIsListenedTo)); newInstructions.Add(ilProcessor.BranchIfFalse(ret)); if (returnsValue) { outputVar = new VariableDefinition("GlobalRouteOutput", objectType); ilProcessor.Body.Variables.Add(outputVar); if (method.ReturnType.IsPrimitive || method.ReturnType.IsGenericParameter) { newInstructions.Add(ilProcessor.Create(OpCodes.Box, method.ReturnType)); } newInstructions.Add(ilProcessor.Create(OpCodes.Stloc, outputVar)); } newInstructions.Add(ilProcessor.PushInt32ToStack(index)); newInstructions.Add(ilProcessor.PushStringToStack(injecteeType)); newInstructions.Add(ilProcessor.PushStringToStack(injecteeMethod)); if (returnsValue) { newInstructions.Add(ilProcessor.Create(OpCodes.Ldloca, outputVar)); } int argIndex = 0; newInstructions.AddRange(ilProcessor.CreateArray(objectType, method.Parameters.Count + (hasThis ? 1 : 0))); if (method.HasThis) { newInstructions.AddRange(ilProcessor.InsertParameterIntoArray(ilProcessor.Body.ThisParameter, argIndex++)); } foreach (var param in method.Parameters) { newInstructions.AddRange(ilProcessor.InsertParameterIntoArray(param, argIndex++)); } newInstructions.Add(ilProcessor.Call(methodDefinition)); if (returnsValue) { newInstructions.Add(ilProcessor.Create(OpCodes.Ldloc, outputVar)); if (method.ReturnType.IsPrimitive || method.ReturnType.IsGenericParameter) { newInstructions.Add(ilProcessor.Create(OpCodes.Unbox_Any, method.ReturnType)); } else { newInstructions.Add(ilProcessor.Create(OpCodes.Castclass, method.ReturnType)); } } ilProcessor.Body.SimplifyMacros(); if (newInstructions.Any()) { var previousInstruction = newInstructions.First(); ilProcessor.InsertBefore(ret, previousInstruction); for (var i = 1; i < newInstructions.Count; ++i) { ilProcessor.InsertAfter(previousInstruction, newInstructions[i]); previousInstruction = newInstructions[i]; } } ilProcessor.Body.OptimizeMacros(); } }
private static void InjectMethod <TParam, TThis, TInput, TLocal>(CecilContext stardewContext, ILProcessor ilProcessor, Instruction target, MethodReference method, bool isExit = false, bool cancelable = false) { ilProcessor.Body.SimplifyMacros(); var callEnterInstruction = ilProcessor.Create(OpCodes.Call, method); if (method.HasThis) { var loadObjInstruction = ilProcessor.Create(OpCodes.Ldarg_0); ilProcessor.InsertBefore(target, loadObjInstruction); } if (method.HasParameters) { Instruction prevInstruction = null; var paramLdInstruction = target; var first = true; foreach (var parameter in method.Parameters) { paramLdInstruction = GetInstructionForParameter <TParam, TThis, TInput, TLocal, Patcher, Patcher>(stardewContext, ilProcessor, parameter); if (paramLdInstruction == null) { throw new Exception($"Error parsing parameter setup on {parameter.Name}"); } if (isExit) { if (first) { first = false; ilProcessor.Replace(target, paramLdInstruction); } else { ilProcessor.InsertAfter(prevInstruction, paramLdInstruction); } prevInstruction = paramLdInstruction; } else { ilProcessor.InsertBefore(target, paramLdInstruction); } } if (isExit) { if (first) { ilProcessor.Replace(target, callEnterInstruction); ilProcessor.InsertAfter(callEnterInstruction, ilProcessor.Create(OpCodes.Ret)); } else { ilProcessor.InsertAfter(prevInstruction, callEnterInstruction); ilProcessor.InsertAfter(callEnterInstruction, ilProcessor.Create(OpCodes.Ret)); } } else { ilProcessor.InsertAfter(paramLdInstruction, callEnterInstruction); } } else { if (isExit) { ilProcessor.Replace(target, callEnterInstruction); ilProcessor.InsertAfter(callEnterInstruction, ilProcessor.Create(OpCodes.Ret)); } else { ilProcessor.InsertBefore(target, callEnterInstruction); } } if (cancelable) { if (ilProcessor.Body.Method.MethodReturnType.ReturnType.FullName != stardewContext.GetTypeReference(typeof(void)).FullName) { throw new InvalidOperationException("Cancelable hooks are only supported for methods returning void"); } var branch = ilProcessor.Create(OpCodes.Brtrue, ilProcessor.Body.Instructions.Last()); ilProcessor.InsertAfter(callEnterInstruction, branch); } ilProcessor.Body.OptimizeMacros(); }