游戏修改器的基本工作原理

    所谓游戏修改器,主要是通过修改游戏程序的内存数据或存盘文件来修改游戏中的相关数据,使之达到无敌等效果。  

     游戏修改器主要分为两类:单一游戏的修改器和通用游戏修改器。顾名思义,前者只能修改特定的游戏,此类修改器也叫无敌引导程序游戏作弊器;而后 者则能够以不变应万变,可以修改大多数的游戏。本文主要讨论后者,相比之下,前者是后者只留下修改功能的简化版  

    总的来说,游戏修改器主要的功能便是反复搜索并筛选某一特定内存地址并将其按照固定的周期修改为特定的值(所谓的锁定),当然,将内存换为文件便可以以同样的方式搜索文件了。  

    搜索和筛选是如何实现的呢?假如我们已经可以访问游戏的内存,我们将游戏的内存分块读入一个缓冲区,然后借由下面的函数搜索:  

//在一个内存块中搜索内容 
//
成功返回位置 
//
失败返回-1 
//(SE:
现在我发现这个顺序查找函数完全可以用 STL  std::find() 函数取代,原因是写文章的时候我还不太了解 STL:) 
int MemFind(int iStartPosition, LPBYTE pDestBuffer, int iDestBufferLength, LPBYTE pPatternBuffer, int iPatternBufferLength) 

    signed int iFoundPosition, i; 
    iFoundPosition = -1; 
    if(iStartPosition > iDestBufferLength)return -1;   
    for(i = iStartPosition; i < (iDestBufferLength + 1); i++) 
    { 
        if(memcmp(&pDestBuffer[i], pPatternBuffer, iPatternBufferLength) == 0) 
        { 
            iFoundPosition = i; 
           break; 
        } 
    } 
    return iFoundPosition; 

    由于游戏在内存或文件中保存的数据是二进制格式,因此,当我们搜索一个为123的整数时应用如下的格式:  
int nPattern = 123
 
dwOffset = MemFind(nStartPosition, pDestBuffer, nDestBufferLength, (LPBYTE)(&nPattern), sizeof(nPattern)) 
同样,可以这样搜索浮点类型: 
float rPattern = 123
 
dwOffset = MemFind(nStartPosition, pDestBuffer, nDestBufferLength, (LPBYTE)(&rPattern), sizeof(rPattern)) 

     由于内存中肯定会存在许多相同的数据,所以第一次我们肯定会搜索到许多地址,而真正我们要找的地址一定包含在其中。所以,我们建立一个临时文件将这些地址保存起来,并设置有效标志,如下代码所示:  
struct CTempItem 

    DWORD dwAddress; 
    BOOL bEnable; 
}; 
CTempItem item; 
item.dwAddress = ?????????; 
item.bEnable = TRUE; 
fwrite(&item, sizeof(item), 1, fp); 
    
接下来,当用户进行第二次搜索时,将这些保存在临时文件中的数据取出来,先看item.bEnable若等于FALSE则跳过,否则读取 item.dwAddress所指示的游戏内存,并于用户第二次输入的数值相比较,若发现相同,则设置item.bEnable =& nbsp;TRUE;若不同设置item.bEnable=FALSE,表示废弃。完成之后,再次把item写回文件,当所有item分析完之后,我们就 完成了第二次搜索。再接下来,bEnable=TRUE的地址还有很多,则仍然用第二次搜索的方式反复搜索,直到剩下1-2个地址为止。& nbsp;SE:应用读写缓冲区,即成批地读写,可以大大加快速度)  

     由以上介绍可以看出,游戏修改器的搜索分为2个阶段:第一次搜索和第234……次搜索,游戏修改器在第一次搜索出的很多地址中分析出与用户输入的数据 始终相同的地址。当我们有了目标地址,就可以按照用户的意愿定时或手动方式写入用户指定的数据,这便是游戏修改器的基本原理。  

    当然,这只是基本原理,当具体编写修改器将遇到许多具体的技术困难,以下章节将为您一一解答。  

    如何访问游戏程序的内存 
    
