【第四弹】设置特技专有效果
上一次中,我们已经把寻宝概率+300%设置为了全局效果,接下来我们将其设置为特技风水的转有效果。
首先,我们先要找到武将的指针位置。因为只有武将指针确定了,才能据此判定是否是指定特技。我们回到上一弹中最开始就提到的“发现宝物后结算”。由于发现宝物后要给武将加功绩、经验,因此这里是一定有武将指针的,我们只需要追踪它一路传进来的路径即可。
用快捷键F5进入C代码。在结算中,有一行代码为调用sub_4A70D0(a2, 3, 1, 1)。在RK的资料中查询4A70D0,可以发现第一个参数a2就是武将指针。结算代码所在函数为int sub_5D3D90(void *lp, void *a2, void *a3),其中a2为第二个参数。我们根据XREF回到调用这个函数的地方,发现函数的调用方法为sub_5D3D90(v9, v3, v2)。
进一步的,在IDA中我们可以发现v3为esi寄存器存放的内容,也就是说,esi中就是我们要找的武将指针。事实上在很多情况下,esi存放的都是武将指针。
接下来,我们在RK的资料里搜索判定特技的代码模板,然后照葫芦画瓢。判断是否有指定特技的模板如下:
6a [**] -push [**] [**]=待判断的特技ID
8b ce - mov ecx,esi
e8 db fa bd ff - call 004890f0
85 c0 -test eax,eax
0f 84 [X(4bytes)] - je [Y] 如果没有这个特技,那么跳转到[Y]地址,[X]=([Y]-当前指令的下一条指令的首地址)的小端序(如,当前指令为00000000,当前指令的下一条为00000005,要跳转0000000f,那么X为0000000f-00000005=0000000A,对应的小端序为0A000000)
在我们这里,特技·风水的id为97,在16进制里是61。我们要实现的效果是:如果特技为风水,那么跳到新的公式,否则还沿用原有公式。我们写出下面的汇编码:
005D5B45 0F B6 573C movzx edx, byte ptr [edi+3Ch] // edx = 宝物价值,不变
005D5B49 b8 3d 0000 00 mov eax, 3Dh // eax = 61
005D5B4E 2bc2 sub eax, edx // eax = eax – edx
// 以下四行判断是否有风水特技,有的话,标志寄存器置为1
*005D5B50 6a 61 push 061h
*005D5B52 8b ce mov ecx, esi
*005D5B54 E8 ?? ?? ?? ?? call 004890f0
*005D5B59 85 C0 test eax, eax
// 判断标志寄存器。如果有风水特技,则jne (jump if not equal to 0)成功跳转到被除数为1
*005D5B5B 0f 85 ?? ?? ?? ?? jne Label1
*005D5B61 b9 20 00 00 00 mov ecx, 20h
*005D5B66 e9 ?? ?? ?? ?? jmp Label2
Label1:
*005D5B6A b9 01 00 00 00 mov ecx, 1h // 20改为1,搜索概率为原先20倍
Label2:
*005D5B6F 99 cdq
*005D5B70 f7 f9 idiv ecx // eax = eax / 参数(1)
这里所有的?? ?? 都是跳转的偏移量。算法就是我上边写的那个公式。实际上我懒得算了,因为我们会发现,写完之后已经最后一条指令到了005D5B70,已经超出了原来的代码范围005D5B60,因此会覆盖掉原有的无辜代码。换言之,原来的内存空间已经容不下我们新增的逻辑了。因此,我们需要扩展代码范围。
好消息是,在整个exe的最后边,存在大量未使用的代码区域。SIRE的做法是把所有新增的逻辑都先跳转到8Axxxx的新增代码,再跳转回原来的代码,这样就相当于“插入”了新的代码逻辑。SIRE用了8Axxxx,我们则从920000开始使用,避免和SIRE冲突。(9200000这个代码段算是RK钦定让我使用的了,如果以后修改的人多了、比如真的有10个喜羊羊的话,可以统一分配一下各个修改者使用的代码段)
新的代码为:
005D5B45 0f b6 573c movzx edx, byte ptr [edi+3Ch] // edx = 宝物价值,不变
*005D5B49 e9 b6 a4 2e 00 jmp 008C000 // 跳转到新增代码段
// 新增代码段,位于920000
00920000 b8 3d 0000 00 mov eax, 3Dh // eax = 61
00920005 2bc2 sub eax, edx // eax = eax - edx
00920007 b9 14 0000 00 mov ecx, 14h // ecx = 20
0092000C 99 cdq
0092000D f7f9 idiv ecx // eax = eax / ecx
0092000F 50 push eax // 保护eax寄存器,因为判断特技会用到eax
00920010 6a61 push 061h // 检验是否为风水
00920012 8bce mov ecx, esi
00920014 E8 d7 90B6 FF call 004890f0
00920019 85C0 test eax, eax
0092001B 58 pop eax // 恢复eax
0092001C 0f 84 0600 00 00 je 00920028 // 不是风水的话直接跳过乘法
00920022 6b c0 1400 00 00 imul eax,eax,14 // *20
00920028 e9 34 5bcb ff jmp 005D5B61 // 跳回
注意在这里,我们又做了一定的改进,原先是直接改掉/20的参数20,精确度不是很高,我们在这里改成先除20、再判断风水、进而决定要不要在做乘法。我们这里边是在00920022里边将基础倍率乘以20,如果要修改成+300%,就乘以把14h改成04。
此外,我们也可以乘以浮点数,更加精确。浮点数的表示方法依靠IEEE 754标准,转换方法可以使用python的struct类库:
Python 2.7.4
>>> import struct
>>> struct.pack("<f",238.3).encode('hex')
'cd4c6e43'
也就是说,浮点数238.3的表示方法为cd4c6e43.
但是在这里,由于基础倍率最高才3%,乘以浮点数的意义不大,直接按整数倍翻倍即可。如果想改为更精确的乘浮点数,可以参考RK关于“能吏”等特技的修改。这一部分留做练习
