零基础制作《武林外传》辅助工具 (VB)

作者:眼镜(游戏外挂研究院)
来源:JiaJia's Home

需要工具:
VB6企业版(盗版)/VB6精装版
CE(英文或汉化)

一,打开游戏和CE用CE载入游戏

二,让你的血量回到最满状态,记住血量值

如上图,我的是1312点血,好的,用CE搜"1312",.
用"Exact value"方式,4字节

搜到N个址,好,再回到游戏里,我们让人物掉点血(打怪什么的,最好多掉点,这样方便后面来查数),再用"Decreased value"(减少了的数)来搜,这样掉点血,搜一次,搜点血搜一次,几次下来,好的,找到的地址就只剩四个了(我是两次就找到只剩四个了,电脑不同,可能找的次数也不同, 不过方法是一样的,先找最大血量值,然后,再掉点血,再用减少的搜,如此循环几次就OK了)记下第一个地址(为什么用第一个地址,CE的教程里说,一般如果找到少量的地址后,正确的一般在第一个,如果你不确定,你可以让人物自动回血,就可以看到,第一个地址的数也在跟着增加~)

接着,把第一个地址双击,加入下面的栏里,再在地址上单击右键,弹出菜单中选"Find out what writes to this address"(谁在改定这个地址),调出监视窗口,再回到游戏,掉点血,就可以看到监视窗口里己经有改写该地址的命令了~

选中该命令,点"Mor information",得到如下图

mov [esi+00000254],ecx 红色加亮的这句,(+00000254也就是血量的偏移量了)
可以看到,是把ECX的值写入ESI+00000254这个内存地址中,所以,我们记下下面的ESI地址:ESI=05C0B548,再回到CE主界面,搜"05C0B548"这个数(选中HEX,用十六进制四字节方式搜)

得到26个地址都含有这个数,好的,把最前面的三个数保存到下栏里,然后,小退一下(回到人物选择界面,再进入游戏),就可以看到,三个地址的数值都有变化了.好,再重新找血量地址,然后再"Find out what writes to this address"(谁在改定这个地址)这个地址,得到ESI的值,记下来,对比刚保存 下来的三个地址内的数,呵呵,发现
010AEAE4里的值刚好等于ESI,这就可以肯定,010AEAE4一定二级基址了,好,我们监视010AEAE4,"Find out what writes to this address".好,再次小退一下,再进入游戏,这时监视窗口有东东了,如下图

得到:mov [esi+24],0000000
记下ESI的值,010AEAC0,再回到CE主界面,用十六进制的方法找010AEACO
得到N个数,反复搜几次,好,得到下图

按CE教程说的,一般最是地址最小的就是正确的,呵呵,有点那个啊,~~~~~~~~~~~~
选第一个008BE594,加到下面的栏里,完全退出游戏,再进入游戏
重复,最后还是得到008BE594,这样就可以肯定,008BE594就是一级基址了
得出公式如下
一级基址:008BE594
008BE594地址里面保存的数值+24的偏移=二级基址
二级基址里面保存的地址数+254的偏移得到血量的地址
下面开始搜索数据!
用CE打开武林外传。运行CE,按照下图的次序打开(…\武林外传\element\elementclient.exe)

2.游戏启动后,别忘了选择游戏窗口。

3.这是游戏中人物的数据。

4.好,现在我们要在CE中显示人物的生命值。同样按照图中的顺序打开“添加地址”对话框,在数据框中添加我们已知的内存地址。当然,我们要用指针的方式,因为存储这些数据的地址是不固定的,我们可以用8C6A54这个固定的基地址以及相关的偏移地址找到我们需要的数据。我们就来看看生命的值,完成图上的项目点“确定”来看看结果。

5.怎么样!是不是角色的生命值呢,使自己失去一些血量看看这个值是不是也在跟着变化。

6.根据已知的人物地址,我们还可以显示其它数据。

相关武林外传地址:
一级基址=8CF51C
人物基址=+24
当前血=+254
最大血=+26C
当前蓝=+258
最大蓝=+270
以上数字均为16进制
————————————————————————————————————————————

下面是VB代码部分:

1.建立一个新的标准EXE工程,我们就可以开始这次的学习了。

2.我们要建立一个模块,然后添加以下代码:

