stasi浅谈游戏修改器的vb编程


以前总是自己为自己服务,很多游戏都被我改过,也写过和发布过很多修改器,如:轩辕剑2和3,风色幻想,等等,关于这方面的文章不多的,我多没怎么看到过:(,总以为是些不入流的小技巧,但刚在看雪看到Vegeta写的《游戏修改器DIY 》,觉得很好啊,所以随便也写点关于这方面的东西,贻笑大方。Vegeta写了vc的修改器写法,那我主要就写点vb修改器的要领。


游戏的修改器分两种的,一是,内存修改器型的,二是,存档修改器型的,大概还有其他的型,我一时还想不起来,但主要就这两种了。

存档修改器的写法:

先说比较简单的存档修改器型的关键的数据的查找,所谓存档型的修改器,就是修改存档的方式,改变游戏数据,通常正对的是SLG,RPG,经营类的,养成类的等等,这类游戏一般不是计时的,随时可以存档的,游戏的数据也就以存档的形式,保存下来,以便以后再次游戏的时候读取。一般这样的修改器,比较好写,也主要是存档的是以磁盘文件的形式,放在硬盘上的,一般通过对存档文件的修改就能达到修改游戏数据的目的了。

步骤:

一:首先确定要修改的游戏数据,一般只能确定一,两种,如经验,金钱等,太多的数据锁定就会影响对存档的处理。获得游戏中关键的数据——这是制作游戏修改器的前提。这部分其实在我看来,是最难的地方,很多时候就卡在这里了,俗话说,“巧妇难为无米之炊”,这里的关过不了,后面程序的能力再强也是没有用的。

二:记录当前游戏的数据,并及时保存。

三:修改当前游戏数据,并保存。

四:寻找存档文件的目录,有时也会遇见点困难的:),主要就是在游戏安装目录下找,那些:*save*.dat的文件,有些国外的游戏存档,不是在游戏安装目录下的,而是在我的文档里的,如:nba2003系列,古墓丽影系列,当然也有在系统目录里的了,所以有时候要仔细找找,配合系统快照,应该能解决的了.

五:比对两个存档的区别,一般用winhex 和 ultraedit 都有文件比对功能的,区别的地方会以高亮显示,很容易就能区别的。

六:尝试修改数据,要这样做的目的很简单,因为,很多时候比对的区别处有很多,这样就要使用尝试修改的方法,再次通过启动游戏读入存档,看游戏的数据是不是达到预期修改的目的。

七:确定数据的存放地址和存放格式。

八:就能写修改器了。用vb 写修改器,主要用到的就是二进制文件格式读取得和修改的几个函数了,主要掌握关于文件指针的seek语句.在我看来只要你熟练掌握以下几个函数,都能解决问题的,没有什么技巧可言的.


1)Open 语句

能够对文件输入/输出 (I/O)。

语法

Open pathname For mode [Access access] [lock] As [#]filenumber [Len=reclength]

Open 语句的语法具有以下几个部分:

部分 描述 
pathname 必要。字符串表达式,指定文件名,该文件名可能还包括目录、文件夹及驱动器。 
mode 必要。关键字,指定文件方式,有 Append、Binary、Input、Output、或 Random 方式。如果未指定方式,则以 Random 访问方式打开文件。 
access 可选。关键字,说明打开的文件可以进行的操作,有 Read、Write、或 Read Write 操作。 
lock 可选。关键字,说明限定于其它进程打开的文件的操作,有 Shared、Lock Read、Lock Write、和 Lock Read Write 操作。 
filenumber 必要。一个有效的文件号,范围在 1 到 511 之间。使用 FreeFile 函数可得到下一个可用的文件号。 
reclength 可选。小于或等于 32,767(字节)的一个数。对于用随机访问方式打开的文件,该值就是记录长度。对于顺序文件,该值就是缓冲字符数。 


2)Get /Put语句

将一个已打开的磁盘文件读入一个变量之中。

语法

Get/Put [#]filenumber, [recnumber], varname 

Get/Put 语句的语法具有以下几个部分:

部分 描述 
filenumber 必要。任何有效的文件号。 
recnumber 可选。Variant (Long)。记录号(Random 方式的文件)或字节数(Binary 方式的文件),以表示在此处开始读出数据。 
varname 必要。一个有效的变量名,将读出的数据放入其中。 

3)Seek 语句

在 Open 语句打开的文件中,设置下一个读/写操作的位置。

语法

Seek [#]filenumber, position

Seek 语句的语法具有以下几个部分:

部分 描述 
filenumber 必要。任何有效的文件号。 
position 必要。介于 1 - 2,147,483,647 之间的数字,指出下一个读写操作将要发生的位置。 

只要以上几个函数的组合,你就能写出任何要求的存档型的修改器,是不是很容易啊?


接下来说说内存型修改器的写法:

步骤:

一:就是金山游侠的使用了,怎么使用我就不说了,很多文章介绍的,也很有用的,不懂的要先了解一下的,也可以看《游戏修改器DIY之二》的第一部分,已经很详细了:)

