人人做人人澡人人爽欧美,国产主播一区二区,久久久精品五月天,羞羞视频在线观看免费

當前位置:蘿卜系統下載站 > 技術開發教程 > 詳細頁面

“掃雷”游戲的幕后

“掃雷”游戲的幕后

更新時間:2022-08-28 文章作者:未知 信息來源:網絡 閱讀次數:

介紹

    曾想了解“掃雷”游戲在幕后所發生的一切嗎?嗯,我想過,還由此決定對其進行了研究。本文是我的研究結果,現公之于眾。

主要概念

1. 使用 P/Invoke 調用 Win32 API。

2. 直接讀取另一個進程的內存。

注1:本文的第一部分包括一些匯編代碼,如果你不是很明白,無關要緊,這不是本文的目的,你盡可以跳過不管。然而,如果你想問我有關這些代碼的問題,非常歡迎你寫信給我。

注2:本程序是在Windows XP下測試的,所以如果它不能運行在其它的系統下,請注明該系統的信息,好讓我們大家都知道。

注2之更新: 本代碼現在經過修改后也能在Windows 2000下運行。謝謝Ryan Schreiber找到了Win2K下的內存地址。



第一步 – 探索 winmine.exe

   如果你不是一個匯編迷,可以跳到這一步的最后,只看結論。

   為了更好地了解“掃雷”幕后所發生的一切,我以一個調試器打開此文件作為開端。我個人最喜歡的調試器是Olly Debugger v1.08, 這是一個非常簡單且直觀的調試器。總之,我在調試器中打開winmine.exe,并查看該文件。 我發現在Import區(列出在程序中用到的所有dll函數的區域)有下面一行:

010011B0  8D52C377 DD msvcrt.rand

    這就意味著“掃雷”用到了VC運行庫的隨機函數,因此我認為這對我可能有幫助。我搜索了該文件,看看到底在哪里調用了rand()函數,不過只在一個地方找到了這個函數:

01003940  FF15 B0110001 CALL DWORD PTR DS:[<&msvcrt.rand>]

    接著我在這一行單步調用插入了一個斷點并運行程序。我發現每當點擊笑臉圖標時,一個新的布雷圖就生成了。布雷圖按以下步驟創建:

1.      首先,給布雷圖分配一塊內存區,并把所有的內存字節都設置成0x0F,說明在該單元(cell)中沒有地雷。

2. 其次,按地雷數遍歷每一個地雷:

2.1. 隨機化 x 位置 (取值在1至寬度之間)。
2.2. 隨機化 y 位置 (取值在1至高度之間)。
2.3. 設置內存塊中被選中的單元的值為0x8F,這意味著在該單元中有一個地雷。

下面是原碼,我已加入了一些注釋,并加粗了重點部分。

010036A7  MOV DWORD PTR DS:[1005334],EAX    ; [0x1005334] = 寬度(即橫向格數)

010036AC  MOV DWORD PTR DS:[1005338],ECX    ; [0x1005338] = 高度(即縱向格數)

010036B2  CALL winmine.01002ED5  ; 生成空的內存塊并進行清除

010036B7  MOV EAX,DWORD PTR DS:[10056A4]

010036BC  MOV DWORD PTR DS:[1005160],EDI

010036C2  MOV DWORD PTR DS:[1005330],EAX    ; [0x1005330] = 地雷的個數

                    ; 以地雷個數進行循環

010036C7  PUSH DWORD PTR DS:[1005334] ; 把最大寬度(max width)壓入棧

010036CD  CALL winmine.01003940       ; Mine_Width  = 隨機化 x 位置 (0 至 max width-1) (即在0和max width-1之間隨機選一個值)

010036D2  PUSH DWORD PTR DS:[1005338] ; 把最大高度壓入棧

010036D8  MOV ESI,EAX

010036DA  INC ESI                ; Mine_Width = Mine_Width + 1

010036DB  CALL winmine.01003940  ; Mine_Height =隨機化 y 位置

                                 ; (0 至 max height-1)

010036E0  INC EAX                ; Mine_Height = Mine_Height +1

010036E1  MOV ECX,EAX            ;計算單元在內存塊(布雷圖)中的地址

010036E3  SHL ECX,5              ; 按這樣計算:

                                 ; 單元內存地址 = 0x1005340 + 32 * height + width

010036E6  TEST BYTE PTR DS:[ECX+ESI+1005340],80 ; [單元內存地址] ==是否已是地雷?

010036EE  JNZ SHORT winmine.010036C7   ; 如果已是地雷,則重新迭代

010036F0  SHL EAX,5                    ; 否則,設置此單元為地雷

010036F3  LEA EAX,DWORD PTR DS:[EAX+ESI+1005340]

010036FA  OR BYTE PTR DS:[EAX],80

010036FD  DEC DWORD PTR DS:[1005330]        

01003703  JNZ SHORT winmine.010036C7   ; 進行下一次迭代



    正如你從代碼所看到的,我發現了4個要點:

讀內存地址[0x1005334]得出布雷圖的寬度。

讀內存地址[0x1005338]得出布雷圖的高度。