Option Explicit
'---------------声明函数-----------------------
'得到窗体句柄的函数,FindWindow函数用来返回符合指定的类名( ClassName )和窗口名( WindowTitle )的窗口句柄
Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
'得到窗体控件句柄的函数
Public Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
'得到进程标识符的函数
Public Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As Long, lpdwProcessId As Long) As Long
'得到目标进程句柄的函数
Public Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
'关闭句柄的函数
Public Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
'读取进程内存的函数
Public Declare Function ReadProcessMemory Lib "kernel32.dll" (ByVal hProcess As Long, ByVal lpBaseAddress As Long, ByRef lpBuffer As Any, ByVal nSize As Long, ByRef lpNumberOfBytesWritten As Long) As Long
'参数决定了对进程的存储权限,使用完全控制
Public Const PROCESS_ALL_ACCESS = &H1F0FFF

3.接下来Form中,我们要在程序启动时连接游戏窗口,以下是Form_Load的代码:

Dim hwd As Long ‘ 储存 FindWindow 函数返回的句柄
Dim pid As Long
Dim hProcess As Long '存放进程句柄

Private Sub Form_Load()
hwd = FindWindow("QElementClient Window", "Element Client")
If hwd = 0 Then
MsgBox "未启动游戏", vbOKOnly, "提示"
Unload Form1
End If
GetWindowThreadProcessId hwd, pid '获取进程标识符
'将进程标识符做为参数,返回目标进程PID的句柄,得到此句柄后
'即可对目标进行读写操,PROCESS_ALL_ACCESS表示完全控制,权限最大
hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid)
If hProcess = 0 Then
MsgBox "不能打开进程", vbOKOnly, "提示"
Unload Form1
End If
CloseHandle hProcess

4.我们在Form中添加一个Label控件和一个Timer控件,设置Timer的Interval属性为100,Timer1_Timer的代码如下:

Dim h As Long
hProcess = OpenProcess(PROCESS_ALL_ACCESS, False, pid)

If hProcess Then
ReadProcessMemory hProcess, ByVal &H8C6A54, h, 4, 0& ‘这三条代码读取获得生命值
ReadProcessMemory hProcess, ByVal h + &H24, h, 4, 0&
ReadProcessMemory hProcess, ByVal h + &H254, h, 4, 0&

CloseHandle hProcess
End If

Label1.Caption = h '输出生命值

下面我们来给外挂增加自动保护功能.
1.首先我们要绘制一个界面,最先添加一个Frame控件、最少两个Label控件用于输出生命和真气值、两个Text控件用于输入数据还有两个Timer控件,分别改名为TimerList及TimerAdd,最后添加一个Command控件。可以参考下图,呵呵~我知道你可以画的更好看!

2.下一步就是添加代码了,和上次一样新建一个模块,模块内容如下:

Option Explicit
'---------------声明函数-----------------------
'得到窗体句柄的函数,FindWindow函数用来返回符合指定的类名( ClassName )和窗口名( WindowTitle )的窗口句柄
Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
'得到窗体控件句柄的函数
Public Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
'得到进程标识符的函数
Public Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As Long, lpdwProcessId As Long) As Long
'得到目标进程句柄的函数
Public Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
'关闭句柄的函数
Public Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
'读取进程内存的函数
Public Declare Function ReadProcessMemory Lib "kernel32.dll" (ByVal hProcess As Long, ByVal lpBaseAddress As Long, ByRef lpBuffer As Any, ByVal nSize As Long, ByRef lpNumberOfBytesWritten As Long) As Long
'参数决定了对进程的存储权限,使用完全控制
Public Const PROCESS_ALL_ACCESS = &H1F0FFF
'发送信息的函数
Public Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Public Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
'延迟函数
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)

发送消息函数和延迟函数是以前内容没有用过的,这回我们将涉及发送模拟键盘消息给窗口,所以加入这两个函数。
3.做好模块,下一步该写Form了。同样还是声明一些变量及Form_Load,代码如下:

Dim hwd As Long
Dim pid As Long
Dim hProcess As Long '存放进程句柄
Dim base As Long '存放人物基地址
Dim hp As Long '存储生命值
Dim hpmax As Long '存储生命最大值
Dim mp As Long '存储真气值
Dim mpmax As Long '存储真气最大值

