Reverse Engineering WinMine: Finding the Board Representation

Disclaimer: This article documents my reverse engineering process for the classic Windows Minesweeper (WinMine). The goal is to understand how the game represents the board internally, not simply to modify it.

The Goal

I wanted to find where WinMine stores the actual game board in memory.

Finding counters such as the timer or remaining mine count is fairly straightforward with Cheat Engine, but the board itself is much more interesting. Rather than searching random memory regions, I wanted to work backwards from the program's behavior.


Following the "New Game" Button

A new game has to generate a fresh board somewhere, so I started by looking for the New menu item.

Searching for the string "New" in Ghidra brought me to the menu resources:

0101e8be 26 00 4e 00   unicode  u"&New\tF2"
0101e8ce 00 00         dw       0h
0101e8d0 00 00         dw       0h
0101e8d2 00 00         unicode  u""
0101e8d4 00 00         dw       0h
0101e8d6 09 02         dw       209h

The interesting value here is the menu ID:

0x209

Searching for the scalar 0x209 led me to a function I renamed:

PUSH    0x209
CALL    UpdateMenuChecks()

Most of the rest of this function interacted with several global variables.

One of them (DAT_010056a0) was referenced four different times and appeared to hold the selected difficulty.

Difficulty determines board dimensions, so I followed the cross references hoping they would eventually lead to board generation.

Unfortunately, I hit a dead end.


Trying a Different Approach

Rather than continuing down that rabbit hole, I asked myself:

What else always happens when a new game starts?

The timer.

Looking through the imported functions I searched for allocation routines:

  • malloc
  • HeapCreate
  • LocalAlloc

Nothing useful appeared.

Next I searched for timer-related strings.

That led me to SetTimer.

Showing references to SetTimer brought me to another function that appeared to initialize a new game.

Inside that function one instruction immediately stood out:

MOV DL, byte ptr [EDX + EAX + 0x1005340]

The address looked suspiciously like an array access.


Looking at the Decompiler

The corresponding decompiled code looked like this:

if (_DAT_01005144 == 0) {
    if ((((&DAT_01005340)[DAT_01005118 +
         DAT_0100511c * 0x20] & 0x40) == 0) &&
        (((&DAT_01005340)[DAT_01005118 +
         DAT_0100511c * 0x20] & 0x1f) != 0xe)) {

        FUN_01003512(DAT_01005118,
                     DAT_0100511c);
    }
}
else {
    FUN_010035b7(DAT_01005118,
                 DAT_0100511c);
}

FUN_01002913(DAT_01005160);

Several things immediately stood out.

  • The same global array is accessed repeatedly.
  • The array is indexed by X and Y coordinates.
  • Individual bits are masked off before decisions are made.

At this point my hypothesis became:

DAT_01005340 is probably the board.

Verifying with x32dbg

Opening the program in x32dbg and watching memory around 0x01005340 confirmed the suspicion.

A fresh game looked like this:

01005340  10 10 10 10 10 10 10 10 10 10 10 ...
01005360  10 0F 0F 0F 0F 0F 0F 0F 0F 0F 10 ...
01005380  10 0F 8F 0F 0F 0F 0F 0F 0F 0F 10 ...
...

After revealing several cells the same memory changed to:

01005380  10 8F 0F 0F 8F 0F 0F 0F 41 0F ...
010053A0  10 41 41 41 41 0F 0F 0F 0F 8F ...
010053C0  10 40 40 40 41 0F 0F 0F 0F 0F ...

Clearly this memory region was changing as the game progressed.


Reverse Engineering the Cell Format

By comparing multiple games and observing changes after clicking squares, I inferred the meaning of several values.

ValueMeaning
0x8FMine
0x40Revealed blank cell
0x41-0x48Revealed numbered cells
0x0FHidden cell
0x10Sentinel border around the board

The interesting discovery here is the sentinel border.

If we remove the unused spacer rows, the layout becomes much easier to visualize.

10 10 10 10 10 10 10 10 10 10 10
10 0F 0F 0F 0F 0F 0F 0F 0F 0F 10
10 0F 0F 0F 0F 8F 0F 8F 0F 0F 10
10 0F 0F 0F 0F 0F 0F 0F 8F 0F 10
10 0F 8F 0F 0F 0F 0F 0F 0F 0F 10
10 0F 0F 0F 0F 0F 0F 0F 0F 0F 10
10 0F 0F 0F 0F 0F 8F 0F 0F 0F 10
10 0F 0F 0F 0F 8F 0F 0F 0F 0F 10
10 0F 8F 0F 0F 0F 0F 8F 0F 0F 10
10 8F 0F 0F 0F 0F 0F 8F 0F 0F 10
10 10 10 10 10 10 10 10 10 10 10

The playable 9×9 board is surrounded by a one-cell border.


What Can We Do With This?

Once the board representation is understood, several possibilities open up.

The obvious one is reading the locations of every mine directly from memory.

Another is modifying the values.

For example, replacing each 0x8F with 0x40 effectively removes every mine from the board, making every click safe.