这两天一直在玩换装游戏《闪耀暖暖》,其中的联盟玩法里面,有个入夜派对,可以获取一定量的联盟币,但是这个活动是定时的,而且赶在晚饭的时候(7:10),所以我做了这个脚本,定时参加这个活动。
下面简单讲一下主要的思路和代码。
一、思路
因为这个是手游,创建手游的脚本很麻烦,要先去破解游戏,然后还要运行的手机都有root权限才可以。所以这里另辟蹊径,在电脑上面的模拟器中来进行。因为是电脑脚本,所以这方面主要流行的语言是c++,易语言,python等等。c++是万能的,所以我选择了 c++和MFC框架来实现这个脚本。主要用到的API是一些鼠标键盘的函数,也是都挺简单的。
二、代码
首先在visual studio里面创建一个mfc程序,对话框的形式就可以:
然后打开资源列表,添加一个按键:
(请忽略右边预览界面我已经添加好的一堆按键)
然后把按键的captain也就是显示出来的字,改成“进入联盟”(当然这个名字随便取的),然后在它的消息处理的界面,添加一个BN_CLICKED的消息处理函数:
然后就是整个脚本流程的编写。
首先找到模拟器的窗口,这个任务由FindWindow函数来完成:
CWnd* cwnd = CWnd::FindWindow(L"Qt5QWindowIcon", NULL);
这个函数的第一个参数为窗口的类名,窗口的类名可以使用visual studio自带的spy++工具来查看,这里不细说。
第二个参数为窗口的名字。
这里我只传入了窗口的类名,名字直接置空。(可以看出这个模拟器是用qt开发的)
然后根据返回的窗口指针来判断一下窗口是否存在,如果存在,则继续操作:
if (cwnd) {
...
}
else
{
AfxMessageBox(L"没找到这样的窗口");
}
return 0;
下一步,得到窗口的位置:
RECT windowRect;
::GetWindowRect(cwnd->GetSafeHwnd(), (LPRECT)&windowRect);
因为窗口是矩形的,所有将窗口的位置都保存在一个RECT结构里面。
然后将窗口置顶:
::SetForegroundWindow(cwnd->GetSafeHwnd());
很简单,用窗口的句柄就可以将这个窗口置顶啦。
下面开始进入联盟,首先找到一个像素点,根据进入之前和进入之后的不同颜色,来判断是否已经进入联盟:
//像素点224 ,140 ,157
COLORREF ref = GetPixel(GetDC(NULL), windowRect.left + 317, windowRect.top + 375);
//创建一个循环,没进去就一直点击联盟图标
while (GetRValue(ref) != 224) {
POINT currentPos;
GetCursorPos(¤tPos);
singleClick(windowRect.left + 494, windowRect.top + 479);
SetCursorPos(currentPos.x, currentPos.y);
Sleep(2500);
ref = GetPixel(GetDC(NULL), windowRect.left + 317, windowRect.top + 375);
}
上面主要用到的API是GetPixel,GetCursurPos,SetCursurPos,他们分别是用来获取像素点,获取当前鼠标的位置,设置鼠标的位置。上面有个函数singleClick,明眼人一眼可以看出这是不符合微软函数命名规范的,微软一直是用Pascal命名规范,即每个单词的首字母大写,我这里用的是驼峰命名,这个函数也是我自己创建的,用来单击屏幕的某处,mfc的框架里面是没有这个函数的,它的定义如下:
BOOL singleClick(int positionX, int positionY) {
INPUT input;
input.type = INPUT_MOUSE;
input.mi.dx = static_cast<long>(65535.0f / (GetSystemMetrics(SM_CXSCREEN) - 1) * positionX);
input.mi.dy = static_cast<long>(65535.0f / (GetSystemMetrics(SM_CYSCREEN) - 1) * positionY);
input.mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE | MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP;
UINT result = SendInput(1, &input, sizeof(INPUT));
return result == 0;
}
以前的老的程序都用mouseEvent和keboardEvent这两个函数来产生键盘鼠标消息,而现在微软推荐的做法是SendInput函数。(当然这个函数也是符合微软命名规范的),其中比较难的地方就是要根据系统鼠标的特性来计算实际上鼠标要滑动多少的距离。其他都是比较简单的概念。
进入了联盟之后,进行一个时间的判断,如果是到了7点10分,才进行下一步的操作:
CTime time = CTime::GetCurrentTime();
while (time.GetHour() != 19 || time.GetMinute() != 10) {
TRACE("现在的时间是%d : %d\n", time.GetHour(), time.GetMinute());
time = CTime::GetCurrentTime();
Sleep(300);
}
这里主要用了一个CTime类来获取系统时间,然后再用一个循环,来判断是否到了规定的事件,到了则进行下一步。
然后下面的基本差不多,还是根据某一点的像素是否改变来判断操作是否完成,下面是点开入夜派对的活动入口:
ref = GetPixel(GetDC(NULL), windowRect.left + 363, windowRect.top + 375);
//点开入夜排队的入口
while (GetRValue(ref) != 76) {
TRACE("这个地方的像素点的分量为:%d ,%d ,%d\n", GetRValue(ref), GetGValue(ref), GetBValue(ref));
POINT currentPos;
GetCursorPos(¤tPos);
singleClick(windowRect.left + 328, windowRect.top + 752);
SetCursorPos(currentPos.x, currentPos.y);
Sleep(4000);
ref = GetPixel(GetDC(NULL), windowRect.left + 317, windowRect.top + 375);
}
然后点击参加:
//参加入夜派对
ref = GetPixel(GetDC(NULL), windowRect.left + 317, windowRect.top + 375);
while (GetRValue(ref) == 76) {
POINT currentPos;
GetCursorPos(¤tPos);
singleClick(windowRect.left + 282, windowRect.top + 650);
SetCursorPos(currentPos.x, currentPos.y);
Sleep(3000);
ref = GetPixel(GetDC(NULL), windowRect.left + 317, windowRect.top + 375);
}
进入之后,点击输入框:
//输入框像素点239 ,239 ,248
ref = GetPixel(::GetDC(NULL), windowRect.left + 314, windowRect.top + 975);
while (GetRValue(ref) == 239) {
POINT currentPos;
GetCursorPos(¤tPos);
singleClick(windowRect.left + 314, windowRect.top + 975);
SetCursorPos(currentPos.x, currentPos.y);
Sleep(3000);
ref = GetPixel(::GetDC(NULL), windowRect.left + 314, windowRect.top + 975);
}
上面这三段代码的API和前面基本相同,所以不多说。
这里又来到另一个难点,就是如何输入文字,如果用键盘鼠标消息的话,太繁琐,所幸前面讲到的SendInput函数在这里也是可以用到的,c++里面的输入比较复杂,要分成ASCII输入,和宽字节的Unicode输入,所以分别创建两个函数来进行输入,然后统一成一个API:
void sendASCII(wchar_t data, BOOL shift) {
INPUT input[2];
memset(input, 0, 2 * sizeof(INPUT));
if (shift) {
input[0].type = INPUT_KEYBOARD;
input[0].ki.wVk = VK_SHIFT;
SendInput(1, input, sizeof(INPUT));
}
input[0].type = INPUT_KEYBOARD;
input[0].ki.wVk = data;
input[1].type = INPUT_KEYBOARD;
input[1].ki.wVk = data;
input[1].ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(2, input, sizeof(INPUT));
if (shift)
{
input[0].type = INPUT_KEYBOARD;
input[0].ki.wVk = VK_SHIFT;
input[0].ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(1, input, sizeof(INPUT));
}
}
void SendUnicode(wchar_t data)
{
INPUT input[2];
memset(input, 0, 2 * sizeof(INPUT));
input[0].type = INPUT_KEYBOARD;
input[0].ki.wVk = 0;
input[0].ki.wScan = data;
input[0].ki.dwFlags = 0x4;//KEYEVENTF_UNICODE;
input[1].type = INPUT_KEYBOARD;
input[1].ki.wVk = 0;
input[1].ki.wScan = data;
input[1].ki.dwFlags = KEYEVENTF_KEYUP | 0x4;//KEYEVENTF_UNICODE;
SendInput(2, input, sizeof(INPUT));
}
void SendKeys(CString msg)
{
short vk;
BOOL shift;
USES_CONVERSION;
wchar_t* data = T2W(msg.GetBuffer(0));
int len = wcslen(data);
for (int i = 0; i < len; i++)
{
if (data[i] >= 0 && data[i] < 256) //ascii??
{
vk = VkKeyScanW(data[i]);
if (vk == -1)
{
SendUnicode(data[i]);
}
else
{
if (vk < 0)
{
vk = ~vk + 0x1;
}
shift = vk >> 8 & 0x1;
if (GetKeyState(VK_CAPITAL) & 0x1)
{
if (data[i] >= 'a' && data[i] <= 'z' || data[i] >= 'A' && data[i] <= 'Z')
{
shift = !shift;
}
}
sendASCII(vk & 0xFF, shift);
}
}
else //unicode??
{
SendUnicode(data[i]);
}
}
}
如上,我将两种输入方式统一成了sendKeys这个函数。
因为上面已经点击了输入框,这时候输入框已经弹出来了,所以这个时候调用sendKeys来输入打卡两个字:
SendKeys(L"打卡");
然后再用老办法点击确定即可:
ref = GetPixel(::GetDC(NULL), windowRect.left + 314, windowRect.top + 975);
//白色255,255,255
while (GetRValue(ref) == 255) {
POINT currentPos;
GetCursorPos(¤tPos);
singleClick(windowRect.left + 490, windowRect.top + 970);
SetCursorPos(currentPos.x, currentPos.y);
Sleep(1000);
ref = GetPixel(::GetDC(NULL), windowRect.left + 314, windowRect.top + 975);
}
for (int i = 0; i < 3; i ++) {
POINT currentPos;
GetCursorPos(¤tPos);
singleClick(windowRect.left + 490, windowRect.top + 970);
SetCursorPos(currentPos.x, currentPos.y);
Sleep(3000);
}
用这种方法来参加入夜派对即可领取所有奖励。
最后我优化了一下,因为这是个耗时的操作,所以我另外开启了一条线程来执行这个任务:
UINT nightParty(LPVOID param) ;
AfxBeginThread(nightParty,NULL);
演示视频本来我打算上传到b站的,但是b站直接给我退回了(╯‘□′)╯(┴—┴,所以想看演示视频可以到我的qq空间看。