• 吞食鱼2(FeedingFrenzyTwo) 修改器


    吞食鱼2(FeedingFrenzyTwo) 修改器

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

    image-20201230224005827

    image-20201231012316488

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

    警告!

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

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

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

    大,大,大

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

    image-20201230224112420

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

    image-20201230225023229

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

    image-20201230225514423

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

    image-20201230225654162

    四两吃千斤

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

    image-20201230224112420

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

    image-20201230231355759

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

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

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

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

    image-20201230232503913

    image-20201230232204382

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

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

    image-20201230232932352

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

    image-20201230233834408

    速度和位置

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

    image-20201230234630832

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

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

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

    image-20201230235851446

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

    image-20201231000302729

    image-20201231000449674

    鱼是有了,可是数量不知道……这个数组没有结束标识,貌似是像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)
    
    

    image-20201231010422689

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

    image-20201231010500391

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

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

    变小魔法

    image-20201231154633471

    游戏里有很多有意思的道具,比如蘑菇,可以让周围的大鱼变小;还有个红色的疯狂鱼,可以时间停止然后自动吃掉屏幕里的鱼,如果能找到对应的 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!!!哇,运气好到爆炸!

    image-20201231125320859

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

    image-20201231114603233

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

    吃蘑菇返回到这里

    image-20201231114743831

    吃鱼返回到这里

    image-20201231115001960

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

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

    image-20201231152036835

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

    image-20201231152131801

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

    [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)
    

    狂吃

    image-20201231160844486

    还是按照上面的思路,在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 行汇编。

    image-20201231183108794

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

    [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
    

    结语

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

    CT文件

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

  • 相关阅读:
    鲍尔默称微软将投入数十亿美元打造数据中心业务(转)
    程序流程的辅助控制
    获取程序所在目录
    越界赋值
    长字串与宽字串
    长字串与短字串
    指针的运算
    Windows API 的数据类型与 Delphi 数据类型对照表
    给动态数组添加一个元素
    Delphi 与 C/C++ 数据类型对照表
  • 原文地址:https://www.cnblogs.com/adjwang/p/14214695.html
Copyright © 2020-2023  润新知