【文章标题】: Vista 的扫雷
【文章作者】: rdsnow[BCG][PYG][D.4s]
【作者邮箱】: rdsnow@163.com
【作者主页】: http://rdsnow.ys168.com
【作者QQ号】: 83757177
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
好长时间不上网了,无聊中跟了一遍 Vista 下的扫雷,发现新版除了界面及效果好看多了以外,代码上跟 XP 下的扫雷也有一些不同的地方。
1、并不在开始新游戏的时候随机位置产生雷,而改成了在第一次点击鼠标后再产生雷的位置
2、雷的位置不再放在内存中一块固定不变的位置上,开始每一局游戏后,程序都会重新申请内存,并且把雷的位置分散保存,既然分散保存,内存中应该有一个地址表
3、除了每局保存位置不同外,程序还带有重定位信息,以增加找到雷位置的难度
4、程序受到鼠标点击消息后,会 GetCursorPos() 得到鼠标的位置,计算鼠标点击的是哪个格子不是仅仅根据消息中的 LPARAM
--------------------------------------------------------------------------------
既然是随机位置产生雷,应该会用到 rand() 函数,bp rand 下个断吧。跟出后 rand() 后会发现很多 rand() 是在 call MineSwee.00343069 中调用的。如下面:
00338EBE |. 50 push eax ; /Arg2
00338EBF |. 6A 00 push 0 ; |Arg1 = 00000000
00338EC1 |. E8 A3A10000 call MineSwee.00343069 ; \MineSwee.00F63069
00338EC6 |. 8BF8 mov edi,eax
00338EC8 |. 8B46 54 mov eax,dword ptr ds:[esi+54]
00338ECB |. 48 dec eax
00338ECC |. 50 push eax ; /Arg2
00338ECD |. 6A 00 push 0 ; |Arg1 = 00000000
00338ECF |. E8 95A10000 call MineSwee.00343069 ; \MineSwee.00F63069
call MineSwee.00343069 的作用是产生 Agr1 到 Arg2 之间的随机数。于是去掉原来断点,改为:bp 343069,断下后看到信息窗口显示:
┌──────────────────────────────────────────────┐
│本地调用来自 00336479, 00338EC1, 00338ECF, 003398CB, 0033CEB4, 0033CFF3, 0033D870, 0033D882 │
└──────────────────────────────────────────────┘
对以上地址一一下断,断下后跟一遍,发现都不是产生雷的代码。随手用鼠标点了一个格子,竟然断下了,跟了一下,果然是生成雷的代码,来到这里:
003363F8 |> \8B46 08 mov eax,dword ptr ds:[esi+8] ; 行数 dwLine
003363FB |. 0FAF46 0C imul eax,dword ptr ds:[esi+C] ; 跟列数 dwColumn 相乘得到总的格子数 dwTotal
003363FF |. 33FF xor edi,edi ; i = 0 作为下面循环的计数器
00336401 |. 85C0 test eax,eax
00336403 |. 7E 49 jle short MineSwee.0033644E
00336405 |> 8B4E 0C /mov ecx,dword ptr ds:[esi+C]
00336408 |. 8BC7 |mov eax,edi
0033640A |. 99 |cdq
0033640B |. F7F9 |idiv ecx ; i / dwColumn = i 所在行号 余数 = 所在列号
0033640D |. 33C9 |xor ecx,ecx
0033640F |. 2B55 08 |sub edx,dword ptr ss:[ebp+8] ; i所在列号 - 鼠标点击的水平格子数
00336412 |. 2B45 0C |sub eax,dword ptr ss:[ebp+C] ; i所在行号 - 鼠标点击的竖直格子数
00336415 |. 85D2 |test edx,edx
00336417 |. 0F9DC1 |setge cl ; 判断是否要取绝对值
0033641A |. 8D4C09 FF |lea ecx,dword ptr ds:[ecx+ecx-1]
0033641E |. 0FAFCA |imul ecx,edx ; 若是负数就乘 -1
00336421 |. 83F9 01 |cmp ecx,1
00336424 |. 7F 13 |jg short MineSwee.00336439 ; 第一个绝对值若小于 1 ,就什么都不做
00336426 |. 33C9 |xor ecx,ecx
00336428 |. 85C0 |test eax,eax
0033642A |. 0F9DC1 |setge cl ; 判断是否要取绝对值
0033642D |. 8D4C09 FF |lea ecx,dword ptr ds:[ecx+ecx-1]
00336431 |. 0FAFC8 |imul ecx,eax
00336434 |. 83F9 01 |cmp ecx,1 ; 第二个绝对值若小于 1 ,就什么都不做
00336437 |. 7E 09 |jle short MineSwee.00336442
00336439 |> 8B4D FC |mov ecx,dword ptr ss:[ebp-4]
0033643C |. 57 |push edi ; /Arg1
0033643D |. E8 47630000 |call MineSwee.0033C789 ; \将序号保存到 realloc 的空间中去
00336442 |> 8B46 08 |mov eax,dword ptr ds:[esi+8]
00336445 |. 0FAF46 0C |imul eax,dword ptr ds:[esi+C]
00336449 |. 47 |inc edi
0033644A |. 3BF8 |cmp edi,eax ; i 从 0 到 dwTotal 循环
0033644C |.^ 7C B7 \jl short MineSwee.00336405 ; ( i 相当于将所有的格子编号 )
这段代码的作用就是给所有格子编上号,然后把鼠标点击位置周围的 9 个格子去掉,以保证不在这 9 个格子中产生雷,当然如果鼠标点在边上,也不一定是 9 格。
以“9 行 * 9 列”为例,如果点击在第三行第六列,如下图:
┌─┬─┬─┬─┬─┬─┬─┬─┬─┐
│ 0│ 1│ 2│ 3│ 4│ 5│ 6│ 7│ 8│
├─┼─┼─┼─┼─┼─┼─┼─┼─┤
│ 9│10│11│12│ │ │ │16│17│
├─┼─┼─┼─┼─┼─┼─┼─┼─┤
│18│19│20│21│ │鼠│ │25│26│-> 鼠标点击在这一行第六列
├─┼─┼─┼─┼─┼─┼─┼─┼─┤
│27│28│29│30│ │ │ │34│35│
├─┼─┼─┼─┼─┼─┼─┼─┼─┤
│36│37│38│39│40│41│42│43│44│
├─┼─┼─┼─┼─┼─┼─┼─┼─┤
│45│46│47│48│49│50│51│52│53│
├─┼─┼─┼─┼─┼─┼─┼─┼─┤
│54│55│56│57│58│59│60│61│62│
├─┼─┼─┼─┼─┼─┼─┼─┼─┤
│63│64│65│66│67│68│69│70│71│
├─┼─┼─┼─┼─┼─┼─┼─┼─┤
│72│73│74│75│76│77│78│79│80│
└─┴─┴─┴─┴─┴─┴─┴─┴─┘
没有了 13 14 15 22 23 24 31 32 33 ,保存下来的数组就是:0 1 2 3 4 5 6 7 8 9 10 11 12 16 17 18 19 20 21 25 26 27 28 29 30 34 35 36 37 38 39 40 41 42 43 44 …… 80 ,随机数就在这些数中间产生,这样就让鼠标点击的位置周围 9 格不会产生地雷。
接下来肯定要用一个循环产生 n 个不重复随机数了,n 就是要产生的地雷数
00336466 |. EB 31 jmp short MineSwee.00336499
00336468 |> 33FF xor edi,edi
0033646A |. EB 2D jmp short MineSwee.00336499
0033646C |> 8B45 FC /mov eax,dword ptr ss:[ebp-4]
0033646F |. 8B00 |mov eax,dword ptr ds:[eax]
00336471 |. 85C0 |test eax,eax
00336473 |. 76 2B |jbe short MineSwee.003364A0
00336475 |. 48 |dec eax
00336476 |. 50 |push eax ; /Arg2:限制号码的范围不能超过
00336477 |. 6A 00 |push 0 ; |Arg1 = 00000000
00336479 |. E8 EBCB0000 |call MineSwee.00343069 ; \取得一个随机号码
0033647E |. 8BD8 |mov ebx,eax
00336480 |. 8B45 FC |mov eax,dword ptr ss:[ebp-4]
00336483 |. 8B40 0C |mov eax,dword ptr ds:[eax+C]
00336486 |. FF3498 |push dword ptr ds:[eax+ebx*4] ; /Arg1:凭上面取得的随机号码到上面数组中取数值
00336489 |. 8BCF |mov ecx,edi ; |
0033648B |. E8 F9620000 |call MineSwee.0033C789 ; \保存到一个新的数组中
00336490 |. 8B4D FC |mov ecx,dword ptr ss:[ebp-4]
00336493 |. 53 |push ebx ; /Arg1
00336494 |. E8 E9F7FFFF |call MineSwee.00335C82 ; \将已经取出的数据从原数组中删除,以防重复
00336499 |> 8B07 >mov eax,dword ptr ds:[edi]
0033649B |. 3B46 04 |cmp eax,dword ptr ds:[esi+4] ; 如果小于需要产生的地雷数,则跳上继续循环
0033649E |.^ 75 CC \jnz short MineSwee.0033646C
好了现在得到了 n 个数,这 n 个数就代表了 n 个雷。不过 call MineSwee.0033C789 中每次都会申请不同位置的内存,当然不能在这里读取这 n 个地雷,下面我们要找找在雷真正保存的地方。
继续跟下去:
003364A0 |> \33C9 xor ecx,ecx
003364A2 |. 390F cmp dword ptr ds:[edi],ecx ; [edi] = 地雷总数
003364A4 |. 76 21 jbe short MineSwee.003364C7
003364A6 |> 8B47 0C /mov eax,dword ptr ds:[edi+C]
003364A9 |. 8B0488 |mov eax,dword ptr ds:[eax+ecx*4] ; 取第 i 个雷
003364AC |. 8B5E 0C |mov ebx,dword ptr ds:[esi+C] ; 列数 dwColumn
003364AF |. 99 |cdq
003364B0 |. F7FB |idiv ebx ; 雷[i] / dwColumn = 地雷所在行
003364B2 |. 8B5E 44 |mov ebx,dword ptr ds:[esi+44]
003364B5 |. 8B5B 0C |mov ebx,dword ptr ds:[ebx+C] ; [[ ESI + 0x44 ] + 0xC ] 保存有一个地址表
003364B8 |. 41 |inc ecx
003364B9 |. 8B1493 |mov edx,dword ptr ds:[ebx+edx*4] ; 每一行雷在地址表总都有一个地址
003364BC |. 8B52 0C |mov edx,dword ptr ds:[edx+C] ; [ 取得地址 + 0xC ]保存了一些 0 1 组合
003364BF |. C60410 01 |mov byte ptr ds:[eax+edx],1 ; 0 表示无雷, 1 表示有雷
003364C3 |. 3B0F |cmp ecx,dword ptr ds:[edi]
003364C5 |.^ 72 DF \jb short MineSwee.003364A6 ; 处理完所有的地雷
可见,ESI 的数值来源非常重要,只要有了 ESI,就能找到保存雷位置的地方。
以“16 行 * 30 列”为例:
有了 ESI ,[[ ESI + 0x44 ] + 0xC ] 有个地址表,里面有 30 个地址 ( 跟列数相同 ),每一个地址 + 0xC 得到就是每一列的各个格子是否有雷的信息。也就是这些信息分 30 个地方保存了。
我们要向上追踪 ESI 的来源。在这个 Call 内部,ESI 的数值并没有被修改。在堆栈中看到这一行:
┌──────────────────────────────────┐
│0025F15C |00336F7E 返回到 MineSwee.00336F7E 来自 MineSwee.003363BE│
└──────────────────────────────────┘
右击鼠标,选择在反汇编窗口跟随,或者直接 ENTER ,来到:
00336F2B /$ 8BFF mov edi,edi
00336F2D |. 55 push ebp
00336F2E |. 8BEC mov ebp,esp
00336F30 |. 51 push ecx
00336F31 |. 53 push ebx
00336F32 |. 8B5D 08 mov ebx,dword ptr ss:[ebp+8]
00336F35 |. 56 push esi
┌─────────────────────────────────────────────┐
│00336F36 |. 8BF1 mov esi,ecx ; ESI 的数值来源于 ECX│
└─────────────────────────────────────────────┘
00336F38 |. 8B46 40 mov eax,dword ptr ds:[esi+40]
00336F3B |. 8B40 0C mov eax,dword ptr ds:[eax+C]
……………
00336F62 |. 74 5C je short MineSwee.00336FC0
00336F64 |. 51 push ecx ; /Arg3 => 00000000
00336F65 |. 51 push ecx ; |Arg2 => 00000000
00336F66 |. 51 push ecx ; |Arg1 => 00000000
00336F67 |. E8 79EB0000 call MineSwee.00345AE5 ; \MineSwee.00F65AE5
00336F6C |. 33C9 xor ecx,ecx
00336F6E |. EB 50 jmp short MineSwee.00336FC0
00336F70 |> 394E 18 cmp dword ptr ds:[esi+18],ecx ; 判断是不是第一次鼠标点击
00336F73 |. 75 20 jnz short MineSwee.00336F95 ; 不是就跳走
00336F75 |. 57 push edi
00336F76 |. 53 push ebx
00336F77 |. 8BCE mov ecx,esi
┌─────────────────────────────────────────────────────┐
│00336F79 |. E8 40F4FFFF call MineSwee.003363BE ; 产生地雷位置的关键代码就是从这进去的│
│00336F7E |. 6A 00 push 0 ; ------> ENTER 后到这里 │
└─────────────────────────────────────────────────────┘
ECX 的数值又是从哪里来的呢?在堆栈中找下面一个“返回到”,继续跟随,来到:
┌──────────────────────────────────────────────┐
│0033CA51 |. 8B0D B8313900 mov ecx,dword ptr ds:[3931B8] ; 哈哈,地址在这里呢?│
└──────────────────────────────────────────────┘
0033CA57 |. 32DB xor bl,bl
0033CA59 |. E8 22ACFFFF call MineSwee.00337680
┌─────────────────────────────────────────────┐
│0033CA5E |. 85C0 test eax,eax ; 跟随到这里,往上看│
└─────────────────────────────────────────────┘
程序一定是在开始每局游戏时,申请内存,并把地址的相关信息保存在 [ 3931B8 ] 中,跟一下,把不同地方取来的代码放在一起就看到取地址的过程了:
┌─────────────────────────────┐
│0033CA51 |. 8B0D B8313900 mov ecx,dword ptr ds:[3931B8] │-> 就从 [3931B8] 开始索引吧
└─────────────────────────────┘
┌────────────────────────────┐
│00337685 . 8B49 0C mov ecx,dword ptr ds:[ecx+C]│
└────────────────────────────┘
┌────────────────────┐ [ESI+4] 保存了雷的总数 dwTotal
│00336F36 |. 8BF1 mov esi,ecx │-> [ESI+8] 保存了行数 dwLine
└────────────────────┘ [ESI+C] 保存了列数 dwColumn
┌─────────────────────────────┐
│003364B2 |. 8B5E 44 |mov ebx,dword ptr ds:[esi+44]│
└─────────────────────────────┘
┌─────────────────────────────┐
│003364B5 |. 8B5B 0C |mov ebx,dword ptr ds:[ebx+C] │-> 这里 ebx 保存了地址表的地址
└─────────────────────────────┘
┌───────────────────────────────┐
│003364B9 |. 8B1493 |mov edx,dword ptr ds:[ebx+edx*4] │-> 地址表中每一个地址
└───────────────────────────────┘
┌─────────────────────────────┐
│003364BC |. 8B52 0C |mov edx,dword ptr ds:[edx+C] │-> 这里就是保存地雷信息的地方
└─────────────────────────────┘
[ 3931B8 ] 虽然是个固定地址,但是由于程序是重定位的。每次载入程序这个地址不一定相同。查一下文件头和区块表。
┌────────────┐
数据表目录中的 Base Relocation Table:│00 10 0D 00 0C 46 00 00 │
└────────────┘
No | 名称 | VSize | VOffset | RSize | ROffset | Charact. |
01 | .pexe | 0000448F | 00001000 | 00004600 | 00000600 | 40000040 |
02 | .text | 0006ABC8 | 00006000 | 0006AC00 | 00004C00 | 60000020 |
03 | .data | 00003FF5 | 00071000 | 00004000 | 0006F800 | C0000040 |
04 | .rsrc | 0005B910 | 00075000 | 0005BA00 | 00073800 | 40000040 |
05 | .reloc | 0000460C | 000D1000 | 00004800 | 000CF200 | 42000040 |
程序的重定位给编写扫雷机带来麻烦,但是我们还是想在不改动原扫雷程序的情况下编写出扫雷机。
┌───────┐
│重定位的处理:│
└───────┘
┌────┐
--->│方案1: │
└────┘
扫雷机中直接向扫雷程序的可执行文件中入口代码处写入 int3 ,然后 CreateProcess() 创建被调试进程 DEBUG_PROCESS,不能这时写入 int3 ,因为不知道写到什么地址上
并使用 WaitForDebugEvent 等待调试事件,当检测到 EXCEPTION_DEBUG_EVENT 时,GetThreadContext 读取线程环境,取得 EIP 的值,并恢复代码。
但是实际操作时,创建 DEBUG_EVENT 进程总是不能顺利运行,调试 XP 中的扫雷可以,将扫雷拷贝出系统目录也不行,是不是 Vista 对自己的程序有权限保护,或者扫雷自己有检测,我也不知道,OD可以顺利调试,看来要好好的熟悉 Vista 这个新系统了。
由于自己水平有限,只能放弃这个方案了。
┌────┐
--->│方案2: │
└────┘
因为不能顺利的调试扫雷进程,只好来个笨办法,在扫雷进程的内存中搜索入口点代码,然后计算出 [3931B8] 相对于入口点地址的相对偏移。
入口点代码:
0034382A > $ E8 3A040000 call MineSwee.00433C69
0034382F .^ E9 4AFDFFFF jmp MineSwee.0043357E
首先搜索 0xE8 找到后,判断后面的数值是不是 0x43A,不是继续,是则判断 0xE9 后面是不是 0xFFFFFD4A,是则表示找到,不是继续。
搜索步进不必要为 1,由于入口地址的低位 0x382A 是不变的,只是高位有变换,可以让步进设为 0x10000。
找到入口地址设为 dwCodeAddress,[3931B8] 这个地址得到的方法是: ( dwCodeAddress & 0xFFFF0000 ) + 0x531B8
┌────────┐
│鼠标动作的模拟:│
└────────┘
实际编程时发现,直接向扫雷程序发送 WM_LBUTTONDOWN 消息,并不能将格子点开,看下程序的导入表,发向有 GetCursorPos( ) 函数,当受到 WM_LBUTTONDOWN 消息时,程序用 GetCursorPos( ) 取得鼠标点击的区域是否在格子上,并也有代码判断是点击在哪个格子上。
具体代码我们并不关心了,不过有以下几行值得注意:
00343F8B |. 50 push eax ; /pRect
00343F8C |. FF35 E43C3900 push dword ptr ds:[393CE4] ; |hWnd = 00120320 (class='Static',parent=0009031A)
00343F92 |. 8BF1 mov esi,ecx ; |
00343F94 |. FF15 40633200 call dword ptr ds:[<&USER32.GetWindowRec>; \GetWindowRect
00343F9A |. 8D45 EC lea eax,dword ptr ss:[ebp-14]
00343F9D |. 50 push eax ; /pRect
00343F9E |. FF35 E43C3900 push dword ptr ds:[393CE4] ; |hWnd = 00120320 (class='Static',parent=0009031A)
00343FA4 |. FF15 BC613200 call dword ptr ds:[<&USER32.GetClientRec>; \GetClientRect
地雷的所有格子都放在 Static 控件中,不过这个控件没有 ID 和 标题。不容易找到它的句柄,不过扫雷程序已经把 Static 的窗口句柄保存在 [393CE4] 中了,我们只要直接到哪儿去取。
注意重定位,地址转换应该是:( dwCodeAddress & 0xFFFF0000 ) + 0x53CE4
┌────────┐
│格子位置的测量:│
└────────┘
程序中 BitBlt 时每个雷大小是 0x11 即 17 ,实际加上线条宽度,每个格子宽度是 18
另外第一个雷的 left 相对于 Static 的 Left 再加上 30
另外第一个雷的 Top 相对于 Static 的 Top 再加上 30
那么我们如果加上 ( 30 + 18 / 2 ) 就可以点在第一个雷的中央。
┌────────┐
│自动扫雷的实现:│
└────────┘
//------------------------------------------
// AutoMineSweep.exe
//
// 本程序针对 32 位 Vista 系统中的扫雷编写
//
// 对于 64 位系统没有经过测试
//
// rdsnow[BCG][PYG][D.4s]
//
// 13:43 2007/2/25
//
//------------------------------------------
#include <windows.h>
int WINAPI WinMain ( HINSTANCE hInstance ,
HINSTANCE hPrevInstance ,
PSTR szCmdLine ,
int iCmdShow )
{
//寻找扫雷窗口,若无,则试着运行扫雷
HWND hWnd = ::FindWindow ( NULL ,TEXT("扫雷"));
TCHAR szPath[80] ;
if ( !hWnd ){
::GetEnvironmentVariableW ( TEXT("ProgramFiles") ,szPath ,80 );
lstrcat(szPath ,TEXT("\\Microsoft Games\\Minesweeper\\"));
if ( ::ShellExecute( NULL ,TEXT("open") ,TEXT("Minesweeper.exe") ,
NULL ,szPath ,SW_SHOW ) > (HINSTANCE)32){
::Sleep ( 5000 );
hWnd = ::FindWindow ( NULL ,TEXT("扫雷"));
}
else{
MessageBox ( NULL ,TEXT("没有找到扫雷程序") ,NULL ,MB_OK | MB_ICONWARNING);
return 0 ;
}
}
//打开扫雷进程
DWORD dwProcessId ;
GetWindowThreadProcessId ( hWnd ,&dwProcessId );
HANDLE hProcess = ::OpenProcess ( PROCESS_ALL_ACCESS ,FALSE ,dwProcessId );
if ( !hProcess ){
MessageBox ( NULL ,TEXT("打开进程失败") ,NULL ,MB_OK | MB_ICONWARNING);
return 0 ;
}
//在进程内存中寻找程序入口点
unsigned char byteCode = 0 ;
DWORD dwCode ,dwCodeAddress = 0x382A ;
while ( true ){
::ReadProcessMemory ( hProcess ,(LPCVOID)dwCodeAddress ,&byteCode ,1 ,NULL );
if ( byteCode == 0xE8 ){
::ReadProcessMemory ( hProcess ,(LPCVOID)(dwCodeAddress+1) ,&dwCode ,4 ,NULL );
if ( dwCode == 0x43A ){
::ReadProcessMemory ( hProcess ,(LPCVOID)(dwCodeAddress+6) ,&dwCode ,4 ,NULL );
if ( dwCode == 0xFFFFFD4A )
break;
else dwCodeAddress += 0x10000 ;
}
else dwCodeAddress += 0x10000 ;
}
else dwCodeAddress += 0x10000 ;
}
dwCodeAddress &= 0xFFFF0000 ;
//读取子控件 static 的句柄
HANDLE hStatic;
::ReadProcessMemory ( hProcess ,(LPCVOID)( dwCodeAddress+0x53CE4 ) ,&hStatic ,4 ,NULL);
//取得子控件 static 的窗口矩形
RECT stWndRect ;
::GetWindowRect ( (HWND)hStatic , &stWndRect );
//模拟鼠标点击第一个格子
bool isMine[30][24];
POINT stCurrentPoint ,stNewPoint;
::GetCursorPos ( &stCurrentPoint );
stNewPoint.x = stWndRect.left + 39;
stNewPoint.y = stWndRect.top + 41;
::SetCursorPos ( stNewPoint.x ,stNewPoint.y );
::SetWindowPos ( hWnd ,HWND_TOPMOST ,0 ,0 ,0 ,0 ,SWP_NOSIZE | SWP_NOMOVE );
::SendMessage ( hWnd ,WM_LBUTTONDOWN ,NULL ,MAKELPARAM( stNewPoint.x ,stNewPoint.y ));
::SendMessage ( hWnd ,WM_LBUTTONUP ,NULL ,MAKELPARAM( stNewPoint.x ,stNewPoint.y ));
//读出雷所在的位置,并关闭进程句柄
DWORD dwESI ,dwTotal ,dwLine ,dwColumn ,dwAddress ,dwMine;
::ReadProcessMemory ( hProcess ,(LPCVOID)(dwCodeAddress + 0x531B8 ) ,&dwESI ,4 ,NULL);
::ReadProcessMemory ( hProcess ,(LPCVOID)(dwESI+0xC) ,&dwESI ,4 ,NULL);
::ReadProcessMemory ( hProcess ,(LPCVOID)(dwESI+4) ,&dwTotal ,4 ,NULL);
::ReadProcessMemory ( hProcess ,(LPCVOID)(dwESI+8) ,&dwLine ,4 ,NULL);
::ReadProcessMemory ( hProcess ,(LPCVOID)(dwESI+0xC) ,&dwColumn ,4 ,NULL);
::ReadProcessMemory ( hProcess ,(LPCVOID)(dwESI+0x44) ,&dwAddress ,4 ,NULL);
::ReadProcessMemory ( hProcess ,(LPCVOID)(dwAddress+0xC) ,&dwAddress ,4 ,NULL);
unsigned char i,j;
for( i=0 ;i<dwColumn ;i++){
::ReadProcessMemory ( hProcess ,(LPCVOID)(dwAddress+4*i) ,&dwMine ,4 ,NULL);
::ReadProcessMemory ( hProcess ,(LPCVOID)(dwMine+0xC) ,&dwMine ,4 ,NULL);
for ( j=0 ;j<dwLine ;j++ )
::ReadProcessMemory ( hProcess ,(LPCVOID)(dwMine+j) ,(LPVOID)&isMine[i][j] ,1 ,NULL);
}
::CloseHandle(hProcess);
//模拟扫雷鼠标动作
for ( i=0 ;i<dwLine ;i++ ){
for ( j=0 ;j<dwColumn ;j++ ){
::SetCursorPos ( stNewPoint.x +18*j ,stNewPoint.y +18*i );
if ( isMine[j][i] ){
//::Sleep( 500 );
::SendMessage ( hWnd ,WM_RBUTTONDOWN ,NULL ,MAKELPARAM( stNewPoint.x +18*j ,stNewPoint.y +18*i ));
::SendMessage ( hWnd ,WM_RBUTTONUP ,NULL ,MAKELPARAM( stNewPoint.x +18*j ,stNewPoint.y +18*i ));
}
else {
::SendMessage ( hWnd ,WM_LBUTTONDOWN ,NULL ,MAKELPARAM( stNewPoint.x +18*j ,stNewPoint.y +18*i ));
::SendMessage ( hWnd ,WM_LBUTTONUP ,NULL ,MAKELPARAM( stNewPoint.x +18*j ,stNewPoint.y +18*i ));
}
}
}
::SetWindowPos ( hWnd ,HWND_NOTOPMOST ,0 ,0 ,0 ,0 ,SWP_NOSIZE | SWP_NOMOVE );
::SetCursorPos ( stCurrentPoint.x ,stCurrentPoint.y );
return 0 ;
}
实际操作时一定要注意在扫雷过程中尽量不要移动鼠标。会导致扫雷失败。
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!
2007年02月26日 22:19:55