Private Sub Form_Load()
hwd = FindWindow("QElementClient Window", "Element Client")
If hwd = 0 Then
MsgBox "未启动游戏", vbOKOnly, "提示"
Unload Form1
End If
GetWindowThreadProcessId hwd, pid '获取进程标识符
'将进程标识符做为参数,返回目标进程PID的句柄,得到此句柄后
'即可对目标进行读写操,PROCESS_ALL_ACCESS表示完全控制,权限最大
hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid)
If hProcess = 0 Then
MsgBox "不能打开进程", vbOKOnly, "提示"
Unload Form1
End If
CloseHandle hProcess
End Sub

4.下一步,我们通过TimerList显示人物信息,设置TimerList的Interval属性值为1000,其代码如下:

Private Sub TimerList_Timer() '显示人物信息时钟
Dim name(31) As Byte '存储人物名称
Dim name_temp As Long

hProcess = OpenProcess(PROCESS_ALL_ACCESS, False, pid)
If hProcess Then
ReadProcessMemory hProcess, ByVal &H8C9E54, base, 4, 0&
ReadProcessMemory hProcess, ByVal base + &H24, base, 4, 0& '得到为人物基地址,方便以后使用
ReadProcessMemory hProcess, ByVal base + &H254, hp, 4, 0& '得到生命值
ReadProcessMemory hProcess, ByVal base + &H26C, hpmax, 4, 0& '得到生命最大值
ReadProcessMemory hProcess, ByVal base + &H258, mp, 4, 0& '得到真气值
ReadProcessMemory hProcess, ByVal base + &H270, mpmax, 4, 0& '得到真气最大值
ReadProcessMemory hProcess, ByVal base + &H390, name_temp, 4, 0&
ReadProcessMemory hProcess, ByVal name_temp, name(0), 32, 0& '得到人物名称
CloseHandle hProcess
End If
Frame1.Caption = name '显示人物名称
Label2.Caption = "生命值:" & hp & "/" & hpmax '显示生命值
Label3.Caption = "真气值:" & mp & "/" & mpmax '显示真气值
End Sub

现在可以运行一下看看数值是否能正常显示!
5.在来做第二个TimerAdd,设置Enabled = False,Interval属性值为100,期代码如下:

Private Sub TimerAdd_Timer() '加血判断时钟
If Val(Text1.Text) > hp Then '比较当前血量是否比预定值低,是则按下F1健
SendMessage hwd, &H100, &H70, 0& '按住F1键,&H100代表按下,&H70代表F1
SendMessage hwd, &H101, &H70, 0& '松开F1键,&H101代表松开,&H70代表F1
Sleep Val(Text2.Text) '延迟text2中的数值,用val()取数值
End If
End Sub

6.最后就剩下Command了,设置其Caption属性为“开始”,期代码如下:

Private Sub Command1_Click()
If Command1.Caption = "开始" Then '按下标签为“开始”的按钮,激活TimerAdd并改变标签为“停止”
TimerAdd.Enabled = True
Command1.Caption = "停止"
ElseIf Command1.Caption = "停止" Then '刚好和上面相反
TimerAdd.Enabled = False
Command1.Caption = "开始"
End If
End Sub

7.小功告成!运行测试看看,能否实现加血功能!那加蓝、补助技能呢?

8.本次内容重点:SendMessage /通过此函数实现模拟键盘操作功能Sleep /必不可少的延迟函数

9.当然,你看完整个文章或者在测试的时候会发现,这个程序还有很多的漏洞或者说还可以做的更完善,没错,这就是接下来你要做的,还是那句话:“因为我知道你可以做的到”

ps:自己设置按键

SendMessage hwd, &H100, Key(Combo1.ListIndex), 0&
SendMessage hwd, &H101, Key(Combo1.ListIndex), 0&

Private Function Key(Anjian As Long) As Long '用于转换按键的函数
Select Case Anjian
Case 0
Key = &H70 ‘F1
Case 1
Key = &H71 'F2
Case 2
Key = &H72 'F3
Case 3
Key = &H73 'F4
Case 4
Key = &H74
Case 5
Key = &H75
Case 6
Key = &H76
Case 7
Key = &H77
Case 8
Key = &H31 '1
Case 9
Key = &H32 '2
Case 10
Key = &H33 '3
Case 11
Key = &H34
Case 12
Key = &H35
Case 13
Key = &H36
Case 14
Key = &H37
Case 15
Key = &H38
Case 16
Key = &H39 '9
Case 17
Key = &H30 '0
End Select
End Function

我们已经学会如何监视血量达到加血的功能,其实自动攻击和加血的核心原理是一样的,同样是发送消息给游戏窗口,只不过要先通过按Tab键选去身边的怪然后按攻击快捷键打怪。

