Python 开发植物大战僵尸杂交版辅助【全网最详细_查找 + 代码编写一体化零基础也能学会】

安全不再安全 2024-08-25 17:35:04 阅读 67

目录

辅助最终展示效果

一、文章介绍

二、工具介绍

三、基址搜索

3.1、寻找阳光基址

3.2、寻找卡槽冷却基址

3.3、寻找僵尸刷新时间基址

3.4、寻找大阳光刷新时间基址

3.5、寻找植物编号基址

3.6、寻找场上僵尸数量基址

3.7、寻找僵尸 X 坐标基址

3.8、通过找到的僵尸 X 轴坐标基址寻找 Y 轴、血量等基址

3.9、寻找每只僵尸之间的地址偏移

四、代码编写

4.1、基础代码

4.2、功能代码

4.2.1、编写实现无限阳光

4.2.2、编写植物无冷却

4.2.3、编写僵尸全出功能

4.2.4、编写无限刷新大太阳功能

4.2.5、编写植物替换功能

4.2.6、编写获取场上僵尸数量功能

4.2.7、编写获取获取僵尸数组功能

4.2.8、编写绘制僵尸方框功能

4.3、完整代码

五、总结


辅助最终展示效果

演示视频

一、文章介绍

        写这篇文章的目的是告诉大家游戏单机辅助是如何编写的,这种方法在其他部分非单机游戏也能适用,开发者该如何去防范,知攻善防,并不鼓励大家写游戏外挂。

        游戏辅助本身就是利用游戏漏洞进行开发的,在单机游戏中,游戏数据都是存储在本地的,运行游戏后通过使用相应的内存查找工具获取到当前游戏数据的存储地址,通过对其数据进行修改来达到游戏规则之外的效果,使玩家可以轻松通过。

        通过本篇文章的学习我们就可以知道辅助开发者是如何获取到游戏数据的,对我们游戏开发人员是有一定的帮助的,比如我们可以对游戏数据进行加密,增加辅助开发的难度,来尽量维持游戏的平衡等......

二、工具介绍

1、CE(内存搜索工具)

2、植物大战僵尸杂交版 2.0.88

3、精易助手 (后文用到的时候有附带下载链接,读者现在不必理会)

4、PyCharm Community Edition 2023.3.4(Python 开发环境)

