Initial commit
This commit is contained in:
28
mod/PLibCore/Detours/DetourException.cs
Normal file
28
mod/PLibCore/Detours/DetourException.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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 System;
|
||||
|
||||
namespace PeterHan.PLib.Detours {
|
||||
/// <summary>
|
||||
/// An exception thrown when constructing a detour.
|
||||
/// </summary>
|
||||
public class DetourException : ArgumentException {
|
||||
public DetourException(string message) : base(message) { }
|
||||
}
|
||||
}
|
||||
53
mod/PLibCore/Detours/DetouredField.cs
Normal file
53
mod/PLibCore/Detours/DetouredField.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 System;
|
||||
|
||||
namespace PeterHan.PLib.Detours {
|
||||
/// <summary>
|
||||
/// Stores delegates used to read and write fields or properties.
|
||||
/// </summary>
|
||||
/// <typeparam name="P">The containing type of the field or property.</typeparam>
|
||||
/// <typeparam name="T">The element type of the field or property.</typeparam>
|
||||
internal sealed class DetouredField<P, T> : IDetouredField<P, T> {
|
||||
/// <summary>
|
||||
/// Invoke to get the field/property value.
|
||||
/// </summary>
|
||||
public Func<P, T> Get { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The field name.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoke to set the field/property value. Null if the field is const or readonly.
|
||||
/// </summary>
|
||||
public Action<P, T> Set { get; }
|
||||
|
||||
internal DetouredField(string name, Func<P, T> get, Action<P, T> set) {
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
Get = get;
|
||||
Set = set;
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return string.Format("DetouredField[name={0}]", Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
73
mod/PLibCore/Detours/DetouredMethod.cs
Normal file
73
mod/PLibCore/Detours/DetouredMethod.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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 System;
|
||||
|
||||
namespace PeterHan.PLib.Detours {
|
||||
/// <summary>
|
||||
/// Stores a detoured method, only performing the expensive reflection when the detour is
|
||||
/// first used.
|
||||
///
|
||||
/// This class is not thread safe.
|
||||
/// <typeparam name="D">The delegate type to be used to call the detour.</typeparam>
|
||||
/// </summary>
|
||||
public sealed class DetouredMethod<D> where D : Delegate {
|
||||
/// <summary>
|
||||
/// Emulates the ability of Delegate.Invoke to actually call the method.
|
||||
/// </summary>
|
||||
public D Invoke {
|
||||
get {
|
||||
Initialize();
|
||||
return delg;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The method name.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The delegate method which will be called.
|
||||
/// </summary>
|
||||
private D delg;
|
||||
|
||||
/// <summary>
|
||||
/// The target type.
|
||||
/// </summary>
|
||||
private readonly Type type;
|
||||
|
||||
internal DetouredMethod(Type type, string name) {
|
||||
this.type = type ?? throw new ArgumentNullException(nameof(type));
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
delg = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the getter and setter functions immediately if necessary.
|
||||
/// </summary>
|
||||
public void Initialize() {
|
||||
if (delg == null)
|
||||
delg = PDetours.Detour<D>(type, Name);
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return string.Format("LazyDetouredMethod[type={1},name={0}]", Name, type.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
44
mod/PLibCore/Detours/IDetouredField.cs
Normal file
44
mod/PLibCore/Detours/IDetouredField.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 System;
|
||||
|
||||
namespace PeterHan.PLib.Detours {
|
||||
/// <summary>
|
||||
/// An interface that describes a detoured field, which stores delegates used to read and
|
||||
/// write fields or properties.
|
||||
/// </summary>
|
||||
/// <typeparam name="P">The containing type of the field or property.</typeparam>
|
||||
/// <typeparam name="T">The element type of the field or property.</typeparam>
|
||||
public interface IDetouredField<P, T> {
|
||||
/// <summary>
|
||||
/// Invoke to get the field/property value.
|
||||
/// </summary>
|
||||
Func<P, T> Get { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The field name.
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoke to set the field/property value.
|
||||
/// </summary>
|
||||
Action<P, T> Set { get; }
|
||||
}
|
||||
}
|
||||
93
mod/PLibCore/Detours/LazyDetouredField.cs
Normal file
93
mod/PLibCore/Detours/LazyDetouredField.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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 System;
|
||||
|
||||
namespace PeterHan.PLib.Detours {
|
||||
/// <summary>
|
||||
/// Stores delegates used to read and write fields or properties. This version is lazy and
|
||||
/// only calculates the destination when it is first used.
|
||||
///
|
||||
/// This class is not thread safe.
|
||||
/// </summary>
|
||||
/// <typeparam name="P">The containing type of the field or property.</typeparam>
|
||||
/// <typeparam name="T">The element type of the field or property.</typeparam>
|
||||
internal sealed class LazyDetouredField<P, T> : IDetouredField<P, T> {
|
||||
/// <summary>
|
||||
/// Invoke to get the field/property value.
|
||||
/// </summary>
|
||||
public Func<P, T> Get {
|
||||
get {
|
||||
Initialize();
|
||||
return getter;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The field name.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoke to set the field/property value.
|
||||
/// </summary>
|
||||
public Action<P, T> Set {
|
||||
get {
|
||||
Initialize();
|
||||
return setter;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The function to get the field value.
|
||||
/// </summary>
|
||||
private Func<P, T> getter;
|
||||
|
||||
/// <summary>
|
||||
/// The function to set the field value.
|
||||
/// </summary>
|
||||
private Action<P, T> setter;
|
||||
|
||||
/// <summary>
|
||||
/// The target type.
|
||||
/// </summary>
|
||||
private readonly Type type;
|
||||
|
||||
internal LazyDetouredField(Type type, string name) {
|
||||
this.type = type ?? throw new ArgumentNullException(nameof(type));
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
getter = null;
|
||||
setter = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the getter and setter functions immediately if necessary.
|
||||
/// </summary>
|
||||
public void Initialize() {
|
||||
if (getter == null && setter == null) {
|
||||
var dt = PDetours.DetourField<P, T>(Name);
|
||||
getter = dt.Get;
|
||||
setter = dt.Set;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return string.Format("LazyDetouredField[type={1},name={0}]", Name, type.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
600
mod/PLibCore/Detours/PDetours.cs
Normal file
600
mod/PLibCore/Detours/PDetours.cs
Normal file
@@ -0,0 +1,600 @@
|
||||
/*
|
||||
* 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user