提示:
SYSKEYDOWN = &H104
KeyDOWN = &H100
KeyUP = &H101
CHAR = &H102
SHIFT = &H10 'Shift键的常数
CONTROL = &H11 'Ctrl键的常数
MENU = &H12 'Windows键的常数
TAB = &H9 'Tab键的常数

[[eax]+ &H798] '&H798 或地址 &H0354AF44 当前目标怪物ID ,为负就是怪,为正就是NPC或玩家,为0则怪物死亡或没有选择
[[eax] + &H408] '人物攻击状态,攻击时为1,无动作为0
[[eax] + &H25C] '&H25C 当前经验值,十进制
[[[[eax]+&h8]+&h24]+&h14] '地上所有物品数量,包含别人打掉地上的物品

2.我们要做一个小程序,用于显示地面上的所有物品。添加一个List控件、一个Time控件,如下图所示。

3.下面添加代码!
3.1.模块:

Option Explicit
'---------------声明函数-----------------------
'得到窗体句柄的函数,FindWindow函数用来返回符合指定的类名( ClassName )和窗口名( WindowTitle )的窗口句柄
Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
'得到窗体控件句柄的函数
Public Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
'得到进程标识符的函数
Public Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As Long, lpdwProcessId As Long) As Long
'得到目标进程句柄的函数
Public Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
'关闭句柄的函数
Public Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
'读取进程内存的函数
Public Declare Function ReadProcessMemory Lib "kernel32.dll" (ByVal hProcess As Long, ByVal lpBaseAddress As Long, ByRef lpBuffer As Any, ByVal nSize As Long, ByRef lpNumberOfBytesWritten As Long) As Long
'参数决定了对进程的存储权限,使用完全控制
Public Const PROCESS_ALL_ACCESS = &H1F0FFF

3.2.Form_Load:
Option Explicit
Dim hwd As Long
Dim pid As Long
Dim hProcess As Long '存放进程句柄

Private Sub Form_Load()
hwd = FindWindow("QElementClient Window", "Element Client")
If hwd = 0 Then
MsgBox "未启动游戏", vbOKOnly, "提示"
Unload Form1
End If
GetWindowThreadProcessId hwd, pid '获取进程标识符
'将进程标识符做为参数,返回目标进程PID的句柄,得到此句柄后
'即可对目标进行读写操,PROCESS_ALL_ACCESS表示完全控制,权限最大
hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid)
If hProcess = 0 Then
MsgBox "不能打开进程", vbOKOnly, "提示"
Unload Form1
End If
End Sub
[/code]

3.3.Timer_Timer:

'Timer.interval=1000,利用1秒的延迟显示列表

Private Sub Timer1_Timer() '显示地面物品名称列表
Dim base As Long '存储地址
Dim mecxi As Long '存储地址
Dim pn As Integer '循环变量
Dim WpName(65) As Byte '存储物品名称

List1.Clear '用于刷新物品列表
If hProcess Then
ReadProcessMemory hProcess, ByVal &H8C9E54, mecxi, 4, 0
ReadProcessMemory hProcess, ByVal mecxi + &H8, mecxi, 4, 0
ReadProcessMemory hProcess, ByVal mecxi + &H24, mecxi, 4, 0 '得到物品数量
If mecxi <> 0 Then
For pn = 0 To 768 '循环用来判断那个值内存在物品
ReadProcessMemory hProcess, ByVal mecxi + &H18, base, 4, 0
ReadProcessMemory hProcess, ByVal base + pn * 4, base, 4, 0 '从列表中选出地面上物品的地址
If base > 0 Then '判断是否存在物品
ReadProcessMemory hProcess, ByVal base + 4, base, 4, 0
ReadProcessMemory hProcess, ByVal base + &H164, base, 4, 0
ReadProcessMemory hProcess, ByVal base, WpName(0), 64, 0 '得到物品名称
List1.AddItem WpName '添加到List控件
End If
Next pn
End If
End If
End Sub

3.4.Form_Unload:

Private Sub Form_Unload(Cancel As Integer)
CloseHandle hProcess
End Sub


OK了~收工!
接下来我们就能让可恶的配方从此在背包中消失

2.首先看看图片,我要用到那些控件

