自动扫雷程序

很多人喜欢玩Windows的扫雷程序,去年11月份写了这个小程序,那时还在Infosys。

以下是去年在我的MSN Sapce上写的:

这两天挺无聊的,Bob的扫雷战绩越来越令人惊叹,中级达到26秒,于此情况,我不得不使出了杀手锏——改注册表,将战绩定格为1秒,不过或许这样显得太贪心了,所以马上被识破,于是,鄙视的眼光投了过来,似乎我成了一个“卑鄙龌鹾”,用下三烂的手段获取胜利的人,一下子一种“窝囊的火气”涌上心来,我决定要以技取胜,于是,上网查了一点WINDOWS扫雷程序的资料,发现原来还真容易,欣喜,马上动手写了个自动扫雷程序,详情如下:
 
对于WIN2000,雷区的宽度数据保存在0x10056f8,高度数据保存在0x1005a68,雷的数目保存在0x1005a6c,雷的布局数据起始地址为0x1005700;
对于WINXP,分别是0x1005334、0x1005338、0x1005330、0x1005340。
如果某个按钮下是雷,那么这个内存数据对应的数值必然为0x8F,如此,离开胜利不远了,然后用VS的SPY程序获取到了扫雷界面第一个按钮的坐标为:(11, 54),并且获取到了鼠标消息的参数lParam和wParam,OK,根据这些数据,就可以还原出暴露的雷区布局,再将坐标做一下简单的数学转换,那么按下这个程序的某个按钮,将鼠标消息同时发给WINDOWS扫雷程序就可以了,如此,相应的扫雷程序的那个按钮也被按下,再加个自动进行所有非雷按钮的MOUSE_UP事件进行一遍,不就实现了自动扫雷了吗?为了降低速度,再加个速度调节,根据指定速度对主线程进行休眠,哈哈,搞定!

起初这个软件我是用C#写的,后来发现在.NET中发送大量的消息到某个窗口时,.显得很慢,于是用Delphi重写了一遍,结果速度非常快。

1。首先取得操作系统的版本,以便获知究竟从什么内存地址读雷区数据:
procedure TfrmMain.FormCreate(Sender: TObject);
var
  ver:Cardinal;
  majorVer: Cardinal;
  minorVer: Cardinal;
begin
  ver :
=  GetVersion();
  majorVer :
=  LOByte(ver);
  minorVer :
=  HIByte(ver);
  
if  majorVer  >=   5  then
  begin
    
if  minorVer  =   0  then     // Win2000
    begin
      nWidthAddress    :
=  $10056F8;
            nHeightAddress :
=  $1005A68;
            nMinesAddress    :
=  $1005A6C;
            nCellBaseAddress :
=  $ 1005700 ;
    end
    
else                      // WinXP
    begin
      nWidthAddress    :
=  $ 1005334 ;
      nHeightAddress :
=  $ 1005338 ;
      nMinesAddress    :
=  $ 1005330 ;
      nCellBaseAddress :
=  $ 1005340 ;
    end;
  end
  
else
  begin
    MessageDlg(
' This program support only Win2000 and WinXP! ' , mtInformation, [mbOK],  0 );
    Application.Terminate;
  end;
  
// edtSeconds.Text := IntToStr(self.traSeconds.Position);
end;

2。使用ReadProcessMemory(pid, Pointer(nWidthAddress), @nWidth, 4, dwNumOfBytesRead)读取宽度数据;ReadProcessMemory(pid, Pointer(nHeightAddress), @nHeight, 4, dwNumOfBytesRead)读取高度数据。

3。生成扫雷界面,如果按钮下是雷,则其内存数据必然是0x8F:
  for  y: =   0  to nHeight  -   1   do
      begin
        
for  x : =   0  to nWidth  -   1   do
        begin
          MineButtons[x, y] :
=  TSpeedButton.Create(self);
          MineButtons[x, y].Parent :
=  self.pnlMines;
          MineButtons[x, y].Width :
=   16 ;
          MineButtons[x, y].Height :
=   16 ;
          MineButtons[x, y].Left :
=  x * 16 ;
          MineButtons[x, y].Top :
=  y * 16 ;
          MineButtons[x, y].Name :
= ' btn_ '   +  IntToStr(x)  +   ' _ '   +  IntToStr(y);
          MineButtons[x, y].Caption :
=   '' ;
          MineButtons[x, y].OnMouseUp :
=  self.SweepMouseUp;
          nCellAddress :
=  (nCellBaseAddress)  +  ( 32   *  (y + 1 ))  +  (x + 1 );
          ReadProcessMemory(pid, Pointer(nCellAddress), @nIsMine, 
1 , dwNumOfBytesRead);
          
if  nIsMine  =  $8f then
          begin
            MineButtons[x, y].Glyph :
=  imgmine.Picture.Bitmap;
            MineButtons[x, y].Tag :
=  BUTTON_MINE;
          end;
          gauge.Progress :
=  gauge.Progress  +   1 ;
          Application.ProcessMessages;
        end;    
//  for
      end;     //  for

4。对某个按钮发送MouseUP消息的过程:
  首先很容易查到Windows的鼠标按钮消息代码为:
  const WM_LBUTTONDOWN = $0201;
  const WM_LBUTTONUP =  $0202;
  const WM_RBUTTONDOWN = $0204;
  const WM_RBUTTONUP = $0205; 
procedure TfrmMain.SweepMouseUp(Sender: TObject; Button: TMouseButton;  Shift: TShiftState; X, Y: Integer);
var
  bbtn: TSpeedButton;
  xpos: Cardinal;
  ypos: Cardinal;
  pos: cardinal;
begin
  bbtn :
=  (Sender  as  TSpeedButton);
      
// The coordinator are (x,y) -> y<<16 | x}
  xpos : =  bbtn.Left  +   11   +   8 // make it click on center of the button
  ypos : =  bbtn.Top  +   54   +   8 ;
  pos:
=  (ypos shl  16 ) or xpos;

  If Button 
=  mbLeft then
  Begin
    bbtn.Caption:
= ' # ' ;
    
if  handleMine  <>   0  then
    begin
      SendMessage(handleMine, WM_LBUTTONDOWN, $
0001 , pos);
      SendMessage(handleMine, WM_LBUTTONUP, $
0001 , pos);
    end;
  End
  Else 
if  Button  =  mbRight then
  Begin
    
if  handleMine  <>   0  then
    begin
      
if  bbtn.Tag  =  BUTTON_NONE then
      begin
        bbtn.Glyph :
=  imgFlag.Picture.Bitmap;
        bbtn.Tag :
=  BUTTON_FLAG;
      end
      
else   if  bbtn.Tag  =  BUTTON_FLAG then
      begin
        bbtn.Glyph :
=  imgQuestion.Picture.Bitmap;
        bbtn.Tag :
=  BUTTON_QUESTION;
      end
      
else   if  bbtn.Tag  =  BUTTON_QUESTION then
      begin
        bbtn.Glyph :
=  nil;
        bbtn.Tag :
=  BUTTON_NONE;
      end;

      SendMessage(handleMine, WM_RBUTTONDOWN, $
0002 , pos);
      SendMessage(handleMine, WM_RBUTTONUP, $
0000 , pos);
    end;
  End;
end;

5。自动扫雷过程,显然就是对所有的非雷按钮进行一遍消息发送即可。

6。效果图:
自动扫雷程序_第1张图片

注意:Windows的扫雷程序似乎是保证第一个按下的按钮下面不是雷,如果是雷,它会自动重新排雷。所以要使用自动扫雷,保证其正确性,最好不要手动先去翻几个按钮。

你可能感兴趣的:(小软件)