PInvokeHooks.cs
using Microsoft.Xna.Framework;
using MonoMod.InlineRT;
using SDL2;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using XnaToFna.ProxyForms;
namespace XnaToFna {
public delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
public delegate IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam);
public static clast PInvoke {
public static int MessageSize = Marshal.SizeOf(typeof(Message));
// Global hooks added by SetWindowsHookEx.
// Delegate is required as the actual type stems from the game and differs from HookProc.
public static Dictionary Hooks = new Dictionary();
// Required to properly keep track of the hook handle and to efficiently unregister hooks.
public static List AllHooks = new List();
public static ThreadLocal CurrentHookChain = new ThreadLocal();
public static ThreadLocal CurrentHookIndex = new ThreadLocal();
static PInvoke() {
Array hookTypes = Enum.GetValues(typeof(HookType));
foreach (HookType hookType in hookTypes)
Hooks[hookType] = new List();
}
public static void CallHooks(Messages Msg, IntPtr wParam, IntPtr lParam, bool global = true, bool window = true, bool allWindows = false)
=> CallHooks(Msg, wParam, new Message() {
HWnd = IntPtr.Zero, // What do we give global hooks?
Msg = (int) Msg,
WParam = wParam,
LParam = lParam
}, global: global, window: window, allWindows: allWindows);
public static void CallHooks(Messages Msg, IntPtr wParam, Message lParamMsg, bool global = true, bool window = true, bool allWindows = false) {
IntPtr lParam = Marshal.AllocHGlobal(MessageSize);
Marshal.StructureToPtr(lParamMsg, lParam, false);
CallHooks(Msg, wParam, lParam, lParamMsg: ref lParamMsg, global: global, window: window, allWindows: allWindows);
Marshal.FreeHGlobal(lParam);
}
public static void CallHooks(Messages Msg, IntPtr wParam, IntPtr lParam, ref Message lParamMsg, bool global = true, bool window = true, bool allWindows = false) {
// Order of hook calling?
if (global) {
// WH_GETMESSAGE seems to await 1 as wParam and a message struct as lParam.
CallHookChain(HookType.WH_GETMESSAGE, (IntPtr) 1, lParam, ref lParamMsg);
// TODO: CallHooks should handle most HookTypes.
}
if (allWindows) {
for (int i = 0; i < Control.AllControls.Count; i++)
// The global index + 1 also functions as the control handle.
lParamMsg.Result = CallWindowHook((IntPtr) (i + 1), Msg, wParam, lParam);
} else if (window)
lParamMsg.Result = CallWindowHook(Msg, wParam, lParam);
return;
}
public static IntPtr CallHookChain(HookType hookType, IntPtr wParam, IntPtr lParam, ref Message lParamMsg) {
List hooks = Hooks[hookType];
if (hooks.Count == 0)
return IntPtr.Zero;
CurrentHookChain.Value = hooks;
for (int i = 0; i < hooks.Count; i++) {
Delegate hook = hooks[i];
// Find the first non-null (still registered) hook.
if (hook == null)
continue;
CurrentHookIndex.Value = i;
// HookProc expects HC_ACTION (0; take action) or < 0 (past to next hook).
object[] args = { 0, wParam, lParamMsg };
object result = hook.DynamicInvoke(args);
lParamMsg = (Message) args[2];
return result != null ? (IntPtr) Convert.ToInt32(result) : IntPtr.Zero;
}
return IntPtr.Zero;
}
public static IntPtr ContinueHookChain(int nCode, IntPtr wParam, IntPtr lParam) {
List hooks = CurrentHookChain.Value;
for (int i = CurrentHookIndex.Value + 1; i < hooks.Count; i++) {
Delegate hook = hooks[i];
// Find the next non-null (still registered) hook.
if (hook == null)
continue;
CurrentHookIndex.Value = i;
return (IntPtr) hook.DynamicInvoke(nCode < 0 ? nCode + 1 : 0, wParam, lParam);
}
// End of chain. What should we return here?
return IntPtr.Zero;
}
public static IntPtr CallWindowHook(Messages Msg, IntPtr wParam, IntPtr lParam)
=> CallWindowHook(GameForm.Instance?.Handle ?? IntPtr.Zero, (uint) Msg, wParam, lParam);
public static IntPtr CallWindowHook(IntPtr hWnd, Messages Msg, IntPtr wParam, IntPtr lParam)
=> CallWindowHook(hWnd, (uint) Msg, wParam, lParam);
public static IntPtr CallWindowHook(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam) {
Form form = Control.FromHandle(hWnd) as Form;
if (form == null || form.WindowHookPtr == IntPtr.Zero)
return IntPtr.Zero;
return (IntPtr) form.WindowHook.DynamicInvoke(hWnd, Msg, wParam, lParam);
}
}
public static partial clast PInvokeHooks {
public static IntPtr GetForegroundWindow() {
if (XnaToFnaHelper.Game.IsActive)
return GameForm.Instance.Handle;
return IntPtr.Zero;
}
public static bool SetForegroundWindow(IntPtr hWnd) {
if (GameForm.Instance.Handle != hWnd)
return false;
SDL.SDL_RaiseWindow(XnaToFnaHelper.Game.Window.Handle);
return true;
}
public static int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong) {
// All other nIndex values seem to be style-related.
if (nIndex == -4) {
Form form = Control.FromHandle(hWnd)?.Form;
if (form == null)
return 0;
IntPtr prevHook = form.WindowHookPtr;
form.WindowHookPtr = (IntPtr) dwNewLong;
form.WindowHook = Marshal.GetDelegateForFunctionPointer(form.WindowHookPtr, typeof(WndProc));
XnaToFnaHelper.Log($"[PInvokeHooks] Window hook set on ProxyForms.Form #{form.GlobalIndex}");
return (int) prevHook;
}
return 0;
}
public static IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam) {
if (lpPrevWndFunc == IntPtr.Zero)
return IntPtr.Zero;
return (IntPtr) Marshal.GetDelegateForFunctionPointer(lpPrevWndFunc, typeof(MulticastDelegate))
.DynamicInvoke(hWnd, Msg, wParam, lParam);
}
public static IntPtr SetWindowsHookEx(HookType hookType, HookProc lpfn, IntPtr hMod, uint dwThreadId) {
// TODO: SetWindowsHookEx currently ignores the module and thread.
int handle = PInvoke.AllHooks.Count + 1;
List hooks = PInvoke.Hooks[hookType];
PInvoke.AllHooks.Add(Tuple.Create(hookType, lpfn, hooks.Count));
hooks.Add(lpfn);
XnaToFnaHelper.Log($"[PInvokeHooks] Added global hook #{handle} of type {hookType}");
return (IntPtr) handle;
}
public static bool UnhookWindowsHookEx(IntPtr hhk) {
int index = (int) hhk - 1;
if (index < 0 || PInvoke.Hooks.Count new Cursor(str).Handle;
public static IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam) {
// This gets called when Duck Game dies...
// TODO: Find more games using SendMessage.
Form form = Control.FromHandle(hWnd) as Form;
if (form == null) {
XnaToFnaHelper.Log($"[PInvokeHooks] Called GetWindowThreadProcessId for non-existing hWnd {hWnd}");
form = GameForm.Instance;
}
if (Msg == 16 /*WM_CLOSE*/) {
form.Close();
// TODO: What's the proper return value for SendMessage on WM_CLOSE?
return IntPtr.Zero;
}
return IntPtr.Zero;
}
}
}