主要控件:
List1 /用于显示地面物品
List2 /用于显示需要过滤的物品名称
Timer1 /用于刷新地面物品
Timer2 /用于过滤地面物品
Combo1 /用于添加或保存过滤物品名称
Command1 /添加按钮
Command2 /删除按钮

模块添加:

'存储进程内存的函数
Public Declare Function WriteProcessMemory Lib "kernel32" (ByVal hProcess As Long, lpBaseAddress As Any, lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long

3.我们来看看过滤物品的核心代码,以下为Timer2_Timer代码:

Private Sub Timer2_Timer()
Dim base As Long '存储地址
Dim mecxi As Long '存储地址
Dim WpNameT As Long '存储地址
Dim pn As Integer '循环变量
Dim WpName(65) As Byte '存储物品名称
Dim x As Integer

If hProcess Then
ReadProcessMemory hProcess, ByVal &H8C9E54, mecxi, 4, 0
ReadProcessMemory hProcess, ByVal mecxi + &H8, mecxi, 4, 0
ReadProcessMemory hProcess, ByVal mecxi + &H24, mecxi, 4, 0 '得到物品数量
If mecxi <> 0 Then
For pn = 0 To 768 '循环用来判断那个值内存在物品
ReadProcessMemory hProcess, ByVal mecxi + &H18, base, 4, 0
ReadProcessMemory hProcess, ByVal base + pn * 4, base, 4, 0 '从列表中选出地面上物品的地址
If base > 0 Then '判断是否存在物品
ReadProcessMemory hProcess, ByVal base + 4, base, 4, 0
ReadProcessMemory hProcess, ByVal base + &H164, WpNameT, 4, 0
ReadProcessMemory hProcess, ByVal WpNameT, WpName(0), 64, 0 '得到物品名称
For x = 0 To List2.ListCount - 1 '用循环查找是否是过滤表内要过滤的物品
If InStr(WpName, List2.List(x)) > 0 Then '用InSet()进行对比,存在过滤表内容则过滤
WriteProcessMemory hProcess, ByVal base + &H110, 0, 4, 0 '变ID为0,有捡物品动作但背包内无此物品
Label1.Caption = "已过滤:" & CStr(WpName) '过滤提示,观察用
End If
Next x
End If
Next pn
End If
End If
End Sub

3.1.地面上的物品得用一个0 to 768循环来判断哪个值内存在物品,比如说地面上有四个物品,则0 to 768里面就会有4个值是存在着物品,也就说游戏最多能显示769件地面物品。

3.2.首先判断地面上是否存在物品,如果存在则循环769次查找物品,当找到0 to 768中的一个物品时,程序得到物品的名称,接下来用一个循环来对比物品名称与过滤表做对比,如果为要过滤的物品则改变当前物品在内存的ID,游戏程序再执行捡取动作就会实现过滤功能,物品检起来了,但是并不在背包内,也没有捡到物品的提示。

4.最后就是过滤表的制作了,这个很简单,只要有添加项目和删除项目的功能就可以。这里我用到Combo控件作为输入框,因为可以记录一些可能会用到的物品名称。

Private Sub Command1_Click() '添加物品名称
If Combo1.Text <> "" Then List2.AddItem Combo1.Text
End Sub

Private Sub Command2_Click() '删除物品名称
If List2.ListIndex <> -1 Then List2.RemoveItem (List2.ListIndex) '当选中某项则删除某项
End Sub

5.最后的成品!标签显示了已经过滤的物品。最后,默哀5分钟以悼念做测试时销毁的5个大补丸

下面我们来实现刷新背包

1.在模块添加以下代码:

'读配置文件
Public Declare Function GetPrivateProfileString& Lib "kernel32" Alias "GetPrivateProfileStringA" (ByVal lpApplicationName As String, ByVal lpKeyName As String, ByVal lpDefault As String, ByVal lpReturnedString As String, ByVal nSize As Long, ByVal lpFileName As String)

2.以下是用于刷新背包物品名称Timer3的代码:

Private Sub Timer3_Timer() '用于刷新背包物品名称
Dim base As Long '存储地址
Dim mecxi As Long '存储地址
Dim pn As Integer '循环变量
Dim pd As Long '判断变量
'Dim BaoName(35) As Byte '存储物品名称
Dim BaoN As Long '背包格数

List3.Clear '清除,用于刷新物品列表
If hProcess Then
ReadProcessMemory hProcess, ByVal Buffer, mecxi, 4, 0
ReadProcessMemory hProcess, ByVal mecxi + &H24, mecxi, 4, 0
ReadProcessMemory hProcess, ByVal mecxi + &H854, mecxi, 4, 0
ReadProcessMemory hProcess, ByVal mecxi + &H10, BaoN, 4, 0 '得到背包格数
ReadProcessMemory hProcess, ByVal mecxi + &HC, mecxi, 4, 0
For pn = 0 To BaoN - 1 '循环查询背包内所有格子内的物品
ReadProcessMemory hProcess, ByVal mecxi + pn * 4, base, 4, 0
ReadProcessMemory hProcess, ByVal base + &H14, pd, 4, 0 '得到格子内物品数量,因为没有确认格内存在物品的地址,所以暂用这个判断是否存在物品
If pd > 0 Then '大于0表示存在物品
ReadProcessMemory hProcess, ByVal base + &H8, base, 4, 0 '得到物品ID
ItemList (CStr(base)) '交给转换名称函数处理
'ReadProcessMemory hProcess, ByVal base + &H44, base, 4, 0
'ReadProcessMemory hProcess, ByVal base + &HE, BaoName(0), 34, 0
'If InStr(BaoName, "\r") - 1 > 0 Then List3.AddItem Left(BaoName, InStr(BaoName, "\r") - 1)
End If
base = 0
pd = 0
Next pn
Frame3.Caption = "背包物品 * " & List3.ListCount '根据list3的项目数得到物品数量
End If
End Sub

3.自建一个函数用于把得到ID转换成物品名称,以下是代码:

Private Sub ItemList(ItemName As String) '用于转换物品名称函数
Dim name As String '存储物品名称
Dim dz As String '存放文件地址
Dim a As Integer '用于判断
name = Space$(35) '定义读取值的字串宽度
a = GetPrivateProfileString("item", ItemName, "", name, 35, App.Path & "\config.ini") '在文件中查找匹配物品名称
name = Trim$(name) '去掉多余字符
If a = 0 Then '输出到List的判断
List3.AddItem ItemName & "****" '当无匹配项目则输出物品ID和无资料
Else
List3.AddItem name
End If
End Sub

4.我用config.ini保存物品名称对应表
[item]
28=精炼石
34=菜鸟布衣(男)
35=丝衣(男)
36=缎衣(男)
38=掩心甲(男)
39=鳞甲(男)
40=金缕甲(男)
41=豹皮战甲(男)
42=狮蛮甲(男)
…………

5.程序运行,物品名称正确的显示出来,不过随着游戏的更新你也许需要不断的更新你的文config.ini。

注:
背包物品地址:
[[[[&H8C9E54]+&h24]+&h854]+&h10] 是角色背包最大容量
[[[[&H8C9E54]+&h24]+&h854]+&hC] 是角色背包首地址
[[[[[&H8C9E54]+&h24]+&h854]+&hC]+4*格子序号] 是格子物品首地址(格子数从0开始)
[[[[[[&H8C9E54]+&h24]+&h854]+&hC]+4*格子序号]+&h14] 是此格物品的数量
[[[[[[&H8C9E54]+&h24]+&h854]+&hC]+4*格子序号]+&h18] 是此格物品的堆叠上限
[[[[[[&H8C9E54]+&h24]+&h854]+&hC]+4*格子序号]+&h4] 背包内物品类型
(装备&戒指&项链为0,生产材料&声望材料为1,药品为2,精炼石为7,垃圾石头为8,配方为17,宠物牌为23)
[[[[[[&H8C9E54]+&h24]+&h854]+&hC]+4*格子序号]+&h8] 物品ID

GetPrivateProfileString:
DWORD GetPrivateProfileString( LPCTSTR lpAppName, LPCTSTR lpKeyName, LPCTSTR lpDefault, LPTSTR lpReturnedString, DWORD nSize, LPCTSTR lpFileName);

参数的意义:
lpAppName : 配置文件的section名
lpKeyName : 配置文件的key名
lpDefault : 如果INI文件中没有前两个参数指定的字段名或键名,则将此值赋给变量.
lpReturnedString : 接收INI文件中的值的CString对象,即目的缓存器.
nSize : 目的缓存器的大小.
lpFileName : 是完整的INI文件名.

例:
config.ini的内容:
[item]
28=精炼石
34=菜鸟布衣(男)
35=丝衣(男)
36=缎衣(男)
38=掩心甲(男)
39=鳞甲(男)
40=金缕甲(男)
41=豹皮战甲(男)
42=狮蛮甲(男)

GetPrivateProfileString& "item", 36, "", name, 35, App.Path & "\config.ini")

