简单魔兽改键的基本原理及基于MFC实现

主要原理:通过线程钩子截获魔兽进程的键盘消息,然后模拟我们所需要的按键。软件设计基于MFC和用到的一些windowsAPI。

1.     界面布局和要求功能


软件界功能少见面简单,主要有小键盘和自定义的改键。(玩dota的都知道)

要求:编辑框只能显示一个键的字符且大写,按空格键时最好显示“空”,按键定义不能重复,显示改键开启状态。

 

2.     动态链接库

软件的核心是键盘钩子。先要写个动态链接库DLL。主要函数SetWindowsHookEx,参见MSDN。DLL主要有cpp文件和def输出函数定义文件。

 

将用户界面设置的改建前和改建后的按键字符保存在两个字符数组中。

由于钩子的回调函数LRESULTCALLBACK KeyboardProc在dll中,而用户的改键设置保存在主程序中,所以我们在启动改键时需要将改键的设置传入dll中,通过共享数据块保存。

 

Dll中的函数在主函数中的声明,否则无法调用

在主程序StdAfx.h中:

_declspec(dllimport) void setHook(HWND hwnd,char *pre,char *fin);
_declspec(dllimport) void deleHook();
_declspec(dllimport) void chat(int flag);
#pragma comment(lib,"War3hook.lib")//链接dll编译生成的lib文件

war3hook.def:主要是输出这三个函数然后在主程序中调用

LIBRARY War3hook
EXPORTS
setHook	@2
deleHook @3
chat @4

war3hook.cpp:

#include <windows.h>

#pragma   data_seg("MYSEC")     
char prek[11]={0};
char fink[11]={0};//改键设置保存
HWND g_hWnd=NULL;//保存魔兽窗口句柄
int chaton=0;//监听聊天状态
#pragma   data_seg()
#pragma   comment(linker, "/section:MYSEC,rws")
//共享数据块定义

HHOOK g_hKeyboard;//钩子句柄
char flag=NULL;//用来保存返回的该键字符

void chat(int chflag)//主程序中调用将聊天状态传入dll中
{
	chaton=chflag;
}
char findkey(char ch)//知道需要改建的ch返回改建后的键字符,如果没有则返回’*’
{
	int index=0;
	for (index=0;index<11;index++)
	{
		if (prek[index]==ch)
		{
			if (fink[index]!='*')
			{
				if (fink[index]>=0x31&&fink[index]<=0x38)
				{
					return (fink[index]+0x30);
				}
				return fink[index];
			}
		}
	}
	return '*';
}
void deleHook()//卸载钩子
{
	UnhookWindowsHookEx(g_hKeyboard);
}
LRESULT CALLBACK KeyboardProc(
  int code,       // hook code
  WPARAM wParam,  // virtual-key code
  LPARAM lParam   // keystroke-message information&&((lParam>>30&1)==0)
  )//键盘钩子的回调函数
{
	if (1==chaton)//如果聊天取消改键
	{
		return CallNextHookEx(g_hKeyboard,code,wParam,lParam);
	}
	flag=findkey((char)wParam);//获取改键,若不许改建则返回‘*’
	if (flag!='*'&&((lParam>>31&1)==0))//判断是否改键和键盘的按下状态,否则按键弹起时也执行,造成按两次键
	{
		keybd_event(flag,0,0,0 );
		keybd_event(flag,0,KEYEVENTF_KEYUP,0);//模拟改键
		return 1;//使已经按下的键失效
	}
	return CallNextHookEx(g_hKeyboard,code,wParam,lParam);
}

void setHook(HWND hwnd,char *pre,char *fin) 
{
	g_hWnd=hwnd;//保存魔兽句柄
	for (int i=0;i<11;i++)
	{
		prek[i]=*(pre+i);
	}
	for (int j=0;j<11;j++)
	{
		fink[j]=*(fin+j);
	}//将用户改键设置传入dll中的共享数组中
	g_hKeyboard=SetWindowsHookEx(WH_KEYBOARD,KeyboardProc,GetModuleHandle("War3hook"),GetWindowThreadProcessId(g_hWnd,NULL));//设置键盘钩子
}

 

3.主程序编辑框的处理

 

在相应编辑框数据改变的响应函数中处理:

void CWar3KeyDlg::OnChangeEdit7() 
{
	CString tempstr;
	tempstr=m_EDIT7;
	UpdateData(TRUE);
	m_EDIT7=inputkey(m_EDIT7,tempstr);//inputkey为自定义函数,将编辑框中现在的内容减去原来的内容
	if (checkrepeat(m_EDIT7)==FALSE)// checkrepeat为自定义函数,防止按键定义重复
	{
		m_EDIT7="";
		AfxMessageBox("按键重复定义!");
	}
	if (m_EDIT7==" ")
	{
		m_EDIT7="空";
		UpdateData(FALSE);//为了显示‘空’
		m_EDIT7=" ";
	}
	else
	{
		UpdateData(FALSE);
	}
}

