吞食鱼2(FeedingFrenzyTwo) 修改器

童年回忆系列。小时候特别喜欢玩这类游戏,软件不大,很慢的网速也不会下载太久,然后对配置要求不高,很破的电脑也可以玩得很开心。不过也有糟心的时候啊,大鱼太多,无数次死于挑战咬梭子鱼的尾巴……今年最后一天,就休闲一小会吧。

小时候看不懂是啥意思,现在知道了,我来翻译下第一段。

警告!

发现梭子鱼在珊瑚礁附近游荡。留意警告标志,远离它张开的大嘴!要是你有迷之自信,可以试着咬它的尾巴。咬 4 次就会有惊喜……如果到那时候你还活着……

现在再玩已经没有当年的感觉了——鼠标换了……其实我老早就想,为什么到了下一关,我的鱼就变小了??太不爽了。游戏里各种对玩家不利的设定:小鱼不能吃大鱼、被大鱼追、被贝壳吃掉、被乌贼喷、被水母电、被大嘴鸟吞、被鱼类炸、被河豚刺、吸水还要 CD、跳出水面翻滚的话进水眩晕……所以只有修改内存数据才是称霸海洋的秘诀!

工具:Cheat Engine 6.4 (以前搞的汉化版,其实翻译不完全,建议用英文版)

大,大,大

这个游戏的规则是吃小鱼,躲大鱼,等长大了以后就可以吃遍全图。成长值进度条在左上角,前面的关卡都是 3 个成长阶段,到了后面还有更多的级别。

好的,按照程序员的思路,姑且猜测 growth 数据用整型存储,每种鱼的加成不一样,小鱼少些,大鱼多些,初始值为 0,打开 CE ,开始搜索,每吃一条鱼就搜一次“增加的数值”。

好的,没搜几次,轻松搜到 2 个值。尝试修改第一个,发现鱼没反应;再尝试第二个,鱼长大了!然后自然是一顿操作:找出改写 -> 显示反汇编,然后看到了如下代码:

这也太舒服了,直接找到静态地址005AC624,省的找基址了,直接手动添加地址,growth 就有了。基址005AC624偏移+3C, +40(跟基址写成 "FeedingFrenzyTwo.exe"+001AC624 是一样的)

四两吃千斤

修改 growth 的数值,就可以瞬间变大,通关,但是这一点也不爽啊,我一定要把被吃的仇报了才行!

还是刚才的进度条,用通常思路来猜,成长阶段数值分别是 0, 1, 2 ,姑且先试一下。借助修改 growth 值快速长大,搜索这几个值就很快了。

先尝试修改第一个,图中我修改 0245AE24 的值为 0 以后,我 2 阶的大鱼被比我小的鱼吃掉了,那应该就是它了。观察了一会发现,这个值就是用来确定会不会被吃的,但是查找访问它的代码并没找到什么关键判定,代码太多了,我也懒得看,反正只要不被吃复位,这个值就不会变,直接把它改大点就行了。

在鱼变大的时候查找改写它的地址,可以找到偏移量 EC

00496B47 - 89 99 EC000000 - mov [ecx+000000EC],ebx

再稍微调试跟踪下,找到基址。基址005AC624偏移+40, +344, +0, +EC,类型我选了 byte ,不过应该没啥影响。

然而事实是,我不会被大鱼吃掉了,但是在大鱼旁边的时候也不会触发吃鱼的动作了……是太难吃了吗?哈哈……不过,离成功不远了。经过一番探索,我用“增大的数值”搜索前边的成长阶段,找到了另一个数值。简单点来说,这两个值一个是玩家鱼在电脑鱼面前的大小,一个是电脑鱼在玩家鱼面前的小大。<- 我说小大,因为第二个值数值越大判定电脑鱼越小。

基址005AC624偏移+40, +344, +0, +F8,就在上一个值旁边,很狡猾啊,这个值是从 1 到 3 的,之前搜精确数值的时候没找到,早知道就先去看看数据结构了。

把这两个值同时改成 5 ,终于报了当年的血海深仇,啊哈哈哈哈哈哈哈……

速度和位置

吸取刚才的教训,现在来看看数据结构。

嘿嘿,果然有了意外收获。仔细观察鱼的状态和数值,可以发现上面的 4 组浮点数分别代表鱼的位置和速度,修改这些值可以让鱼瞬移到地图任何地方。

