Win32軟件開發: 按鍵精靈 鼠標模擬器(VibraClick)

1. 簡介

玩遊戲的時候難免會遇到一些遊戲的 "折磨"。例如:我們要使用道具的時候,可這個道具居然沒有批量使用!!!

那行吧,我們就來動手做一個按鍵精靈解放我們的雙手。

PS:目前只做了鼠標按鍵版的,如果有需要鍵盤的,可以私信或下方留言,後續看需補充吧~

 

2. 那我們就開始吧~

①. 首先是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了哦,有興趣的讀者可以去下載了~

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章