鬼泣修改吧 关注:1,382贴子:38,298
  • 5回复贴,共1

【教程】使用CE分析修改鬼泣4程序指令的方法与技巧实例—初级篇

取消只看楼主收藏回复

1LBS度娘自动吞楼……


1楼2012-11-25 18:36回复
    在看这篇教程之前需要先看过这样三篇准备知识,看过或已经知道的则直接跳过。
    第一篇,内存修改的准备知识:
    http://tieba.baidu.com/p/1151770288
    第二篇:修改游戏时常见指令的含义和用法:
    http://tieba.baidu.com/p/1151672121
    第三篇:CheatEngine修改器的安装与使用:
    http://tieba.baidu.com/p/1151655323


    2楼2012-11-25 18:36
    回复
      现在用实例来讲解如何用CE修改游戏程序的指令,来达到自己期望的修改效果。
      其实只要仔细看过上面三篇教程,完全理解里面所写的内容,完全就可以自己试着修改的,因为需要的知识和方法都已经写在里面了。与其说现在要讲的是方法,倒不如说是修改的思路,指令是死的,方法也是死的,但是思路却可以千变万化。不同游戏的处理判定的方法其实都大同小异,表面上看用的指令各不相同,但原理其实都差不多,所以这里最重要的就是理解和记熟修改时常用指令的含义和用法,然后就完全是个人的经验和思路了。首先从最简单的魔力值修改举例:

      运行游戏,运行CE,用CE选定游戏进程,然后进入游戏任务用CE搜索魔力值的内存地址。
      搜索类型[Value type]选[Float](什么时候应该选什么类型在上面CE教程中有讲解)
      搜索范围[Memory Scan Options]设为[From]00400000[To]1FFFFFFF,勾选上[Fast scan](这个选项表示只搜索4字节对齐的地址,也就是只搜索地址最低位为0/4/8/C的地址,因为浮点数的存取一定是这样4字节对齐的,这样可以提高搜索效率)
      搜索的方法在上面第三篇教程中也有讲解,拿到一个没改过游戏,在不知道游戏存取数据的格式前,我们一般只能用模糊查找,整数浮点都试过,只到找出有效地址。这里因为主要讲解的是修改指令,所以地址搜索部分就用最简单的方法。
      游戏中进入任务后,人物有3格魔力,本来应该是要用模糊搜索的,这里我们直接搜索[Float]数3000.0,然后在游戏中变魔人,变魔人的瞬间马上按ESE暂停游戏,因为变魔人要减一格半魔力,所以这里再用CE继续搜索1500.0,然后就会如图,左边结果框里面出现两个地址结果[19AE2F94]和[19AE65A4]。(模糊搜索的话就用未知值初始第一次搜索,然后在游戏中减少魔力,用CE搜索数值减小,在游戏中增加魔力,用CE搜索数值增大,反复几次以后同样也会出现上面两个地址结果。)
      将这两个地址[19AE2F94]和[19AE65A4]添加到地址列表框(注意因为魔力值在游戏中是指针地址,所以地址每次游戏都会变化,所以你们看到的不一定是这个地址),然后手动修改或者锁定地址对应的数值,比如说改成10000.0 或者0.0,然后看游戏中的魔力槽是否有对应的变化,游戏中魔力槽根据CE中数值修改对应变化的那个地址就是真正的魔力值地址,那么我们删掉第二个没用的地址。然后用内存查看[19AE2F94]这个地址,发现紧接着后面还有一个类似大小的浮点数,把它的地址也加入列表[19AE2F98],加入地址列表后显示这个值为10000.0,这个就是魔力的上限值(一般来说当前魔力和魔力上限或者是当前体力和体力上限都会像这样存在相隔不会太远的地址,大部分情况都会像这样放在一起)。现在当前魔力值和魔力上限值的地址都找到了,然后我们就开始正式追踪是什么指令在修改这个数值。
      选定地址列表中当前魔力值[19AE2F94]这个地址,先把魔力值改成满魔10000.0,然后在上面用鼠标右键击出菜单,选[Find out what writes to this address](什么指令在往这个地址写数据),然后CE会弹出一个[The following opcodes changed the selected address](下列指令修改过这个选定地址)。然后我们再回到游戏中变一次魔人,我们就会看到下图这样结果。

      注意这里的结果是程序的指令,所以只要程序本身是相同的版本,没有被动过手脚的话,每个人得到的地址结果应该都是一样的。其中,
      007a80a6 - movss [edi+00001f24],xmm0
      这个指令是在变魔人瞬间出现的,表示这条指令是专门处理变魔人时扣掉一格半魔力的指令。


      7楼2012-11-25 18:40
      回复
        007a8187 - movss [edi+00001f24],xmm0
        这是在变身成魔人状态后一定时间后出现的指令,这条指令显示就是保持魔人状态所扣魔力值的指令。
        我们先选定007a80a6这条指令,然后点选[More information](更多信息),就会看到附加信息窗:

        我们可以看到程序运行到这条指令时每个寄存器的状态,其中EDI=19AE1070(图是第二条指令的,不过无所谓,因为我们这里只是要看一下EDI的值),可以看到指令movss [edi+00001f24],xmm0中[edi+00001f24]的结果就是 [19AE2F94],而这个地址每次切换场景都会发生变化的原因就是EDI的值在变化,这里我们先不管它。
        然后我们点选[Extra info]窗中右边的[>]按键,就会弹出浮点寄存器和SSE寄存器的状态,如图。

        我们可以看到xmm0=8500.0,这正好就是满魔10000.0减掉变魔人的一格半1500.0后的结果。
        上面这些就是如何查看追踪出的指令的每个寄存器的值,因为但看指令本身也难以理解其用途,但是把指令运行时寄存器的值代入到指令中,就很容易明白指令的作用。比如刚才
        007a80a6 - movss [edi+00001f24],xmm0
        我们把EDI=19AE1070和xmm0=8500.0代入后,就会发现这条指令的含义就是把8500.0这个值写入到[19AE2F94]这个地址,也就是满魔减去变魔人的一格半后的值写到魔力的地址。
        还是选定这条指令,点选指令结果窗中[Show disassembler](查看反汇编),出现下图这样的窗口:

        我们现在就来讲怎么来修改程序来达到变魔人不扣魔力的效果,修改的方法有很多很多,当然最简单的方法就是直接把007a80a6 - movss [edi+00001f24],xmm0这条指令用nop指令替换掉,也就是把原指令f3 0f 11 87 24 1f 00 00的8个字节全部用90替换。也就是禁止把程序把变魔人时消耗一格半魔后的结果写回内存。
        这样改就要改很多字节(特别是我自己写SSG的时候),而且如果是x87浮点指令,读写指令还带栈操作的,单nop一条还会造成栈错位,所以我们应该还有更合理的方法。我们看007a80a6这条指令前面的几条指令:
        7a8074 call edx //不知道调用哪里的子程序
        7a8076 test al,al //测试al是否为零,因为从这里一直到7a80a6,都没有看到重新对al赋值的指令,而刚才我们看7a80a6这条指令的每个寄存器状态时看到,EAX=08DEC334,所以这里al=34,不=0
        7a8078 je 007a8215 //上面test的结果=0则跳转,显然结果不为零,所以这里不会跳转,而是继续下条指令
        7a807e cmp byte ptr [edi+00001f2d],00 //比较[edi+00001f2d]这里的一字节结果和00的大小,因为从这里到 7a80a6之前也没有给edi重新赋值的指令,所以这里的edi同样是=19AE1070,所以我们可以知道edi+00001f2d这里是什么值,把这个值加入地址列表(注意是1字节,因为指令上含有byte ptr),我们可以看到,普通状态时这个值为0,魔人状态时这个值为1,显然这条指令是在判定当前是否是魔人状态。
        7a8085 jne 007a80e7 //如果上面比较结果不为零则跳转,显示这里是判定如果已经魔人状态了那么就不再重复扣变魔人时的一格半魔,如果是普通状态则不跳转,也就是要继续下面扣一格半魔力的指令。
        7a8087 cmp byte ptr [edi+0000216c],01 //因为edi还是没有变化,所以我们还是可以知道这里的值,添加计算后的结果到地址列表,可以看到如果是普通装的话值为0,如果是无限魔力装的话这里就是01。当然因为我们之前要搜索魔力的地址,所以这里肯定是普通装也就是=0,因为这条指令是在比较和01的大小,所以我们这里手动把地址列表的结果改成1,会发现这里我们变成无限魔力装的效果了,这样也可以证实这个字节的作用。


        8楼2012-11-25 18:40
        收起回复
          7a808e je 007a80d5 //如果上面无限魔力装地址处=01则跳转,也就是跳过紧接在下面的扣一格半魔的指令。如果不是无限魔力装,则继续往下运行。
          7a8090 movss xmm0,[edi+00001f24] //我们已经知道[edi+00001f24]这是当前魔力的地址,所以这条指令是读取当前魔力值,然后写入到xmm0寄存器中
          7a8098 subss xmm0,[00db6dd8] //用xmm0中的值减去[00db6dd8]的值,可以看到[00db6dd8]这是一个没有寄存器变量的直接地址值,也就表示这是一个不会变化地址,将[00db6dd8]这个地址添加到地址列表,因为这是一条SSE 指令,所以我们把数值类型选为[Float],添加后可以看到这个地址的值=1500.0,所以这条指令的作用就是当前魔力值减去1500.0也就是一格半魔力。
          7a80a0 xorps xmm1,xmm1 //清空xmm1寄存器,使xmm1=0.0
          7a80a3 comiss xmm1,xmm0 //比较xmm1也就是0和xmm0也就是当前魔力减掉1500.0后的结果之间的大小
          7a80a6 movss [edi+00001f24],xmm0 //这条也就是我们前面追踪到指令,将之前的当前魔力值减去1500.0之后的结果写回到存放魔力值的地址。继续往下看……
          7a80ae jna 007a80b8 //如果上面7a80a3的结果不大于则跳转,因为xmm1=0,而xmm0=剩余魔力值,所以xmm1肯定是不大于xmm0。所以这条指令是肯定跳转,除非xmm0也就是剩余魔力值减成负数,这样xmm1才会大于。因为变魔人需要最少三格魔力也就是3000.0,减去1500肯定是不会小于0的,所以从意义上来说这其实是一条废指令。当然这是因为程序前面还有魔力必须大于3000.0才能变魔人作为先决条件,这里我们先不管。
          7080b0 movss [edi+00001f24],xmm1 //假设上面当前魔力值减去一格半魔后,结果小于零也就是负数时,那么 7a80ae这条比较指令这里xmm1就会大于xmm0,因为xmm1=0,xmm0小于零为负数。那么这个时候就会把xmm1=0写回到存放魔力的地址。显然这条指令的作用就是当魔力被减为负值时,则把结果限制到零,写回地址。
          7a80b8 movss xmm0,[edi+00001f28] //从存放魔力上限值地址处读取值,写入到xmm0
          7a80c0 movss xmm1,[edi+00001f24] //从存放当前魔力值地址处读取值,写入到xmm1
          7a80c8 comiss xmm1,xmm0 //比较当前魔力和魔力上限值的大小
          7a80cb jna 007a80d5 //因为前面是处理变魔人时扣魔力的指令,所以这里xmm1肯定是不大于xmm0,所以这也是一条肯定跳转指令,因为一般只会在魔力增加时,才会判定当前魔力是否因为增加后会超过魔力上限。
          7080cd movss [edi+00001f24],xmm0 //当上面一条比较指令xmm1>xmm0,也就是当前魔力大于魔力上限时,将xmm0 也就是魔力上限值写回存放当前魔力值的地址。显然这也是正常情况下肯定不会运行到的指令。
          PS:鬼泣4在这里的处理比较特殊,不管是在魔力数值减小还是增加时,都会都同时判定下限0和上限值。一般大部分情况下,游戏都只会在数值减少时判定下限零,在数值增加时判定是否超过指定上限。


          9楼2012-11-25 18:40
          回复
            这里就非常详细的讲解了程序是如何处理变魔人时,扣一格半魔力的处理指令,其实只要自己详细看过前面常用汇编指令的教程,自己就可以分析出来。现在我们就来看要怎么根据分析的结果来修改这些指令来达到我们想要的效果。
            首先,我们看
            7a8078 je 007a8215 //在修改前我们先用双击这条指令弹出指令修改窗,然后用Ctrl+C复制这条指令,以便还原。再来这条指令在汇编指令教程里面我们已经说过了,在test指令之后,用jae可以表示绝对跳转,jb可以表示绝对不跳转。因为紧接在下面的就是扣一格半魔力处理指令,所以我们这里先把这里改成jae也就是绝对跳转,那么就应该不会运行变身时扣一格半魔的处理。修改后进游戏验证,果然变魔人时不会扣一格半魔了。但是这个跳转跳得太远,中间还跳过了很多其它的指令,在没有确定被跳过的指令都是无用的,而且我们也不知道前面test al,al 的确实作用之前,这里暂时还是不要改这条。再往下看,
            7a8085 jne 007a8087
            这条指令跳转的条件是上面一条[edi+00001f2d]不等于00,根据上面将这个地址结果添加到地址列表,我们知道 [edi+00001f2d]这里的一字节用00表示普通状态,01表示魔人状态。那么我们把这条jne改成jae就表示绝对跳转,因为00or01一定>or=0。不过这条指令还是跳过了7a80df 这里一个暂时未知作用的call edx,所以暂时也不要改这条。再往下看,
            7a808e je 007a80d5
            这条指令跳转的条件是[edi+0000216c]=01,也就是当前为无限魔人装(把地址加入地址列表后,手动修改列表中的数字,来测试满足条件和不满足条件两种状态,很容易就能试出这个字节表示是否为无限魔力装,因为这个值还会有别的判定,自动三红、RG自动满槽等效果)。而且这条跳转指令跳过的都是刚才分析过的指令,确信跳过它们不会对程序有其它影响。因为是[edi+0000216c]=01比较,所以我们这里改成jbe就表示[edi+0000216c]这里不管是00 还是01都会满足条件。修改后测试确实游戏中变魔人时不扣一格半魔,然后我们把这条还原,再往下看。
            7a8098 subss xmm0,[00db6dd8]
            显然这条就是真正的减运算了,nop掉这条指令,或者是改成subss xmm0,[一个确实值为零的地址]也可以达到不减的效果。这个[一个确实值为零的地址]可以用CE勾选上[Also scan read-only memory](总是搜索只读内存)来找一个肯定等于零且不会变化的地址。比如subss xmm0,[00400014],因为[00400014]这里肯定等于零且肯定不会变化( 因为只读),所以就变成了xmm0=xmm0-0,也就是不变。测试结果,还原,再往下看,
            7a80ae jna 007a80b8
            这条指令跳转的条件是7a80a3这条comiss xmm1,xmm0,正常情况这条的条件是一定满足,这里我们改成一定不跳转试试看,jna改成je,je表示xmm1=xmm0=0时跳转,因为当前魔力肯定不=0,所以肯定不跳转,那么就会继续运行 7a80b0这条,而这条是将xmm1=0写回当前魔力的地址,结果就成了只要一变魔力,魔力就会马上减成零。虽然这种改法没什么实际意义,但这里我们可以知道有些时候我们需要一些值一减就马上减到零的改法。还原,再往下看,
            7a80cb jna 007a80d5
            这条指令跳转的条件是7a800c8这条comiss xmm1,xmm0,因为当前魔力xmm1一定不大于魔力上限xmm0,所以jna也表示一定跳转。这里我们把它改成je,则表示如果当前魔力=魔力上限则跳转,如果不相等则不跳转。而不跳转就会要执行7a80cd这条将魔力上限写回当前魔力值的地址。就成了不管当前剩多少魔力只要一变魔人,魔力就会加满到上限。看看,简单的一条变魔人扣一格半魔力的处理指令,就可以有这么多种改法,只要理解了指令原来的作用,根据指令的规则就完全可以修改出自己想要的结果。同样的,我们在最开始指令追踪窗口追踪到的第二条指令,保持魔人状态扣魔力的处理指令:
            007a8187 - movss [edi+00001f24],xmm0
            同样往上看,
            7a814c cmp byte ptr [edi+0000216c],01
            还记得上面这条么,当前是否是无限魔力装状态?那么
            7a8153 je 007a81b6这条改成jbe 007a81b6就成了一定跳转,那么维持魔人也不会减魔力了。还原
            7a81ac jna 00781b6改成je,就成了只要维持魔人一开始耗魔,魔力就会加满了,也就成了,无限满魔了。
            另外,除了变魔人和维持魔人会有扣魔指令以外,魔人但丁的KICK13和路西法锁定前插也都有扣魔判定,原理和上面这些也差不多,试试自己来分析修改后面这两条耗魔处理?另外还有但丁的厄运槽、RG槽,在我们追踪这些值增加时出现的指令时,会看到前面也肯定会看到有
            cmp byte ptr [寄存器+0000216c],01
            这样的指令,因为无限魔力装时厄运槽、RG槽是自动加满的,试试自己找到它们,并修改成没有选无限魔力装也会自动回满厄运槽、RG槽的修改?


            10楼2012-11-25 18:41
            回复