用途,减少腱鞘炎发生。鼠标坏了可以换,手废了,就别想当码农了。
- 按住副按键1,自动连击鼠标左键
- 单击副按键2,自动按住W键前进,再次单击副按键2,取消按住W键前进
- 滚轮滚动,自动连击F键拾取物品
- 单击键盘R_CTRL+P键,切换工具启动和停止
副按键是特殊按键。我这里的鼠标是 m618 plus。其他鼠标请自行改键码。
使用了 C++20 的 format 库,编译需要vs2019 开 lastest 编译。或者需要vs2022。
或者可以直接屏蔽掉那几句语句,不使用c++20特性。
仅提供完整代码,不提供已编译的程序。
一是不通用,二是都有现成的鼠标宏,不差我这个。
三是想学习原理的才会看这,既然想学习,那肯定有基础了。
编译后生成后,需要使用 管理员权限启动 才能发送按键到游戏中。
这是因为windows的限制,低等级程序不允许对高级别程序发送消息。
/*
* 用于原神的PC的工具
* 按住副按键1,自动连击鼠标左键
* 单击副按键2,自动按住W键前进,再次单击副按键2,取消按住W键前进
* 滚轮滚动,自动连击F键拾取物品
* 单击键盘R_CTRL+P键,切换工具启动和停止
*
* 用于减少腱鞘炎的发生概率。
*/
#include <Windows.h>
#include <iostream>
#include <format>
#include <string>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
HHOOK keyboardHook = 0;
HHOOK mouseHook = 0;
bool need_quit = false;
bool do_auto_left_click = false;
int n_auto_f_repeat = false;
bool do_auto_w_press = false;
bool is_pause = true;
// 简单的多线程日志打印工具,避免命令行无法打印字符时导致死锁
mutex log_mutex;
list<string> log_queue;
void add_log_str(const string s)
{
unique_lock<mutex>(log_mutex);
while (log_queue.size() > 10)
log_queue.pop_front();
log_queue.push_back(s);
}
bool get_log_str(string& s)
{
if (log_queue.size() == 0)
return false;
unique_lock<mutex>(log_mutex);
s = log_queue.front();
log_queue.pop_front();
return true;
}
void log_run()
{
while (!need_quit)
{
string s;
if (get_log_str(s))
{
cout << s << endl;
}
else
{
Sleep(500);
}
}
}
// -------------------------------------------------
/*
* 对指定数值进行一些随机偏移
*/
int get_random_offset(int d1, int of1)
{
float percent = rand() / (float)RAND_MAX;
percent = percent * 2 - 1;
d1 = d1 + of1 * percent;
return d1;
}
/*
* 自动连击左键线程体
*/
void auto_left_click_run()
{
while (!need_quit)
{
if (is_pause)
{
Sleep(500);
do_auto_left_click = false;
continue;
}
if (do_auto_left_click)
{
//cout << "Send left click." << endl;
add_log_str("Send left click.");
INPUT Input ={ 0 };
Input.type = INPUT_MOUSE;
Input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN; // 模拟按下
SendInput(1, &Input, sizeof(INPUT));
Sleep(get_random_offset(70, 30));
Input.mi.dwFlags = MOUSEEVENTF_LEFTUP; // 模拟弹起
SendInput(1, &Input, sizeof(INPUT));
Sleep(get_random_offset(70, 30));
}
else
{
Sleep(100);
}
}
}
/*
* 自动连击F键线程体
*/
void auto_press_f_run()
{
while (!need_quit)
{
if (is_pause)
{
Sleep(500);
n_auto_f_repeat = 0;
continue;
}
// 限制最大连按3次。
n_auto_f_repeat = min(n_auto_f_repeat, 3);
if (n_auto_f_repeat > 0)
{
n_auto_f_repeat -= 1;
//cout << "Send F key." << endl;
add_log_str("Send F key.");
INPUT Input ={ 0 };
Input.type = INPUT_KEYBOARD;
Input.ki.wVk = 'F';
Input.ki.wScan = VkKeyScanA('F');
//Input.ki.dwFlags = KEYEVENTF_SCANCODE;
Input.ki.dwFlags = 0;
SendInput(1, &Input, sizeof(INPUT));
Sleep(get_random_offset(70, 30));
//Input.ki.dwFlags = KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP;
Input.ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(1, &Input, sizeof(INPUT));
Sleep(get_random_offset(70, 30));
}
else
{
Sleep(100);
}
}
}
/*
* 自动按住W键线程体
*/
void auto_toggle_press_w_run()
{
while (!need_quit)
{
if (is_pause)
{
Sleep(500);
do_auto_w_press = false;
continue;
}
if (do_auto_w_press)
{
do_auto_w_press = false;
INPUT Input ={ 0 };
Input.type = INPUT_KEYBOARD;
Input.ki.wVk = 'W';
Input.ki.wScan = VkKeyScanA('F');
if (GetAsyncKeyState('W') & 0x8000)
{
//cout << "Send W up key." << endl;
add_log_str("Send W up key.");
Input.ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(1, &Input, sizeof(INPUT));
}
else
{
//cout << "Send W down key." << endl;
add_log_str("Send W down key.");
Input.ki.dwFlags = 0;
}
SendInput(1, &Input, sizeof(INPUT));
Sleep(get_random_offset(70, 30));
}
else
{
Sleep(100);
}
}
}
LRESULT CALLBACK KbHookCallback(int code, WPARAM wParam, LPARAM lParam)
{
KBDLLHOOKSTRUCT* ks = (KBDLLHOOKSTRUCT*)lParam;
//auto s = format("vkCode:0x{:x} wParam:0x{:x} flags:0x{:x}", ks->vkCode, wParam, ks->flags);
//cout << s << endl;
if (ks->vkCode == 'P' && (GetAsyncKeyState(VK_RCONTROL) & 0x8000))
{
if (wParam == 0x101)
{
is_pause = !is_pause;
if (is_pause)
//cout << "工作已暂停" << endl;
add_log_str("工作已暂停");
else
//cout << "工作已恢复" << endl;
add_log_str("工作已恢复");
}
return 1;
}
return CallNextHookEx(0, code, wParam, lParam);
}
LRESULT CALLBACK MsHookCallback(int code, WPARAM wParam, LPARAM lParam)
{
MSLLHOOKSTRUCT* ms = (MSLLHOOKSTRUCT*)lParam;
// 当鼠标输入特殊键时,mouseData 不为 0。
if (ms->mouseData != 0)
{
auto s = format("mouseData:0x{:x} wParam:0x{:x} flags:0x{:x}", ms->mouseData, wParam, ms->flags);
//cout << s << endl;
add_log_str(s);
//cout << hex << "mouseData:0x" << ms->mouseData << "wParam:0x" << wParam << "flags:0x" << ms->flags << endl;
// 按下副按键1时,自动连击左键
if (ms->mouseData == 0x20000)
{
if (wParam == 0x20b)
{
// 按键按下
do_auto_left_click = true;
return 1;
}
else if (wParam == 0x20c)
{
// 按键弹起
do_auto_left_click = false;
return 1;
}
}
// 按下副按键2时,切换自动跑步键
else if (ms->mouseData == 0x10000)
{
if (wParam == 0x20b)
{
// 按键按下
do_auto_w_press = true;
return 1;
}
//else if (wParam == 0x20c)
//{
// // 按键弹起
// do_auto_w_press = false;
// return 1;
//}
}
// 滚动滚轮时,自动点击F键
else if (ms->mouseData == 0xff880000 || ms->mouseData == 0x780000)
{
if (wParam == 0x20a)
{
// 滚动
n_auto_f_repeat += 1;
// 滚轮滚动事件不要吞掉。
// return 1;
}
}
}
return CallNextHookEx(0, code, wParam, lParam);
}
int main()
{
cout << "用于原神PC版的自动连击工具。\n\n按住副按键1,自动连击鼠标左键。\n点击副按键2,自动切换按住W键\n滚动滑轮,自动连击F键\nR_CTRL+P键切换开始和停止\n\n用于减少腱鞘炎的发生概率。\n" << endl;
keyboardHook = SetWindowsHookExA(WH_KEYBOARD_LL, KbHookCallback, GetModuleHandleA(0), 0);
if (keyboardHook == 0)
{
cout << "挂钩键盘失败" << endl;
return -1;
}
mouseHook = SetWindowsHookExA(WH_MOUSE_LL, MsHookCallback, GetModuleHandleA(0), 0);
if (mouseHook == 0)
{
cout << "挂钩鼠标失败" << endl;
return -1;
}
// 启动连击工作线程
auto t = thread(auto_left_click_run);
auto t2 = thread(auto_press_f_run);
auto t3 = thread(auto_toggle_press_w_run);
auto t4 = thread(log_run);
cout << "程序正常运行" << endl;
cout << "按下 R_CTRL+P 组合键开始工作" << endl;
//不可漏掉消息处理,不然程序会卡死
MSG msg;
while (GetMessageA(&msg, 0, 0, 0))
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
need_quit = true;
UnhookWindowsHookEx(keyboardHook);
UnhookWindowsHookEx(mouseHook);
cout << "程序正常退出。" << endl;
return 0;
}