当我们的修改器运行于Windows时,首先遇到的问题便是如何访问游戏的内存。  

     首先,在访问游戏的内存前我们还必须获得游戏进程的句柄,这可以通过ToolHelp函数获取系统中当前运行的所有进程的列表和各进程的ID,经由用户选择之后通过OpenProcess函数来获取。若您的修改器运行于后台,而前台是游戏的话,可以在用户按下弹出热键时使用 GetForegroundWindow函数获取游戏窗口的HWND,再使用GetWindowThreadProcessId转换成游戏进程的ID,再 使用OpenProcess函数获取游戏进程的句柄。  

    有了游戏 进程的句柄之后,便可以使用Windows提供的ReadProcessMemoryWriteProcessMemory这两个API来读写游戏的内 存了。但是,在Windows9x中每个进程均拥有各自独立的1GB虚拟地址空间,而在Win2000/XP下更是达到了2GB。显然,搜索这样大的地址 空间是不现实的,而且游戏也仅仅用了其中的几十到几百MB。所以,我们需要使用VirtualQueryEx这个函数来查询哪些是已经分配的地址,哪些是未用的地址。以下查询与搜索相结合的示范代码:  

DWORD dwBaseAddress; 
SYSTEM_INFO si; 
GetSystemInfo(si);
dwBaseAddress = si.lpMinimumApplicationAddress; 
while(dwBaseAddress < si.lpMaximumApplicationAddress) 

    mbi.BaseAddress = (LPVOID)dwBaseAddress; 
ProcessMem.Query((PVOID)dwBaseAddress, &mbi); 
VirtualQueryEx(hProcess, (LPVOID)dwAddress), mbi, sizeof(mbi) 
    dwBaseAddress = (DWORD)mbi.BaseAddress + mbi.RegionSize; 
    if(mbi.State !=& nbsp;MEM_COMMIT || mbi.AllocationProtect !=  PAGE_READWRITE); //
跳过未分配或不可读写的区域 
    { 
       continue; 
    } 
    //
搜索这块内存区域 

资源:请到 http://alphasun.betajin.com下载我写的一个很简陋的游戏修改器——GameProbe的源程序。 
    
如何实现热键 
    
热键的原理很简单,使用全局键盘HOOK就可以了,鉴于这方面的资料较多,具体的Hook使用方法请参阅MSDN或相关资料。  
    
如何实现暂停游戏 
    
这是游戏修改器必须具备的一项功能了,若不暂停游戏,搜索之前数值很可能会改变,从而造成找不到数据的现象。暂停游戏的办法有很多种,主要有:  
1. Debug
法,这种方法利用DebugActiveProcess这个Windows API将游戏修改器作为游戏的调试程序,游戏修改器可通过 Windows提供的调试事件(DebugEvents)来获取游戏的各个线程的句柄。但此法有一个缺点就是不能在关闭游戏前关闭修改器,否则 Windows将会自动终止游戏的运行,运用此方法的典型是FPE2000  

2. 通过使用 ToolHelp系列函数获取游戏进程的所有线程,并使用公开的OpenThread这个API来获得各线程的句柄并使用 SuspendThread  ResumeThread来暂停或恢复游戏的运行,目前大部分程序运用此法。(BTW:著名 的 GameMaster使用的是CreateProcess并通过Suspend游戏主线程的方法暂停游戏的,很显然,若游戏采用了多线程,此方法是欠妥的。)  

    但恼人的是Win9x下并没有 OpenThread这个API,不过我们可以通过一个未公开的使用 TCB 的方法在Win9x下替代OpenThread而获取 线程的句柄,从而达到了暂停游戏运行的目的。您可以到 http://www.windrun.com/ ;下载Ligtest前辈编写的PauseProcess程序的源代码来学习,或者下载我的 TestPopup  

    如何在游戏中弹出自己的界面 
    