然后我就有了一个大胆的想法——能不能把地图里的其他鱼瞬移到我嘴边呢?省的乱跑了。事实是——YES! 首先要找到存放地图上所有鱼的地方。

拿玩家鱼的位置来说,地址是 基址005AC624偏移+40, +344, +0, +98,按照程序的对象模型来想,+344指针应该是玩家指针,里面存放了很多和玩家相关的数据,刚才做四两吃千斤的时候,数据也在这个对象下,那么看下+0指针应该就是玩家的鱼的指针了,我这次游戏的指针是 09397280 。好的现在假设有这么个全局鱼数组,那么这个 09397280 也一定在里面,直接搜索这个指针:

嘿嘿,我为什么单独标出来这个 07EDC488 呢?因为看数据结构,在之前的 +344 指针前面,在 +324 指针的地方,指向的数据不就是 07EDC488 嘛,这应该是个数组首地址,展开一看,果然全都是鱼!

鱼是有了,可是数量不知道……这个数组没有结束标识,貌似是像vector那样管理的,有固定大小,靠整数标记结束的位置,而游戏本身可能不记录实时的鱼数量,所以我找了一遍,一直没找到数据,也可能是我找的方法不对。鱼在不同状态的时候地图上的鱼数量会变,但是因为不知道具体值,非常难找,我猜测是一些常数,规定了不同关卡鱼的数量上限。

我还强行试了下移动所有鱼直到空指针,会访问销毁过的鱼对象而导致访问越界崩溃,问题应该出在这里 mov ebx,[eax+edi]//4*nedi 的值增大以后,eax+edi 就不一定是有效地址了。然而令人惊奇的是,只要不点错误窗口上的确定键,就还可以继续游戏!真神奇……脚本如下:

[ENABLE]
//code from here to '[DISABLE]' will be used to enable the cheat
//0051A002
alloc(newm,512)
label(nextfish)
label(exit)
newm:
pushad
mov eax,["FeedingFrenzyTwo.exe"+001AC624]
mov eax,[eax+40]
//GetPlayersFish
mov esi,[eax+344]
mov esi,[esi]//ThisIsThePlayersFishClass
//PlayerPosition
mov ecx,[esi+98]
mov edx,[esi+9C]
//GetYou(heiheihei)
mov eax,[eax+324]
mov edi,0
nextfish:
mov ebx,[eax+edi]//4*n
cmp ebx,0//NoNextFish
je exit
add edi,4
cmp ebx,esi//IsPlayer?
je nextfish
mov [ebx+98],ecx
mov [ebx+9C],edx
jmp nextfish exit:
popad
ret
createthread(newm)
LdrInitializeThunk:
DB 8B FF 55 8B EC [DISABLE]
//code from here till the end of the code will be used to disable the cheat
dealloc(newm)

可以不理它,别点确定,直接返回游戏:

如果想稳妥一点,就去掉 jmp nextfish 这句,这样就每次只移动 1 只鱼,因为地图上一直会有一堆鱼,所以低频调用移动 1 只鱼的脚本是没事的,也可以轻松叠满 FRENZY

因为已经可以四两吃千斤了,所以继续搞这个功能也没啥必要了,就到此一游吧,不找鱼总数了。

变小魔法

游戏里有很多有意思的道具,比如蘑菇,可以让周围的大鱼变小;还有个红色的疯狂鱼,可以时间停止然后自动吃掉屏幕里的鱼,如果能找到对应的 call 就爽了。下面就来试一试。

首先我大概想了个思路,因为之前得到了存储鱼 growth 数值的地方,电脑鱼与玩家鱼用的是同一个类,所以存储的偏移应该也是一样的,所以查看数据结构里全局鱼数组,随便找条大鱼,查找改写 growth 数值的地方,然后去吃个蘑菇。之后代码断在了 0041D744 ,这里没什么有用信息,返回到上层函数,发现了有意思的东西。

0042863F - 68 08B35500 - push 0055B308 : ["shrinkBurstFx"]

这个单词 "shrink" 就是缩小的意思,再继续向上返回查找,又发现了一个位置:

0049AA93 - 68 B8E65400 - push 0054E6B8 : ["fishShrink"]

