601 lines
26 KiB
C#
601 lines
26 KiB
C#
/*
|
|
* Copyright 2022 Peter Han
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
|
* and associated documentation files (the "Software"), to deal in the Software without
|
|
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
|
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all copies or
|
|
* substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
|
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
using PeterHan.PLib.Core;
|
|
using System;
|
|
using System.Reflection;
|
|
using System.Reflection.Emit;
|
|
|
|
namespace PeterHan.PLib.Detours {
|
|
/// <summary>
|
|
/// Efficiently detours around many changes in the game by creating detour methods and
|
|
/// accessors which are resilient against many types of source compatible but binary
|
|
/// incompatible changes.
|
|
/// </summary>
|
|
public static class PDetours {
|
|
/// <summary>
|
|
/// Creates a dynamic detour method of the specified delegate type to wrap a base game
|
|
/// method with the same name as the delegate type. 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 the method with the name of the delegate type.</returns>
|
|
/// <exception cref="DetourException">If the delegate does not match any valid target method.</exception>
|
|
public static D Detour<D>(this Type type) where D : Delegate {
|
|
return Detour<D>(type, typeof(D).Name);
|
|
}
|
|
|
|
/// <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
|
|
/// 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.
|
|
///
|
|
/// This overload creates a lazy detour that only performs the expensive reflection
|
|
/// when it is first used.
|
|
/// </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 DetouredMethod<D> DetourLazy<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));
|
|
return new DetouredMethod<D>(type, name);
|
|
}
|
|
|
|
/// <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;
|
|
if (parentType == null)
|
|
throw new ArgumentException("Method is not declared by an actual type");
|
|
// 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
|
|
/// 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);
|
|
if (parentType == null)
|
|
throw new ArgumentException("Method is not declared by an actual type");
|
|
// 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 dynamic detour methods to wrap a base game field or property with the
|
|
/// specified name. The detour will still work even if the field is converted to a
|
|
/// source compatible property and vice versa.
|
|
/// </summary>
|
|
/// <typeparam name="P">The type of the parent class.</typeparam>
|
|
/// <typeparam name="T">The type of the field or property element.</typeparam>
|
|
/// <param name="name">The name of the field or property to be accessed.</param>
|
|
/// <returns>A detour element that wraps the field or property with common getter and
|
|
/// setter delegates which will work on both types.</returns>
|
|
public static IDetouredField<P, T> DetourField<P, T>(string name) {
|
|
if (string.IsNullOrEmpty(name))
|
|
throw new ArgumentNullException(nameof(name));
|
|
var pt = typeof(P);
|
|
var field = pt.GetField(name, PPatchTools.BASE_FLAGS | BindingFlags.
|
|
Static | BindingFlags.Instance);
|
|
IDetouredField<P, T> d;
|
|
if (field == null) {
|
|
try {
|
|
var property = pt.GetProperty(name, PPatchTools.BASE_FLAGS |
|
|
BindingFlags.Static | BindingFlags.Instance);
|
|
if (property == null)
|
|
throw new DetourException("Unable to find {0} on type {1}".
|
|
F(name, typeof(P).FullName));
|
|
d = DetourProperty<P, T>(property);
|
|
} catch (AmbiguousMatchException) {
|
|
throw new DetourException("Unable to find {0} on type {1}".
|
|
F(name, typeof(P).FullName));
|
|
}
|
|
} else {
|
|
if (pt.IsValueType || (pt.IsByRef && (pt.GetElementType()?.IsValueType ??
|
|
false)))
|
|
throw new ArgumentException("For accessing struct fields, use DetourStructField");
|
|
d = DetourField<P, T>(field);
|
|
}
|
|
return d;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates dynamic detour methods to wrap a base game field or property with the
|
|
/// specified name. The detour will still work even if the field is converted to a
|
|
/// source compatible property and vice versa.
|
|
///
|
|
/// This overload creates a lazy detour that only performs the expensive reflection
|
|
/// when it is first used.
|
|
/// </summary>
|
|
/// <typeparam name="P">The type of the parent class.</typeparam>
|
|
/// <typeparam name="T">The type of the field or property element.</typeparam>
|
|
/// <param name="name">The name of the field or property to be accessed.</param>
|
|
/// <returns>A detour element that wraps the field or property with common getter and
|
|
/// setter delegates which will work on both types.</returns>
|
|
public static IDetouredField<P, T> DetourFieldLazy<P, T>(string name) {
|
|
if (string.IsNullOrEmpty(name))
|
|
throw new ArgumentNullException(nameof(name));
|
|
return new LazyDetouredField<P, T>(typeof(P), name);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates dynamic detour methods to wrap a base game field with the specified name.
|
|
/// </summary>
|
|
/// <typeparam name="P">The type of the parent class.</typeparam>
|
|
/// <typeparam name="T">The type of the field element.</typeparam>
|
|
/// <param name="target">The field which will be accessed.</param>
|
|
/// <returns>A detour element that wraps the field with a common interface matching
|
|
/// that of a detoured property.</returns>
|
|
private static IDetouredField<P, T> DetourField<P, T>(FieldInfo target) {
|
|
if (target == null)
|
|
throw new ArgumentNullException(nameof(target));
|
|
var parentType = target.DeclaringType;
|
|
string name = target.Name;
|
|
if (parentType != typeof(P))
|
|
throw new ArgumentException("Parent type does not match delegate to be created");
|
|
var getter = new DynamicMethod(name + "_Detour_Get", typeof(T), new[] {
|
|
typeof(P)
|
|
}, true);
|
|
var generator = getter.GetILGenerator();
|
|
// Getter will load the first argument and use ldfld/ldsfld
|
|
if (target.IsStatic)
|
|
generator.Emit(OpCodes.Ldsfld, target);
|
|
else {
|
|
generator.Emit(OpCodes.Ldarg_0);
|
|
generator.Emit(OpCodes.Ldfld, target);
|
|
}
|
|
generator.Emit(OpCodes.Ret);
|
|
DynamicMethod setter;
|
|
if (target.IsInitOnly)
|
|
// Handle readonly fields
|
|
setter = null;
|
|
else {
|
|
setter = new DynamicMethod(name + "_Detour_Set", null, new[] {
|
|
typeof(P), typeof(T)
|
|
}, true);
|
|
generator = setter.GetILGenerator();
|
|
// Setter will load both arguments and use stfld/stsfld (argument 1 is ignored
|
|
// for static fields)
|
|
if (target.IsStatic) {
|
|
generator.Emit(OpCodes.Ldarg_1);
|
|
generator.Emit(OpCodes.Stsfld, target);
|
|
} else {
|
|
generator.Emit(OpCodes.Ldarg_0);
|
|
generator.Emit(OpCodes.Ldarg_1);
|
|
generator.Emit(OpCodes.Stfld, target);
|
|
}
|
|
generator.Emit(OpCodes.Ret);
|
|
}
|
|
#if DEBUG
|
|
PUtil.LogDebug("Created delegate for field {0}.{1} with type {2}".
|
|
F(parentType.FullName, target.Name, typeof(T).FullName));
|
|
#endif
|
|
return new DetouredField<P, T>(name, getter.CreateDelegate(typeof(Func<P, T>)) as
|
|
Func<P, T>, setter?.CreateDelegate(typeof(Action<P, T>)) as Action<P, T>);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates dynamic detour methods to wrap a base game property with the specified name.
|
|
/// </summary>
|
|
/// <typeparam name="P">The type of the parent class.</typeparam>
|
|
/// <typeparam name="T">The type of the property element.</typeparam>
|
|
/// <param name="target">The property which will be accessed.</param>
|
|
/// <returns>A detour element that wraps the property with a common interface matching
|
|
/// that of a detoured field.</returns>
|
|
/// <exception cref="DetourException">If the property has indexers.</exception>
|
|
private static IDetouredField<P, T> DetourProperty<P, T>(PropertyInfo target) {
|
|
if (target == null)
|
|
throw new ArgumentNullException(nameof(target));
|
|
var parentType = target.DeclaringType;
|
|
string name = target.Name;
|
|
if (parentType != typeof(P))
|
|
throw new ArgumentException("Parent type does not match delegate to be created");
|
|
var indexes = target.GetIndexParameters();
|
|
if (indexes != null && indexes.Length > 0)
|
|
throw new DetourException("Cannot detour on properties with index arguments");
|
|
DynamicMethod getter, setter;
|
|
var getMethod = target.GetGetMethod(true);
|
|
if (target.CanRead && getMethod != null) {
|
|
getter = new DynamicMethod(name + "_Detour_Get", typeof(T), new[] {
|
|
typeof(P)
|
|
}, true);
|
|
var generator = getter.GetILGenerator();
|
|
// Getter will load the first argument and call the property getter
|
|
if (!getMethod.IsStatic)
|
|
generator.Emit(OpCodes.Ldarg_0);
|
|
generator.Emit(OpCodes.Call, getMethod);
|
|
generator.Emit(OpCodes.Ret);
|
|
} else
|
|
getter = null;
|
|
var setMethod = target.GetSetMethod(true);
|
|
if (target.CanWrite && setMethod != null) {
|
|
setter = new DynamicMethod(name + "_Detour_Set", null, new[] {
|
|
typeof(P), typeof(T)
|
|
}, true);
|
|
var generator = setter.GetILGenerator();
|
|
// Setter will load both arguments and call property setter (argument 1 is
|
|
// ignored for static properties)
|
|
if (!setMethod.IsStatic)
|
|
generator.Emit(OpCodes.Ldarg_0);
|
|
generator.Emit(OpCodes.Ldarg_1);
|
|
generator.Emit(OpCodes.Call, setMethod);
|
|
generator.Emit(OpCodes.Ret);
|
|
} else
|
|
setter = null;
|
|
#if DEBUG
|
|
PUtil.LogDebug("Created delegate for property {0}.{1} with type {2}".
|
|
F(parentType.FullName, target.Name, typeof(T).FullName));
|
|
#endif
|
|
return new DetouredField<P, T>(name, getter?.CreateDelegate(typeof(Func<P, T>)) as
|
|
Func<P, T>, setter?.CreateDelegate(typeof(Action<P, T>)) as Action<P, T>);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates dynamic detour methods to wrap a base game struct field with the specified
|
|
/// name. For static struct fields, use the regular DetourField.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of the field element.</typeparam>
|
|
/// <param name="parentType">The struct type which will be accessed.</param>
|
|
/// <param name="name">The name of the struct field to be accessed.</param>
|
|
/// <returns>A detour element that wraps the field with a common interface matching
|
|
/// that of a detoured property.</returns>
|
|
public static IDetouredField<object, T> DetourStructField<T>(this Type parentType,
|
|
string name) {
|
|
if (parentType == null)
|
|
throw new ArgumentNullException(nameof(parentType));
|
|
if (string.IsNullOrEmpty(name))
|
|
throw new ArgumentNullException(nameof(name));
|
|
var target = parentType.GetField(name, PPatchTools.BASE_FLAGS | BindingFlags.
|
|
Instance);
|
|
if (target == null)
|
|
throw new DetourException("Unable to find {0} on type {1}".F(name, parentType.
|
|
FullName));
|
|
var getter = new DynamicMethod(name + "_Detour_Get", typeof(T), new[] {
|
|
typeof(object)
|
|
}, true);
|
|
var generator = getter.GetILGenerator();
|
|
// Getter will load the first argument, unbox. and use ldfld
|
|
generator.Emit(OpCodes.Ldarg_0);
|
|
generator.Emit(OpCodes.Unbox, parentType);
|
|
generator.Emit(OpCodes.Ldfld, target);
|
|
generator.Emit(OpCodes.Ret);
|
|
DynamicMethod setter;
|
|
if (target.IsInitOnly)
|
|
// Handle readonly fields
|
|
setter = null;
|
|
else {
|
|
setter = new DynamicMethod(name + "_Detour_Set", null, new[] {
|
|
typeof(object), typeof(T)
|
|
}, true);
|
|
generator = setter.GetILGenerator();
|
|
// Setter will load both arguments, unbox and use stfld
|
|
generator.Emit(OpCodes.Ldarg_0);
|
|
generator.Emit(OpCodes.Unbox, parentType);
|
|
generator.Emit(OpCodes.Ldarg_1);
|
|
generator.Emit(OpCodes.Stfld, target);
|
|
generator.Emit(OpCodes.Ret);
|
|
}
|
|
#if DEBUG
|
|
PUtil.LogDebug("Created delegate for struct field {0}.{1} with type {2}".
|
|
F(parentType.FullName, target.Name, typeof(T).FullName));
|
|
#endif
|
|
return new DetouredField<object, T>(name, getter.CreateDelegate(typeof(
|
|
Func<object, T>)) as Func<object, T>, setter?.CreateDelegate(typeof(
|
|
Action<object, T>)) as Action<object, T>);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates the required method parameters for the dynamic detour method.
|
|
/// </summary>
|
|
/// <param name="caller">The method where the parameters will be defined.</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 FinishDynamicMethod(DynamicMethod caller,
|
|
ParameterInfo[] actualParams, Type[] expectedParams, int offset) {
|
|
int n = expectedParams.Length;
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <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 == null)
|
|
throw new ArgumentException("Method is not declared by an actual type");
|
|
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>
|
|
/// Stores information about a delegate.
|
|
/// </summary>
|
|
private sealed class DelegateInfo {
|
|
/// <summary>
|
|
/// Creates delegate information on the specified delegate type.
|
|
/// </summary>
|
|
/// <param name="delegateType">The delegate type to wrap.</param>
|
|
/// <returns>Information about that delegate's return and parameter types.</returns>
|
|
public static DelegateInfo Create(Type delegateType) {
|
|
if (delegateType == null)
|
|
throw new ArgumentNullException(nameof(delegateType));
|
|
var expected = delegateType.GetMethodSafe("Invoke", false, PPatchTools.
|
|
AnyArguments);
|
|
if (expected == null)
|
|
throw new ArgumentException("Invalid delegate type: " + delegateType);
|
|
return new DelegateInfo(delegateType, expected.GetParameterTypes(),
|
|
expected.ReturnType);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The delegate's type.
|
|
/// </summary>
|
|
private readonly Type delegateType;
|
|
|
|
/// <summary>
|
|
/// The delegate's parameter types.
|
|
/// </summary>
|
|
public readonly Type[] parameterTypes;
|
|
|
|
/// <summary>
|
|
/// The delegate's return types.
|
|
/// </summary>
|
|
public readonly Type returnType;
|
|
|
|
private DelegateInfo(Type delegateType, Type[] parameterTypes, Type returnType) {
|
|
this.delegateType = delegateType;
|
|
this.parameterTypes = parameterTypes;
|
|
this.returnType = returnType;
|
|
}
|
|
|
|
public override string ToString() {
|
|
return "DelegateInfo[delegate={0},return={1},parameters={2}]".F(delegateType,
|
|
returnType, parameterTypes.Join());
|
|
}
|
|
}
|
|
}
|
|
}
|