最近看了鬱金香老師的《VC++外掛編程》系列視頻,試著按這個思路寫了一個筆者自己的連連看外掛。實驗了一下,比較成功,已經從0分刷到了近20000分。下面來分享一下經驗。
首先說明一下需要用到的工具:
1、 CE(cheat engine),筆者用的是cheat engine6.2,中文補丁。
2、 VC++ 6.0
好了,廢話不多說,開始詳細講解整個製作過程。首先開啟QQ遊戲用戶端,選擇QQ連連看並選好座位。
開啟CE,這是如果不出意外會彈出QQ遊戲崩潰發送錯誤報表的提示。筆者從網上尋找資料得知,QQgame啟動了某個線程來檢測CE,所以只要檢測到CE開啟了,即便你什麼都沒做,QQ遊戲也會崩潰而推出。
既然知道原因了,那就有對策。過檢測的方法就不講了,這邊引用一篇看雪的文章,有詳細解釋。地址:http://bbs.pediy.com/showthread.php?t=147811&highlight。
要是閑麻煩也沒關係,筆者根據上面這篇文章寫的一個小工具,可以在開啟QQ遊戲的時候直接幹掉這個檢測線程。網盤:http://vdisk.weibo.com/s/bhIhB。
運行如果提示成功,那接下來就可以放心運行CE了。
遊戲分析部分
我們首先是要找出棋盤的記憶體位址。記憶體中存放棋盤的方法可以假定為,如果該格子上有圖片,那麼資料是大於0的某個值;而如果該格子上沒有圖片,那麼資料應該是等於0。
我們先來驗證一下以上假設是否正確。筆者的方法是尋找棋盤最左上方的那個格子。具體過程,開啟CE,點選擇進程,選擇連連看進程。
在scan type處選擇ExactValue,Value type處選擇Byte。最後,在Value處填入0(在遊戲還沒開局前,這個格子對應的記憶體資料應該是0,表示該格子沒有圖片)。其它設定預設即可。填寫完後如下:
然後點擊FirstScan,第一次搜尋完後會發現記憶體中為0的資料成千上萬。沒關係,繼續搜尋即可。
點擊連連看的練習,視棋盤左上方的格子而定,如果格子上有圖片,則在scan type處選擇Bigger Than,然後在Value處填入0,點擊Next Scan;如果棋盤左上方的格子沒有圖片,則在scan type處選擇Exact Value,然後在Value處填入0,點擊Next Scan。
搜尋完一次之後,再點一下練習。一直重複上面步驟,最後會發現有5個值是一直符合上述情況的:
(5個可能會有出入,具體是當時情況而定)
這幾個值並非都是我們想要的。關閉連連看,再開啟連連看,重新在CE載入連連看進程,點擊練習,這是會發現又有幾個值和情況不符合了,直接在地址出把他們刪掉。
,第二個和第三個已經已經不符和了,刪掉即可。
再多按幾次練習切換遊戲棋盤資料,最終只留下一個正確的數值,只有第一個地址是符合的。
我們把這個地址記下來。筆者是64位的系統,記憶體位址可能有所出入。具體視自己找出來的地址為準。
程式編寫部分
有了棋盤的記憶體位址,接下去就可以開始編寫程式了。開啟VC++,建立工程,選擇MFC AppWizard(exe),選擇基於對話方塊的應用程式,接下來全部預設即可。
首先我們要做的是把遊戲棋盤資料從記憶體中讀取出來放入我們自己程式的數組中。在讀取記憶體之前先列舉一下等下要用到的幾個關鍵函數以及他們的作用。
HWND FindWindow(LPCTSTR lpClassName, // 指向視窗類別名(in)LPCTSTR lpWindowName // 指向視窗名稱(in));
我們用這個函數來通過視窗標題名稱獲得視窗的控制代碼
DWORD GetWindowThreadProcessId(HWND hWnd, //視窗控制代碼(in)LPDWORD lpdwProcessId //進程ID(out));
我們用這個函數來通過視窗控制代碼獲得它的進程ID
HANDLE OpenProcess(DWORD fdwAccess, //希望獲得的存取權限標誌(in) //這裡用PROCESS_ALL_ACCESSBOOL fInherit, //是否繼承控制代碼,這裡用FALSE(in)DWORD IDProcess //進程ID(in));
我們用這個函數來通過進程ID來開啟進程
BOOL ReadProcessMemory(HANDLE hProcess, //遠程進程控制代碼(in)LPCVOID lpBaseAddress, //遠程進程中記憶體位址,機上面CE分析出的地址(in)LPVOID lpBuffer, //本地進程中記憶體位址(out)DWORD nSize, //要傳送的位元組數(in)LPDWORD lpNumberOfBytesRead //實際傳送的位元組數(out));
我們用這個函數來遠端存取記憶體並讀取指定位元組數的記憶體
你可能已經發現以上幾個函數是一環扣一環的,即前一個函數的傳回值正好是後一個函數的參數。
然後我們還要用到VC++6.0內建的一個小工具,SPY++來獲得遊戲視窗標題。詳細方法在文章最後講述,獲得的視窗標題為"QQ遊戲 - 連連看角色版"。
清楚了這幾個函數的參數即功能之後,我們就可以開始讀取棋盤的記憶體資料了。
由於本篇不是介紹MFC編程的,所以設計具體編寫的就不講了,下面把輔助製作的幾個關鍵函數講一下。
讀取棋盤資料函數
#define GameCaption "QQ遊戲 - 連連看角色版"#define MAX_X 11 #define MAX_Y 19 BOOL UpdateChessData(){ HWND hgame =::FindWindow(NULL, GameCaption); //獲得遊戲視窗進程ID DWORD pid; GetWindowThreadProcessId(hgame,&pid); //開啟指定進程 HANDLE hndProcess; hndProcess =OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); DWORD bfread; LPVOID nbuffer = (LPVOID)&chessdata; //存放棋盤資料 //MAX_X*MAX_Y = 11*19,通過觀察發現棋盤大小是19*11的,所以應該讀取這麼位元組數 if(!ReadProcessMemory(hndProcess, LPCVOID(0x0018A3BC), nbuffer, MAX_X*MAX_Y,&bfread)) {#ifdef _DEBUG TRACE0("readchessdata failed");#endif return FALSE; } return TRUE;}
有了棋盤資料之後,通過構造一個演算法來找出其中一對能消除的即可。
BOOL ClearPair(POINT&point1, POINT &point2);
這個函數是本函數最關鍵的函數,也是核心演算法。但是由於篇幅關係,這裡不再詳細講述,百度一下連連看消除演算法,都能找到的。
通過上述函數,就能獲得一對可以消除的圖片。
之後我們要做的就是像滑鼠一樣在視窗上消除一對。通過類比滑鼠的移動的移動和點擊是一種方法,但是不太可取。因為我們開輔助的時候要想做自己的事情,滑鼠就不能由程式來控制。所以我們採取第二種方法,向遊戲視窗發送滑鼠訊息。函數如下:
BOOL Click2p(POINT p1,POINT p2){ //點擊p1 HWND hwnd =::FindWindow(NULL, GameCaption); int lparam; //每個小方格的長為35,寬為31 lparam=((p1.y*35+192)<<16)+(p1.x*31+21); //滑鼠按下 ::SendMessage(hwnd,WM_LBUTTONDOWN,0x1,lparam); //滑鼠鬆開 ::SendMessage(hwnd,WM_LBUTTONUP,0x0,lparam); //點擊p2 lparam=((p2.y*35+192)<<16)+(p2.x*31+21); ::SendMessage(hwnd,WM_LBUTTONDOWN,0x1,lparam); ::SendMessage(hwnd,WM_LBUTTONUP,0x0,lparam); return true;}
解釋下SendMessage函數
LRESULT SendMessage(HWND hWnd, //要發送訊息的目標視窗UINT Msg, //訊息類型WPARAM wParam, //指定附加的訊息特定資訊LPARAM lParam //指定附加的訊息特定資訊);
通過spy++可以捕獲訊息的附加訊息資訊,這個在最後介紹捕獲訊息的方法。發現滑鼠按下時,wParam的值為0x1,lParam的高16位是y座標,低16位是x座標。
最後通過一個定時器每隔一定時間觸發一下ClearPair,然後調用Click2P函數,即可實現自動消除方塊了。
附連連看輔助可執行程式(64位版,32位已測試失敗)。
附Spy++使用方法。
1、獲得視窗標題
VC++ 工具 SPY ++。
選擇工具,按住,把滑鼠移到連連看遊戲視窗上,顯示
Caption即我們上面FindWindow函數要用到的第二個參數。選擇Properties,點擊OK後把它複製下來。
2、截獲視窗訊息
前面方法相同,之後選擇Messages即可,messages options 的Messages標籤下選擇
這兩個點擊OK即可。截取到足夠訊息後點擊即可停止。雙擊訊息可以查看訊息的附加資訊(lParam和wParam);