// minehack.cpp
// Reads winmine.exe process memory to find out location of bombs
// sub < sub@room641a.net >

// Requirements: Link with psapi.lib

/* Copyright (c) 2008, Charles Hooper
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Subversity.net nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Charles Hooper ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Charles Hooper BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <windows.h>
#include <stdio.h>
#include <tlhelp32.h>
#include <tchar.h>
#include <psapi.h>
#include <iostream>

// It's probably safe to leave these two alone
#define PROCESS_LIST_SIZE 1024
#define MAX_BUFFER 32768

// Process name you want to access
#define FIND_NAME "winmine.exe"

// Image base. Minesweeper is 0x01000000 but MOST apps are 0x00400000. Use PE Explorer to find this value
#define IMAGE_BASE 0x01000000

// The offset from IMAGE_BASE where the height of the grid is defined
#define HEIGHT_OFFSET 0x005338

// The width, obviously
#define WIDTH_OFFSET 0x005334

// The offset from IMAGE_BASE where the actual grid array is stored in memory
#define GRID_OFFSET 0x005360

using namespace std;

// Define some prototypes
int main(void);
bool check_process_name(DWORD pid);
DWORD get_process_list(DWORD *process_list);
DWORD find_winmine_pid(void);

int main(void) {
/* Once I saw the light at the end of the tunnel, this function got MESSY. I was in a rush to finish!
 * Anyways, this is the main entry point (obviously.) This calls my created function to find
 * (in this case) Minesweeper's process ID. From there, it reads Minesweeper's memory and gets the
 * height and width of the grid from known offsets relative to the image base. After that, we iterate
 * through the grid array and use some ugly code to display our own grid.
 */

    DWORD pid;
    HANDLE proc_handle;
    char buffer[MAX_BUFFER];
    DWORD read_status;

    // Height and width of the grid in Minesweeper. These are used to determine how large the array is.
    DWORD grid_height;
    DWORD grid_width;

    // Re-usable variable we use for drawing our own grid.
    bool bomb;

    // Re-usable variable used for grid drawing
    bool inside_row = false;

    cout << "Minehack - Reverse Engineering and Coding by Sub <sub@room641a.net>" << endl;
    cout << "---" << endl;
    cout << "Fairly simple program to display already-placed bombs in minesweeper." << endl;
    cout << "---" << endl;
    pid = find_winmine_pid();
    cout << "PID: " << pid << endl;

    proc_handle = OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, FALSE, pid);

    if(proc_handle > 0) {
        read_status = ReadProcessMemory(proc_handle, (LPCVOID) IMAGE_BASE, &buffer, sizeof(buffer), NULL);

        if(read_status > 0) {
            // Get height and width
            grid_height = buffer[HEIGHT_OFFSET];
            grid_width = buffer[WIDTH_OFFSET];

            cout << "Height: " << grid_height << endl;
            cout << "Width: " << grid_width << endl;
            cout << "---" << endl;

            /* Did you know?
             * 
             * Minesweeper uses a char-sized (1 byte) multi-dimensional array. Each byte
             * in memory corresponds to a specific location on the grid. It appears to use
             * simple bit masks.
             *
             * 0x10 Start/End of row (border)
             * 0x40 Button has been pressed already
             * 0x80 Bomb is in place. These can move mid-game if clicked on
             * Bit-wise OR with:
             * 0x00 The square is exposed or the right-hand 4 bits are 0x10
             * 0x0X # on square (if exposed)
             * 0x0D Square is marked with question mark
             * 0x0E Square is marked with flag
             * 0x0F Blank square
             *
             * AND THEN THERE'S 0xCC WHICH MEANS YOU CLICKED A DAMN BOMB AND LOST!
             */

            // This is where the fixed width of 32-bytes comes into play
            for(DWORD grid_loc = 0; grid_loc < grid_height * 32; grid_loc++) {
                if(buffer[GRID_OFFSET + grid_loc] & 0x10) {
                    if(inside_row == true) {
                        inside_row = false;
                        cout << endl;
                    } else {
                        inside_row = true;
                    }
                } else {
                    if(inside_row == true) {
                        // If this evaluates to true, we have a bomb.
                        bomb = buffer[GRID_OFFSET + grid_loc] & 0x80;

                        if(bomb)
                            cout << "[B]";
                        else
                            cout << "[ ]";
                    }
                }
            }
        } else {
            cout << "Memory access FAILED: " << GetLastError() << endl;
        }
    }

    CloseHandle(proc_handle);
    return 0;
}

DWORD get_process_list(DWORD *process_list) {
// Prepare and return an array of PIDs

    DWORD bytes_returned;
    DWORD process_count;

    // TODO: Fix this ugly HACK of multiplying process list by PROCESS_LIST_SIZE (It works at least.)
    if(!EnumProcesses(process_list, sizeof(process_list)*PROCESS_LIST_SIZE, &bytes_returned))
        return false;

    // EnumProcesses doesn't tell us how many processes it has returned, so we divide
    // it by the size of a DWORD.
    process_count = bytes_returned / sizeof(DWORD);

    return process_count;    
}


DWORD find_winmine_pid(void) {
// Iterate through the process list and return the PID of a specific process.
// In our case, check_process_name returns true if the process name of PID
// is "winmine.exe" and WE return that.
    DWORD process_list[PROCESS_LIST_SIZE];
    DWORD process_count;

    process_count = get_process_list(process_list);

    for(DWORD index=0; index < process_count; index++) {
        if(check_process_name(process_list[index]))
            return process_list[index];
    }

    return 0;
}

bool check_process_name(DWORD pid) {
// Query the process for its process name, if it matches FIND_NAME, return true
    TCHAR proc_name[MAX_PATH] = TEXT("<unknown>");
    TCHAR find_name[MAX_PATH] = TEXT(FIND_NAME);
    HANDLE proc_handle;
    HMODULE mod_handle;
    DWORD mod_handle_size;

    proc_handle = OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, FALSE, pid);

    if(proc_handle > 0) {
        if(EnumProcessModules(proc_handle, &mod_handle, sizeof(mod_handle), &mod_handle_size)) {
            GetModuleBaseName(proc_handle, mod_handle, proc_name, sizeof(proc_name)/sizeof(TCHAR));
//            _tprintf(TEXT("pid: %u\tname: %s\n"), pid, proc_name);

            if(_tcscmp(proc_name, find_name) == 0) {
                CloseHandle(proc_handle);
                return true;
            } else {
                CloseHandle(proc_handle);
                return false;
            }
        }
    }
    return false;
}