玩游戏的时候难免会遇到一些游戏的 "折磨"。例如:我们要使用道具的时候,可这个道具居然没有批量使用!!!
那行吧,我们就来动手做一个按键精灵解放我们的双手。
PS:目前只做了鼠标按键版的,如果有需要键盘的,可以私信或下方留言,后续看需补充吧~
①. 首先是Win32的框架(这里我就直接套用过来了,不懂可以看下我之前的文章哈~)
//++++++++++++++++++++++++++++++++++
// 宏定义
//----------------------------------
#ifndef UNICODE
#define UNICODE // 使用UNICODE编码,如果在编译器设置了使用UNICODE字符集此处可免
#endif
#ifndef _UNICODE
#define _UNICODE // 使用UNICODE编码,如果在编译器设置了使用UNICODE字符集此处可免
#endif
//++++++++++++++++++++++++++++++++++
// 头文件
//----------------------------------
#include <windows.h> // Win32程序最重要的头文件
#include <tchar.h> // 兼容字符集头文件
#include "VibraClick.h" // 鼠标模拟器头文件
//++++++++++++++++++++++++++++++++++
// 全局变量
//----------------------------------
TCHAR g_lpszClassName[] = _T("VibraClick"); // 窗口类的名称
TCHAR g_lpszWindowName[] = _T("VibraClick"); // 窗口的名称,(也就是窗口的标题)
VibraClick g_vibraClick; // 鼠标模拟器
//++++++++++++++++++++++++++++++++++
// 函数声明
//----------------------------------
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // 窗口消息处理过程
VOID VibraClick_Init(HWND); // 初始化软件
//++++++++++++++++++++++++++++++++++
// 游戏主函数
//----------------------------------
INT APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, INT nCmdShow)
{
/* 1.设计一个窗口类 */
...
/* 2.注册窗口类 */
...
/* 3.创建窗口, 并居中显示 */
...
/* 4.更新显示窗口 */
...
/* 初始化 */
VibraClick_Init(hWnd);
/* 5.消息循环 */
...
return msg.wParam;
}
//++++++++++++++++++++++++++++++++++
// 窗口消息处理过程
//----------------------------------
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_HOTKEY:
g_vibraClick.OnHotKey(wParam);
break;
case WM_DESTROY:
// 窗口销毁,释放资源
::PostQuitMessage(0);
break;
default:
return ::DefWindowProc(hWnd, message, wParam, lParam);
}
return ((LRESULT)0);
}
VOID VibraClick_Init(HWND hWnd)
{
g_vibraClick.Init(hWnd);
}
1. 定义了一个按键模拟器类(VibraClick) 的变量 g_vibraClick
2. 初始化这个按键模拟器
3. 由于注册了热键 CTRL + S 和 CTRL + R 进行录制和运行,所以消息处理了WM_HOTKEY
②. VibraClick(按键模拟器类)
VibraClick.h
#pragma once
#ifndef __VIBRA_CLICK_H__
#define __VIBRA_CLICK_H__
#include <Windows.h>
#include <vector>
// 参照 INPUT 类
struct MouseRecInput {
DWORD type;
MOUSEINPUT mi;
};
// 定时器回调
VOID CALLBACK TimerProc(HWND hwnd, UINT message, UINT nIDEvent, DWORD dwTime);
// 鼠标Hook消息处理
LRESULT CALLBACK MouseMessageProc(INT nCode, WPARAM wParam, LPARAM lParam);
class VibraClick
{
using MouseRecInputVector = std::vector<MouseRecInput *>;
public:
VibraClick();
~VibraClick();
public:
void Init(HWND hWnd); // 初始化
void StartMouseRec(); // 开始录制鼠标操作
void StopMouseRec(); // 停止录制鼠标操作
void StartRunMouseRec(); // 开始运行鼠标录制的内容
void StopRunMouseRec(); // 停止运行鼠标录制的内容
void CleanMouseRecInput(); // 清除鼠标记录内容
void OnHotKey(WPARAM nHotKeyId); // 热键处理
public:
static VibraClick *GetInstance() { return _inst; }
bool IsStartMouseRec() { return m_IsStartMouseRec; }
bool IsStartRunMouseRec() { return m_IsStartRunMouseRec; }
HHOOK GetHHMouseHook() { return m_hhMouseHook; }
MouseRecInputVector &GetMouseRecInputVector() { return m_vecMouseRecInput; }
int GetMouseRecIndex() { return m_MouseRecIndex; }
void SetMouseRecIndex(int value) { m_MouseRecIndex = value; }
protected:
static VibraClick *_inst; // 实例自己
HWND m_hWnd; // 窗口句柄
MouseRecInputVector m_vecMouseRecInput; // 鼠标操作数据
bool m_IsStartMouseRec; // 是否开始记录鼠标操作
bool m_IsStartRunMouseRec; // 是否开始鼠标操作
int m_MouseRecIndex; // 鼠标操作索引
UINT_PTR m_RunTimerId; // 运行的定时器Id
ATOM m_VibraClick_StartRec; // 开始记录热键id
ATOM m_VibraClick_RunRec; // 开始运行记录热键id
// 鼠标的Hook
HHOOK m_hhMouseHook;
};
#endif // !__VIBRA_CLICK_H__
INPUT 结构体(WinUser.h头文件中)
typedef struct tagINPUT {
DWORD type;
union
{
MOUSEINPUT mi;
KEYBDINPUT ki;
HARDWAREINPUT hi;
} DUMMYUNIONNAME;
} INPUT, *PINPUT, FAR* LPINPUT;
简单讲解上面的内容:
1. MouseRecInput结构体,具体参照 INPUT 结构体。因为MOUSEINPUT是在共用体内,所以对其进行一个扩展
2. TimerProc是一个定时器的处理,用在运行按键模拟的时候
3. MouseMessageProc这个一个鼠标的录制Hook处理
4. 类的方法和成员变量都有对应的注释就不详细说明了哈~(不懂的话在下方留言吧,到时候再进行补充)
VibraClick.cpp
#include "VibraClick.h"
#include <tchar.h>
VibraClick *VibraClick::_inst = nullptr;
VibraClick::VibraClick()
{
// 初始化一些变量
m_IsStartMouseRec = false;
m_IsStartRunMouseRec = false;
m_MouseRecIndex = 0;
m_RunTimerId = -1;
m_hhMouseHook = NULL;
m_hWnd = NULL;
_inst = this;
}
VibraClick::~VibraClick()
{
// 清除鼠标记录内容
CleanMouseRecInput();
StopMouseRec();
StopRunMouseRec();
// 反注册热键
UnregisterHotKey(this->m_hWnd, m_VibraClick_StartRec); // Ctrl + S
UnregisterHotKey(this->m_hWnd, m_VibraClick_RunRec); // Ctrl + R
}
void VibraClick::Init(HWND hWnd)
{
m_hWnd = hWnd;
m_VibraClick_StartRec = GlobalAddAtom(_T("VibraClick_StartRec")) - 0xC000;
m_VibraClick_RunRec = GlobalAddAtom(_T("VibraClick_RunRec")) - 0xC000;
RegisterHotKey(this->m_hWnd, m_VibraClick_StartRec, MOD_CONTROL, 'S'); // Ctrl + S
RegisterHotKey(this->m_hWnd, m_VibraClick_RunRec, MOD_CONTROL, 'R'); // Ctrl + R
}
void VibraClick::StartMouseRec()
{
// 清除鼠标记录内容
CleanMouseRecInput();
// 设置当前为录制鼠标操作状态
m_IsStartMouseRec = true;
// 开始鼠标Hook(全局鼠标钩子)
m_hhMouseHook = SetWindowsHookEx(WH_MOUSE_LL, &MouseMessageProc, GetModuleHandle(NULL), NULL);
}
void VibraClick::StopMouseRec()
{
// 取消设置当前为录制鼠标操作状态
m_IsStartMouseRec = false;
// 释放鼠标Hook
if (m_hhMouseHook != NULL)
UnhookWindowsHookEx(m_hhMouseHook);
}
void VibraClick::StartRunMouseRec()
{
// 开始模拟鼠标操作
m_IsStartRunMouseRec = true;
// 设置运行索引
m_MouseRecIndex = 0;
// 开启定时器
m_RunTimerId = SetTimer(m_hWnd, 999, 10, &TimerProc);
}
void VibraClick::StopRunMouseRec()
{
// 停止运行鼠标记录的内容
m_IsStartRunMouseRec = false;
// 关闭定时器
if (m_RunTimerId != -1)
{
KillTimer(m_hWnd, m_RunTimerId);
m_RunTimerId = -1;
}
}
void VibraClick::CleanMouseRecInput()
{
// 清除记录的内容
for (auto input : m_vecMouseRecInput)
{
delete input;
input = nullptr;
}
// 释放vector占用内存
MouseRecInputVector tmp;
m_vecMouseRecInput.swap(tmp);
// 设置运行索引
m_MouseRecIndex = 0;
}
void VibraClick::OnHotKey(WPARAM nHotKeyId)
{
if (nHotKeyId == m_VibraClick_StartRec)
{
if (m_IsStartMouseRec)
StopMouseRec();
else
StartMouseRec();
}
else if (nHotKeyId == m_VibraClick_RunRec)
{
if (m_IsStartRunMouseRec)
StopRunMouseRec();
else
StartRunMouseRec();
}
}
VOID CALLBACK TimerProc(HWND hwnd, UINT message, UINT nIDEvent, DWORD dwTime)
{
switch (nIDEvent)
{
case 999:
{
// 取余方式进行循环运行
int MouseRecIndex = VibraClick::GetInstance()->GetMouseRecIndex();
MouseRecIndex %= VibraClick::GetInstance()->GetMouseRecInputVector().size();
// 读取当前索引的鼠标模拟消息
auto pMHD = VibraClick::GetInstance()->GetMouseRecInputVector()[MouseRecIndex++];
// 通过INPUT进行模拟操作
INPUT Input;
Input.type = pMHD->type;
memcpy((void *)&Input.mi, (void *)&pMHD->mi, sizeof(MOUSEINPUT));
// 发送模拟消息
SendInput(1, &Input, sizeof(INPUT));
VibraClick::GetInstance()->SetMouseRecIndex(MouseRecIndex);
}
break;
}
}
LRESULT CALLBACK MouseMessageProc(INT nCode, WPARAM wParam, LPARAM lParam)
{
PMSLLHOOKSTRUCT pStruct = (PMSLLHOOKSTRUCT)lParam;
// LLMHF_INJECTED标志着: 事件是否被注入,通过SendInput后会触发这个标志,也就是模拟的处理消息则不记录了
// nCode 表示有关Hook的消息
/*
* Hook Codes
*
* #define HC_ACTION 0
* #define HC_GETNEXT 1
* #define HC_SKIP 2
* #define HC_NOREMOVE 3
* #define HC_NOREM HC_NOREMOVE
* #define HC_SYSMODALON 4
* #define HC_SYSMODALOFF 5
*/
if (nCode < 0 || pStruct->flags & LLMHF_INJECTED)
{
return CallNextHookEx(VibraClick::GetInstance()->GetHHMouseHook(), nCode, wParam, lParam);
}
// 是否开启录制鼠标操作
if (!VibraClick::GetInstance()->IsStartMouseRec())
return CallNextHookEx(VibraClick::GetInstance()->GetHHMouseHook(), nCode, wParam, lParam);
// 判断是否为鼠标数据, 当前只是列举一部分的鼠标消息,有需要可以自己加哈~
if (
wParam == WM_LBUTTONDOWN ||
wParam == WM_LBUTTONUP ||
wParam == WM_RBUTTONDOWN ||
wParam == WM_RBUTTONUP ||
wParam == WM_MBUTTONDOWN ||
wParam == WM_MBUTTONUP ||
wParam == WM_MOUSEMOVE)
{
MouseRecInput *Input = new MouseRecInput;
// 现在固定为鼠标的模拟输入
Input->type = INPUT_MOUSE;
// 设置输入的数据
Input->mi.dx = pStruct->pt.x;
Input->mi.dy = pStruct->pt.y;
Input->mi.mouseData = pStruct->mouseData;
Input->mi.time = pStruct->time;
Input->mi.dwExtraInfo = pStruct->dwExtraInfo;
switch (wParam)
{
case WM_LBUTTONDOWN:
Input->mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
break;
case WM_LBUTTONUP:
Input->mi.dwFlags = MOUSEEVENTF_LEFTUP;
break;
case WM_RBUTTONDOWN:
Input->mi.dwFlags = MOUSEEVENTF_RIGHTDOWN;
break;
case WM_RBUTTONUP:
Input->mi.dwFlags = MOUSEEVENTF_RIGHTUP;
break;
case WM_MBUTTONDOWN:
Input->mi.dwFlags = MOUSEEVENTF_MIDDLEDOWN;
break;
case WM_MBUTTONUP:
Input->mi.dwFlags = MOUSEEVENTF_MIDDLEUP;
break;
case WM_MOUSEMOVE:
{
int cx_screen = ::GetSystemMetrics(SM_CXSCREEN);
int cy_screen = ::GetSystemMetrics(SM_CYSCREEN);
Input->mi.dx = pStruct->pt.x * 65536 / cx_screen;
Input->mi.dy = pStruct->pt.y * 65536 / cy_screen;
Input->mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE;
}
break;
}
VibraClick::GetInstance()->GetMouseRecInputVector().push_back(Input);
}
return CallNextHookEx(VibraClick::GetInstance()->GetHHMouseHook(), nCode, wParam, lParam);
}
简单讲解上面的内容:
1. 对静态的_inst进行一个初始化,做这个主要是在Hook和定时器中进行使用到这个按键模拟器
2. 初始化的时候进行保存窗口的句柄,然后进行注册热键和定时器的使用
3. 讲一下这个HOOK的MOUSEMOVE,鼠标点击的x和y坐标需要转换到绝对位置,屏幕的全屏范围是 0~65535,所以需要用当前的电脑分辨率进行转换到绝对的位置
最后PS:这个按键模拟器没有按钮,所以目前只能靠热键进行模拟
分别是:
CTRL + S 开启和关闭录制
CTRL + R 运行模拟
后续有需求可以下方留言,到时候在补充吧~~~
源码:已上传到Github了哦,有兴趣的读者可以去下载了~