Search code examples
c#unity-game-enginewinapiinterop

WinAPI FindWindow not working for Unreal engine games


I am making a Unity app that will run in Windows in a custom arcade cabinet and will load and stop games. I have been abusing winAPI to do this. Eg. I start the game process, then use findwindow to wait until a window of the correct name exists and also get an IntPtr for that window which I can send to the SetForegroundWindow winAPI function to make sure the game is in front and focused for inputs.

This all works fine.

Except for some unreal games I was using to test. Eg. this game 'Peekaboo', despite calling findwindow every frame in my Unity app a window called Peekaboo is never found, though when I look in Windows it is clearly there. Its the same story for another Unreal engine game 'Mechwarrior 5 mercenaries'

I think it might have something to do with the fact that unreal games seems to launch several nested processes like in the image below.

enter image description here

(Eg. To stop non-unreal games I can just stop the process using the reference I got when I started it. But this did not work with Unreal games, the exe I started is gone and I needed to find that Win64-Shipping process and stop that.)

Here is the code I am using to call findwindow and also setforground

using UnityEngine;
using System.Collections;
using System;
using System.Runtime.InteropServices;

public static class WinAPIGetWindowIfExists
{
    [DllImport("user32.dll", SetLastError = true)]
    public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

    public static IntPtr DoesWindowExist(string windowName)
    {
        Debug.Log("looking for window: " + windowName);
        return FindWindow(null, @windowName);
    }
}

public static class WinAPISetForegroundWindow
{
    [DllImport("user32.dll")]
    public static extern bool SetForegroundWindow(IntPtr handle);

    public static void SetForeground(IntPtr windowHandle)
    {
        SetForegroundWindow(windowHandle);
    }
}

So how can I get a reference to an unreal game window so I can set it to the forground?

UPDATE: I am super confused now, as I have called EnumWindows, and called GetWindowText for every window returned by it, and I have found a window called Peekaboo.. If I pass that window's handle to SetForeground the correct game window is set to foreground, also if I sendmessage with this message 0x000D; I get the text peekboo back. Yet, findwindow still finds no window named peekaboo...

So I can use this EnumWindows to solve my issue.. but this utterly sucks. Why does iterating through every window, calling get windowtext, then checking if the text contains the window title work, whereas findwindow doesnt work?


Solution

  • So here is the code for using enumwindows. This code does work for Unreal engine games. I found this somewhere on the internet. It's a good thing too cus figuring out how to make these functions work with interop from just MS documentation is not trivial imo. It was really not clear to me from the MS documentation that after calling the enumwindows function it would call the callback function over and over for every single window returned. It seems like a kind of ridiculous pattern to me, just to avoid having any unsafe code.

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Runtime.InteropServices;
    using System.Text;
    
    
    public delegate bool WNDENUMPROC(
       UInt32 hWnd,
       IntPtr lParam
    );
    
    internal static class WinAPI
    {
    
        [DllImport("user32.dll")]
        internal static extern bool EnumWindows(
            WNDENUMPROC cb,              // WNDENUMPROC lpEnumFiunc
            IntPtr manageObject               // LPARAM      lParam (the managed object))
         );
    
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        internal static extern bool GetWindowText(
            UInt32 hWnd,
            StringBuilder title,
            int size
         );
    
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        internal static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, int wParam,
        StringBuilder lParam);
    }
    
    public class WindowEnumerator
    {
    
        private const uint WM_GETTEXT = 0x000D;
    
        public static IntPtr FindWindowWithThisTitle(string findthis)
        {
    
            List<UInt32> hWnds = new List<UInt32>();
    
            //
            // Prevent the managed object (hWnds) from being collected
            // by the garbage collector:
            //
            GCHandle objHandle = GCHandle.Alloc(hWnds);
    
            //
            // Create an instance of a delegate in order to
            // provide a «callback» for EnumWindows:
            //
            WNDENUMPROC enumProc = new WNDENUMPROC(callback);
    
            //
            // Get an internal representation for the gc handle
            // so as to be able to pass it to the second parameter
            // of EnumWindows:
            //
            IntPtr objHandlePtr = GCHandle.ToIntPtr(objHandle);
    
            WinAPI.EnumWindows( // Let Windows iterate over each window and
               enumProc,    // call enumProc (which is «initialized» for the method callback)
               objHandlePtr     // and pass this pointer to the method
            );
    
            //
            // Free the handle of the object so that
            // the object can be collected and
            // thus to prevent memory leaks:
            //
            objHandle.Free();
    
            StringBuilder title = new StringBuilder(256);
    
            foreach (UInt32 hWnd in hWnds)
            {
                WinAPI.GetWindowText(hWnd, title, 256);          
                if (title.ToString().Contains(findthis))
                {
                    return (IntPtr)hWnd;
                } 
                //UnityEngine.Debug.Log(" "+ hWnd+" "+ title);
            }
            return IntPtr.Zero;
        }
    
        private static bool callback(
        //
        // After calling WinAPI.EnumWindows, Windows calls
        // this method for each Window and passes it
        // the hWnd of the respective Window and
        // the value that was given as second parameter
        // to EnumWindows (objHandlePtr);
        //
           UInt32 hWnd,
           IntPtr objHandlePtr
        )
        {
    
            //
            // Get the handle to the object from the pointer:
            //
            GCHandle objHandle = GCHandle.FromIntPtr(objHandlePtr);
    
            //
            // and cast the handle's target into the underlying
            // managed object:
            //
            List<UInt32> obj = (List<UInt32>)objHandle.Target;
            obj.Add(hWnd);
    
            return true;
        }
    }