然后我再继续返回,发现到了外层大循环,而函数内部的断点是在魔法光球打在鱼身上的时候才会中断,我们需要的函数却是吃蘑菇的事件函数。好的,现在先暂停一下,缕缕思路:

  1. 玩家吃蘑菇
  2. 触发吃蘑菇事件函数,发出光球,目标是电脑大鱼
  3. 电脑大鱼被光球打中,触发缩小事件,缩小

我们刚才找的 "shrink" 相关的函数应该是步骤 3 ,需要找的是步骤 2 ,而步骤 2 到步骤 3 应该不在同一个函数中,它们之间应该只是消息传递的过程,所以这个线索就断了。

不过,嘿嘿,代码注释里已经给我们提供了新的线索——"shrink" 字符串。每次触发事件的时候,就会引用和 "shrink" 相关的字符串,所以新的思路有了——搜索 "shrink" 字符串,然后查找什么访问了字符串,看看吃蘑菇的时候会断在哪里。首先找到 078BBCF8 的位置是我们刚才找到的 "fishShrink" ,而就在它下面不远的位置,发现了 "shrinkPickup" ,地址是 078BBE10!!!哇,运气好到爆炸!

查找什么访问了 078BBE10 ,然后查看堆栈,在这个字符串附近的函数一个一个进去看,终于在 004747CD 的地方找到了线索。

004747CD 下断点,然后吃东西就会中断,再向上返回,发现返回的位置和吃的东西有关。

吃蘑菇返回到这里

吃鱼返回到这里

试了几次,吃什么就会返回到什么地方,所以附近应该就有吃的函数了,传入的指针就是吃的东西,用多态的思想,吃什么就执行什么的事件函数,所以离胜利不远了……

经过一番调试,终于找到了吃东西的函数——call [eax+90] ,就在 0042A98C 的地方,esi 是被吃的对象,+90 大概是虚函数表里执行被吃事件的函数指针,参数是 edi ,储存发起吃东西事件对象的指针,还有个寄存器参数 ecx ,储存的是被吃对象指针。一个以吃东西为游戏内容的游戏,把吃东西的函数找到了,游戏结束!

后面的工作就轻而易举了,下断点在每次吃掉蘑菇的时候步入,就到了 004A249D ,这个 call 00493BD0 就是我们苦苦寻找的吃蘑菇的事件函数了,这个函数只有一个寄存器参数,就是 esi 储存发起事件的对象指针,这里我们把玩家填进去就好。还记得玩家指针在哪吗?没错,就是之前找四两吃千斤的时候找到的对象:基址005AC624偏移+40, +344, +0

然后写出脚本,只要执行这个脚本,就相当于吃了蘑菇,变小魔法就会触发了,大功告成。

[ENABLE]
//code from here to '[DISABLE]' will be used to enable the cheat
//004A249D
alloc(newm,512)
label(exit)
newm:
pushad
mov eax,["FeedingFrenzyTwo.exe"+001AC624]
mov eax,[eax+40]
//GetPlayersFish
mov esi,[eax+344]
mov esi,[esi]//ThisIsThePlayersFishClass
call 00493BD0 exit:
popad
ret
createthread(newm)
LdrInitializeThunk:
DB 8B FF 55 8B EC [DISABLE]
//code from here till the end of the code will be used to disable the cheat
dealloc(newm)

狂吃

还是按照上面的思路,在0042A98C 的吃东西函数 call [eax+90] 下断点,然后吃个 "FEEDING FURY" 道具,找到对应的代码。很轻松就找到了,然后写出下面的脚本。

[ENABLE]
//code from here to '[DISABLE]' will be used to enable the cheat
//0053D05D
alloc(newm,512)
label(exit)
newm:
pushad
mov eax,["FeedingFrenzyTwo.exe"+001AC624]
mov eax,[eax+40]
//GetPlayersFish
mov esi,[eax+344]
mov esi,[esi]//ThisIsThePlayersFishClass
//feeding fury 00502399
add esi,00000154
mov eax,[esi]
mov ecx,esi
call [eax+20]
test eax,eax
je exit
mov eax,[esi]
mov ecx,esi
call [eax+20]
mov edx,[eax]
mov ecx,eax
call [edx+70] exit:
popad
ret
createthread(newm)
LdrInitializeThunk:
DB 8B FF 55 8B EC [DISABLE]
//code from here till the end of the code will be used to disable the cheat
dealloc(newm)

防雷

直接找出是什么访问了玩家鱼指针,然后去撞水雷。查询过程非常卡,所以到了水雷旁边再开始查询。撞到水雷时,会出现一些新的代码,一个一个找。运气很不错,找第一个就发现了关键跳转。

0050B773 - 75 30 - jne 0050B7A5 有鱼死掉时的关键跳转

再向下找,就在下面找到了鱼撞雷死亡调用的函数

0050B798 - FF 90 DC000000 - call dword ptr [eax+000000DC]

当玩家单位触发时步入,里面只有 3 行汇编。

只要在玩家触发水雷的时候跳过这个函数就可以了

[ENABLE]
//code from here to '[DISABLE]' will be used to enable the cheat
alloc(newmem,2048)
label(returnhere)
label(originalcode)
label(exit) newmem: //this is allocated memory, you have read,write,execute access
//place your code here
push eax
mov eax,["FeedingFrenzyTwo.exe"+001AC624]
mov eax,[eax+40]
//GetPlayersFish
mov eax,[eax+344]
mov eax,[eax]//ThisIsThePlayersFishClass
//if player hit a mine
//then jump
cmp eax,ecx
pop eax
je exit originalcode:
mov eax,[ecx]
call dword ptr [eax+000000EC] exit:
jmp returnhere "FeedingFrenzyTwo.exe"+102BE3:
jmp newmem
nop
nop
nop
returnhere: [DISABLE]
//code from here till the end of the code will be used to disable the cheat
dealloc(newmem)
"FeedingFrenzyTwo.exe"+102BE3:
mov eax,[ecx]
call dword ptr [eax+000000EC]
//Alt: db 8B 01 FF 90 EC 00 00 00

因为这里的函数在其他事件触发时也有被调用到,所以不知道这么改有什么副作用,暂且先这样,等以后出了问题再回来看……(懒)

另外还发现了销毁玩家对象的函数,不知道以后用不用得上。

00438EBD - FF 50 1C - call dword ptr [eax+1C] 玩家鱼对象销毁

其他

  • 吸水能量条 基址"FeedingFrenzyTwo.exe"+001AC624 偏移 +40, +344, +0, +20C 类型是 float 从 0 到 1
  • 吸水持久度 基址 005A7314 浮点数 float,数值越小持续时间越长
  • 吸水恢复速度 基址 005A7318 浮点数 float,数值越大恢复速度越快
  • 直接过关 基址 005AC624 偏移 +40, +8C 字节 byte ,改成 1 即可。大部分时候好用,个别关不行,不知道为啥

从空中翻滚后落到水里会晕一会,这个脚本可以防止眩晕。其实功能非常简单,就是跳过 mov byte ptr [ebx+0000019D],01 这一句。

[ENABLE]
//code from here to '[DISABLE]' will be used to enable the cheat
alloc(newmem,2048)
label(returnhere)
label(originalcode)
label(exit) newmem: //this is allocated memory, you have read,write,execute access
//place your code here
jmp exit originalcode:
mov byte ptr [ebx+0000019D],01 exit:
jmp returnhere "FeedingFrenzyTwo.exe"+13FC74:
jmp newmem
nop
nop
returnhere: [DISABLE]
//code from here till the end of the code will be used to disable the cheat
dealloc(newmem)
"FeedingFrenzyTwo.exe"+13FC74:
mov byte ptr [ebx+0000019D],01
//Alt: db C6 83 9D 01 00 00 01

结语

权当是休闲娱乐,没做太多复杂的东西,想做成品修改器的小伙伴也可以写一个。

今年最后一天了,祝大家玩得开心。

吞食鱼2(FeedingFrenzyTwo) 修改器的更多相关文章

  1. CE修改器修改DNF 测试视频 阿修罗提升智力增加攻击力

    使用CE修改器来修改网络游戏,如DNF 测试视频: CE修改器:指的是Cheat Engine,字面上的意思指的是作弊引擎的意思,是一款内存修改编辑工具.通过修改游戏的内存数据来得到一些原本无法实现的 ...

  2. tp5 中 model 的修改器

    修改器可以在数据赋值的时候自动进行转换处理 class User extends Model { public function setNameAttr($value){ return strtolo ...

  3. Blender 之修改器代码分析

                           Blender的修改器(modifier)模块,默认界面右下块(Property)面板的扳手,分类(修改.生成.形变.模拟)列出所有的修改器.也可以空格键 ...

  4. mongodb的修改器

    在mongodb中通常文档只会有一部分要更新,利用原子的更新修改器,可以做到只更新文档的一部分键值,而且更新极为高效,更新修改器是种特殊的键,用来指定复杂的更新操作,比如调整.增加.或者删除键,还可以 ...

  5. 【MongoDB】4.MongoDB 原子修改器的 极速修改

    文档转自:http://blog.csdn.net/mcpang/article/details/7752736 对于文档的更新除替换外,针对某个或多个文档只需要部分更新可使用原子的更新修改器,能够高 ...

  6. MongoDB修改器的使用1

    为什么要使用修改器?     通常我们只会修改文档的一部分,这时候更新整个文档就显得很麻烦,通常是通过原子性的更新修改器来完成. 1."$set"修改器    "$set ...

  7. UWP游戏防内存修改器的方法

    最近我一直在编写适用于Windows 10商店的游戏.这款游戏比较怕玩家用修改器改金钱,因为这种修改会导致某些内购失效并且损害公平性.于是我把自己见过的三种反修改器的方法给网友们介绍一下. 首先说明一 ...

  8. mongodb_修改器($inc/$set/$unset/$push/$pop/upsert......)

    主从复制:http://blog.csdn.net/drifterj/article/details/7833883 对于文档的更新除替换外,针对某个或多个文档只需要部分更新可使用原子的更新修改器,能 ...

  9. ce游戏内存修改器(Cheat Engine)

    ce修改器(Cheat Engine)一款专门修改内存修改编辑的游戏工具它包括16进制编辑,反汇编程序,内存查找工具新版6.1 版的CE与6.0 最大的区别就是添加了修改器制作工具,比之前 5.6.1 ...

随机推荐

  1. Spark存储Parquet数据到Hive,对map、array、struct字段类型的处理

    利用Spark往Hive中存储parquet数据,针对一些复杂数据类型如map.array.struct的处理遇到的问题? 为了更好的说明导致问题的原因.现象以及解决方案,首先看下述示例: -- 创建 ...

  2. socket阻塞与非阻塞,同步与异步,select,pool,epool

    概念理解 一.与I/O相关的五个重要概念 1. 第一个概念:用户空间与内核空间 1. 现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方) 2. ...

  3. 被老猿误解的Python匿名函数lambda

    在<第2.3节 Python运算符大全>老猿这样描述lambda:"上述运算符中有个lambda,这是个lambda就是用来定义一个匿名函数的.老猿认为用处不大,具体内容大家可以 ...

  4. Python使用import导入模块时报ValueError: source code string cannot contain null bytes的解决方案

    老猿在导入一个Python模块时报错: >>> import restartnet.py Traceback (most recent call last): File " ...

  5. CSP-S2020 浙江 游记

    2020.10.9 今天是 \(2020\) 年 \(10\) 月 \(9\) 日,距离初赛还有两天(算两天吗,完整的应该只有一天多了). 原本对于比赛还是没什么感觉的,每天做做题,水水文章,感觉时间 ...

  6. STL——容器(Set & multiset)的删除 erase

    set.clear();             //清除所有元素 set.erase(pos);     //删除pos迭代器所指的元素,返回下一个元素的迭代器. set.erase(beg,end ...

  7. STL—— 容器(vector)数据插入insert()方法 的返回值

    vector 容器下的 insert() 方法拥有返回值,由于insert() 方法拥有4种重载函数,他的返回值不尽相同. 第一种,插入单个元素后的返回值: 1 #include <iostre ...

  8. C++异常之四 异常类型的生命周期

    异常类型的生命周期 1. throw 基本类型: int.float.char 这三种类型的抛出和函数的返回传值类似,为参数拷贝的值传递. 1 int test_1(int num) throw (i ...

  9. Linux相关介绍和安装

    目录 前言 第一章 linux介绍 1.1 linux简介 第二章 Linux起源 2.1 Unix的历史 2.2 Unix操作系统的革命 2.3 Linux系统的诞生 2.4 Linux系统的发展史 ...

  10. QQ电话能定位

    QQ电话是网络电话,可以定位吗??? 下面介绍利用wireshark获取QQ好友IP实施精准定位 超详!! Wireshark Wireshark是一个使用WinPcap作为接口,直接与网卡进行数据报 ...