讀內存地址[0x1005330]得出布雷圖中地雷的個數。

   給出x、y,它們代表布雷圖中的一個單元,位于x列,y行。地址 [0x1005340 + 32 * y + x] 給出了該單元的值,這樣我們就進入了下一步。

第2 步– 設計一個解決方案

   你可能在想,我將會談到了哪一種解決方案呢?顯然,在發現了所有的地雷信息均可為我所用后,我所要做的就是從內存中讀取數據。我決定編寫讀取這些信息的一個小程序,并給予說明。 它能自己繪出布雷圖,顯示出每一個被發現的地雷。

   那么,怎么設計呢?我所做的就是把地址裝到一個指針中(是的,它在C#中還存在),并讀出其所指的數據,這樣行嗎?嗯,并不完全如些。因為場合不同,存儲這些數據的內存并不在我的應用程序之中。要知道,每一個進程都擁有自己的地址空間,所以它就不會“意外地”訪問屬于別的程序的內存。因此,為了能讀出這此數據,就必須找到一種方法,用來讀取另一個進程的內存。 在本例中,這個進程就是“掃雷”進程。

    我決定寫一個小小的類庫,它將接收一個進程,并提供讀取該進程內存地址的功能。之所以這樣做,是因為我還要在很多程序中用到它,沒有必要反反復復地編寫這些代碼。這樣,你就可以得到這個類,并在應用程序中使用它,且是免費的。例如,如果你編寫一個調試器,這個類對你會有所幫助。據我所知,所有的調試器都具有讀取被調試程序內存的能力。

    那么,我們怎么才能讀取別的進程的內存呢?答案在于一個叫做ReadProcessMemory的API。 這個API實際上可以讓你讀取進程內存中的一個指定地址。但在進行此操作之前,必須以特定的模式打開進程,而在完成操作之后,就必須關閉句柄以避免資源泄漏。我們利用OpenProcess 和  CloseHandle這幾個API的幫助說明,完成了相應的操作。

     為了在C#中使用API,必須使用P/Invoke,這意味著在使用API之前需要先對其進行聲明。一般情況下都很簡單,但要是讓你以.NET的方式實現的話,有時就不那么容易了。我在MSDN中找到了這些API聲明:

HANDLE OpenProcess(

    DWORD dwDesiredAccess,       // 訪問標志

    BOOL bInheritHandle,         // 句柄繼承選項

    DWORD dwProcessId            // 進程ID

    );



BOOL ReadProcessMemory(

    HANDLE hProcess,            // 進程句柄

    LPCVOID lpBaseAddress,      // 內存區基址

    LPVOID lpBuffer,            // 數據緩沖

    SIZE_T nSize,               // 要讀的字節數

    SIZE_T * lpNumberOfBytesRead  // 已讀字節數

    );



BOOL CloseHandle(

    HANDLE hObject              // 進程句柄

    );



      這些聲明轉換為如下的C#聲明:

[DllImport("kernel32.dll")]

public static extern IntPtr OpenProcess(

    UInt32 dwDesiredAccess,

    Int32 bInheritHandle,

    UInt32 dwProcessId

    );



[DllImport("kernel32.dll")]

public static extern Int32 ReadProcessMemory(

    IntPtr hProcess,

    IntPtr lpBaseAddress,

    [In, Out] byte[] buffer,

    UInt32 size,

    out IntPtr lpNumberOfBytesRead

    );



[DllImport("kernel32.dll")] public static extern Int32 CloseHandle(

    IntPtr hObject

    );

如果你想知道在c++和c#之間有關類型轉換的更多信息,我建議你從msdn.microsoft.com站點搜索此話題:“Marshaling Data with Platform Invoke”。 基本上, 如果你把邏輯上是正確的程序擱在那兒, 它便能運行, 但有時還需要一點點的調整。

    在聲明了這些函數之后,我要做的是用一個簡單的類把它們包裝起來,并使用這個類。我把聲明放在一個叫做ProcessMemoryReaderApi的類中,這樣做更有條有理。主要的實用類稱為ProcessMemoryReade。這個類有一個ReadProcess屬性,它源于System.Diagnostics.Process類型,用于存放你要讀取其內存的進程。類中有一個方法,用來以讀模式打開進程。   

public void OpenProcess()



{

    m_hProcess = ProcessMemoryReaderApi.OpenProcess(

                         ProcessMemoryReaderApi.PROCESS_VM_READ, 1,

                         (uint)m_ReadProcess.Id);



}

PROCESS_VM_READ 常量告訴系統以讀模式打開進程, 而m_ReadProcess.Id 聲明了我要打開的是什么進程。

    在該類中最重要的是一個方法,它從進程中讀取內存:

public byte[] ReadProcessMemory(IntPtr MemoryAddress, uint bytesToRead,

                                out int bytesReaded)

{

    byte[] buffer = new byte[bytesToRead];



    IntPtr ptrBytesReaded;

    ProcessMemoryReaderApi.ReadProcessMemory(m_hProcess,MemoryAddress,buffer,

                                             bytesToRead,out ptrBytesReaded);



    bytesReaded = ptrBytesReaded.ToInt32();



    return buffer;



}

這個函數以所請求的大小聲明一個字節數組,并使用API讀取內存。就這么簡單!



最后,下面這個方法關閉了進程。





public void CloseHandle()



{

    int iRetValue;

    iRetValue = ProcessMemoryReaderApi.CloseHandle(m_hProcess);

    if (iRetValue == 0)

        throw new Exception("CloseHandle failed");



}





第三步 – 使用類

    現在輪到了有趣的部分。使用這個類就是為了讀取“掃雷”的內存并揭開布雷圖。要使用類,需要先對其進行初始化:

ProcessMemoryReaderLib.ProcessMemoryReader pReader

                   = new ProcessMemoryReaderLib.ProcessMemoryReader();

   接著,必須設置你想要讀取其內存的進程。以下是如何獲得“掃雷”進程的例子,這個進程一旦被裝入,就被設置為ReadProcess屬性:

System.Diagnostics.Process[] myProcesses

                   = System.Diagnostics.Process.GetProcessesByName("winmine");

pReader.ReadProcess = myProcesses[0];



    我們現在需要做的是:打開進程,讀取內存,并在完成后關閉它。下面還是有關操作的例子,它讀取代表布雷圖寬度的地址。

pReader.OpenProcess();



int iWidth;

byte[] memory;

memory = pReader.ReadProcessMemory((IntPtr)0x1005334,1,out bytesReaded);

iWidth = memory[0];



pReader.CloseHandle();

   簡單吧!



     在結論部分,我列出了顯示布雷圖的完整代碼。別忘了,我要訪問的所有內存位置就是在本文第一部分中所找到位置。

// 布雷圖的資料管理器

System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(Form1));



