日志标题:游戏修改(五)——修改器的制作-MFC篇
发表时间:2005-7-20 18:00:56
最近有活干,没什么时间更新,今天终于忙完了,抽空写点东西吧。
今天讲讲修改器是怎样练成的。其实很简单啦,就是几个API的调用罢了,它的灵魂还是搜索到的地址。下面就以上次的计算器锁定为一直显示“yes2”来演示,环境为VC6+MFC。
先讲一下流程。一:搜索地址,已经在上一篇完成了;二:建立修改器的工程,照下面的图,建立一个工程以及摆好修改器的外壳;
1`新建,MFC程序;

2`选择dialog based,意思是生成的程序是个对话框;

3`默认,不用改;

4`选择as a statically linked library,意思是不需要MFC环境也可以运行(大概吧),然后点‘完成’;

5`开始摆控件了,把默认的这三个控件删除:选中,按delete键。

6`放一个自己的按钮空间上去,选中,按‘回车键’看它的属性,IDC_aButton是它的标识,可以更改。“有个按钮”是按钮的文本,会显示出来,也可以更改;

7`改好之后,双击该按钮,出现下面的对话框,不用改,直接点“OK”就可以了。

8`这时候就进入代码编辑界面了,不给图了。下面就是“有个按钮”的响应函数:
void CTestDlg::OnaButton()
{
// TODO: Add your control notification handler code here
//这是双击按钮之后的样子,一会的代码就写在这两个大括号之间;
}
三:开始添加代码。我们的目标是锁定内存,但是所谓的锁定,大部分是靠短时间内不断修改内存来实现的,我们就用这种简单的方法——定时器。而修改内存的动作就在定时器的响应函数里面,所以“有个按钮”的响应函数中所需要做的就是设置一个定时器。
1`下面给“有个按钮”的响应函数添加设置定时器的代码:
void CTestDlg::OnaButton()
{
// TODO: Add your control notification handler code here
SetTimer(1 , 55 , NULL);
}
就是这一句,SetTimer是一个函数,用来设置一个定时器;括号里面的是这个函数的参数,1表示这次设置的定时器标号为1,当我们设置多个定时器的时候就用这个进行区分了;55表示每隔55毫秒进行一次响应,就是说定时器的响应函数会55毫秒执行一次,理论上来说;NULL表示我们不自己写响应函数,使用MFC提供的,自己写比较麻烦,所以还是用现成的吧。
2`添加定时器的响应函数。上面提到,我们使用MFC提供的,下面就进行添加操作:
按“Ctrl+W”打开ClassWizard,在messages中找到WM_TIMER,双击;下面的member functions中就会出现“OnTimer ON_WM_TIMER”:

3`在“OnTimer ON_WM_TIMER”上双击,进入编辑界面,下面就是定时器的响应函数了:
void CTestDlg::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
//在这里加入代码;
CDialog::OnTimer(nIDEvent);
}
4`添加代码。前面讲到,我们这次的“锁定”其实是短时间内不断的写入内存,于是我们需要知道写内存的函数:
WriteProcessMemory(HANDLE hProcess , LPVOID lpBaseAddress , LPVOID lpBuffer , DWORD nSize , LPDWORD lpNumberOfBytesWritten);
第一个参数是目标程序的进程句柄,放后面讲;第二个参数是我们要修改的地址,可以定义一个变量 DWORD dwAdr = 0x1014E84(上一篇教程得到的),第三个参数是一个指针,指向的数据就是我们要求锁定的结果,可以定义一个数组 BYTE sWrite[26] = {0x6D,0, 0x61,0, 0x64,0, 0x65,0, 0x20,0, 0x62,0, 0x79,0, 0x20,0, 0x79,0, 0x65,0, 0x73,0, 0x32,0, 0,0};,第四个参数是要求修改的字节,数数看,是26个,可以定义一个变量DWORD dwSize = 26;,最后一个参数用来接收实际写入的字节数,可以不要,设置为NULL,最后这个函数就是这个样子: WriteProcessMemory(hProc , (LPVOID)dwAdr , (LPVOID)sWrite , dwSize , NULL);
下面来研究第一个参数,hProc是我们定义的变量,类型是HANDLE,作为目标程序的进程句柄,怎么得到呢?可以先通过 FindWindow 得到目标程序的窗口句柄,该窗口句柄作为参数执行 GetWindowThreadProcessId 函数得到进程ID,把进程ID作为参数执行 OpenProcess 函数,返回值就是我们所要的进程句柄。下面看代码:
HWND hCalc = ::FindWindow("SciCalc" , "计算器");
DWORD pID;
GetWindowThreadProcessId(hCalc , &pID);
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE , FALSE , pID);
讲讲上面三个函数。首先,::FindWindow("SciCalc" , "计算器");,第一个参数是目标程序的窗口类,第二个参数是目标程序的窗口标题,可以用VC的spy++察看。看图:


返回值就是计算器的窗口句柄了,存到hCalc中;GetWindowThreadProcessId(hCalc , &pID);,第一个参数就是计算器的窗口句柄了,第二个参数,在pID的前面加个‘&’符号表示pID的地址,这个函数得到pID的地址是用来把进程ID储存到pID中;HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE , FALSE , pID);,第一个参数是打开这个进程,我们要求得到什么权限,“PROCESS_ALL_ACCESS | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE”表示读写权限我们都要,第二个参数似乎是问我们,当我们得到进程句柄之后要不要给子进程继承,我们用不到,使用FALSE,第三个参数就是进程ID了,上个函数得到的,就是这里要用。返回值就是进程句柄,OK了。
基本上就是这样了,看完成的代码:
void CTestDlg::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
DWORD dwAdr = 0x001014E84;
DWORD dwSize = 26;
BYTE sWrite[26] = {0x6D,0, 0x61,0, 0x64,0, 0x65,0, 0x20,0, 0x62,0, 0x79,0, 0x20,0, 0x79,0, 0x65,0, 0x73,0, 0x32,0, 0,0};
HWND hCalc = ::FindWindow("SciCalc" , "计算器");
DWORD pID;
GetWindowThreadProcessId(hCalc , &pID);
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS | PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE , FALSE , pID);
WriteProcessMemory(hProc , (LPVOID)dwAdr , (LPVOID)sWrite , dwSize , NULL);
CDialog::OnTimer(nIDEvent);
}
这样的代码并不是很好,虽然他在正常情况下可以使用,但是还是有必要进行一些修改的,比如出错处理,效率问题之类的。修改后的代码在文章结尾提供下载。
编译执行按“Ctrl+F5”,可以先选择编译版本再进行编译,下图为如何选择编译版本:

debug版本是调试用的,release版本是发行用的,一般来说如果做好修改器了要给别人用的话都是编译成release版本的。操作就是:选择“test - Win32 Release”点“OK”,然后按“Ctrl+F5”进行编译,生成的文件就在 \test\release\文件夹里面。
源代码下载上一篇:游戏修改(四)——搜索的艺术
下一篇:游戏修改(六)——修改器的制作-SDK篇