这个问题可以和热键问题一并解决:众所周知,Windows是一个消息驱动的32位操作系统。在Windows中,所有正在运的进程都有一个独立的2GB 的虚拟地址空间,进程之间相互不可见。Windows的绝大多数API与消息是不能跨越进程的。  

   “Hook” Windows中主要是用来截取消息的,形象说,就是用来” 消息的。Hook实际上是一个处理消息的程序段,每当特定的消息发出,在没有到达目的窗口前,Hook函数就先捕获该消息,即Hook函数先得到处理消息的控制权。而且如果你把Hook实现在DLL文件中,那么Hook函数将会自动被系统映射到会处理那个特定消息的窗口所在的进程虚拟地址空间中。例如,你可以用Hook来捕获系统中所有的键盘输入消息(WM_KEYDOWN)来 实现对电脑使用者的输入进行记录(关于Windows进程管理与Hook 的详细用法,请参阅MSDN与相关资料)。  

     微软的 DirectX Windows下的游戏带来了华丽的声光效果。但是由于DirectX采用直接访问硬件的方法提高多媒体与游戏程序的速度,因此导致了人们误以为在DirectX(确切地说是DirectDraw)下不能显示普通的Windows对话 框。  

    幸运的是,DirectX 是支持GDI的,也 就是说游戏程序可以用常规的方法在DirectX下显示对话框(在微软 DirectX 8 SDK 中有名为 “FullScreenDialog”的例子)。所以现在我们的问题变为:如上所述,如何让我们的程序进入游戏的程序的内部并显示对话框。 

     这似乎是一个很棘手的问题。但是,有了上面所讲的Hook情况就大为不同了。我们知道既然Hook可以映射到别的进程内部,那么只要将显示对话框的函数以及对话框资源包括在Hook DLL 中不就可以调用DialogBox() 了吗?完全正确!我们用 SetWindowsHookEx() 为系统设置一个键盘消息Hook,系统会自动将这个DLL映射到游戏的进程中。每当有键 盘消息,我们只要判断是不是我们所设定的热键,如果是就调用 DialogBox() 显示对话框即可。  (SE: 还有一种方法可以把 DLL 插入别的进程,那就是利 用 RemoteThread  远线程,具体程序请参阅上文提到的 PauseProcess 程 序。)  

    您可以参阅我的TestPopup程序,就演示了在DirectX下弹出界面、暂停游戏、热键等功能。  

    如何调整游戏速度 
    
游戏通常是通过timeGetTimeGetTickCount等几个与时间相关的API来控制速度的。因此,我们只要抢在游戏调用这些API之前调用 它们,并修改返回值,便可以调整游戏的速度了。具体的程序可以参阅我的 SpeedMan,您可以免费下载它的源程序。 SpeedMan 使用了《Windows核心编程》所提供的 CApiHook 类来拦 截 timeGetTime  Windows API  

    抓图功能 
    
抓图功能很好实现。具体就是先暂停游戏,再通过使用GetForegroundWindow函数获取游戏窗口的HWND,最后DC就可以获得Bitmap 格式的屏幕图像了,至于如何保存成文件,或转换为Jpeg(可以使用IJLib)等,则不在本文讨论范围里了。  

获取进程代码:

#include "stdafx.h"

#include

#include

int main(int argc, char* argv[])

{

     HANDLE hSnapshot = CreateToolhelp32Snapshot (TH32CS_SNAPPROCESS, 0);

     if (!hSnapshot){

         printf("CreateToolhelp32Snapshot ERROR!/n");

         return 1;

     }

     PROCESSENTRY32 pe32;

     pe32.dwSize = sizeof(PROCESSENTRY32 );

     if (!Process32First (hSnapshot, &pe32))

     {

         printf("Process32First ERROR!/n");

     }

     do

     {

         printf("ProcID:%d---%s/n",pe32.th32ProcessID ,pe32.szExeFile );

     }while(Process32Next (hSnapshot, &pe32));

     return 0;

}

 

#include "stdafx.h"

#include "windows.h"

#include "tlhelp32.h"

#include "stdio.h"

int main(int argc, char* argv[])

{

     PROCESSENTRY32 pe32;

     pe32.dwSize = sizeof(pe32);

     HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);

     if (hProcessSnap == INVALID_HANDLE_VALUE)

     {

         printf("CreateToolhelp32Snapshot 调用失败./n");

         return -1;

     }

     BOOL bMore = ::Process32First(hProcessSnap,&pe32);

     while (bMore)

     {

         printf("进程名称:%s/n",pe32.szExeFile);

         printf("进程ID%u/n/n",pe32.th32ProcessID);

         bMore = ::Process32Next(hProcessSnap,&pe32);

     }

     ::CloseHandle(hProcessSnap);

     return 0;

}

 

转自http://hack.gameres.com/showthread.asp?threadid=75739

你可能感兴趣的:(C/C++)