紧接着 扫雷(1)博客,我们接着说,接下来我们需要找到存放宽和高的内存地址,一般肯定也是立即数地址,这种小游戏不会存在那种太难的偏移。
第一步:拿到游戏的窗口句柄:
HWND hwnd = ::FindWindowA("扫雷", "扫雷");//获取游戏的窗口句柄
第二步:通过窗口句柄拿到进程ID:
GetWindowThreadProcessId(hwnd, &pid);
这个pid是上面提前定义好的DWORD类型
第三步:通过进程ID拿到进程句柄:
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid)
第四步:随便找个基地址读取一下,观察进程是否存在(打开):
这里的0x1005361是通过CE观察出来的,大家可以自行尝试
if (!ReadProcessMemory(hProcess, (LPCVOID)0x1005361, &gamedata, 32 * 24, &pid))
{
::MessageBoxA(NULL, "扫雷游戏未打开", "错误", MB_OK);
return 0;
}
第五步(综合讲解所需要的一些值计算):把每格的大小,以及坐标利用VS自带的SPY++计算一下:
a.这里我算出来是(20,60);
short gamex = 20;
short gamey = 60;
每格大小的话,利用两个点计算一下就好,这里用图表示一下:

这是点击了雷区的左上角

这是点击了雷区的右上角
然后两个相减,最后再来除以格子数量,就可以得到长度了,既可以找到鼠标点击的位置坐标。
b.至于雷区的宽度和高度利用CE立即数搜索就可以搜索到它们的立即数地址,即不变的地址,就可以找到
c.通过观察,高度最大可以24,长度最大可为32,如何判断终止的地方呢?

通过观察,遇到0x10后它就停止了一行的有效区,这就是扫雷模式的改变,雷区所放的地方是不变的,它只需要改变一下一行结尾。每行的起始位置是不变的,它只改变终止位置。这就改变了扫雷模式的宽度和高度。所以一行的长度是不变的,如何控制呢?我们只需要找到0x10地点,高度呢?高度只需要读取一下CE里面找出来的立即数不就行喽,思路就是这样
根据上述我们即可 初始化一个雷区数组unsigned char gamedata[24][32] = { 0 };
第六步:遍历之后判断并用鼠标点击:
unsigned short xypos[2] = { 0 };
for (int i = 0; i < dwHight;++i) {
for (int j = 0; j < 32;++j) {
if (0x10 == gamedata[i][j])
break;
xypos[0] = gamex + j * 16;
xypos[1] = gamey + i * 16;
if (0x8F != gamedata[i][j]) {
::PostMessage(hwnd, WM_LBUTTONDOWN,MK_LBUTTON,*(int *)xypos);
::PostMessage(hwnd, WM_LBUTTONUP,0, *(int*)xypos);
}
}
}
完整代码如下
阅读下面的c++代码需要一点点的windows核心编程基础,扫雷程序的话可以参照模仿一下就行
#include<iostream>
#include<windows.h>
using namespace std;
int main() {
DWORD pid;
HWND hwnd = ::FindWindowA("扫雷", "扫雷");//获取游戏的窗口句柄
if (NULL == hwnd)
{
MessageBoxA(NULL, "扫雷游戏未打开", "错误", MB_OK);
return 0;
}
GetWindowThreadProcessId(hwnd, &pid);//通过窗口句柄拿到进程ID
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);//通过进程ID拿到进程句柄
if (NULL == hProcess) {
::MessageBoxA(NULL, "扫雷游戏未打开","错误", MB_OK);
return 0;
}
//扫雷基址:0x1005361 炸弹:0x8F
//扫雷的高 0x01005338
//扫雷的宽 0x01005334
unsigned char gamedata[24][32] = { 0 };
if (!ReadProcessMemory(hProcess, (LPCVOID)0x1005361, &gamedata, 32 * 24, &pid))
{
::MessageBoxA(NULL, "扫雷游戏未打开", "错误", MB_OK);
return 0;
}
DWORD dwHight = 0;
if (!ReadProcessMemory(hProcess, (LPCVOID)0x01005338, &dwHight, sizeof(dwHight), &pid))
{
::MessageBoxA(NULL, "读取扫雷进程未打开", "错误", MB_OK);
return 0;
}
short gamex = 20;
short gamey = 60;
unsigned short xypos[2] = { 0 };
for (int i = 0; i < dwHight;++i) {
for (int j = 0; j < 32;++j) {
if (0x10 == gamedata[i][j])
break;
xypos[0] = gamex + j * 16;
xypos[1] = gamey + i * 16;
if (0x8F != gamedata[i][j]) {
::PostMessage(hwnd, WM_LBUTTONDOWN,MK_LBUTTON,*(int *)xypos);
::PostMessage(hwnd, WM_LBUTTONUP,0, *(int*)xypos);
}
}
}
cout << "彪哥出品,必为废品";
CloseHandle(hProcess);
}
运行结果图:


前三步总结也就是
通过窗口的标题拿到窗口的句柄,FindWindowA
然后通过窗口的句柄拿到进程的ID,GetWindowThreadProcessId
最后通过进程的ID拿到进程的句柄OpenProcess
插入一个提示(vs如何打开MSDN帮助文档)
比如要查看float的特征。
在vs中输入float,然后按F1键,会在默认浏览器上打开帮助文档。
本文介绍了如何通过编程实现扫雷游戏的自动化,包括获取游戏窗口句柄,读取进程内存,计算雷区坐标,遍历并模拟鼠标点击。详细步骤包括查找内存中的宽度和高度,使用CE搜索立即数地址,以及通过PostMessage发送点击消息。
660

被折叠的 条评论
为什么被折叠?



