今天心血来潮,准备写个外挂练练手,当然也是从简单的开始了,就选腾讯连连看下手。
之前整合过2个外部的exe程序,接触了找句柄、读写内存的操作,写游戏外挂找基址是关键,当然离不开CE(不知道CE的直接退下)了。
启动VC6,新建一个简单的基于对话框的工程,捋一捋游戏流程,先写上几个功能函数的空函数,这样的好处是写代码的逻辑跟着游戏流程走,不会乱。先看一下我们都需要什么吧,头文件功能函数如下:
void autoPlay(); //进入游戏后,自动开始游戏 void OnBtnChessdata(); //获取棋盘数据 void dotaReplay(); //被踢重进游戏 BOOL checkDataChange(); //检查棋盘是否发生改变,如果消除未发生改变,则需要使用重排道具 void useProp(); //使用重排道具 bool checkEnd(); //判断当前棋盘是否全部消除 int m_x; //第一个棋子x坐标 int m_y; //第一个棋子y坐标 int m_num; //记录棋盘未消除棋子个数
先把框架写好,一个个的去实现。
首先是开始游戏,获取游戏窗口句柄 -> 移动光标点击开始按钮
开始游戏代码如下,注意相对坐标和绝对坐标。
HWND gameH = ::FindWindow(NULL,gameCaption); if(!gameH) { return ; } CPoint pOld; GetCursorPos(&pOld); //保存移动光标前的鼠标位置 CRect rect; ::GetWindowRect(gameH,&rect); //获取窗口位置 SetCursorPos(rect.left+m_x,rect.top+m_y); //将相对坐标 转换成屏幕绝对坐标 //移动光标,左键按下、左键抬起 mouse_event(MOUSEEVENTF_LEFTDOWN,rect.left+m_x,rect.top+m_y,NULL,NULL); mouse_event(MOUSEEVENTF_LEFTUP,rect.left+m_x,rect.top+m_y,NULL,NULL); //还原光标 SetCursorPos(pOld.x,pOld.y);
游戏开局很轻松实现,那么现在很关键很重要的就是要找整个棋盘的基址,即找左上角第一个棋子的内存位置。用CE工具不断查找,最终获取到棋盘基址是:0x00129F78 //基址每次游戏更新可能会变,这样外挂辅助要要跟着更新了。
读取棋盘数据功能函数如下:棋盘数据最好声明为全局变量或者对话框类的数据成员
//读取棋盘数据 byte chessData[11][19]; / HWND gameH = ::FindWindow(NULL,gameCaption); if(!gameH) { return ; } DWORD processID; //通过窗口句柄,获取进程ID ::GetWindowThreadProcessId(gameH,&processID); //通过进程ID,以所有权限打开进程 HANDLE processH = ::OpenProcess(PROCESS_ALL_ACCESS,false,processID); LPDWORD realSize = 0; LPCVOID pbase = (LPCVOID)0x00129F78; //棋盘基址,即第一个棋子的内存地址 LPVOID buffer = (LPVOID)&chessData; //从基址开始,读取11*19个字节的数据 ::ReadProcessMemory(processH,pbase,buffer,11*19,realSize); ///统计棋盘数据,棋盘大小为19*11 for (int y=0;y<=10;y++) //行 { for (int x=0;x<=18;x++) //列 { if(chessData[y][x]!=0) //统计棋子数 m_num++; } }
成功读取到棋盘所有数据,接下来的流程就是检查棋盘数据,为0的就跳过,数据相同的就移动光标进行模拟点击,每消除一对棋子就重新刷新一次棋盘数据,刷新数据变量m_num 直到棋盘数据数组中所有元素为0,结束循环,游戏结束。代码就不一段段的帖了,想要代码或者程序的在下面链接下载。
http://download.csdn.net/detail/shortcoder/6566501
棋子消除判断实现不理想,本程序思想是判断2棋子相同,即去模拟点击,并未判断2个棋子间是否可消除。
程序中通过在循环中刷新数据,来避免死循环,能实现游戏的一键秒杀,但会有很短暂的卡屏。
判断2棋子是否可消除算法,请期待下一节。
附:秒杀图片: