首先明确下 Sire 自定义包的概念:
- 文件名后缀为 scp,放入 sire 的 customize 目录下生效,同名的 scpv 文件为对应的参数配置文件
- scp文件内容一般为 xml 文本,也有一些二进制文件(可能加密过? TODO: 需找大佬确认)
- sire 的工作原理本质是对 SAN11PK 进程的内存中的机械码和数据进行修改,所以自定义包也是基于这一点,可以看到大佬们的自定义包中的 xml 文件都是描述对内存中的数据进行修改的
- 包中最重要的部分是
<Codes>
标签,包含了一系列修改内存的操作<Code>
,<Code>
中包含了代码的地址<Address>
、启用该功能时的机器码EnableCode
和不启用该功能时的机器码DisableCode
(大部分情况下为原机器码)。
下面是一个简单的显示武将真实忠诚度包 显示真实忠诚.scp的例子:
<?xml version="1.0" encoding="UTF-8"?>
<CustomModifyPackage>
<PackageName>显示真实忠诚</PackageName>
<PackageAuthor>龙哥</PackageAuthor>
<PackageDiscription>显示超过100的忠诚的真实数值</PackageDiscription>
<CustomModifyItems>
<CustomModifyItem>
<Caption>开启</Caption>
<Enabled>true</Enabled>
<Codes>
<Code>
<Description>代码</Description>
<Address>004C8A9B</Address><!--长度:5-->
<EnableCode>B8 64 00 00 00</EnableCode>
<DisableCode>BE 64 00 00 00</DisableCode>
</Code>
</Codes>
</CustomModifyItem>
</CustomModifyItems>
</CustomModifyPackage>
(疑问:观察到每个<Address>
后面都有一个长度的注释,实测为非必需,猜测自定包文件是由某编辑工具生成? TODO: 需找大佬确认)
做的操作是将地址为 004C8A9B 处的机械码 BE 64 00 00 00 用 B8 64 00 00 00 替换,
在内存资料全局搜索 004C8A9B 可以搜到:
[命令编号为23:返回武将忠诚度]
004C8A8B - 0f b6 b7 ac 00 00 00 - movzx esi,byte ptr [edi+000000ac] esi = 忠诚度
004C8A92 - 83 fe 64 - cmp esi,64
004C8A95 - 0f 8c e4 03 00 00 - jl 004c8e7f
004C8A9B - be 64 00 00 00 - mov esi,00000064
004C8AA0 - 8b c6 - mov eax,esi 返回武将忠诚度
004C8AA2 - 5e - pop esi
004C8AA3 - 5b - pop ebx
004C8AA4 - 5f - pop edi
004C8AA5 - c3 - ret
这段汇编代码主要处理的是返回某个武将的忠诚度。我们可以逐行分析它的功能:
-
004C8A8B - 0f b6 b7 ac 00 00 00 - movzx esi, byte ptr [edi+000000ac]
movzx esi, byte ptr [edi+000000ac]
:这个指令从edi
寄存器指向的地址偏移0xAC
处读取一个字节,并将其零扩展后存储到esi
寄存器中。这里的edi
为武将指针,esi
存放的是武将的忠诚度。
-
004C8A92 - 83 fe 64 - cmp esi, 64
cmp esi, 64
:将esi
中的值(忠诚度)与十六进制的64
(即十进制的100)进行比较。
-
004C8A95 - 0f 8c e4 03 00 00 - jl 004c8e7f
jl 004c8e7f
:如果esi
中的值小于100(即忠诚度小于100),则跳转到地址004c8e7f
。jl
是“jump if less”(如果小于则跳转)的缩写。
-
004C8A9B - be 64 00 00 00 - mov esi, 00000064
mov esi, 00000064
:将十六进制的64
(即十进制的100)赋值给esi
寄存器。如果前面的比较没有跳转,说明忠诚度不小于100,这里将忠诚度设为100。
-
004C8AA0 - 8b c6 - mov eax, esi
mov eax, esi
:将esi
寄存器中的值(此时的忠诚度值)复制到eax
寄存器中。eax
寄存器通常用于函数的返回值,所以这里是将忠诚度值准备好作为返回值。
-
004C8AA2 - 5e - pop esi
pop esi
:从栈顶弹出一个值到esi
寄存器,恢复之前保存的esi
值。
-
004C8AA3 - 5b - pop ebx
pop ebx
:从栈顶弹出一个值到ebx
寄存器,恢复之前保存的ebx
值。
-
004C8AA4 - 5f - pop edi
pop edi
:从栈顶弹出一个值到edi
寄存器,恢复之前保存的edi
值。
-
004C8AA5 - c3 - ret
ret
:返回调用函数。这条指令会从栈顶弹出返回地址,并跳转到这个地址,恢复到调用该函数的地方继续执行。
这段代码的功能是读取一个武将的忠诚度,并确保返回的忠诚度不会超过100。如果忠诚度超过100,则将其限制在100以内,然后将这个值返回给调用者。
而我们做的修改是将地址 004C8A9B
处的代码改为:
B8 64 00 00 00 ; mov eax, 00000064
这条指令的含义是:将十六进制的 64 (十进制 100) 赋值给 eax
寄存器,而esi
的值还是原来的,然后下一条将esi
值加载给eax
返回,就达到了返回真实忠诚度的作用了。
(PS: 这里实际使得武将忠诚度上限不再是100了,感觉对其他忠诚度的操作如登用武将会有影响,不是很推荐)