/// <summary> /// Creates a dynamic detour method of the specified delegate type to wrap a base game /// constructor. 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 constructor to be called.</param> /// <returns>The detour that will call that constructor.</returns> /// <exception cref="DetourException">If the delegate does not match the target.</exception> public static D Detour <D>(this ConstructorInfo target) where D : Delegate { if (target == null) { throw new ArgumentNullException(nameof(target)); } if (target.ContainsGenericParameters) { throw new ArgumentException("Generic types must have all parameters defined"); } if (target.IsStatic) { throw new ArgumentException("Static constructors cannot be called manually"); } var expected = DelegateInfo.Create(typeof(D)); var parentType = target.DeclaringType; var expectedParamTypes = expected.ParameterTypes; var actualParams = ValidateDelegate(expected, target, parentType); // Method will be "declared" in the type of the target, as we are detouring around // a constructor of that type var caller = new DynamicMethod("Constructor_Detour", expected.ReturnType, expectedParamTypes, parentType, true); var generator = caller.GetILGenerator(); LoadParameters(generator, actualParams, expectedParamTypes, 0); generator.Emit(OpCodes.Newobj, target); generator.Emit(OpCodes.Ret); FinishDynamicMethod(caller, actualParams, expectedParamTypes, 0); #if DEBUG PUtil.LogDebug("Created delegate {0} for constructor {1} with parameters [{2}]". F(caller.Name, parentType.FullName, actualParams.Join(","))); #endif return(caller.CreateDelegate(typeof(D)) as D); }
/// <summary> /// Creates a dynamic detour method of the specified delegate type to wrap a base game /// constructor. 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="type">The target type.</param> /// <returns>The detour that will call that type's constructor.</returns> /// <exception cref="DetourException">If the delegate does not match any valid target method.</exception> public static D DetourConstructor <D>(this Type type) where D : Delegate { if (type == null) { throw new ArgumentNullException(nameof(type)); } var constructors = type.GetConstructors(PPatchTools.BASE_FLAGS | BindingFlags. Instance); // Determine delegate return type var expected = DelegateInfo.Create(typeof(D)); ConstructorInfo bestMatch = null; int bestParamCount = int.MaxValue; foreach (var constructor in constructors) { try { var result = ValidateDelegate(expected, constructor, type); int n = result.Length; // Choose overload with fewest parameters to substitute if (n < bestParamCount) { bestParamCount = n; bestMatch = constructor; } } catch (DetourException) { // Keep looking } } if (bestMatch == null) { throw new DetourException("No match found for {0} constructor".F(type. FullName)); } return(Detour <D>(bestMatch)); }
/// <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="type">The target type.</param> /// <param name="name">The method name.</param> /// <returns>The detour that will call that method.</returns> /// <exception cref="DetourException">If the delegate does not match any valid target method.</exception> public static D Detour <D>(this Type type, string name) where D : Delegate { if (type == null) { throw new ArgumentNullException(nameof(type)); } if (string.IsNullOrEmpty(name)) { throw new ArgumentNullException(nameof(name)); } var methods = type.GetMethods(PPatchTools.BASE_FLAGS | BindingFlags.Static | BindingFlags.Instance); // Determine delegate return type var expected = DelegateInfo.Create(typeof(D)); MethodInfo bestMatch = null; int bestParamCount = int.MaxValue; foreach (var method in methods) { if (method.Name == name) { try { var result = ValidateDelegate(expected, method, method.ReturnType); int n = result.Length; // Choose overload with fewest parameters to substitute if (n < bestParamCount) { bestParamCount = n; bestMatch = method; } } catch (DetourException) { // Keep looking } } } if (bestMatch == null) { throw new DetourException("No match found for {1}.{0}".F(name, type.FullName)); } return(Detour <D>(bestMatch)); }
/// <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(nameof(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, target.ReturnType); 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(); LoadParameters(generator, actualParams, expectedParamTypes, offset); if (parentType.IsValueType || target.IsStatic) { generator.Emit(OpCodes.Call, target); } else { generator.Emit(OpCodes.Callvirt, target); } generator.Emit(OpCodes.Ret); FinishDynamicMethod(caller, actualParams, expectedParamTypes, offset); #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); }
/// <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); }