ProcessMemoryReaderLib.ProcessMemoryReader pReader

                   = new ProcessMemoryReaderLib.ProcessMemoryReader();



System.Diagnostics.Process[] myProcesses

               = System.Diagnostics.Process.GetProcessesByName("winmine");



// 獲得“掃雷”進程的第一個實列

if (myProcesses.Length == 0)

{

    MessageBox.Show("No MineSweeper process found!");

    return;

}

pReader.ReadProcess = myProcesses[0];



// 以讀內存模式打開進程

pReader.OpenProcess();



int bytesReaded;

int iWidth, iHeight, iMines;

int iIsMine;

int iCellAddress;

byte[] memory;



memory = pReader.ReadProcessMemory((IntPtr)0x1005334,1,out bytesReaded);

iWidth = memory[0];

txtWidth.Text = iWidth.ToString();



memory = pReader.ReadProcessMemory((IntPtr)0x1005338,1,out bytesReaded);

iHeight = memory[0];

txtHeight.Text = iHeight.ToString();



memory = pReader.ReadProcessMemory((IntPtr)0x1005330,1,out bytesReaded);

iMines = memory[0];

txtMines.Text = iMines.ToString();



// 刪除以前的按鈕數組

this.Controls.Clear();

this.Controls.AddRange(MainControls);



// 創建一個按鈕數組, 用于畫出布雷圖的每一格

ButtonArray = new System.Windows.Forms.Button[iWidth,iHeight];



int x,y;

for (y=0 ; y<iHeight ; y++)

    for (x=0 ; x<iWidth ; x++)

    {

        ButtonArray[x,y] = new System.Windows.Forms.Button();

        ButtonArray[x,y].Location = new System.Drawing.Point(20 + x*16, 70 + y*16);

        ButtonArray[x,y].Name = "";

        ButtonArray[x,y].Size = new System.Drawing.Size(16,16);



        iCellAddress = (0x1005340) + (32 * (y+1)) + (x+1);

        memory = pReader.ReadProcessMemory((IntPtr)iCellAddress,1,out bytesReaded);

        iIsMine = memory[0];



        if (iIsMine == 0x8f)//如果有雷,則畫出地雷位圖

            ButtonArray[x,y].Image = ((System.Drawing.Bitmap)

                                     (resources.GetObject("button1.Image")));



        this.Controls.Add(ButtonArray[x,y]);

    }



// 關閉進程句柄

pReader.CloseHandle();

就是這些,希望你能學到新的東西。

溫馨提示:喜歡本站的話,請收藏一下本站!

本類教程下載

系統下載排行

網站地圖xml | 網站地圖html
主站蜘蛛池模板: 吕梁市| 静海县| 饶平县| 沐川县| 壶关县| 浦北县| 嘉义县| 青海省| 郯城县| 高平市| 竹山县| 平和县| 牙克石市| 鄂伦春自治旗| 察隅县| 大足县| 四子王旗| 灵山县| 开远市| 成武县| 岚皋县| 靖安县| 迭部县| 南江县| 静海县| 宁蒗| 陈巴尔虎旗| 古丈县| 夹江县| 永登县| 石阡县| 宿州市| 确山县| 明溪县| 瓦房店市| 杭州市| 衡东县| 梅河口市| 文水县| 惠安县| 仁寿县|