在[item]下,找36这项,将值存入变量name,宽度为35,文件地址为App.Path & "\config.ini"

最后…

内容:
1.我们将使用EnumWindows枚举Windows所有窗口。首先来看看MSDN说明:
函数功能:该函数枚举所有屏幕上的顶层窗口,办法是先将句柄传给每一个窗口,然后再传送给应用程序定义的回调函数。EnumThreadWindows函数继续到所有顶层窗口枚举完为止或回调函数返回FALSE为止函数原型:BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam);

参数:

lpEnumFunc:指向一个应用程序定义的回调数指针,请参看EnumWindowsProc。

lPararm:指定一个传递给回调函数的应用程序定义值。

返回值:如果函数成功,返回值为非零;如果函数失败,返回值为零。若想获得更多错误信息,请调用GetLastError函数。

2.模块部分:

Option Explicit
'得到窗口的标题条文本
Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
'枚举所有屏幕上的顶层窗口
Declare Function EnumWindows Lib "user32" (ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long

Function EnumWindowsProc(ByVal hwnd As Long, ByVal lParam As Long) As Boolean
Dim S As String
S = String(80, 0)
Call GetWindowText(hwnd, S, 80)
S = Left(S, InStr(S, Chr(0)) - 1)
If Len(S) > 0 Then Form1.List1.AddItem S
EnumWindowsProc = True
End Function

3.Form部分,我们需要一个List控件用于显示:

Private Sub Form_Load()
EnumWindows AddressOf EnumWindowsProc, 0&
End Sub

4.运行程序,显示了所有窗口的文本。

5.好了,我们完成了核心程序。
现在我们要接着往下做三个工作:(1)只显示我们想要的窗口、(2)显示人物名称、(3)选择目标人物连接游戏窗口。

5.1.筛选枚举出来的窗口。这个很简单,只要在插入List之前的If中再加入一个判断窗口文本内容就可以实现。

If Len(S) > 0 and UCase(S) = "ELEMENT CLIENT" Then Form1.List1.AddItem S

5.2.显示人物名称,我想这个大家都会吧!

If Len(S) > 0 And UCase(S) = "ELEMENT CLIENT" Then
Dim ProcID As Long, hpID As Long, BuffEnum(35) As Byte, ECXI As Long, eax As Long, Str As String

GetWindowThreadProcessId hwnd, hpID
ProcID = OpenProcess(PROCESS_ALL_ACCESS Or PROCESS_VM_OPERATION Or PROCESS_VM_READ Or PROCESS_VM_WRITE, False, hpID)

ReadProcessMemory ProcID, ByVal &H8C9E54, ECXI, 4, 0 '这个是存放基址的地址
ReadProcessMemory ProcID, ByVal ECXI + &H24, eax, 4, 0
ReadProcessMemory ProcID, ByVal eax + &H390, ECXI, 4, 0
ReadProcessMemory ProcID, ByVal ECXI, BuffEnum(0), 36, 0
Str = Left$(BuffEnum, 36)
Form1.List1.AddItem hpID & " " & Str ‘这里我耍了一个小聪明,可以省好多事。
End If

5.3.选择目标窗口连接游戏。
当然要在List1_Click中插入代码了。我使用两个Form,先显示Form1选择游戏窗口,传递游戏窗口的PID给Form2,然后再Form2里OpenProcess即可。所以我的List1_Click代码:
Form2.Show
Me.Hide

6.写到这里,后面的大家就应该知道怎么吧。这次就不公布源码了,因为特征码的关系遭封杀的几率太大,另外这样也好给大家留出一些设计的空间。

注意:
如果你在原有程序的基础上,添加新的Form2用于选择游戏窗口,请设置从Form2启动。
程序关闭时别忘卸载隐藏的Form。

相关日志

楼被抢了 3 层了... 抢座Rss 2.0或者 Trackback

  • pxm

    这篇文章应该是外挂研究院的眼镜是原创吧~

  • hysia

    这样写容易被封,最好加上动态随机程序标题,就像冰刃 一样变换标题,尽量搞乱码字符做程序 :smile: 标题.

  • 鬼仔

    感谢提醒,我刚才去游戏外挂研究院看了下
    现在文中标明作者了。

发表评论