二:步骤一的目的就是得到数据在内存里的存放地址和数据的存放格式,有了这两个就能通过vb写修改器了.

一般要用的函数有:FindWindow,GetWindowThreadProcessId,OpenProcess,WriteProcessMemory,ReadProcessMemory,CloseHandle,CloseHandle,rMem,wMem,我索性把他们写在一起,你就用这个起个模块,想用就用吧.
ps:以下很多函数,还有很多详细的参数,我就在以下例子里写了基本必要的参数和格式,其他的功能没办法全部举例了,大家注意平时编程的积累就是了:)

Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As Long, lpdwProcessId As Long) As Long
Private Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Private Declare Function WriteProcessMemory Lib "kernel32" (ByVal hProcess As Long, ByVal lpBaseAddress As Any, lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
Private Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As Long, ByVal lpBaseAddress As Any, lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Const PROCESS_ALL_ACCESS = &H1F0FFF
Public hProcess As Long
Function rMem(vAdd As Long, l As Integer) As Long
Dim ret As Long
Call ReadProcessMemory(hProcess, vAdd, ret, l, 0&)
rMem = ret
End Function
Function wMem(vAdd As Long, value As Long, l As Integer)
Call WriteProcessMemory(hProcess, vAdd, value, l, 0&)
End Function

规范的修改器需要对游戏是否已经启动作判断的:
这里我用FindGame这个函数了:

Function FindGame() As Boolean
Dim GameHwnd As Long, Pid As Long
hProcess = 0
GameHwnd = FindWindow(vbNullString, "********") '*******就是系统的进程名,就是你运行游戏看到的系统的进程名
If GameHwnd = 0 Then Exit Function ' 游戏没有运行,你可以选择没有运行的,就帮着运行,也可以错误提示
GetWindowThreadProcessId GameHwnd, Pid
hProcess = OpenProcess(PROCESS_ALL_ACCESS, False, Pid)
If (hProcess = 0) Then Exit Function ' 无法打开进程
FindGame = True
End Function

然后就是主体了----对内存的读取,修改和保存

建个Command和多个Text1,最好用index的,方便书写,我这里就是这样的简单例子:
数据读取:
Private Sub Command1_Click()
If FindGame Then
Text1(0) = rMem(&H4700**, 1)   ' &H4700**就是你要修改的内存地址,十六进制修改
Text1(1) = rMem(&H4700**, 1)   '比如你要改十个地方,就这样写
Text1(2) = rMem(&H4700**, 1)   'rMem函数在上面已经申明过了,这里就能用
Text1(3) = rMem(&H4700**, 1)
Text1(4) = rMem(&H4700**, 1)
Text1(5) = rMem(&H4700**, 1)
Text1(6) = rMem(&H4700**, 1)
Text1(7) = rMem(&H4700**, 1)
Text1(8) = rMem(&H4700**, 1)
Text1(9) = rMem(&H4700**, 1)

Else
MsgBox "请先运行游戏!", vbInformation, "*******" '*******就是系统的进程名,就是你运行游戏看到的系统的进程名
End If
End Sub
以上的地址当然你的编程习惯好的话,就随手写成const,也方便后面的保存.

数据保存:
Private Sub Command2_Click()
If FindGame Then
Call wMem(&H4700**, Val(Text1(0)), 1)  '这里就要注意和上面的对应就可以,不对应的后果是很严重的哦!
Call wMem(&H4700**, Val(Text1(1)), 1)
Call wMem(&H4700**, Val(Text1(2)), 1)
Call wMem(&H4700**, Val(Text1(3)), 1)
Call wMem(&H4700**, Val(Text1(4)), 1)
Call wMem(&H4700**, Val(Text1(5)), 1)
Call wMem(&H4700**, Val(Text1(6)), 1)
Call wMem(&H4700**, Val(Text1(7)), 1)
Call wMem(&H4700**, Val(Text1(8)), 1)
Call wMem(&H4700**, Val(Text1(9)), 1)
Else
MsgBox "请先运行游戏!", vbInformation, "*********"
End If
End Sub
//////////////////////////////////////////////////////////////////////////////////////////

以上代码在Win2000P+SP4 + Vb6测试通过。

三:最后就要在主界面上告诉使用者,这是在修改什么,那是在修改什么,写上数据的范围,就完成了!

总结:

以上就是我个人的经验,大家能学到点就拿点,算我新年送的礼:)
再说说游戏修改的问题,我一般喜欢完美的玩游戏,想想,难得有时间玩游戏,无论大小总要玩出个名堂,什么最强结果啊,什么完美结局啊,一般都要通过修改游戏来完成,要不就是很浪费时间的!
最近经朋友介绍玩了个Insaniquarium Deluxe的养鱼小游戏,不错哦,推荐大家去试一试,我修改了下,在www.chinadfcg.com的游戏版里有我的截图,我想如果要玩出我这个结果恐怕几年时间+上百个鼠标的代价吧?:)大家不妨试一试:)

ps:祝大家新年快乐!


stasi
2005年元月
上海