自動掃雷程式

zerostone發表於2006-10-26
很多人喜歡玩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。效果圖:


注意:Windows的掃雷程式似乎是保證第一個按下的按鈕下面不是雷,如果是雷,它會自動重新排雷。所以要使用自動掃雷,保證其正確性,最好不要手動先去翻幾個按鈕。

相關文章