写游戏脚本/辅助的背景
去年找了一款传奇游戏玩,后续发现现在的传奇游戏都是氪金为主,除了活动时间以外大家都是挂机刷混沌神石,然后再通过混沌神石换灵符,灵符又是零氪党快速提升实力的唯一绝经,后面就萌生了用脚本来刷混沌神石的想法。然后就通过"按键精灵"写了一个自动挂机(切图)刷混沌神石的脚本。随着时间的推移,后面又陆陆续续增加了功能,比如固定时间的活动(魔龙宝藏,世界BOSS,行会试炼等)。也用了很长一段时间,中途还在游戏里面跟别人吵架了,被别人开着榜一的号连续24小时追杀了好长一段时间,因为挂机的时候被杀的话就一直躺着,如果是晚上睡觉之后被别人杀了就很伤...然后就继续优化脚本,增加角色死亡检测和死亡自动复活等功能。总之在游戏里面通过脚本跟别人各种斗智斗勇,但是随着时间的推移,游戏里面增加的地图也越来越多,需要用到脚本做的事情也越来越多。最主要是把脚本给别人用,同时还要控制辅助的使用时间(过期不给用)。。。越到后面越发觉得用"按键精灵"写的脚本性能和UI界面(UI界面固定大小,放不下太多东西)不够用,然后今年就用Qt重构了脚本,把脚本的功能都通过Qt来实现。
一、用Qt写游戏脚本/辅助的前提准备工作
- 通过Qt界面来配置游戏参数
毕竟辅助是给别人(小白)使用的,所以界面是必须要有的,用Qt写界面这是基本操作
- 对游戏窗口实现鼠标点击操作
对游戏窗口实现鼠标点击操作最简单的是在屏幕对应的坐标点击鼠标左键/右键。但是大部分玩家都是游戏双开甚至多开,所以就不能直接点击屏幕,而是要实现后台操作(毕竟不可能每个窗口要操作的时候都临时切换到顶层来点击屏幕)。后台操作游戏窗口就离不开窗口句柄,游戏多开的情况下需要精准找到各个游戏的窗口句柄(精准区分大小号),再通过游戏窗口句柄实现相对坐标系统的鼠标点击。
- 对游戏窗口实现按键(键盘)操作
找到窗口句柄后,再通过游戏窗口句柄实现对窗口的按键操作
- 对游戏窗口进行监测
对游戏窗口的数据进行检测和分析,比如人物死亡检测,通常是检测人物血条,也就是对某一坐标的颜色进行抓取和判断。还有就是活动期间的检测,比如某一时间段到活动了,简单一点来就是获取电脑本地时间。
- 对辅助的时间控制
辅助毕竟是给别人用的,如何实现对辅助的时间控制,比如过期后就用不了。最简单的就是设置一个日期,每次运行辅助的时候检测本地时间,到期后就不能用。但是这个有一弊端,电脑本地时间是可以调准的。那就得获取网络时间,并且还要能动态控制时间,比如上服务器动态给辅助设置有效期。还有就是如何精准知道是哪一个用户等。。。
二、用Qt写游戏辅助的实现
- 辅助UI界面的实现



- 找到游戏的所有窗口句柄
找到所有窗口句柄
cpp
// 枚举所有顶层窗口
EnumWindows(EnumWindowsProc, 0);
筛选需要的游戏窗口句柄,并且放到list中
cpp
// 枚举顶层窗口的回调函数
BOOL CALLBACK Widget::EnumWindowsProc(HWND hwnd, LPARAM lParam) {
// 获取窗口标题
int length = GetWindowTextLength(hwnd);
if (length > 0) {
TCHAR *buffer = new TCHAR[length + 1];
GetWindowText(hwnd, buffer, length + 1);
QString windowTitle = QString::fromWCharArray(buffer);
delete[] buffer;
if (windowTitle.startsWith("霸者xx")) {
qDebug() << "hwnd:" << hwnd << ", title:" << windowTitle;
hwndList.append(hwnd);
hwndNameList.append(captureHiddenWindow(hwnd));
}
}
return TRUE;
}
- 通过窗口句柄实现后台鼠标点击
cpp
//后台点击
void Worker::backgroundClick(MyPoint point)
{
if (!IsWindow(hwnd)) {
qDebug() << "backgroundClick: 窗口不存在";
return;
}
LPARAM lParam = MAKELPARAM(point.getX(), point.getY());
PostMessage(hwnd, WM_MOUSEMOVE, 0, lParam);
PostMessage(hwnd, WM_LBUTTONDOWN, MK_LBUTTON, lParam);
PostMessage(hwnd, WM_LBUTTONUP, 0, lParam);
}
- 通过窗口句柄实现后台按键操作
cpp
//后台按键
void Worker::backgroundKeyPress(int keyCode)
{
if (!IsWindow(hwnd)) {
qDebug() << "backgroundKeyPress: 窗口不存在";
return;
}
DWORD dwVKFkeydata; //lParam 参数值
WORD dwScanCode = MapVirtualKey(keyCode, MAPVK_VK_TO_VSC);
dwVKFkeydata = 1;
dwVKFkeydata |= dwScanCode << 16;
dwVKFkeydata |= 0 << 24;
dwVKFkeydata |= 1 << 29;
PostMessage(hwnd, WM_KEYDOWN, keyCode, dwVKFkeydata);
dwVKFkeydata = 1;
dwVKFkeydata |= dwScanCode << 16;
dwVKFkeydata |= 0 << 24;
dwVKFkeydata |= 1 << 29;
dwVKFkeydata |= 3 << 30;
PostMessage(hwnd, WM_KEYUP, keyCode, dwVKFkeydata);
}
- 通过窗口句柄检测目标坐标点的颜色
cpp
/**
* @brief checkPixelColor 根据窗口句柄检查某一坐标是否是某颜色
* @param hwnd 窗口句柄
* @param point 检查的坐标
* @param color 目标颜色
* @return
*/
bool Widget::checkPixelColor(HWND hwnd, QPoint point, QColor color)
{
if (!IsWindow(hwnd)) {
qDebug() << "窗口不存在";
return false;
}
//获取窗口上下文
HDC hdc = GetDC(hwnd);
if (hdc == NULL) {
qDebug() << "hdc获取失败";
return false;
}
COLORREF colorREF = GetPixel(hdc, point.x(), point.y());
//释放窗口上下文
ReleaseDC(hwnd, hdc);
unsigned char red = GetRValue(colorREF);
unsigned char geen = GetGValue(colorREF);
unsigned char blue = GetBValue(colorREF);
qDebug() << "R:" << red << ", G:" << geen << ", B:" << blue;
if ((red > color.red() - 5 && red < color.red() + 5) && (geen > color.green() - 5 && geen < color.green() + 5) && (blue > color.blue() - 5 && blue < color.blue() + 5)) {
return true;
}
return false;
}
三、后话
自己写的辅助给别人用的话,如果还需要控制辅助使用时间,最好是能有一个服务器。没有服务器也能实现,比如自己在某个网页写了一篇文章,在这篇文章里面按一定的格式写上时间,然后辅助启动的时候去访问这个文章的链接,并且获取到所有内容,并且提取出对应格式的内容,然后进行对比时间是否过期...要更新辅助的使用时间那就更新一下文章(修改一下里面的时间)。。。