Solving Minesweeper – Part 2: Game Screenshot

In my last post I told you about the steps we need to be able to make in order to solve Minesweeper. I spent some time to achieve the first step, but it was a roller-coaster of emotions, mainly involving anger.

My initial approach was to write a C# console application that:

  • 1. Search through the processes where the name matched Microsoft Minesweeper.
  • 2. Read the handle to it’s window through the MainWindowHandle property on the ProcessInfo object.
  • 3. DllImport the GetWindowRect function from WinApi and use it to find the screen-coordinates of that window
  • 4. Create a Bitmap object with the appropriate size
  • 5. Attach a Graphics object to the bitmap with the Graphics.FromImage function and use CopyFromScreen to, well copy the game window content into the Bitmap

Simple isn’t it? It shouldn’t have taken more than 30 minutes. Well, finding the process was easy, but the MainWindowHandle was 0. First I thought it has probably a child window so I started to search for those without any success. I imported multiple function from the WinApi to dig deeper but NOTHING. I was cursing at this point. Before I gave up I had an idea which saved me. In the WinApi there are a lot of handy dandy functions that you can import. GetActiveWindow returns the handle of the window which is currently active. I’ve created a separate app, to display its handle once per second then after activating the game I noted the handle down, and I started to look for which process owns that window handle. I’ve never imagined that the actual process that is rendering the game is, drum rolls please, ApplicateFrameHost.exe.

To keep this short, I decided to find the window handle by examining the window title (which also states “Microsoft Minesweeper”), but I will come back at a later point to find a proper solution. What I also decided that, since I need to do all this pInvoking into the WinApi and I plan to use OpenCV later, I will write this in C++ where this interaction with the WinApi is easier.

#include <windows.h>

HWND GetGameHwnd()
{
	HWND gameHwnd = nullptr;
	LPARAM param = reinterpret_cast<LPARAM>(&gameHwnd);
	EnumWindows(GetMinesweeperHwnd, param);
	return gameHwnd;
}

BOOL CALLBACK GetMinesweeperHwnd(HWND hwnd, LPARAM lparam)
{
	auto hwndTarget = reinterpret_cast<HWND*>(lparam);
	auto textLen = GetWindowTextLength(hwnd);
	if (textLen > 0)
	{
		char* winText = new char[textLen + 1];
		GetWindowText(hwnd, winText, textLen + 1);
		char* start = strstr(winText, "Microsoft Minesweeper");
		if (start != nullptr)
		{
			*hwndTarget = hwnd;
			return FALSE; // should not continue
		}
		delete[] winText;
	}
	return TRUE; // not found, keep searching
}

I think the code is pretty much self explanatory. EnumWindows is a function from WinApi that you can give it a Callback and a parameter, and it will call it for every window handle. I passed my validation function and a pointer to where I need my HWND copied. The callback has to return TRUE if it wishes to keep going or FALSE if it needs to stop. GetWindowTextLength and GetWindowText are also part of the WinApi.

Ok moving on to copying the screen image. I knew pretty well how to do this in C# but not in C++. Thankfully I found this handy blog post on causeyourstuck.io and I adapted the solution from there to my problem (love you CYS guys, cheers). My main function looks like this.

<pre>HWND gameHwnd = GetGameHwnd();
if (gameHwnd == nullptr)
{
	std::cout << "window not found";
	return;
}
RECT winRect;
LPRECT lpRect = reinterpret_cast<LPRECT>(&winRect);
GetWindowRect(gameHwnd, lpRect);
int width = winRect.right - winRect.left;
int height = winRect.bottom - winRect.top;
if (width <= 0 || height <= 0)
{
	std::cout << "invalid window size";
	return;
}

HDC screenDC = GetDC(NULL);
HDC dc = CreateCompatibleDC(screenDC);
HBITMAP hBitmap = CreateCompatibleBitmap(screenDC, width, height);
HGDIOBJ old_obj = SelectObject(dc, hBitmap);
BOOL    bRet = BitBlt(dc, 0, 0, width, height, screenDC, 
	winRect.left, winRect.top, SRCCOPY);

LPSTR fname = const_cast<LPSTR>("screen.bmp");
PBITMAPINFO pbi = CreateBitmapInfoStruct(hBitmap, nullptr);
CreateBMPFile(nullptr, fname, pbi, hBitmap, dc);</pre>

To explaing it a bit. I get the window handle, through GetWindowRect I get the screen coordinates of the Window. We create a bitmap with the appropriate size, then we use BitBlt to copy the image of that region from screen memory into the bitmap. CreateBitmapInfoStruct and CreateBMPFile I just use them to write the image into the file. I copied them over from this msdn article. I won’t need them in the final solution. I just wanted to mark this checkpoint by showing you this image.

Well, it was a hustle, but at least we learned something. For my next trick I need to find a way to interpret this image with OpenCV. Stay tuned!