5、SafeDog (Python 游戏安全测试模块,需要模块联系我

【版本不需要和我一样也可以,不过我所用的 Python 是  Python3 ,这点大家尽量和我保持一样,因为有些代码的使用在 Python2 中区别比较大】

三、基址搜索

3.1、寻找阳光基址

打开游戏和 CE 工具并附加进程

附加完成后进入游戏关卡

CE 中填写设置相关信息

回到游戏种植植物或者拾取太阳使阳光数量发生变化

在 CE 中搜索 375,其他东西不用变,然后点击 Next Scan(下一次扫描,是在第一次的基础上进行扫描的),注意不是 New Scan

只剩一条数据,那么这条就是阳光的地址了,不过这只是临时地址,也就是说,游戏关闭再开地址就变了,我们如果要制作辅助就需要找到数据的基址(不会变动的,不关游戏关了几次或是重新安装,都不会变)

然后会打开如下窗口,这个窗口不要关,我们回到游戏再次改变阳光的数量,观察窗口的变化

这个 eax + 5560 的 5560 就是我们的一级偏移

选择 New Scan 再选 First Scan

这里告诉大家怎么去找多级指针,我们找地址比较特殊的和其他差别比较大的那种地址,如图所示

所以我们就右键查看是谁访问了他,这里要选访问,而不是改写

找到这种数据就要偷笑了,因为像这种很整齐的而且偏移都是一样的大概率就是找对了,我们再回到游戏改变一下阳光的数量

因为我们第一次找到的是 eax + 5560 所以这里要找 mov eax 开头的,这些是汇编知识了,寄存器相关的,大家不懂可以去找书或视频看,这里先看操作学习思路

双击打开

继续搜索,其他操作不变,然后你就会发现找到了基址了,当然了如果你的 CE 版本和我的不同他可能显示的是十六进制的地址,而不是名称,都这无所谓,他们都是绿色的,这里我试过了,每一条都可以作为基址,随便选一条就好了,这里我选择最后一条

填完之后点击 OK,然后就会发现关闭游戏再打开它仍然有效(注意 CE 要重新附加进程),写游戏辅助的时候就需要用到它了

PlantsVsZombies.exe+2AA00C + 768 + 5560

3.2、寻找卡槽冷却基址

        好了,通过查找阳光基址相信读者已经大致了解了 CE 的简单使用了,那么接下来的内容里我就不一一截图解释了,部分通过文字的描述来给大家讲解,操作都是大同小异的,聪明的你肯定能明白。

        寻找卡槽基址有什么用呢?当然是实现植物无冷却啊,我们首先要了解游戏的冷却原理,没次种植植物后,植物就会在卡槽中进入生长时间也就是我们说的冷却时间。在植物大战僵尸中冷却结束表示 1,冷却开始表示 0(也就是拿起植物但不种植时卡槽的状态)。

一直重复拿起搜索 0,放下搜索 1,获取搜索完后不要返回游戏继续点击搜索未变动的数值,过滤掉那些变动的值,因为我们没有对卡槽进行操作,那么它对应的值就不会变

扫到 200 多条数据的时候就差不多了,因为不可能每次都和找阳光那样一下就找得只剩下一条,有些数据过滤完还剩下几千条也有,接下来就需要自己去判断了。

点第一条数据,然后把滚动条拉倒中间,按住键盘上的 shift 键然后再随便选一条数据点一下

通用的方法按 shift 选择一半的数据然后按回车对值进行修改,如果我们的卡槽现在是亮的我们就改成 0,反之改成 1 然后按回车全部锁定,再看一下游戏的卡槽里吗有没有变暗(亮),如果有说明正确的地址在我们选择的数据中,另外一部分没选的数据就可以删除了,然后再利用二分法继续寻找,直到找到正确的地址。

接下来就用同样的方法寻找基址即可,不过需要注意的是,要把数据类型改回 4 Byte

这样就可以找到第一个卡槽冷却时间的基址了

PlantsVsZombies.exe+2AA00C + 768 + 144 + 70

通用的方法我们去寻找第二个第三个卡槽的冷却时间,我就会发现他们的基址分别如下:

第二个卡槽:PlantsVsZombies.exe+2AA00C + 768 + 144 + C0

第三个卡槽:PlantsVsZombies.exe+2AA00C + 768 + 144 + 110

他们之间偏移相差 50 (十六进制),那么以此类推就可以知道后面所有卡槽的冷却基址了,我们只需要通过写循环就可以实现全卡槽无冷却。

3.3、寻找僵尸刷新时间基址

        找到关卡进度条基址我们就可以实现僵尸全部出现,然后快速通过。我们首先要知道,植物大战僵尸中,每一波僵尸出现的间隔是一个倒计时,比如第一波出现了那么就进入倒计时,时间一到就刷新第二波僵尸,我们要找进度条基址也就是找到这个间隔的时间地址,把它改成 1 并且锁定,那么僵尸就会一次性全部出现。

【注意关卡刚开始还未出僵尸的时候也是在倒计时,这时候就可以开始扫描了,当然了一定要卡好时间,因为我们在倒计时期间扫描的是减少的值,一不小心僵尸刷新了,这时候值是变大了重新开始倒计时,如果还扫描变少的值那就错了,这时候应该扫描变大的值,进入游戏然后再扫描变少的值......】

进入游戏然后暂停,搜索值减少了

一直重复进游戏暂停扫描值减少,直到第二波僵尸出现就搜索值增大了,然后继续重复前面的步骤

通过观察发现这个数据一直重复变小然后变大,变小到一定数的时候僵尸就出现,说明这个就是时间地址了,同样的方法区寻找基址即可,这里就不再赘述。

值改成 1 ,锁定然后进入游戏看效果

PlantsVsZombies.exe+2AA00C + 768 + 559C

3.4、寻找大阳光刷新时间基址

        找大阳光的刷新时间和找僵尸的刷新时间是一样的思路,每次刷新的间隔都是在倒计时,不过阳光刷新的时间要比僵尸刷新的时间短

找到后把值改成 1,然后锁定,进入游戏观察效果

PlantsVsZombies.exe+2AA00C + 768 + 5538

3.5、寻找植物编号基址

通过图鉴我们可以观察到,每个植物是有序号的,如下图所示:

        那么我们就可以通过找到这个表示序号的地址,然后令其改变成其他序号的值,比如樱桃辣椒的序号是 20,那么我们把地址的值改成 20,然后锁定,就会发现在游戏中不管点那个植物种下去的都是樱桃辣椒。

        我们进入游戏按图鉴的序号选择植物,目的是方便我们进行扫描,选择第一个卡槽植物就表示 0,第二个就表示 1,这样就不需要单独去记住植物的序号了。

开始游戏,配置好 CE

我现在拿起坚果,但是并没有种植,坚果在第四个卡槽,编号表示 3,所以我们在 CE 中搜索精确数值 3

把坚果返回去,不要种植,然后再换一个植物拿起,输入编号继续扫描,重复次操作

        找到了两条数据,我们需要进行判断,把其中一条改成 20,然后锁定,看一下游戏中拿起植物后会不会发生变化,会就是锁定的那条数据。(不一定是 20 ,也可以是其他植物编号,我习惯用 20 表示樱桃辣椒)

如图所示,我们是没有选择樱桃辣椒的,但是锁定了第二条数据后,选择任何植物都是樱桃辣椒,那就说明第二条就是植物序号的临时地址了,同样的寻找方法找到该数据的基址。

注意,这个数据的值在选中植物才会表示植物的编号,未选中时是一个比较大的值,这个不用管它,要用的时候直接改成对应的编号然后锁定就好了

PlantsVsZombies.exe+2AA00C + 768 + 138 + 28

3.6、寻找场上僵尸数量基址

        寻找僵尸数量可以用另一种方式,因为僵尸个数是整数同时也比较小,一般第一波出场都几只,然后第二波再慢慢增多,一开始我们就可以把范围缩小。也可以观察僵尸死亡情况使用值减少的方法进行扫描。

扫描后进入游戏

        成功找到场上僵尸数量基址了,这个目前没什么用,但是对后面我们绘制僵尸方框的时候就派上用场了,通过场上僵尸的数量来判断需要绘制多少个方框。虽然绘制方框通常是用在 FPS 游戏中的方框透视,在植物大战僵尸中没什么用处,但是原理是差不多的,直到这里怎么绘制,通用就明白了在 FPS 中该如何绘制

PlantsVsZombies.exe+2AA00C + 768 + A0

3.7、寻找僵尸 X 坐标基址

        找僵尸相关的数据有点特殊,并不是每只僵尸都可以找到,需要通过找到主僵尸(这里我把它称为主僵尸),我们先来分析一下原理:

        每当僵尸死亡后它的地址就重新赋给新生成的僵尸,所以地址并不是固定某一只僵尸,通过我不断的尝试我发现一个规律,主僵尸的地址通常在第二波僵尸中,第一波是没有的,所以我们可以等到第二波僵尸出现的时候再扫描,接下来我会在下图文中详细讲解。

        因为坐标数据是以浮点型来保存的,所以我们要搜索浮点型才能找到,然后让游戏开起来,等待第二波僵尸出现,当然也可以在第一波僵尸出现就开始扫,说不定还有可能在第一波就出现,为了讲解更详细,这里我在第一波僵尸出现后就开始扫描

        扫描的方法:僵尸在向前移动的过程中,x 坐标的值是一直在减小的,所以我们扫描值减少的数据就好了

在第一波僵尸出现后就可以开始扫描了

找到只剩下几十条数据的时候我们就可以手动排除了,如上图所示框起来的那种,值是负数或者值是个位数开头的全部都可以排除留下百位的数据

这种第一次扫描就出现绿色的也可以直接排除,基址一般不会第一次扫描就出来,手动分析完剩下六条数据,再看游戏发现场上也是六只僵尸,那么这六条分别对应尝试的某一只僵尸,里面就很可能有我们要的主僵尸地址

每一条每一条看,第一次右键谁改写了该地址

然后如果在找一级指针的时候第一次扫描就出现了三条或者四条数据的那就证明找到了主僵尸的临时地址,再接着往下找就一定可以找到主僵尸基址,其他的地址在找一级偏移的时候出现的都是两条地址,这是我在寻找的过程中发现的规律,可以提高大家寻找的速度。

如下图所示就是非主僵尸地址扫描后的结果:

在我这个版本的植物大战僵尸杂交版中扫描出来的非主僵尸地址都是两条数据

好,我们回到正题,接着主僵尸地址继续扫描二级偏移

那么我们就成功找到了主僵尸的 x 坐标基址了,通过这个地址添加偏移就可以获得其他僵尸的 x 坐标基址

PlantsVsZombies.exe+2AA00C + 768 + 90 + 2C

3.8、通过找到的僵尸 X 轴坐标基址寻找 Y 轴、血量等基址

        上一小节我们讲过了如何寻找僵尸的 x 轴坐标基址,这小节我们就讲解如何通过找到的 x 坐标基址来找僵尸的其他数据基址,比如:y 轴坐标基址,血量基址等。

        僵尸的数据其实是保存在一个结构体中的,有个僵尸数值,里面就包含了每只僵尸的数据,只要找到其中一条数据的基址那么就可以通过偏移来寻找到其他数据的基址。

刚开始的话我是一个一个试来判断每条地址的意思,这种方式很笨,但是也能找到,有点教程是让你剖析结构体来分析,其实差不多的

然后在这里我就先告诉大家,其他地址的偏移

在 x 轴基址上添加偏移 4 找到僵尸 y 轴基址

在 x 轴基址上添加偏移 9C 找到僵尸 y 轴基址

【都是十六进制的加减法】

那么偏移 + 4 就表示下一个地址,如下图所示

我现在是知道了偏移来给你们分析,如果遇到一个新游戏那就得自己去尝试分析不同地址代表什么然后让其和初始地址相减就可以得到偏移,得到偏移的好处就是可以在初始基址的基础上通过加找到的偏移来得到其他数据的基址,不需要我们重新去扫描然后再分析找基址。

我们已经知道 x 轴 + 4 偏移得到 y 轴,那么就可以在 x 轴基址偏移的基础上加 4 得到 y 轴基址

PlantsVsZombies.exe+2AA00C + 768 + 90 + 30 【主僵尸 y 轴基址】

        同样的方法找主僵尸血量,这个偏差有点大,通过这种方法难找,我给大家说一下另一种方法,和找 x 坐标是一样的,通用是要等主僵尸出现,然后再去找,要先判断出主僵尸是谁,我们可以通过前面找到的主僵尸 x 坐标去判断,改变 x 坐标看看谁动了,动了的就是主僵尸,然后让植物攻击它,搜索减少的值,要保证主僵尸不会死去,接下来的操作都是一样的,直到找到血量基地址。

这里我直接给大家写出来了,前面我们说过了,血量地址就是 x 坐标地址 + 9C,用计算机计算一下:

所以僵尸血量的偏移是 C8

PlantsVsZombies.exe+2AA00C + 768 + 90 + C8 【主僵尸血量基址】

3.9、寻找每只僵尸之间的地址偏移

        回顾 3.7 小节的内容,我们是不是找到了所有在场僵尸的 x 轴临时地址,包括主僵尸的临时地址,哪些非主僵尸的地址虽然说不能找到他们的基址,但是可以通过临时地址相减来找到他们之间的偏移。

        我们继续 3.7 找到的数据来讲解:

1438004C 是我们主僵尸 x 轴的临时地址,通过观察这个地址的排序是升序,也就是越往下越大,并且每个地址之间的差值是一样的,都是 204 ,这个其实就是每只僵尸之间的偏移。

也就是说不管以后找到僵尸的什么数据的基址,只要偏移 + 204 那就是别的僵尸的该数据的基址,通过这个偏移和 3.6 小节我们找到的僵尸数量利用循环我们就可以对场上所有的僵尸进行方框的绘制,或者是配合僵尸的状态来秒杀所有僵尸,当然了,由于篇幅原因,本篇文章就不讲解僵尸秒杀是如何实现的。

四、代码编写

4.1、基础代码

<code>import SafeDog # 辅助开发模块

from ctypes import * # C 语言类型库

import win32process

from win32api import *

from win32process import *

from win32gui import FindWindow

PROCESS_ALL_ACCESS = (0x000F0000|0x00100000|0xFFF) # 调用最高权限打开一个进程

kernel32 = windll.LoadLibrary("kernel32.dll") # 加载内核模块

window_handle = FindWindow(None,"植物大战僵尸杂交版v2.0.88") # 获取窗口句柄

process_id_read = win32process.GetWindowThreadProcessId(window_handle)[1] # 获取进程 ID

process_handles = OpenProcess(0x1F0FFF,False,process_id_read) # 以最高权限打开进程,获取进程句柄

# 获取模块地址

def getModuleAddress():

address = SafeDog.getModuleAddress(process_handles,0x2AA00C)

result = SafeDog.readMemoryInt(process_handles,address)

return result

这里要改的地方可能就只有窗口的标题,如果和我的游戏版本一样的话就可以不用改

不同的版本可能不一样,这里我推荐一个工具精易编程助手,网上搜索可以下载

下载链接:精易编程助手 (125.la)

获取模块地址也就获取 PlantsVsZombies.exe 在内存中的初始地址,该地址加上偏移就是我们的基址 PlantsVsZombies.exe+2AA00C

功能我封装在了模块的 getModuleAddress 方法中,需要模块可联系我,有了模块就可以直接调用。

4.2、功能代码

4.2.1、编写实现无限阳光

3.1 小节中我们找到了阳光的基址 PlantsVsZombies.exe+2AA00C + 768 + 5560

<code>def changeSunNumber():

addFirsP = getModuleAddress() # 获取模块基址

addSeconP = addFirsP + int(0x768) # 通过添加偏移找到一级指针

addSecondPoint = SafeDog.readMemoryInt(process_handles,addSeconP) # 一级指针保存的是二级指针的地址,通过读取内存整数型获取到二级指针地址

addThirP = addSecondPoint + int(0x5560) # 通过二级指针添加偏移找到临时地址

sun = 99999

if (SafeDog.writeMemoryInt(process_handles,addThirP,sun)):

print("[+]The sunlight value is modified")

调用 SafeDog 模块的 readMemoryInt 方法读内存整数型,找到应该基地址并调用 writeMemoryInt 方法写内存整数型修改阳光数量为 99999

4.2.2、编写植物无冷却

卡槽冷却基址:PlantsVsZombies.exe+2AA00C + 768 + 144 + 70  相邻卡槽偏移 50

def ignoreCooling():

print("植物无冷却执行中...")

addFirstP = getModuleAddress()

addSeconP = addFirstP + int(0x768)

addSecondPoint = SafeDog.readMemoryInt(process_handles,addSeconP)

addThirP = addSecondPoint + int(0x144)

addThirdPoint = SafeDog.readMemoryInt(process_handles,addThirP)

for i in range(0,14):

num = i * 80

addFourP_Cardslots = addThirdPoint + (int(0x70) + num)

data = 1

if (SafeDog.writeMemoryInt(process_handles, addFourP_Cardslots, data)):

print(f"[+]Ignore {i + 1} cooling change successful")

其他代码是一样的,这里就不赘述了,主要说一下循环那一块,一共是 14 个卡槽(当然如果刚开始玩卡槽是没这么多的,读者根据自己的需求对循环次数进行修改即可)

range 的值是左闭右开区间,也就是第二个参数 14 取不到,最大值只会取到 13,所以 0 ~ 13 一共循环 14 次,每次循环都让 num 值在原来的基础上 + 80

十六进制的 50 转十进制就是 80,通过传入 1,也就是令倒计时时间为 1 ,就能实现修改卡槽无冷却

需要注意的是这只是封装在一个函数中,所以直接调用这个函数只会循环一次,也就是刷新一次,执行完毕之后,游戏卡槽冷却又会进入倒计时,无法实现无限无冷却,要想实现需要在调用时通过 while 来实现循环调用才能实现无冷却功能。

<code>if __name__ == '__main__':

while True:

ignoreCooling()

4.2.3、编写僵尸全出功能

僵尸冷却时间基址:PlantsVsZombies.exe+2AA00C + 768 + 559C

def monsterAllAppear():

addFirstP = getModuleAddress()

addSeconP = addFirstP + int(0x768)

addSecondPoint = SafeDog.readMemoryInt(process_handles,addSeconP)

addThirP = addSecondPoint + int(0x559C)

data = 1

if(SafeDog.writeMemoryInt(process_handles,addThirP,data)):

print("[+]All the monsters appeared")

4.2.4、编写无限刷新大太阳功能

大太阳刷新时间基址:PlantsVsZombies.exe+2AA00C + 768 + 5538

def sunWithoutCooling():

print("阳光刷新无冷却执行中...")

addFirstP = getModuleAddress()

addSeconP = addFirstP + int(0x768)

addSecondPoint = SafeDog.readMemoryInt(process_handles,addSeconP)

addThirP = addSecondPoint + int(0x5538)

data = 1

SafeDog.writeMemoryInt(process_handles,addThirP,data)

4.2.5、编写植物替换功能

植物编号基址:PlantsVsZombies.exe+2AA00C + 768 + 138 + 28

def changePlant(id):

print("植物替换却执行中...")

addFirstP = getModuleAddress()

addSeconP = addFirstP + int(0x768)

addSecondPoint = SafeDog.readMemoryInt(process_handles,addSeconP)

addThirP = addSecondPoint +int(0x138)

addThirdPoint = SafeDog.readMemoryInt(process_handles,addThirP)

addFourP = addThirdPoint + int(0x28)

data = id

if(SafeDog.writeMemoryInt(process_handles,addFourP,data)):

print("[+]Plant change successful")

4.2.6、编写获取场上僵尸数量功能

场上僵尸数量基址:PlantsVsZombies.exe+2AA00C + 768 + A0

def getzombieNumber():

addFirstP = getModuleAddress()

addSeconP = addFirstP + int(0x768)

addSecondPoint = SafeDog.readMemoryInt(process_handles,addSeconP)

addThirP = addSecondPoint + int(0xA0)

Zombie_Numbers = SafeDog.readMemoryInt(process_handles,addThirP) # 读取到数量

return Zombie_Numbers

4.2.7、编写获取获取僵尸数组功能

PlantsVsZombies.exe+2AA00C + 768 + 90 + C8 【主僵尸血量基址】

PlantsVsZombies.exe+2AA00C + 768 + 90 + 2C 【主僵尸 x 轴基址】

PlantsVsZombies.exe+2AA00C + 768 + 90 + 30 【主僵尸 y 轴基址】

def zombieInformation():

x = []

y = []

b = []

addFirstP = getModuleAddress() # 获取模块基址

addSeconP_ZBP = addFirstP + int(0x768)

addSecondPoint_ZBP = SafeDog.readMemoryInt(process_handles,addSeconP_ZBP)

addThirP_ZBP = addSecondPoint_ZBP + int(0x90)

addThirdPoint_ZBP = SafeDog.readMemoryInt(process_handles,addThirP_ZBP)

Zomb_X_Offset = int(0x2c)

for i in range(0, 4):

# x 轴

addFourP_x = addThirdPoint_ZBP + Zomb_X_Offset

addFourPoint_x = SafeDog.readMemoryFloat(process_handles, addFourP_x)

# y 轴

addFourP_y = addThirdPoint_ZBP + Zomb_X_Offset + int(0x4)

addFourPoint_y = SafeDog.readMemoryFloat(process_handles, addFourP_y)

# 血量

addFourP_b = addThirdPoint_ZBP + Zomb_X_Offset +int(0x9C)

addFourPoint_b = SafeDog.readMemoryInt(process_handles, addFourP_b)

x.append(addFourPoint_x)

y.append(addFourPoint_y)

b.append(addFourPoint_b)

Zomb_X_Offset += int(0x204)

return x,y,b

用列表来保存不同僵尸的信息,不过代码这么写效率很低,所以这里我只获取了 4 只僵尸的信息作为演示,正确的写法应该使用多线程来执行

该函数返回的是僵尸信息的列表,调用该函数的时候需要使用三个列表来进行接收,x 表示僵尸 x 坐标数据、y 表示僵尸 y 坐标数据、b 表示僵尸血量数据,该函数是为下面绘制僵尸方框功能做准备的。

0x4 表示 x 轴基址偏移 +4 为 y 轴基址

0x9C 表示 x 轴基址偏移 +9C 为血量基址

0x204 表示获取下一只僵尸的数据

4.2.8、编写绘制僵尸方框功能

<code>def drawZombiePane():

print("绘制僵尸方框执行中...")

Class_Draw = SafeDog.Draw(window_handle) # 调用模块绘制类

dc, rect = Class_Draw.getDcRect() # 获取屏幕上下文

while True:

for i in range(0, 4):

x, y, b0 = zombieInformation() # 获取僵尸数组信息,列表

x_r = int(x[i] * 1.1) # 坐标换算

y_r = int(y[i] * 0.91)

blood = b0[i]

Class_Draw.getWindowRect(rect) # 获取窗口矩形坐标

l = rect.left + x_r

t = rect.top + y_r

r = l + 70

b = t + 170

if blood > 0: # 血量不为 0 则绘制

Class_Draw.protractRectangle(dc, l, t, r, b, 255, 0, 0) # 绘制方框

line = (170 / 300) * blood # 血量与直线的比值

Class_Draw.protractLine(dc, l - 10, t, l - 10, (b - 170) + int(line), 60, 179, 113) # 绘制血条

4.3、完整代码

import SafeDog # 辅助开发模块

from ctypes import * # C 语言类型库

import win32process

from win32api import *

from win32process import *

from win32gui import FindWindow

PROCESS_ALL_ACCESS = (0x000F0000|0x00100000|0xFFF) # 调用最高权限打开一个进程

kernel32 = windll.LoadLibrary("kernel32.dll") # 加载内核模块

window_handle = FindWindow(None,"植物大战僵尸杂交版v2.0.88") # 获取窗口句柄

process_id_read = win32process.GetWindowThreadProcessId(window_handle)[1] # 获取进程 ID

process_handles = OpenProcess(0x1F0FFF,False,process_id_read) # 以最高权限打开进程,获取进程句柄

# 获取基址

def getModuleAddress():

address = SafeDog.getModuleAddress(process_handles,0x2AA00C)

result = SafeDog.readMemoryInt(process_handles,address)

return result

# 无限阳光

def changeSunNumber():

addFirsP = getModuleAddress()

addSeconP = addFirsP + int(0x768)

addSecondPoint = SafeDog.readMemoryInt(process_handles,addSeconP)

addThirP = addSecondPoint + int(0x5560)

sun = 99999

if (SafeDog.writeMemoryInt(process_handles,addThirP,sun)):

print("[+]The sunlight value is modified")

# 植物无冷却

def ignoreCooling():

print("植物无冷却执行中...")

addFirstP = getModuleAddress()

addSeconP = addFirstP + int(0x768)

addSecondPoint = SafeDog.readMemoryInt(process_handles,addSeconP)

addThirP = addSecondPoint + int(0x144)

addThirdPoint = SafeDog.readMemoryInt(process_handles,addThirP)

for i in range(0,14):

num = i * 80

addFourP_Cardslots = addThirdPoint + (int(0x70) + num)

data = 1

if (SafeDog.writeMemoryInt(process_handles, addFourP_Cardslots, data)):

print(f"[+]Ignore {i + 1} cooling change successful")

# 植物替换

def changePlant(id):

print("植物替换却执行中...")

addFirstP = getModuleAddress()

addSeconP = addFirstP + int(0x768)

addSecondPoint = SafeDog.readMemoryInt(process_handles,addSeconP)

addThirP = addSecondPoint +int(0x138)

addThirdPoint = SafeDog.readMemoryInt(process_handles,addThirP)

addFourP = addThirdPoint + int(0x28)

data = id

if(SafeDog.writeMemoryInt(process_handles,addFourP,data)):

print("[+]Plant change successful")

# 阳光刷新无冷却

def sunWithoutCooling():

print("阳光刷新无冷却执行中...")

addFirstP = getModuleAddress()

addSeconP = addFirstP + int(0x768)

addSecondPoint = SafeDog.readMemoryInt(process_handles,addSeconP)

addThirP = addSecondPoint + int(0x5538)

data = 1

SafeDog.writeMemoryInt(process_handles,addThirP,data)

# 僵尸全出

def monsterAllAppear():

print("僵尸全出却执行中...")

addFirstP = getModuleAddress()

addSeconP = addFirstP + int(0x768)

addSecondPoint = SafeDog.readMemoryInt(process_handles,addSeconP)

addThirP = addSecondPoint + int(0x559C)

data = 1

if(SafeDog.writeMemoryInt(process_handles,addThirP,data)):

print("[+]All the monsters appeared")

# 获取场上僵尸数量

def getzombieNumber():

addFirstP = getModuleAddress()

addSeconP = addFirstP + int(0x768)

addSecondPoint = SafeDog.readMemoryInt(process_handles,addSeconP)

addThirP = addSecondPoint + int(0xA0)

Zombie_Numbers = SafeDog.readMemoryInt(process_handles,addThirP) # 读取到数量

return Zombie_Numbers

# 获取僵尸信息

def zombieInformation():

x = []

y = []

b = []

addFirstP = getModuleAddress() # 获取模块基址

addSeconP_ZBP = addFirstP + int(0x768)

addSecondPoint_ZBP = SafeDog.readMemoryInt(process_handles,addSeconP_ZBP)

addThirP_ZBP = addSecondPoint_ZBP + int(0x90)

addThirdPoint_ZBP = SafeDog.readMemoryInt(process_handles,addThirP_ZBP)

Zomb_X_Offset = int(0x2c)

for i in range(0, 4):

# x 轴

addFourP_x = addThirdPoint_ZBP + Zomb_X_Offset

addFourPoint_x = SafeDog.readMemoryFloat(process_handles, addFourP_x)

# y 轴

addFourP_y = addThirdPoint_ZBP + Zomb_X_Offset + int(0x4)

addFourPoint_y = SafeDog.readMemoryFloat(process_handles, addFourP_y)

# 血量

addFourP_b = addThirdPoint_ZBP + Zomb_X_Offset +int(0x9C)

addFourPoint_b = SafeDog.readMemoryInt(process_handles, addFourP_b)

x.append(addFourPoint_x)

y.append(addFourPoint_y)

b.append(addFourPoint_b)

Zomb_X_Offset += int(0x204)

return x,y,b

# 绘制僵尸方框

def drawZombiePane():

print("绘制僵尸方框执行中...")

Class_Draw = SafeDog.Draw(window_handle) # 调用模块绘制类

dc, rect = Class_Draw.getDcRect() # 获取屏幕上下文

while True:

for i in range(0, 4):

x, y, b0 = zombieInformation() # 获取僵尸数组信息,列表

x_r = int(x[i] * 1.1) # 坐标换算

y_r = int(y[i] * 0.91)

blood = b0[i]

Class_Draw.getWindowRect(rect) # 获取窗口矩形坐标

l = rect.left + x_r

t = rect.top + y_r

r = l + 70

b = t + 170

if blood > 0: # 血量不为 0 则绘制

Class_Draw.protractRectangle(dc, l, t, r, b, 255, 0, 0) # 绘制方框

line = (170 / 300) * blood # 血量与直线的比值

Class_Draw.protractLine(dc, l - 10, t, l - 10, (b - 170) + int(line), 60, 179, 113) # 绘制血条

def menu():

print("----------1、【无限阳光】----------")

print("----------2、【植物无冷却】----------")

print("----------3、【植物替换】----------")

print("----------4、【阳光刷新无冷却】----------")

print("----------5、【僵尸全出】----------")

print("----------6、【获取场上僵尸数量】----------")

print("----------7、【绘制僵尸方框】----------")

print("----------8、【组合功能:快速通关】----------")

user_choose = int(input("请选择功能:"))

if user_choose == 1:

while True: changeSunNumber()

elif user_choose == 2:

while True: ignoreCooling()

elif user_choose == 3:

id = int(input("请更具植物图鉴获取植物编号,输入方法:植物编码 -1:"))

while True: changePlant(id)

elif user_choose == 4:

while True: sunWithoutCooling()

elif user_choose == 5:

while True: monsterAllAppear()

elif user_choose == 6:

getzombieNumber()

elif user_choose == 7:

while True: drawZombiePane()

elif user_choose == 8:

while True:

changeSunNumber()

monsterAllAppear() # 僵尸全出

ignoreCooling() # 植物无冷却

changePlant(20) # 默认火爆辣椒

if __name__ == '__main__':

menu()

功能全部实现,但是代码需要改进的地方比较多,效率并不高,但本文的内容不是交大家这些,功能能够实现是最主要的。

五、总结

        那么本篇文章到这里就结束了,不知道你对游戏外挂制作是否有一定的了解。其实游戏外观的制作远不止这么简单,内存查找只是比较简单的方法,而且只针对把游戏数据保存在本地的游戏有效。对于网络游戏这种内存修改的方式就失效了,需要用到封包技术,这些都是后话了,读者感兴趣可自行了解。

        最后,不管你学习本文章内容是出于什么目的,不管你是开发者还是游戏辅助制作的初学者,都要记得不要触碰法律的底线

        游戏外挂种类繁多,不同的外挂有不同的制作、运行原理,像这种单机游戏根据游戏的运行规律来制作的辅助工具,不涉及到对客户端和数据包的破解是不构成违法犯罪的,读者可大但的了解和尝试提升自己的编程技术。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。