/// <summary> /// Generates instructions to load arguments or default values onto the stack in a /// detour method. /// </summary> /// <param name="generator">The method where the calls will be added.</param> /// <param name="actualParams">The actual parameters required.</param> /// <param name="expectedParams">The parameters provided.</param> /// <param name="offset">The offset to start loading (0 = static, 1 = instance).</param> private static void LoadParameters(ILGenerator generator, ParameterInfo[] actualParams, Type[] expectedParams, int offset) { // Load the known method arguments onto the stack int n = expectedParams.Length, need = actualParams.Length + offset; if (n > 0) { generator.Emit(OpCodes.Ldarg_0); } if (n > 1) { generator.Emit(OpCodes.Ldarg_1); } if (n > 2) { generator.Emit(OpCodes.Ldarg_2); } if (n > 3) { generator.Emit(OpCodes.Ldarg_3); } for (int i = 4; i < n; i++) { generator.Emit(OpCodes.Ldarg_S, i); } // Load the rest as defaults for (int i = n; i < need; i++) { var param = actualParams[i - offset]; PTranspilerTools.GenerateDefaultLoad(generator, param.ParameterType, param. DefaultValue); } }
/// <summary> /// Verifies that the delegate signature provided in dst can be dynamically mapped to /// the method provided by src, with the possible addition of optional parameters set /// to their default values. /// </summary> /// <param name="expected">The method return type and parameter types expected.</param> /// <param name="actual">The method to be called.</param> /// <param name="actualReturn">The type of the method or constructor's return value.</param> /// <returns>The parameters used in the call to the actual method.</returns> /// <exception cref="DetourException">If the delegate does not match the target.</exception> private static ParameterInfo[] ValidateDelegate(DelegateInfo expected, MethodBase actual, Type actualReturn) { var parameterTypes = expected.ParameterTypes; var returnType = expected.ReturnType; // Validate return types if (!returnType.IsAssignableFrom(actualReturn)) { throw new DetourException("Return type {0} cannot be converted to type {1}". F(actualReturn.FullName, returnType.FullName)); } // Do not allow methods declared in not yet closed generic types var baseType = actual.DeclaringType; if (baseType.ContainsGenericParameters) { throw new DetourException(("Method parent type {0} must have all " + "generic parameters defined").F(baseType.FullName)); } // Validate parameter types string actualName = baseType.FullName + "." + actual.Name; var actualParams = actual.GetParameters(); int n = actualParams.Length, check = parameterTypes.Length; Type[] actualParamTypes, currentTypes = new Type[n]; bool noThisPointer = actual.IsStatic || actual.IsConstructor; for (int i = 0; i < n; i++) { currentTypes[i] = actualParams[i].ParameterType; } if (noThisPointer) { actualParamTypes = currentTypes; } else { actualParamTypes = PTranspilerTools.PushDeclaringType(currentTypes, baseType); n++; } if (check > n) { throw new DetourException(("Method {0} has only {1:D} parameters, but " + "{2:D} were supplied").F(actual.ToString(), n, check)); } // Check up to the number we have for (int i = 0; i < check; i++) { Type have = actualParamTypes[i], want = parameterTypes[i]; if (!have.IsAssignableFrom(want)) { throw new DetourException(("Argument {0:D} for method {3} cannot be " + "converted from {1} to {2}").F(i, have.FullName, want.FullName, actualName)); } } // Any remaining parameters must be optional int offset = noThisPointer ? 0 : 1; for (int i = check; i < n; i++) { var cParam = actualParams[i - offset]; if (!cParam.IsOptional) { throw new DetourException(("New argument {0:D} for method {1} ({2}) " + "is not optional").F(i, actualName, cParam.ParameterType.FullName)); } } return(actualParams); }
/// <summary> /// Creates a dynamic detour method of the specified delegate type to wrap a base game /// method with the specified name. The dynamic method will automatically adapt if /// optional parameters are added, filling in their default values. /// </summary> /// <typeparam name="D">The delegate type to be used to call the detour.</typeparam> /// <param name="target">The target method to be called.</param> /// <returns>The detour that will call that method.</returns> /// <exception cref="DetourException">If the delegate does not match the target.</exception> public static D Detour <D>(this MethodInfo target) where D : Delegate { if (target == null) { throw new ArgumentNullException("target"); } if (target.ContainsGenericParameters) { throw new ArgumentException("Generic types must have all parameters defined"); } var expected = DelegateInfo.Create(typeof(D)); var parentType = target.DeclaringType; var expectedParamTypes = expected.ParameterTypes; var actualParams = ValidateDelegate(expected, target); int offset = target.IsStatic ? 0 : 1; // Method will be "declared" in the type of the target, as we are detouring around // a method of that type var caller = new DynamicMethod(target.Name + "_Detour", expected.ReturnType, expectedParamTypes, parentType, true); var generator = caller.GetILGenerator(); // Load the known method arguments onto the stack int n = expectedParamTypes.Length, need = actualParams.Length + offset; if (n > 0) { generator.Emit(OpCodes.Ldarg_0); } if (n > 1) { generator.Emit(OpCodes.Ldarg_1); } if (n > 2) { generator.Emit(OpCodes.Ldarg_2); } if (n > 3) { generator.Emit(OpCodes.Ldarg_3); } for (int i = 4; i < n; i++) { generator.Emit(OpCodes.Ldarg_S, i); } // Load the rest as defaults for (int i = n; i < need; i++) { var param = actualParams[i - offset]; PTranspilerTools.GenerateDefaultLoad(generator, param.ParameterType, param. DefaultValue); } if (parentType.IsValueType || target.IsStatic) { generator.Emit(OpCodes.Call, target); } else { generator.Emit(OpCodes.Callvirt, target); } generator.Emit(OpCodes.Ret); // Define the parameter names, parameter indexes start at 1 if (offset > 0) { caller.DefineParameter(1, ParameterAttributes.None, "this"); } for (int i = offset; i < n; i++) { var oldParam = actualParams[i - offset]; caller.DefineParameter(i + 1, oldParam.Attributes, oldParam.Name); } #if DEBUG PUtil.LogDebug("Created delegate {0} for method {1}.{2} with parameters [{3}]". F(caller.Name, parentType.FullName, target.Name, actualParams.Join(","))); #endif return(caller.CreateDelegate(typeof(D)) as D); }