/// <summary> /// Add a hook method to plugin /// </summary> /// <param name="name">Hook name</param> /// <param name="method">Method Info instance of the needed method</param> public void AddHookMethod(string name, MethodInfo method) { if (Hooks.ContainsKey(name)) { Log.Warning($"Plugin {Title} tried to register an existing hook"); return; } Hooks[name] = new HookMethod(this, name, method); }
public static void AddPostfix(HookMethod postfix) { if (Data == null) { throw new InvalidOperationException("Please call SetupHook before adding postfixes."); } var invoker = new HookCallbackInvoker(Data.Method, postfix); Data.Postfixes.Add(invoker); Data.Postfixes.Sort(); }
protected override object InvokeMethod(HookMethod method, object[] args) { // TODO: Ignore base_ methods for now if (!hookDispatchFallback && !method.IsBaseHook) { if (args != null && args.Length > 0) { ParameterInfo[] parameters = method.Parameters; for (int i = 0; i < args.Length; i++) { object value = args[i]; if (value == null) { continue; } Type parameter_type = parameters[i].ParameterType; if (!parameter_type.IsValueType) { continue; } Type argument_type = value.GetType(); if (parameter_type != typeof(object) && argument_type != parameter_type) { args[i] = Convert.ChangeType(value, parameter_type); } } } try { if (DirectCallHook(method.Name, out object ret, args)) { return(ret); } PrintWarning("Unable to call hook directly: " + method.Name); } catch (InvalidProgramException ex) { Interface.Oxide.LogError("Hook dispatch failure detected, falling back to reflection based dispatch. " + ex); CompilablePlugin compilablePlugin = CSharpPluginLoader.GetCompilablePlugin(Interface.Oxide.PluginDirectory, Name); if (compilablePlugin?.CompiledAssembly != null) { File.WriteAllBytes(Interface.Oxide.PluginDirectory + "\\" + Name + ".dump", compilablePlugin.CompiledAssembly.PatchedAssembly); Interface.Oxide.LogWarning($"The invalid raw assembly has been dumped to Plugins/{Name}.dump"); } hookDispatchFallback = true; } } return(method.Method.Invoke(this, args)); }
public void ShouldAllowMultipleHooksInaMethod() { var expected = GetType().GetMethod("MultiHook").FullyQuallifiedName(); var beforeScenarioHook = new HookMethod("BeforeScenario", GetType().GetMethod("MultiHook"), typeof(Step).Assembly); Assert.AreEqual(expected, beforeScenarioHook.Method); var beforeSpecHook = new HookMethod("BeforeSpec", GetType().GetMethod("MultiHook"), typeof(Step).Assembly); Assert.AreEqual(expected, beforeSpecHook.Method); }
public void ShouldAllowMultipleHooksInaMethod() { var mockAssemblyLoader = new Mock <IAssemblyLoader>(); var mockMethod = new MockMethodBuilder(mockAssemblyLoader) .WithName("MultipleHookMethod") .WithFilteredHook(LibType.BeforeScenario) .WithFilteredHook(LibType.BeforeSpec) .Build(); var beforeScenarioHook = new HookMethod(LibType.BeforeScenario, mockMethod, mockAssemblyLoader.Object); Assert.AreEqual("MultipleHookMethod", beforeScenarioHook.Method); var beforeSpecHook = new HookMethod(LibType.BeforeSpec, mockMethod, mockAssemblyLoader.Object); Assert.AreEqual("MultipleHookMethod", beforeSpecHook.Method); }
protected override object InvokeMethod(HookMethod method, object[] args) { //TODO ignore base_ methods for now if (!hookDispatchFallback && !method.IsBaseHook) { if (args != null && args.Length > 0) { var parameters = method.Parameters; for (var i = 0; i < args.Length; i++) { var value = args[i]; if (value == null) continue; var parameter_type = parameters[i].ParameterType; if (!parameter_type.IsValueType) continue; var argument_type = value.GetType(); if (parameter_type != typeof(object) && argument_type != parameter_type) args[i] = Convert.ChangeType(value, parameter_type); } } try { object ret; if (DirectCallHook(method.Name, out ret, args)) return ret; PrintWarning("Unable to call hook directly: " + method.Name); } catch (InvalidProgramException ex) { Interface.Oxide.LogError("Hook dispatch failure detected, falling back to reflection based dispatch. " + ex); var compilable_plugin = CSharpPluginLoader.GetCompilablePlugin(Interface.Oxide.PluginDirectory, Name); if (compilable_plugin != null && compilable_plugin.CompiledAssembly != null) { System.IO.File.WriteAllBytes(Interface.Oxide.PluginDirectory + "\\" + Name + ".dump", compilable_plugin.CompiledAssembly.PatchedAssembly); Interface.Oxide.LogWarning($"The invalid raw assembly has been dumped to Plugins/{Name}.dump"); } hookDispatchFallback = true; } } return method.Method.Invoke(this, args); }
protected override object InvokeMethod(HookMethod method, object[] args) { object obj; object obj1; bool compiledAssembly; if (!this.hookDispatchFallback && !method.IsBaseHook) { if (args != null && args.Length != 0) { ParameterInfo[] parameters = method.Parameters; for (int i = 0; i < (int)args.Length; i++) { object obj2 = args[i]; if (obj2 != null) { Type parameterType = parameters[i].ParameterType; if (parameterType.IsValueType) { Type type = obj2.GetType(); if (parameterType != typeof(object) && type != parameterType) { args[i] = Convert.ChangeType(obj2, parameterType); } } } } } try { if (!this.DirectCallHook(method.Name, out obj, args)) { this.PrintWarning(string.Concat("Unable to call hook directly: ", method.Name), Array.Empty <object>()); return(method.Method.Invoke(this, args)); } else { obj1 = obj; } } catch (InvalidProgramException invalidProgramException1) { InvalidProgramException invalidProgramException = invalidProgramException1; Interface.Oxide.LogError(string.Concat("Hook dispatch failure detected, falling back to reflection based dispatch. ", invalidProgramException), Array.Empty <object>()); CompilablePlugin compilablePlugin = CSharpPluginLoader.GetCompilablePlugin(Interface.Oxide.PluginDirectory, base.Name); if (compilablePlugin != null) { compiledAssembly = compilablePlugin.CompiledAssembly; } else { compiledAssembly = false; } if (compiledAssembly) { File.WriteAllBytes(string.Concat(Interface.Oxide.PluginDirectory, "\\", base.Name, ".dump"), compilablePlugin.CompiledAssembly.PatchedAssembly); Interface.Oxide.LogWarning(string.Concat("The invalid raw assembly has been dumped to Plugins/", base.Name, ".dump"), Array.Empty <object>()); } this.hookDispatchFallback = true; return(method.Method.Invoke(this, args)); } return(obj1); } return(method.Method.Invoke(this, args)); }
public KeyboardHook(HookMethod hooker) : base(hooker, HookType.Keyboard) { KeysDown = new HashSet <Keys>(); }
public static void Patch(MethodBase originalMethod, HookMethod prefix, HookMethod postfix) { // WARNING: This code is in no way guaranteed to work, as it makes some wild assumptions // about how the underlying machine code is being generated during JIT // If the code does not work it will likely crash the application immediately with a cryptic or no error message. // This hooking implementation works like this: // 1. Create a JMP at the start of the hooked method that goes to a method of an identical signature (trampoline function) // 2. When the function is entered, rewrite back the old code that was overwritten in step 1 // 3. Call prefixes, then call original method, then call postfixes // 4. Write back in the JMP that was created in step 1 // This is all very simple. The primary issue this library solves is how to do this through a 'nice' // API surface, similar to that of Harmony so that it can be used as a fallback mechanism. // The difficult issue is how to navigate to a method of identical signature to the original one // if you cannot emit any IL code (netstandard2.0 limitation) because it is difficult to // generate a method with the required signature at compile time, that includes types that may // be inaccessible. // This library pre-defines a set of generic classes that each has an 'Override' method, that will // be used as the trampoline function. Each version of the 'Override' method makes different assumptions // about the method signature and uses generic parameters defined on the class in order to do so. // The weakness of this approach is that any arguments (generated by the JITTER) in relation to generics // will *not* be passed to the override method, as it would usually expect. (I think that with the legacy // .NET JITTER in 64-bit, these are often passed as an id in the RCX register of the CPU, but this is likely // different for Mono and other architectures). This means that if any code is generated in the Override // method that requires this information that the application will immediately fail. // This has an especially big impact on the JITTER used in Mono, where accessing any generic arguments // in the override method will immediately cause application failure. This imposes a big limitation // on this approach in that the current implementation is only able to hook methods that return reference // types (or void) because it is unable to use a generic argument (TReturn) to determine the size of the variable // to send back to the caller (Should it use the stack? Should it use a CPU register? It does not know!). // Another limitation of this in Mono is that you cannot use this approach to access static fields on // a per-generic class basis (as that would mean using the generic arguments). To overcome this issue // a new assembly that maintains state for each hooked method is generated at runtime (by renaming an // existing assembly and loading it into memory). This is essentially what this function does. // A better approach than this would be to write the trampoline function in machine code. But that // would take a whole lot more effort than this. And require knowledge about how each architecture // and platform emits machine code. // Attempting to create a StackTrace will also break this implementation. lock ( Sync ) { var flags = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public; // check if we have already hooked the specified method if (!HookedMethodToTrampolineInitializer.TryGetValue(originalMethod, out var trampolineInitializer)) { // obtain data for the assembly that contains the hooking logic/trampolines var assemblySize = Resources.b979b301af8b4ef48f224de6dcf2ddf6.Length; // copy the data into a new array var assemblyData = new byte[assemblySize]; Buffer.BlockCopy(Resources.b979b301af8b4ef48f224de6dcf2ddf6, 0, assemblyData, 0, assemblySize); // rename the assembly, so it can be loaded multiple times (in Mono) var newName = Guid.NewGuid().ToString("N"); FindAndReplace("b979b301af8b4ef48f224de6dcf2ddf6", newName, assemblyData); var assembly = Assembly.Load(assemblyData); // obtain the trampoline initializer type from the dynamically named assembly trampolineInitializer = assembly.GetType("XUnity.RuntimeHooker.Trampolines.TrampolineInitializer"); // call the setup hook method with the originalMethod to hook trampolineInitializer.GetMethod("SetupHook", flags).Invoke(null, new object[] { originalMethod }); // add tbe method to our hooked methods dictionary, so we do not hook it again HookedMethodToTrampolineInitializer.Add(originalMethod, trampolineInitializer); } // after hooking the method, lets apply our prefixes/postfixes if (prefix != null) { trampolineInitializer.GetMethod("AddPrefix", flags).Invoke(null, new object[] { prefix }); } if (postfix != null) { trampolineInitializer.GetMethod("AddPostfix", flags).Invoke(null, new object[] { postfix }); } } }
private bool HookThunderbird() { log.Information("Hooking on to Thunderbird..."); if (thunderbirdProcess != null && thunderbirdMainWindowHandle != IntPtr.Zero) { log.Information("Already hooked!"); // Already hooked return(true); } thunderbirdProcess = Process.GetProcessesByName("thunderbird").FirstOrDefault(); if (thunderbirdProcess == null) { log.Error("Cannot find the Thunderbird process to hook to."); // Hook failure, process does not exist return(false); } thunderbirdMainWindowHandle = FindMainThunderbirdWindow(thunderbirdProcess); if (thunderbirdMainWindowHandle == IntPtr.Zero) { log.Error("Cannot find Thunderbird's main window."); // Main window is lost (hidden) return(false); } else { log.Debug("Hooked on to window handle {@thunderbirdMainWindowHandle}", thunderbirdMainWindowHandle); } thunderbirdProcess.EnableRaisingEvents = true; thunderbirdProcess.Exited += Thunderbird_Exited; switch (Properties.Settings.Default.HookMethod) { case (int)HookMethod.UIAutomation: windowStateHook = new UIAutomation(); break; case (int)HookMethod.Polling: default: windowStateHook = new Polling(); break; } currentHookMethod = (HookMethod)Properties.Settings.Default.HookMethod; if (!windowStateHook.Hook(thunderbirdMainWindowHandle)) { return(false); } windowStateHook.WindowStateChange += Thunderbird_VisualStateChanged; log.Debug("Attached event handlers for window."); // If not already hidden and is currently minimised, hide immediately var isIconic = User32.IsIconic(thunderbirdMainWindowHandle); if (trayLaunched && Properties.Settings.Default.MinimiseOnStart) { log.Information("Thunderbird launched with tray application, hiding now. {@thunderbirdShown}.", thunderbirdShown); HideThunderbird(); } if (thunderbirdShown && isIconic) { log.Information("Thunderbird is already minimised, hiding now. {@thunderbirdShown}, {@isIconic}.", thunderbirdShown, isIconic); HideThunderbird(); } return(true); }
public MouseHook(HookMethod hooker) : base(hooker, HookType.Mouse) { }