public Hook(MethodBase from, MethodInfo to, object target, ref HookConfig config) { Method = from; Target = to; DelegateTarget = target; // Check if hook ret -> method ret is valid. Don't check for method ret -> hook ret, as that's too strict. Type returnType = (from as MethodInfo)?.ReturnType ?? typeof(void); if (to.ReturnType != returnType && !to.ReturnType.IsCompatible(returnType)) { throw new InvalidOperationException($"Return type of hook for {from} doesn't match, must be {((from as MethodInfo)?.ReturnType ?? typeof(void)).FullName}"); } if (target == null && !to.IsStatic) { throw new InvalidOperationException($"Hook for method {from} must be static, or you must pass a target instance."); } ParameterInfo[] hookArgs = Target.GetParameters(); // Check if the parameters match. // If the delegate has got an extra first parameter that itself is a delegate, it's the orig trampoline passthrough. ParameterInfo[] args = Method.GetParameters(); Type[] argTypes; if (!Method.IsStatic) { argTypes = new Type[args.Length + 1]; argTypes[0] = Method.GetThisParamType(); for (int i = 0; i < args.Length; i++) { argTypes[i + 1] = args[i].ParameterType; } } else { argTypes = new Type[args.Length]; for (int i = 0; i < args.Length; i++) { argTypes[i] = args[i].ParameterType; } } Type origType = null; if (hookArgs.Length == argTypes.Length + 1 && typeof(Delegate).IsAssignableFrom(hookArgs[0].ParameterType)) { _OrigDelegateType = origType = hookArgs[0].ParameterType; } else if (hookArgs.Length != argTypes.Length) { throw new InvalidOperationException($"Parameter count of hook for {from} doesn't match, must be {argTypes.Length}"); } for (int i = 0; i < argTypes.Length; i++) { Type argMethod = argTypes[i]; Type argHook = hookArgs[i + (origType == null ? 0 : 1)].ParameterType; if (!argMethod.IsCompatible(argHook)) { throw new InvalidOperationException($"Parameter #{i} of hook for {from} doesn't match, must be {argMethod.FullName} or related"); } } MethodInfo origInvoke = _OrigDelegateInvoke = origType?.GetMethod("Invoke"); // TODO: Check origType Invoke arguments. DynamicMethodDefinition dmd; ILProcessor il; using (dmd = new DynamicMethodDefinition( $"Hook<{Method.GetID(simple: true)}>?{GetHashCode()}", (Method as MethodInfo)?.ReturnType ?? typeof(void), argTypes )) { il = dmd.GetILProcessor(); if (target != null) { _RefTarget = il.EmitReference(target); } if (origType != null) { _RefTrampoline = il.EmitReference <Delegate>(null); } for (int i = 0; i < argTypes.Length; i++) { il.Emit(OpCodes.Ldarg, i); } il.Emit(OpCodes.Call, Target); il.Emit(OpCodes.Ret); TargetReal = dmd.Generate().Pin(); } // Temporarily provide a trampoline that waits for the proper trampoline. if (origType != null) { ParameterInfo[] origArgs = origInvoke.GetParameters(); Type[] origArgTypes = new Type[origArgs.Length]; for (int i = 0; i < origArgs.Length; i++) { origArgTypes[i] = origArgs[i].ParameterType; } using (dmd = new DynamicMethodDefinition( $"Chain:TMP<{Method.GetID(simple: true)}>?{GetHashCode()}", (origInvoke as MethodInfo)?.ReturnType ?? typeof(void), origArgTypes )) { il = dmd.GetILProcessor(); // while (ref == null) { } _RefTrampolineTmp = il.EmitReference <Delegate>(null); il.Emit(OpCodes.Brfalse, il.Body.Instructions[0]); // Invoke the generated delegate. il.EmitGetReference <Delegate>(_RefTrampolineTmp.Value); for (int i = 0; i < argTypes.Length; i++) { il.Emit(OpCodes.Ldarg, i); } il.Emit(OpCodes.Callvirt, origInvoke); il.Emit(OpCodes.Ret); DynamicMethodHelper.SetReference(_RefTrampoline.Value, dmd.Generate().CreateDelegate(origType)); } } _Detour = new Detour(Method, TargetReal, new DetourConfig() { ManualApply = config.ManualApply, Priority = config.Priority, ID = config.ID, Before = config.Before, After = config.After }); _UpdateOrig(null); }
public Hook(MethodBase from, MethodInfo to, object target) { Method = from; _Hook = to; if (_Hook.ReturnType != ((from as MethodInfo)?.ReturnType ?? typeof(void))) { throw new InvalidOperationException($"Return type of hook for {from} doesn't match, must be {((from as MethodInfo)?.ReturnType ?? typeof(void)).FullName}"); } if (target == null && !to.IsStatic) { throw new InvalidOperationException($"Hook for method {from} must be static, or you must pass a target instance."); } ParameterInfo[] hookArgs = _Hook.GetParameters(); // Check if the parameters match. // If the delegate has got an extra first parameter that itself is a delegate, it's the orig trampoline passthrough. ParameterInfo[] args = Method.GetParameters(); Type[] argTypes; if (!Method.IsStatic) { argTypes = new Type[args.Length + 1]; argTypes[0] = Method.DeclaringType; for (int i = 0; i < args.Length; i++) { argTypes[i + 1] = args[i].ParameterType; } } else { argTypes = new Type[args.Length]; for (int i = 0; i < args.Length; i++) { argTypes[i] = args[i].ParameterType; } } Type origType = null; if (hookArgs.Length == argTypes.Length + 1 && typeof(Delegate).IsAssignableFrom(hookArgs[0].ParameterType)) { origType = hookArgs[0].ParameterType; } else if (hookArgs.Length != argTypes.Length) { throw new InvalidOperationException($"Parameter count of hook for {from} doesn't match, must be {argTypes.Length}"); } for (int i = 0; i < argTypes.Length; i++) { Type argMethod = argTypes[i]; Type argHook = hookArgs[i + (origType == null ? 0 : 1)].ParameterType; if (!argMethod.IsAssignableFrom(argHook) && !argHook.IsAssignableFrom(argMethod)) { throw new InvalidOperationException($"Parameter #{i} of hook for {from} doesn't match, must be {argMethod.FullName} or related"); } } MethodInfo origInvoke = origType?.GetMethod("Invoke"); // TODO: Check origType Invoke arguments. DynamicMethod dm; ILGenerator il; dm = new DynamicMethod( $"hook_{Method.Name}_{GetHashCode()}", (Method as MethodInfo)?.ReturnType ?? typeof(void), argTypes, Method.DeclaringType, true ); il = dm.GetILGenerator(); if (target != null) { _RefTarget = il.EmitReference(target); } if (origType != null) { _RefTrampoline = il.EmitReference <Delegate>(null); } // TODO: Use specialized Ldarg.* if possible; What about ref types? for (int i = 0; i < argTypes.Length; i++) { il.Emit(OpCodes.Ldarg, i); } il.Emit(OpCodes.Call, _Hook); il.Emit(OpCodes.Ret); Target = dm.Pin(); _Detour = new Detour(Method, Target); if (origType != null) { DynamicMethodHelper.SetReference(_RefTrampoline.Value, GenerateTrampoline(origInvoke).CreateDelegate(origType)); } }
public Hook(MethodBase from, MethodInfo to, object target) { Method = from; _Hook = to; if (!(OnDetour?.InvokeWhileTrue(this, from, to, target) ?? true)) { return; } // Check if hook ret -> method ret is valid. Don't check for method ret -> hook ret, as that's too strict. Type returnType = (from as MethodInfo)?.ReturnType ?? typeof(void); if (_Hook.ReturnType != returnType && !_Hook.ReturnType.IsCompatible(returnType)) { throw new InvalidOperationException($"Return type of hook for {from} doesn't match, must be {((from as MethodInfo)?.ReturnType ?? typeof(void)).FullName}"); } if (target == null && !to.IsStatic) { throw new InvalidOperationException($"Hook for method {from} must be static, or you must pass a target instance."); } ParameterInfo[] hookArgs = _Hook.GetParameters(); // Check if the parameters match. // If the delegate has got an extra first parameter that itself is a delegate, it's the orig trampoline passthrough. ParameterInfo[] args = Method.GetParameters(); Type[] argTypes; if (!Method.IsStatic) { argTypes = new Type[args.Length + 1]; argTypes[0] = Method.GetThisParamType(); for (int i = 0; i < args.Length; i++) { argTypes[i + 1] = args[i].ParameterType; } } else { argTypes = new Type[args.Length]; for (int i = 0; i < args.Length; i++) { argTypes[i] = args[i].ParameterType; } } Type origType = null; if (hookArgs.Length == argTypes.Length + 1 && typeof(Delegate).IsAssignableFrom(hookArgs[0].ParameterType)) { _OrigDelegateType = origType = hookArgs[0].ParameterType; } else if (hookArgs.Length != argTypes.Length) { throw new InvalidOperationException($"Parameter count of hook for {from} doesn't match, must be {argTypes.Length}"); } for (int i = 0; i < argTypes.Length; i++) { Type argMethod = argTypes[i]; Type argHook = hookArgs[i + (origType == null ? 0 : 1)].ParameterType; if (!argMethod.IsCompatible(argHook)) { throw new InvalidOperationException($"Parameter #{i} of hook for {from} doesn't match, must be {argMethod.FullName} or related"); } } MethodInfo origInvoke = _OrigDelegateInvoke = origType?.GetMethod("Invoke"); // TODO: Check origType Invoke arguments. DynamicMethod dm; ILGenerator il; dm = new DynamicMethod( $"Hook<{Method}>?{GetHashCode()}", (Method as MethodInfo)?.ReturnType ?? typeof(void), argTypes, Method.DeclaringType, true ); il = dm.GetILGenerator(); if (target != null) { _RefTarget = il.EmitReference(target); } if (origType != null) { _RefTrampoline = il.EmitReference <Delegate>(null); } // TODO: Use specialized Ldarg.* if possible; What about ref types? for (int i = 0; i < argTypes.Length; i++) { il.Emit(OpCodes.Ldarg, i); } il.Emit(OpCodes.Call, _Hook); il.Emit(OpCodes.Ret); Target = dm.Pin(); // Temporarily provide a trampoline that waits for the proper trampoline. if (origType != null) { ParameterInfo[] origArgs = origInvoke.GetParameters(); Type[] origArgTypes = new Type[origArgs.Length]; for (int i = 0; i < origArgs.Length; i++) { origArgTypes[i] = origArgs[i].ParameterType; } dm = new DynamicMethod( $"Chain:TMP<{Method}>?{GetHashCode()}", (origInvoke as MethodInfo)?.ReturnType ?? typeof(void), origArgTypes, Method.DeclaringType, true ); il = dm.GetILGenerator(); // while (ref == null) { } Label lblStart = il.DefineLabel(); il.MarkLabel(lblStart); _RefTrampolineTmp = il.EmitReference <Delegate>(null); il.Emit(OpCodes.Brfalse, lblStart); // Invoke the generated delegate. il.EmitGetReference <Delegate>(_RefTrampolineTmp.Value); // TODO: Use specialized Ldarg.* if possible; What about ref types? for (int i = 0; i < argTypes.Length; i++) { il.Emit(OpCodes.Ldarg, i); } il.Emit(OpCodes.Callvirt, origInvoke); il.Emit(OpCodes.Ret); DynamicMethodHelper.SetReference(_RefTrampoline.Value, dm.CreateDelegate(origType)); } _Detour = new Detour(Method, Target); UpdateOrig(null); }