检测魔兽聊天状态和游戏开始的实现:

方法:通过Cheat Engine搜寻魔兽进程的内存内容,其中有两个内存块是聊天状态和游戏是否进行的状态标示。不同的游戏版本应该不一样,然后通过ReadProcessMemory()函数读取该内存的值,通过定时器监视,作出相应的相应。但是,主程序要想读魔兽内存必须要有权限,可以下面方法提升权限。

 

开启改键按钮中的部分代码:

HWND m_hwnd=::FindWindow("Warcraft III","Warcraft III");//需找魔兽句柄
	if (m_hwnd==NULL)
	{
		AfxMessageBox("Warcraft III 未运行!");
		return;
	}
	DWORD PID;
	::GetWindowThreadProcessId(m_hwnd,&PID);//获取魔兽进程id
	if (PID==NULL)
	{
		AfxMessageBox("进程ID获取失败");
		return;
	}
	m_War3ProcessID=::OpenProcess(PROCESS_ALL_ACCESS,FALSE,PID);//获取魔兽进程句柄
	if (m_War3ProcessID==NULL)
	{
		AfxMessageBox("进程句柄获取失败");
		return;
	}

	
	GetDlgItem(IDC_STATIC_yn)->SetWindowText("已开启改键");//静态框状态显示
	GetDlgItem(IDC_BTNRun)->EnableWindow(FALSE);

	

	setHook(m_hwnd,pkey,fkey);//调用dll中函数设置键盘钩子
	SetTimer(1,100,0);//启动定时器,开始监视
	EnableDebugPrivilege(TRUE);//提升权限,该函数为自定义函数

开关函数权限的定义:

BOOL CWar3KeyDlg::EnableDebugPrivilege(BOOL bEnableDebugPrivilege)
{
	HANDLE hToken;
    TOKEN_PRIVILEGES tp;
    LUID luid;
	
    if(!::OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
    {
        ::MessageBox(this->GetSafeHwnd(),"GET_TOKEN_ERROR","MSG_BOX_TITLE", MB_OK);
        return FALSE;
    }
	
    if(!::LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid))
    {
        ::MessageBox(this->GetSafeHwnd(),"GET_PRIVILEGE_VALUE_ERROR","MSG_BOX_TITLE", MB_OK);
        ::CloseHandle(hToken);
        return FALSE;
    }
	
    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luid;
    if(bEnableDebugPrivilege)
    {
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    }
    else
    {
        tp.Privileges[0].Attributes = 0;
    }
	
    if(!::AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL))
    {
        ::MessageBox(this->GetSafeHwnd(),"ADJUST_PRIVILEGE_ERROR","MSG_BOX_TITLE", MB_OK);
        ::CloseHandle(hToken);
        return FALSE;
    }
	
    ::CloseHandle(hToken);
	
    if(::GetLastError() == ERROR_NOT_ALL_ASSIGNED)
    {
        ::MessageBox(this->GetSafeHwnd(),"ENABLE_DEBUG_ERROR","MSG_BOX_TITLE", MB_OK);
        return FALSE;
    }
    return TRUE;
}

定时器中的代码:

int buf=0;//临时保存聊天状态,0为操作状态,1为聊天状态, 
int buf2=0; //临时保存游戏是否开启状态,1为游戏进行状态,0为未开始
		if (::ReadProcessMemory(m_War3ProcessID,(LPCVOID)0x6fad15f0,&buf,4,NULL))
		{//0x6fad15f0为相对内存地址
			chat(buf);//调用dll中函数,将聊天状态传入,共享数据块中,在钩子回调函数需要判断是否改建
		}

		if (::ReadProcessMemory(m_War3ProcessID,(LPCVOID)0x6fadb3d8,&buf2,4,NULL))
		{
			if (buf2==1&&m_GameRun==FALSE)// m_GameRun保存游戏是否进行的状态
			{
				m_GameRun=TRUE;
				if (::IsIconic(temphwnd)==TRUE)
				{
					AfxMessageBox("游戏开始啦!!!");//如果魔兽此时是最小化且魔兽从未开始到进行则说明魔兽运行了,弹出消息提示
					::SetWindowPos(this->GetSafeHwnd(),HWND_TOP,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE);
					::SetForegroundWindow(this->GetSafeHwnd());
				}
			}
			else if (buf2==0)
			{
				m_GameRun=FALSE;
			}
		}

至于

按键设置的保存:主要将两个改建前和改建后的字符串保存在ini文件中,没有加密,关闭时保存,启动时读取即可。

软件防重复:在窗口未创建前在app里的InitInstance()函数中通过EnumWindows函数搜寻是否有同名的函数,有则直接返回。

程序托盘:有固定形式,可百度google

hfut   tdk  

源码http://download.csdn.net/detail/tandyky/3683250





阅读更多
个人分类: MFC
想对作者说点什么? 我来说一句

MFC编写的dota小键盘改键

2013年06月28日 5.86MB 下载

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