植物大战僵尸修改笔记(Steam)
1. 基础修改
基址寻找就不详细记录了,基址有四个
0x00731C50 0x00731CDC 0x00731D08 0x00731DA0
其他记录详细请见
PVZSteam.MSTT 和 PlantsVsZombiesSteam.CT
2. 关于当前地图信息搜索
首先有个地图基本信息,也就是基于哪张基本地图制作的关卡,
0草地 1夜晚 2泳池 3雾 4屋顶 5夜晚屋顶
可用工具MemoryBlockCompare搜索出来
其次,是每行的属性,出僵尸的时候用于检测
每行属性 0裸地1草地 2水路
最后每个土地块都有自己的类型
1草地 2裸地 3水池
不过这里注意的是,这里不是 types[X][Y]而是types[行][列]
4byte*6row*9col
3. CALL_成就
通过存档指针中的成就,用CE定位,OD分析得出
moveax,[00731C50] push0//种类 push1//为1有声音和提示,0则没有 leaedx,[esp] pushedx pusheax leaecx,[esp+C] call0045BC00 addesp,8 |
4. CALL_掉落
通过可点击物品的数量+1,用CE定位,OD分析得出,除了个别CALL,下同
movecx,[00731C50] movecx,[ecx+868] push3 //0从坐标落下1从坐标缓慢落下2从后方跳出3从后方快速跳出4直接收集5稍后自动收集6从屏幕右侧直接蹦出7在卡槽栏后同 push3 //1银币2金币3钻石4太阳5小太阳6大太阳7卡片8奖杯9铲子10图鉴11钥匙12花瓶13水壶14三明治15遗书16消失17花苗礼盒18金币带19礼盒(不消失)20金币带(不消失)21银奖杯22金奖杯23巧克力24巧克力(不消失)25礼品盒(小游戏)26礼品盒(解密模式)27礼品盒(生存模式) push118 //Y坐标 push118 //X坐标 call0040FF90 //CALL |
5. CALL_植物
moveax,[00731C50] moveax,[eax+868] push-1 //标志位参数 push3 //种类 push2 //Y坐标 pusheax //战场基址 moveax,1 //X坐标 call004105A0 //CALL |
6. CALL_僵尸_墓碑全体出僵尸&水草僵尸
最后一大波僵尸的时候,才会调用这个CALL
这个CALL不仅仅是墓碑出僵尸,最后一大波僵尸都是这个
movedi,[00731C50] movedi,[edi+868] cmpedi,0 call004161f0 //CALL |
7. 大嘴花快速吞噬修改
通过植物状态指针变化,定位到相应的代码
00467709 - 8B 47 3C - mov eax,[edi+3C]
此代码读取到植物状态
在之后,有判断该状态的值
分别为1 A B D E
为1的时候是待命状态
为A的时候为开始张嘴动作,张嘴完毕后将状态改为B
为B的时候,开始咀嚼动作,并将状态改为D
为D的时候,要等咀嚼动作完毕,开始吞咽动作,并将状态改为E
为E的时候,要等吞咽动作完毕,恢复待命动作,并将状态改为1
8. CALL_通关
通关CALL寻找比较恶心~….真的比较恶心
首先,先通关所有基础关卡(即通了基础50关,从头循环)
再通关一个基础关卡,会调出一个钱袋子,鼠标点击后
会弹出5个金币,此时在可点击物+1的代码出下断
找外CALL,每层CALL下断,删除轮询的CALL(就是啥时候都会断下来的)
从外层CALL对跳转记录分析
对过关物品和普通物品进行跳转区别
找出关键跳转
在关键跳转后分析代码,找关键CALL
这个关键CALL也比较恶心,耐心找就能找得到
其中一个方法可以把关键跳后面的所有CALL都nop掉
一个一个恢复,即可轻松找到~
9.倭瓜全屏攻击解析
(1) 通过倭瓜检测僵尸在附近,开始攻击动画
(2) 获取攻击动画并在初始化处下断点
(3) 在断点处上方找到关键跳转
(4) 在跳转上方找到设置跳转标志的CALL
(5) 进入CALL在循环处分析是否在同一行,是否在附近,是在前还是在后
(6) 修改关键跳转实现倭瓜全屏攻击,但是倭瓜只能实际攻击到本行第一个僵尸
(7) 继续通过僵尸血量分析,查看所有都是正常情况下哪行代码修改了僵尸血量并下断
(8) 断下后查看外层CALL,找到循环即为循环判断僵尸是否在攻击范围内
(9) 通过不在同一行和正常2种对比分析,找出关键跳转并修改,即达到了倭瓜全屏攻击
伪代码
If 场上有没有僵尸
Ret
Else
If 和倭瓜不在同一行
Ret
Else
If 不在倭瓜附近
Ret
Else
If在倭瓜前方
向前跳
Else
向后跳
Endif
计算落地坐标并消灭在范围内的僵尸
Endif
End if
End if
9. 樱桃炸弹攻击全屏
首先通过僵尸是否消失定位到OD
从OD找外CALL
一直找到外CALL在循环中
查看有什么跳转跳过外CALL,进行分析即可
把关键跳转nop掉即完成樱桃全屏攻击
10. CALL_GameOver
[[00731c50]+91C]游戏场景 0游戏读取 1开始界面 2游戏准备界面 3种植界面 4GameOver 5通关界面 6通关音乐界面 7选关界面
通过游戏场景的切换找到相应的代码
用CE定位,OD跟踪,找到相应CALL即可
11. 解决种植僵尸崩溃问题
这个问题也是比较恶心的事情,从我10年开始研究pvz,这个问题一直没有被很好地解决过。
几个月前想起这个事情,稍微的分析了一下,原因在于种植僵尸的时候,由于资源未载入而导致的程序崩溃。
但是后来一直未作研究,最近又想起这事,最终得到解决,方法如下。
首先不做任何修改的情况下,游戏自身有个解决方法,就是打开僵尸的CG,也就是打开僵尸的相册,再种植僵尸就不会出现任何问题。
也就是说当打开CG的时候,资源就加载到内存中了,这样就不会出现种植了僵尸却找不到资源引起的崩溃。
首先,读取资源肯定要用到CreateFileA或CreateFileW,先打开CG的index,再用OD对这两个函数下断。点开View Zombies。
此时已经断下,打开堆栈,找最外层CALL下断,取消CFA和CFW断点,运行,发现每次都会断下,则关掉游戏,重新对CFA和CFW下断,此时找到第二层CALL,发现也是每次都断下,但是多运行几次后就不会再断下了,也就是说,资源已经加载到内存中了,浏览附近代码
发现上面这个jnz能跳过这个CALL,运行后发现,一开始不跳,多运行计次后就会一直跳过,所以这个jnz就是关键跳。
此时重新运行游戏,并在此CALL下断分析,发现这个CALL没有push参数,却有ecx作为参数,ecx是僵尸id。最终调用这个CALL即可把僵尸信息加入到内存中。
12. 解决种植植物CALL出现崩溃问题
这个问题也比较恶心,比种植僵尸CALL还恶心。
不过最近弄出了解决僵尸CALL崩溃的问题,就趁热打铁,把植物CALL崩溃一并解决了。
方法同上,只不过有个小问题,是这样子的,假如已经通关了(是基础的50关),在启动游戏的时候,绝大部分的植物资源已经加载到了内存中,所以,用打开CG的方式下CreateFileA或CreateFileW断点有时会断不下来,如果出现这样的情况,用以下方法可以完美解决,不过先要找到几个指针。第一个指针是关卡指针,第二个指针是基础通关次数指针(每通关一次所有基础关卡,这指针内容会+1),删除所有存档,重启游戏,新建一个用户,把基础通关次数指针改为1,关卡指针改为2.进入帮助再返回,就会有个CG本了,打开CG本,但是先别点击浏览植物,此时用OD下CFA或CFW断点即可断下。
这里和僵尸CALL不同的是,这个关键CALL不是在第二层,需要耐心的找一下,CALL的上方也是一个数组形式的判断,还有个push 0,最后还有个寄存器赋值。具体代码如下
分析这个CALL也很简单,简单调用即可,不过要注意寄存器和push参数就行。
13. CALL_钉耙_随机行固定第七列
这个虽然思路很简单,但是CALL的定位比较难.
可以用存档指针中,钉耙数量-1来定位
也可以用特殊物品数量+1来定位
但是最终发现,特殊物品+1的外层就是钉耙数量-1
那么特殊物品+1的就是特殊物品放置CALL
但是特殊物品放置CALL单独调用是没效果的
因为这个CALL就是用来申请(这个词用的有点不对感觉,因为PVZ的内存都是线性申请好的)和初始化做相关记录的,并不是真正的或者说是完整的特殊物品放置CALL
那么钉耙CALL应该是在钉耙数量-1的外层,但是从钉耙数量-1的CALL头看起,它读取了存档指针,又读取了是否买了钉耙或者说钉耙剩余量,然后做了比较判断
也就是说,不管怎么调用,除非自己重写部分代码,是跳不过钉耙数量-1的.从外层CALL分析,很容易调用这个CALL
14. CALL_钉耙_坐标
从上图可看到,钉耙个数-1的下面就是特殊物品CALL
但是在13的时候我分析过,这个CALL仅仅是用来初始化相关内存的,并没有真正的种植钉耙
那一个蛋疼的事情就发生了,下面的所有代码就是种植钉耙的剩余代码,没得办法,只能原模原样的抄了.
一直抄到retn为止,但是我们发现一个事情,中途用到了好多esp,对于esp中的数据,最好不要随意修改,否则就容易出现大问题,例如CALL的返回地址找不到之类的。我们回头看看这个函数头。
它先push ebp,然后把esp保存在ebp里,最后对esp做了一堆操作,给自己留了好大一部分空间,这样子的话,我们也可以这么做,但是自己做的时候要注意堆栈平衡。
以下是CEAA相关代码
alloc(start,1024) start: pushad pushfd movesi,[00731C50] movesi,[esi+868] cmpesi,0 jz L pushebp movebp,esp andesp,-0x8 subesp,44 movebx,esi addesi,134 //获取特殊物品头地址 call00421DC0 //初始化相关内存并返回区块头地址 movecx,6 //X坐标 movedi,2 //Y坐标 movesi,eax //把头地址给ESI mov[esi+0x8],0xB //设置类型为钉耙 mov[esi+0x10],ecx //设置X坐标 mov[esi+0x14],edi //设置Y坐标 leaedx,[ecx+ecx*4] shledx,4 addedx,0x28 mov[esp+14],edx fild[esp+14] moveax,edi fstp[esp+14] fld[esp+14] fstp[esi+24] call004202f0 imuledi,edi,0x2710 mov[esp+1c],eax addedi,0x497d1 mov[esi+1c],edi fild[esp+1c] fstp[esp+1c] fld[esp+1c] fst[esi+28] moveax,[ebx+A4] fld[esp+14] movecx,[eax+940] faddqword[00706058] movedi,[ecx+8] fstp[esp+18] fstp[esp+1c] call0047C750 movebx,eax push0 push70 mov[ebx+60],edi mov[ebx+68],0 call0047B660 fld[esp+24] moveax,[00731C78] fstp[esp+4] fld[esp+20] addeax,700 fstp[esp] pushebx mov[ebx],70 call00479A60 fldz movedx,[ebx+9c] fstp[ebx+8] mov[ebx+10],3 movbyte[ebx+64],1 mov[esi+34],edx mov[esi+0xc],0x1a movesp,ebp popebp L: popfd popad ret createthread(start) dealloc(start)
|
在此补充一下,好多僵尸CALL的坐标行为也是这样子的,先是普通放置一个僵尸,再修改僵尸坐标。比较麻烦,目前好像只有一个CALL能自己完成坐标修改,但是我忘了是哪种出僵尸的方式了。从现在开始重新总结吧,我会把出僵尸的所有方式放在备注中。
15. CALL_核弹
通过特殊物品+1可以跟踪到相关代码,外1层是内存初始化,和钉耙CALL类似。
先初始化内存再往里面写数据
弹坑CALL 外2是这样子的
说实话,如果仅仅需要只种核弹坑,可以调用这个CALL,除非要自己设计难度,否则这个功能太LOW了。不过还是挺有意思的。可惜的是经过我试验,这个确实能种核弹坑。。。但是不显示出来,应该是后面的动画部分没注册。
这是弹坑CALL 外3层,也是核弹爆炸CALL。经过分析,上面push 的参数是末日菇的数据区块头地址。
也就是说,调用这个CALL得自己构造一个植物数据,然后跟进CALL分析,查看他用到了那些数据。
虽然研究的是核弹爆炸的内部。
但是我们以外的发现了还有别的植物也是用了这个函数,例如三叶草、核弹、辣椒、樱桃、冰川菇、土豆、咖啡豆、等延时类自爆式植物,这应该是个通用函数,所有延时类自爆式的植物都是调用的这个CALL。意外收获。但是还有个萝卜伞,不了解萝卜伞是怎么工作的。
在下面有个咖啡豆相关的,crumble是碾碎的意思,anim是动画的意思,估计这里就是延时自爆类植物的动画,例如末日菇的蘑菇云。顺便还有动画之后留下的效果,例如弹坑,蘑菇白天苏醒之类的。
那么这个CALL或者说函数,应该是延时自爆类植物自爆后的消息分发机制吧。这样的话,就不用写很多函数了,用同一个接口实现。好办法!GET√
话归正题,看一下push的植物头地址需要我们构造哪些数据。
单单从这里看是需要0x10h 0x8 0x14 0xc但是还是远远不够的,需要一步一步跟进去看看。这里就不多说了,稍微有点耐心的都能找得出来。这里总结一下,需要的偏移有4 8 C 10 14 1C 24 28.通过构造这些数据,我们发现坑的位置好说,就是爆炸点的位置难以确定,这个就需要分析地图坐标转屏幕坐标了。这个问题放在下面再谈。这里先放一下CEAA的代码
moveax,[00731C50] movedi,[006D4EB0] addedi,304 //确定构造植物地址,以及后面的构造参数 mov[edi],eax //基址 moveax,[eax+868] //读取战场基址 mov[edi+4],eax //保存战场基址 mov[edi+8],#520 //图像X坐标 mov[edi+C],#280 //图像Y坐标 mov[edi+10],#80 //格子宽度 mov[edi+14],#80 //格子高度 mov[edi+1C],3 //Y坐标 mov[edi+24],#15 //种类 mov[edi+28],2 //X坐标 movebx,eax //把战场基址给EBX,作为核弹CALL的隐藏参数 moveax,[edi+28] //取出X坐标,下面开始计算图像X坐标 leaeax,[eax+eax*4] shleax,4 addeax,28 mov[edi+8],eax //将计算好的图像X坐标写进内存 moveax,[edi+1C] //取出Y坐标,下面开始计算图像Y坐标 call004202F0 //计算图像Y坐标 mov[edi+C],eax //将计算好的图像Y坐标写进内存 pushedi //将植物头地址压入堆栈做参数 call0046CAF0 //核弹CALL |
16. CALL_地图坐标转屏幕坐标
这个是上一个的遗留问题,这个有什么办法解决一下呢。
首先我们先想想,用到屏幕坐标的是那些个。
算了,具体不一一举例了,就按种植物来说吧。就需要屏幕坐标,分别是+8 +C偏移。
但是这个要注意的事情是,对相应地址定位,会出现多条,是因为有内存区块初始化等赋值,只有一个才是最后真正的坐标。
经过发现,图像X坐标是外层计算出来的,图像Y坐标是CALL计算出来的,返回值在EAX里。
我在CE中写了个简单的AA脚本
movebx,[00731C50] movebx,[ebx+868] movecx,3 //X leaecx,[ecx+ecx*4] shlecx,4 addecx,28 movesi,[006D4EB0]//这里的ESI是我申请了一个空间存放临时数据 mov[esi+304],ecx moveax,1 //Y call004202F0 movesi,[006D4EB0] //这里的ESI是我申请了一个空间存放临时数据 mov[esi+308],eax |
这是结果
17. 攻击类植物模拟猫扑草,全屏攻击跟踪
首先这个问题还是比较好玩的.
首先先找到子弹CALL.这个问题就不赘述了,什么掉落CALL,钉耙CALL之类的很像。
定位到子弹CALL以后,稍微分析一下前后代码
红色箭头处就是我们要找的关键地方
我们可以让代码直接从这里强制跳走
但是运行起来就会发现,游戏会崩溃。为何呢?
因为下图中,猫扑草的这里需要对EDX进行指针读取,而普通的豌豆过来的EDX是0,所以肯定会出错。那么这个EDX到底是哪里来的????
经过跟踪发现,这个EDX是从外面的那层CALL传进来的,是要被攻击僵尸的头地址。我们看看外面的CALL都干了啥。
上图是豌豆射手发射子弹的时候调用的代码。经过跟踪,下图是猫扑草调用的CALL
乍一看一样的,其实不然。请看下图
再往上走,就会发现,有个读取植物种类的。(这里虽然有忧郁菇,但是我试了试,忧郁菇改这些东西好像没用)
顺着跳转往下走,就会发现有个判断是不是猫扑草的。
然后,神奇的地方来了,就是红色箭头指向的跳转,究竟跳到哪去了,可以查看猫扑草调用的CALL,就会发现,其余的三个参数都push好了,只到了CALL前面push了最后一个参数。
好坑爹~
那经过分析,push的参数是这样子的,也就是说,eax是僵尸的头地址,上面的CALL就是寻找僵尸头地址的函数,这里留个扣子,这个函数我们会以后再见,当然这次也得看一下。
在看这个CALL之前,我们先来修改一下,看看什么效果。
发现我们不种在一行里,是不会有任何作用的,这完全没啥用,那么最后的问题就肯定出在之前说的寻找僵尸的CALL中了。
就是这个CALL
在这个CALL里有下图这几条代码
这个就是判断是不是猫尾草的,直接我们JMP掉。
最终结果,dangdangdang~
子弹最后会拐弯了~他是一个被掰弯了的子弹。
附注:
1. 每个植物都有攻击类型
仙人掌是1 猫尾草是B 水草是5 土豆雷4D
樱桃炸弹、辣椒、加农炮、末日菇 是延时类自爆植物,返回7F
西瓜投手、卷心菜、玉米投手、冰西瓜、倭瓜是弧线攻击植物,返回D
小喷菇、水兵菇、大喷菇、忧郁菇、大嘴花是攻击距离有问题的,返回9
00464E20 /$ 8B4024 moveax,dwordptrds:[eax+0x24] ; 把植物种类给EAX 00464E23 |. 83F81A cmpeax,0x1A ; 判断是不是仙人掌/豌豆僵尸; Switch (cases 2..2F) 00464E26 |. 750E jnzshort popcapga.00464E36 ; 如果是豌豆僵尸则不跳 00464E28 |. 33C0 xoreax,eax ; 清空EAX; Case 1A of switch 00464E23 00464E2A |. 837C240401 cmpdwordptrss:[esp+0x4],0x1 00464E2F |. 0F95C0 setneal ; 给EAX清零或者赋1 00464E32 |. 40 inceax ; 给EAX+1,保证EAX是TRUE 00464E33 |. C2 0400 retn0x4 00464E36 |> 83F802 cmpeax,0x2 ; 判断是不是樱桃炸弹 00464E39 |. 0F848C000000je popcapga.00464ECB ; 是返回7F 00464E3F |. 83F814 cmpeax,0x14 ; 判断是不是辣椒 00464E42 |. 0F8483000000je popcapga.00464ECB ; 是返回7F 00464E48 |. 83F82F cmpeax,0x2F ; 判断是不是玉米加农炮 00464E4B |. 747E jeshort popcapga.00464ECB ; 是返回7F 00464E4D |. 83F80F cmpeax,0xF ; 判断是不是末日菇 00464E50 |. 7479 jeshort popcapga.00464ECB ; 是返回7F 00464E52 |. 83F827 cmpeax,0x27 ; 判断是不是西瓜投手 00464E55 |. 746C jeshort popcapga.00464EC3 ; 是返回D 00464E57 |. 83F820 cmpeax,0x20 ; 判断是不是卷心菜 00464E5A |. 7467 jeshort popcapga.00464EC3 ; 是返回D 00464E5C |. 83F822 cmpeax,0x22 ; 判断是不是玉米投手 00464E5F |. 7462 jeshort popcapga.00464EC3 ; 是返回D 00464E61 |. 83F82C cmpeax,0x2C ; 判断是不是冰西瓜 00464E64 |. 745D jeshort popcapga.00464EC3 ; 是返回D 00464E66 |. 83F804 cmpeax,0x4 ; 判断是不是土豆雷 00464E69 |. 7508 jnzshort popcapga.00464E73 ; 是的话不跳,返回4D 00464E6B |. B8 4D000000 moveax,0x4D ; 土豆雷; Case 4 of switch 00464E23 00464E70 |. C2 0400 retn0x4 00464E73 |> 83F811 cmpeax,0x11 ; 判断是不是倭瓜 00464E76 |. 744B jeshort popcapga.00464EC3 ; 是的话返回D 00464E78 |. 83F808 cmpeax,0x8 ; 判断是不是小喷菇 00464E7B |. 743E jeshort popcapga.00464EBB ; 是的话返回9 00464E7D |. 83F818 cmpeax,0x18 ; 判断是不是水兵菇 00464E80 |. 7439 jeshort popcapga.00464EBB ; 是的话返回9 00464E82 |. 83F80A cmpeax,0xA ; 判断是不是大喷菇 00464E85 |. 7434 jeshort popcapga.00464EBB ; 是的话返回9 00464E87 |. 83F82A cmpeax,0x2A ; 判断是不是忧郁菇 00464E8A |. 742F jeshort popcapga.00464EBB ; 是的话返回9 00464E8C |. 83F806 cmpeax,0x6 ; 判断是不是大嘴花 00464E8F |. 742A jeshort popcapga.00464EBB ; 是的话返回9 00464E91 |. 83F82B cmpeax,0x2B ; 判断是不是猫尾草 00464E94 |. 7508 jnzshort popcapga.00464E9E ; 是的话不跳,返回B 00464E96 |. B8 0B000000 moveax,0xB ; 猫尾草; Case 2B of switch 00464E23 00464E9B |. C2 0400 retn0x4 00464E9E |> 83F813 cmpeax,0x13 ; 判断是不是水草 00464EA1 |. 7508 jnzshort popcapga.00464EAB ; 是的话不跳,返回5 00464EA3 |. B8 05000000 moveax,0x5 ; 水草; Case 13 of switch 00464E23 00464EA8 |. C2 0400 retn0x4 00464EAB |> 83E832 subeax,0x32 ; Default case of switch 00464E23 00464EAE |. F7D8 negeax 00464EB0 |. 1BC0 sbbeax,eax 00464EB2 |. 83E0 F0 andeax,-0x10 00464EB5 |. 83C011 addeax,0x11 00464EB8 |. C2 0400 retn0x4 00464EBB |> B8 09000000 moveax,0x9 ; 小喷菇、水兵菇、大喷菇、忧郁菇、大嘴花是攻击距离有问题的; Cases 6,8,A,18,2A of switch 00464E23 00464EC0 |. C2 0400 retn0x4 00464EC3 |> B8 0D000000 moveax,0xD ; 西瓜投手、卷心菜、玉米投手、冰西瓜、倭瓜是弧线攻击植物; Cases 11,20,22,27,2C of switch 00464E23 00464EC8 |. C2 0400 retn0x4 00464ECB |> B8 7F000000 moveax,0x7F ; 樱桃炸弹、辣椒、加农炮、末日菇是延时类自爆植物; Cases 2,F,14,2F of switch 00464E23 00464ED0 \. C2 0400 retn0x4
|
2. 僵尸出现方式
普通刷僵尸,预览僵尸,僵尸水族馆,墓碑出僵尸(关末集体),打地鼠,砸罐子出僵尸,屋顶关卡从天而降出僵尸,小偷僵尸,僵尸博士放僵尸,水下出僵尸。
其中能安坐标直接刷出僵尸的CALL只有小偷僵尸记得。
其他的都是按普通刷僵尸然后再自行修改坐标。