标题: DOS版三国志英杰传的研究心得——肆, 另一块记录人物数据的地址
本主题由 阿尔法孝直 于 2025-7-23 01:43 设置高亮
性别:未知-离线 漫漫苦短

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 64
编号 545816
注册 2023-12-25


发表于 2025-3-13 19:19 资料 短消息 只看该作者
DOS版三国志英杰传的研究心得——肆

上一章简单分析了部队在战场上的数据,先回顾一下。

QUOTE:
以D076为首项,D(14)为公差,2D(45)为项数的一个十六进制下的等差数列中的一个地址值

按照上一章的分析这其中记录了人物代码、战场代码、仇人代码、撤退标志、AI类型、士气值、策略值等等信息,但显然,这不能完全概述其中的任一人物的全部信息,例如一个人物的等级经验兵种、还有武力智力统御力这三个一直被玩家拿来比较综合素质的数值,还有这个部队的名字等等信息,这些都没有在部队战场上的数据中,但实际上又不能不在战场上使用,例如部队在战场使用策略获得经验升级,不能不依靠等级经验兵种,部队也需要用武力智力统御力这三者计算在战场上的攻击力防御力和策略值的数据,那么这些数据又是记录在哪里?

可以想象应该是有新的一块数据是记录于此的,这块数据记为人物的固有数据(虽然等级经验兵种等等这些数据都是有变动的),如果依照部队在战场上的数据记录的特点,那么每一个人物的都是有对应的一个的首地址,也就是说这些地址的应该也是以X为首项,Y为公差,Z为项数的一个十六进制下的等差数列中的一个地址值。那么XYZ对应的十六进制值究竟是什么?


顶部
性别:未知-离线 漫漫苦短

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 64
编号 545816
注册 2023-12-25


发表于 2025-4-13 19:19 资料 短消息 只看该作者
一、人物数据所在位置

根据上一章的内容,战场数据共630字节大小,并且分成14×45字节,战场数据这块上最多只能存下45个人物的数据,但英杰传的人物远远不止45个,实际上在BAKDATA.R3中一共保存了384个人物的初始数据(包括以兵种命名的部队或只在剧情中才出现的人物),也就是说在DS段还有一块能保存384个人物数据的内存空间,而对于每个人物都对应唯一的人物代码。

并且上一章还粗略分析了每只部队战场数据的各个偏移的含义,偏移+0的就是保存2字节大小的人物代码,换句话说,就是每只部队战场数据偏移+0的人物代码的来源就是人物数据所在内存空间对应人物代码,而这人物代码的分配也是和每只部队战场数据偏移+2的战场代码一样是按照顺序进行分配了,但在人物数据这部分的人物代码并不是从0开始,而是从0x200(512)开始的,而战场数据的那块数据的人物代码确实是从0开始的,也就是说,人物数据的人物代码 = 战场数据的人物代码 + 0x200(512),至于具体的细节后文再解析。

扯远了,本章开头的XYZ对应的值是什么才是本节的重点,Z其实已经透露出来了,就是0x180(384),而对应的人物代码的范围就是0200 - 0379,0200就是劉備,0201是关羽,0204是曹操,0208是吕布,0200 - 02FF一共256个人物都是具体姓名的人物,从0300 - 035F是以兵种命名的部队,而0360 - 0379是这部分人物就很杂乱了,比如道具屋0376,李明0377,总之一共384个。

而XY,还是在汇编中找这类似战场数据中这部分代码的,从而对应人物数据的XY。

seg002:7746 B0 0E                          mov     al, 0Eh
seg002:7748 F6 66 FF                       mul     [bp+var_1]                              ; 部队战场代码
seg002:774B 05 76 D0                       add     ax, 0D076h

在代码找到了这两个部分的汇编代码,相似度很高吧,当然只是举出两个例子,arg_0的值还是以0xD076为首项,0xD(14)为公差,0x2D(45)为项数的一个十六进制下的等差数列中的一个地址值,两个部分的汇编代码sub_22F6A和sub_23098这两个函数的具体内容这章也会大致介绍。

seg002:6E7C 8B 76 06                       mov     si, [bp+arg_0]
seg002:6E7F 8A 44 0C                       mov     al, [si+0Ch]
seg002:6E82 50                             push    ax
seg002:6E83 6B 04 2C                       imul    ax, [si], 2Ch                      ; [si]获取战场数据的首地址对应的人物代码,并且乘0x2C(44),并且传给AX
seg002:6E86 05 16 68                       add     ax, 6816h
seg002:6E89 50                             push    ax                                      ; 人物数据的首地址
seg002:6E8A 9A 0A 60 F6 1C                 call    sub_22F6A



seg002:6E98 8B 76 06                       mov     si, [bp+arg_0]
seg002:6E9B 8A 44 0C                       mov     al, [si+0Ch]
seg002:6E9E 50                             push    ax
seg002:6E9F 6B 04 2C                       imul    ax, [si], 2Ch                   ; [si]获取战场数据的首地址对应的人物代码,并且乘0x2C(44),并且传给AX
seg002:6EA2 05 16 68                       add     ax, 6816h
seg002:6EA5 50                             push    ax                                      ; 人物数据的首地址
seg002:6EA6 9A 38 61 F6 1C                 call    sub_23098

seg002:6E83-seg002:6E86/seg002:6E9F-seg002:6EA2,与seg002:7746-seg002:774B的地址计算方法很相似吧,seg002:6E7F/seg002:6E9B都是获取战场数据的一个方法,可以见上一章回忆一下。

所以X对应的十六进制就是0x6816,Y对应的十六进制就是0x2C,这也是龙吟前辈曾经提到的44字节,对比战场数据的Y为0xE,每一个人物都差了44-14=30(0x2C-0xE)字节大小,Z相差更大,用Y×Z就可以算出人物数据占了多大的内存空间,这两块数据占用的内存大小差距相当大。

总结人物数据的首地址为以0x6816为首项,0x2C(44)为公差,0x180(384)为项数的一个十六进制下的等差数列中的一个地址值。


顶部
性别:男-离线 神雕小侠

Rank: 3Rank: 3Rank: 3
组别 士兵
级别 忠义校尉
功绩 3
帖子 251
编号 44352
注册 2005-7-27


发表于 2025-4-14 12:35 资料 短消息 只看该作者
兄弟有没有看过之前龙吟前辈的分析,我感觉您的这些研究成功跟他的应该会有一些交集
顶部
性别:未知-离线 漫漫苦短

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 64
编号 545816
注册 2023-12-25


发表于 2025-4-14 18:18 资料 短消息 只看该作者
回复 #3 神雕小侠 的帖子

岂止说是有交集,龙吟前辈简直是我的领路人,我一开始就是对着他的《三国志英杰传分析结果》来分析,还有周瑜前辈的全补丁的修改方法等等资料,目前关于他们的这些研究成果大部分都找到了程序中对应的位置,不过还是有很多内容要分析的,其实我汇编分析能力也不强,也只是站在巨人的肩膀上。
顶部
性别:未知-离线 漫漫苦短

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 64
编号 545816
注册 2023-12-25


发表于 2025-4-19 19:19 资料 短消息 只看该作者
二、人物数据的重要属性——兵种(1)

毫无疑问,人物数据占用的内存空间远大于战场数据的空间,本章分析的内容的篇幅也会比上一章长,分析人物数据中的44字节数据中大部分内容代表的含义。

每个人物的人物代码都是占用2字节的,上一节已经说了战场数据和人物数据的人物代码上的联系,但我想先从其他的数据开始讲起。如果说想从这44字节中挑出最重要的一项属性来说明,我认为这项属性就是每个人物的兵种代码,兵种代码虽然只占用的1个字节,但还是至关重要的,它在偏移+020处,sub_23A2C函数就是它的Get方法,arg_0的值都是以0x6816为首项,0x2C(44)为公差,0x180(384)为项数的一个十六进制下的等差数列中的一个地址值。

seg001:6ACC                sub_23A2C proc far
seg001:6ACC
seg001:6ACC                arg_0           = word ptr  6
seg001:6ACC
seg001:6ACC 55                             push    bp
seg001:6ACD 8B EC                          mov     bp, sp
seg001:6ACF 8B 5E 06                       mov     bx, [bp+arg_0]
seg001:6AD2 8A 47 20                       mov     al, [bx+20h]
seg001:6AD5 C9                             leave
seg001:6AD6 CA 02 00                       retf    2
seg001:6AD6                sub_23A2C endp

这是根据人物数据的首地址来获取对应的兵种代码,那么有没有方法根据部队战场数据的首地址来获取对应的兵种代码,其实根据上一节的计算我们就能看出,通过人物代码,可以将战场数据的首地址转化成人物数据的首地址,于是就有了sub_3628C函数,arg_0的值都是以0xD076为首项,0xD(14)为公差,0x2D(45)为项数的一个十六进制下的等差数列中的一个地址值。

seg002:936C                sub_3628C proc far
seg002:936C
seg002:936C                arg_0           = word ptr  6
seg002:936C
seg002:936C 55                             push    bp
seg002:936D 8B EC                          mov     bp, sp
seg002:936F 8B 5E 06                       mov     bx, [bp+arg_0]
seg002:9372 6B 07 2C                       imul    ax, [bx], 44
seg002:9375 05 16 68                       add     ax, 6816h
seg002:9378 50                             push    ax
seg002:9379 9A CC 6A F6 1C                 call    sub_23A2C
seg002:937E C9                             leave
seg002:937F CA 02 00                       retf    2
seg002:937F               sub_3628C endp

从理论上来说sub_3628C这个函数是不需要调用sub_23A2C函数,因为在seg002:9375这句就已经求出人物数据的首地址,再根据之前的seg001:6ACF-seg001:6AD2的汇编代码稍改就可以求出兵种代码。

但实际上有可能人物数据的内存空间是甲设计结构,而战场数据的内存空间又是乙设计的,数据之间的逻辑是丙等人设计的,如果说甲设计的人物数据的内存空间结构发生变化,对应乙丙等人的汇编代码有可能出现多处改动,这是没有必要的,所以最好的方式就是就是提供一个类似接口的方法,比如一个调用函数(本例的sub_23A2C),这样无论甲设计的人物数据的内存空间结构发生怎样的变化,只要甲给定一个函数,乙丙等人根据这个函数,就是实现直接获取甲设计的的人物数据其中一项属性。

在此之前,封剑尘和孝直前辈都分别提到过——兵种顺序(及代码)DOS版英杰传兵种攻防、兵力、移动力、策略修改,可能大部分人只知道兵种名称,却不知对应的兵种代码,下一节就探究兵种名称和兵种代码的内在逻辑。
顶部
性别:未知-离线 漫漫苦短

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 64
编号 545816
注册 2023-12-25


发表于 2025-4-23 19:19 资料 短消息 只看该作者
三、人物数据的重要属性——兵种(2)

上节的只知道了可以获取兵种代码,而且内存中很多数据都是与兵种代码相关的,接下来我们就开始研究兵种代码对应的汇编代码,比如说根据兵种代码确定兵种名字。

英杰传中不止一处会显示人物对应的兵种名称,显然这是和兵种代码相关的,显示对应的兵种名称,可以看下面两处的代码,arg_0都是部队战场数据的首地址。

seg002:7978 8B 5E 06                       mov     bx, [bp+arg_0]
seg002:797B 6B 07 2C                       imul    ax, [bx], 44
seg002:797E 05 16 68                       add     ax, 6816h
seg002:7981 89 46 F0                       mov     [bp+var_10], ax
seg002:7984 53                             push    bx
seg002:7985 9A 6C 93 F2 2C                 call    sub_3628C
seg002:798A 88 46 F7                       mov     [bp+var_9], al
...
seg002:7B2A 8A 5E F7                       mov     bl, [bp+var_9]
seg002:7B2D 2A FF                          sub     bh, bh
seg002:7B2F 03 DB                          add     bx, bx
seg002:7B31 FF B7 A2 0A                    push    word ptr [bx+0AA2h]                     ; "兵种名称"的字符串地址
seg002:7B35 68 D9 33                       push    33D9h                                           ; 输出格式"%s"
seg002:7B38 68 26 51                       push    5126h
seg002:7B3B 9A 3E 0C F6 1C                 call    sub_1DB9E                   ; 打印"兵种名称"字符串
seg002:7B40 83 C4 06                       add     sp, 6



seg002:8D44 8B 76 06                       mov     si, [bp+arg_0]
seg002:8D4C 6B 3C 2C                       imul    di, [si], 44
seg002:8D4F 81 C7 16 68                    add     di, 6816h
seg002:8D53 56                             push    si
seg002:8D54 9A 6C 93 F2 2C                 call   sub_3628C
seg002:8D59 88 46 F7                       mov     [bp+var_9], al
...
seg002:8E5A 8A 5E F7                       mov     bl, [bp+var_9]
seg002:8E5D 2A FF                          sub     bh, bh
seg002:8E5F 03 DB                          add     bx, bx
seg002:8E61 FF B7 A2 0A                    push    word ptr [bx+0AA2h]                     ; "兵种名称"的字符串地址
seg002:8E65 68 3A 34                       push    343Ah                                           ; 输出格式"%s"
seg002:8E68 68 26 51                       push    5126h
seg002:8E6B 9A 3E 0C F6 1C                 call    sub_1DB9E                   ; 打印"兵种名称"字符串
seg002:8E70 83 C4 06                       add     sp, 6

seg002:7B38-seg002:7B3B/seg002:8E6B-seg002:8E6B都是同样的调用显示文字的函数,显示文字的内容是根据seg002:7B31/seg002:8E61的值来确定,这就是接下来的重点,要想知道显示文字的内容,那么我们需要找到DS段的内存偏移地址0AA2处看里面的内存数据是什么?

dseg:0AA2 28 0A
dseg:0AA4 2D 0A
dseg:0AA6 32 0A
dseg:0AA8 37 0A
dseg:0AAA 3C 0A
dseg:0AAC 43 0A
dseg:0AAE 4A 0A
dseg:0AB0 51 0A
dseg:0AB2 58 0A
dseg:0AB4 5F 0A
dseg:0AB6 64 0A
dseg:0AB8 69 0A
dseg:0ABA 6E 0A
dseg:0ABC 75 0A
dseg:0ABE 7E 0A
dseg:0AC0 87 0A
dseg:0AC2 8E 0A
dseg:0AC4 95 0A
dseg:0AC6 9A 0A

光看这处内存数据是看不出有兵种代码对应的兵种名称,需要从原来的代码先研究seg002:7B2A-seg002:7B31/seg002:8E5A-seg002:8E61的代码的意义。

seg002:7B2A/seg002:8E5A取出兵种代码,seg002:7B2F/seg002:8E5F将这个兵种代码自加(乘2),再取出DS:[兵种代码*2 + 0AA2h]的2字节值。接下来的问题就是seg002:7B31/seg002:8E61的word ptr [bx+0AA2h]这句汇编代码的意义。

当兵种代码的值从0到0x13(19)变化,word ptr [bx+0AA2h]的结果分别是0A28, 0A2D, 0A32, 0A37, 0A3C, 0A43, 0A4A, 0A51, 0A58, 0A5F, 0A64, 0A69, 0A6E, 0A75, 0A7E, 0A87, 0A8E, 0A95, 0A9A。

再从这几个值的DS段内存的偏移地址处查看内存数据。

dseg:0A28 B5 75 A7 4C 00                asc_411F8       text "BIG5", '短兵',0                             ; DATA XREF: dseg:0AA2↓o
dseg:0A2D AA F8 A7 4C 00                asc_411FD       text "BIG5", '長兵',0                             ; DATA XREF: dseg:0AA4↓o
dseg:0A32 BE D4 A8 AE 00                asc_41202       text "BIG5", '戰車',0                             ; DATA XREF: dseg:0AA6↓o
dseg:0A37 A4 7D A7 4C 00                asc_41207       text "BIG5", '弓兵',0                             ; DATA XREF: dseg:0AA8↓o
dseg:0A3C B3 73 A9 B8 A7 4C 00          asc_4120C       text "BIG5", '連弩兵',0                            ; DATA XREF: dseg:0AAA↓o
dseg:0A43 B5 6F A5 DB A8 AE 00          asc_41213       text "BIG5", '發石車',0                            ; DATA XREF: dseg:0AAC↓o
dseg:0A4A BB B4 C3 4D A7 4C 00          asc_4121A       text "BIG5", '輕騎兵',0                            ; DATA XREF: dseg:0AAE↓o
dseg:0A51 AD AB C3 4D A7 4C 00          asc_41221       text "BIG5", '重騎兵',0                            ; DATA XREF: dseg:0AB0↓o
dseg:0A58 AA F1 BD C3 B6 A4 00          asc_41228       text "BIG5", '近衛隊',0                            ; DATA XREF: dseg:0AB2↓o
dseg:0A5F A4 73 B8 E9 00                asc_4122F       text "BIG5", '山賊',0                             ; DATA XREF: dseg:0AB4↓o
dseg:0A64 B4 63 B8 E9 00                asc_41234       text "BIG5", '惡賊',0                             ; DATA XREF: dseg:0AB6↓o
dseg:0A69 B8 71 B8 E9 00                asc_41239       text "BIG5", '義賊',0                             ; DATA XREF: dseg:0AB8↓o
dseg:0A6E AD 78 BC D6 B6 A4 00          asc_4123E       text "BIG5", '軍樂隊',0                            ; DATA XREF: dseg:0ABA↓o
dseg:0A75 B2 72 C3 7E A7 4C B9 CE 00    asc_41245       text "BIG5", '猛獸兵團',0                           ; DATA XREF: dseg:0ABC↓o
dseg:0A7E AA 5A B3 4E AE 61 B6 A4 00    asc_4124E       text "BIG5", '武術家隊',0                           ; DATA XREF: dseg:0ABE↓o
dseg:0A87 A7 AF B3 4E AE 76 00          asc_41257       text "BIG5", '妖術師',0                            ; DATA XREF: dseg:0AC0↓o
dseg:0A8E B2 A7 A5 C1 B1 DA 00          asc_4125E       text "BIG5", '異民族',0                            ; DATA XREF: dseg:0AC2↓o
dseg:0A95 A5 C1 B2 B3 00                asc_41265       text "BIG5", '民眾',0                             ; DATA XREF: dseg:0AC4↓o
dseg:0A9A B9 42 BF E9 B6 A4 00          asc_4126A       text "BIG5", '運輸隊',0                            ; DATA XREF: dseg:0AC6↓o
dseg:0AA2 28 0A                          dw offset asc_411F8                             ; "短兵"
dseg:0AA4 2D 0A                          dw offset asc_411FD                             ; "長兵"
dseg:0AA6 32 0A                          dw offset asc_41202                             ; "戰車"
dseg:0AA8 37 0A                          dw offset asc_41207                             ; "弓兵"
dseg:0AAA 3C 0A                          dw offset asc_4120C                             ; "連弩兵"
dseg:0AAC 43 0A                          dw offset asc_41213                             ; "發石車"
dseg:0AAE 4A 0A                          dw offset asc_4121A                             ; "輕騎兵"
dseg:0AB0 51 0A                          dw offset asc_41221                             ; "重騎兵"
dseg:0AB2 58 0A                          dw offset asc_41228                             ; "近衛隊"
dseg:0AB4 5F 0A                          dw offset asc_4122F                             ; "山賊"
dseg:0AB6 64 0A                          dw offset asc_41234                             ; "惡賊"
dseg:0AB8 69 0A                          dw offset asc_41239                             ; "義賊"
dseg:0ABA 6E 0A                          dw offset asc_4123E                             ; "軍樂隊"
dseg:0ABC 75 0A                          dw offset asc_41245                             ; "猛獸兵團"
dseg:0ABE 7E 0A                          dw offset asc_4124E                             ; "武術家隊"
dseg:0AC0 87 0A                          dw offset asc_41257                             ; "妖術師"
dseg:0AC2 8E 0A                          dw offset asc_4125E                             ; "異民族"
dseg:0AC4 95 0A                          dw offset asc_41265                             ; "民眾"
dseg:0AC6 9A 0A                          dw offset asc_4126A                             ; "運輸隊"

此时便豁然开朗了,DS:0AA2到DS:0AC6保存的是"兵种名称"的字符串地址,而地址对应的内存空间保存的则是兵种名称的BIG5码,每个兵种名称BIG5码的末尾都是00代表字符串的末尾。
如果想直接在MAIN.EXE中修改兵种名称可以用UE等工具搜索兵种名称对应的BIG码,或者直接查找MAIN.EXE文件位置0x0037BC8处是短兵字符串dseg:0A28的位置,MAIN.EXE文件位置0x0037C72对应dseg:0AA2的位置。

很多修改MOD的爱好者都遵循一个原则,不要改动字符串的长度以免出错,然而事实真是如此吗?在修改兵种名称这块,有没有其他更好的办法?
顶部
性别:未知-离线 漫漫苦短

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 64
编号 545816
注册 2023-12-25


发表于 2025-4-26 19:19 资料 短消息 只看该作者
四、人物数据的重要属性——兵种(3)

从前辈的总结和上一节的内容已经确定兵种代码已经对应的兵种名称,可以大致认为这个兵种代码与兵种名称的对应方式是索引方式,即先在一块地址放入兵种名称字符串的索引,再根据索引找到兵种名称字符串的地址,上一节已经找到了每个兵种名称字符串的地址(DS:0A28-DS:0A9A, MAIN.EXE文件位置0x0037BF8-0x0037C6A)以及兵种名称的索引地址(DS:0AA2-DS:0AC6, MAIN.EXE文件位置0x0037C72-0x0037C96),以下就是兵种代码以及对应的兵种名称。

QUOTE:
00短兵
01長兵
02戰車
03弓兵
04連弩兵
05發石車
06輕騎兵
07重騎兵
08近衛隊
09山賊
0A惡賊
0B義賊
0C軍樂隊
0D猛獸兵團
0E武術家隊
0F妖術師
10異民族
11民眾
12運輸隊

那么根据这些内容可以有怎样的修改达成改动兵种名称的位置?我总结了以下的几种方法。


  1. 修改字符串
    这一种就是最基础的修改方法,也就是直接找到MAIN.EXE文件位置0x0037BF8-0x0037C6A,直接将其中的兵种名称BIG5码进行替换即可。注意兵种名称的字符串长度可以改短,比如可以将"猛獸兵團"改为"猛獸團",改动后DS段内存空间数据变化如下所示。

    dseg:0A75 B2 72 C3 7E B9 CE 00       asc_41245       text "BIG5", '猛獸團',0                            ; DATA XREF: dseg:0ABC↓o
    dseg:0A7C 00                             db    0
    dseg:0A7D 00                             db    0

    在这个例子中,将字符串长度为4的"猛獸兵團"改为字符串长度为3的"猛獸團"并且在dseg:0A7B也就是字符串的末尾修改为00表示字符串的结尾,不然如果只是将"兵"的BIG5码修改为"團",则会显示"猛獸團團"。同时修改后DS:0A7C和DS:0A7D这两个字节被"浪费"了,不过这两个字节可以重新利用,见下面的几个修改。

  2. 修改索引
    上文提到了,代码是根据索引对应的兵种名称,那么有没有可能实现改动索引改变兵种代码对应的兵种名称,来试试这个例子。
    将DS:0AA2的28改为2D,DS:0AA4的2D改为28,如下所示DS段内存的变动。

    dseg:0A28 B5 75 A7 4C 00             asc_411F8       text "BIG5", '短兵',0                             ; DATA XREF: dseg:0AA4↓o
    dseg:0A2D AA F8 A7 4C 00             asc_411FD       text "BIG5", '長兵',0                             ; DATA XREF: dseg:0AA2↓o
    ...
    dseg:0AA2 2D 0A                                      dw offset asc_411FD                             ; "長兵"
    dseg:0AA4 28 0A                                      dw offset asc_411F8                             ; "短兵"

    可以看到DS:0A28的兵种名称依然是"短兵",DS:0A2D的兵种名称依然是"長兵",但是DS:0AA2的字符串索引对象变成了"長兵",DS:0AA4的字符串索引对象变成了"短兵",在游戏中本来兵种代码为00短兵的部队显示的兵种名称却是"長兵",本来兵种代码为01長兵的部队显示的兵种名称却是"短兵",这样的修改只会修改兵种代码对应的兵种名称,虽然游戏中一支显示为"長兵"的部队连火龙都不会用,无法斜向攻击,一支显示为"短兵"的部队却手持长枪,还能可以斜向攻击。

    那么这种修改方式是否有其他的尝试,比如说将0C軍樂隊和0E武術家隊的索引对调,如下所示DS段内存的变动。

    dseg:0A6E AD 78 BC D6 B6 A4 00       asc_4123E       text "BIG5", '軍樂隊',0                            ; DATA XREF: dseg:0ABE↓o
    dseg:0A75 B2 72 C3 7E A7 4C B9 CE 00 asc_41245       text "BIG5", '猛獸兵團',0                           ; DATA XREF: dseg:0ABC↓o
    dseg:0A7E AA 5A B3 4E AE 61 B6 A4 00 asc_4124E       text "BIG5", '武術家隊',0                           ; DATA XREF: dseg:0ABA↓o
    ...
    dseg:0ABA 7E 0A                                      dw offset asc_4124E                             ; "武術家隊"
    dseg:0ABC 75 0A                                      dw offset asc_41245                             ; "猛獸兵團"
    dseg:0ABE 6E 0A                                      dw offset asc_4123E                             ; "軍樂隊"

    根据上个例子,修改索引只会修改兵种代码对应的兵种名称,并不会修改兵种的性质。在这样的改动下,一支敲鼓的部队却叫"武術家隊",还是一支攻防弱血不多的部队,只能让一群人聚到它的四周回复策略,一支手持大刀的部队对外宣称自己叫"軍樂隊",然而是一支攻防强血厚的部队,砍人反击烧梅花五样样精通,这个场景颇有滑稽的感觉。

    这个改动还有另一个特点,就是成功实现了兵种代码为0C軍樂隊这个兵种名称的扩容,普通的修改方式兵种代码为0C的部队修改名称只能保持字符串长度不大于3,如果强行增加字符串长度到4,会导致0D猛獸兵團显示异常,大部分修改者一时间找不到解决的方式只能得出了不能修改字符串长度这条规则,如果知道修改索引的方式,就不会无疾而终了。

    修改索引之后,可以修改DS:0A6E和DS:0A7E的文字内容了,比如将DS:0A6E的"軍樂隊"修改为"武術隊",DS:0A7E的"武術家隊"修改为"軍鼓樂隊",就不会出现显示异常了,兵种代码为0C的部队名称修改成功由"軍樂隊"修改为"軍鼓樂隊",兵种代码为0E的部队名称修改成功由"武術家隊"修改为"武術隊",这里就不再演示了。

    当然也可以举一反三地尝试其他的索引方式的修改。

  3. 修改字符串和索引(字符串长度变化)
    上个例子已经实现了字符串长度的变化,但是注意到原始的兵种名称只有0D猛獸兵團和0E武術家隊两个兵种名称的字符串长度为4,上个例子并不能在保留这两个兵种名称的字符串长度不变的情况下实现增加一个兵种名称的字符串长度为4甚至再增加一个,那么就需要本方法来实现了。

  4. 一段文字两条索引
    这种修改方法需要一定的巧合。



当然方法远不止上述的几种,还可以使用MAIN.EXE文件中代码中DS段未使用的文件空间(MAIN.EXE文件位置0x00371D0以后的位置,有多少地方不确定程序是否使用),需要将MAIN.EXE文件中的地址对应DS段位置,这个就比较复杂了。

[ 本帖最后由 漫漫苦短 于 2025-4-26 23:51 编辑 ]
顶部
性别:男-离线 哆子颜

Rank: 3Rank: 3Rank: 3
组别 士兵
级别 忠义校尉
功绩 2
帖子 214
编号 546252
注册 2024-3-15
来自 棉图


发表于 2025-4-29 06:22 资料 短消息 只看该作者
楼主还是在用反汇编啊,现在有些工具都可以转化成C语言编程了
顶部
性别:未知-离线 漫漫苦短

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 64
编号 545816
注册 2023-12-25


发表于 2025-4-29 20:40 资料 短消息 只看该作者
回复 #8 哆子颜 的帖子

你说的工具是什么?我目前用的是IDAPro,其实是有反汇编转成C语言功能的,限于32位和64位程序才能使用,不过DOS版三国志英杰传是16位程序,IDAPro并不能将其转成C语言,可能需要其他办法。你如果有其他思路也可以说一下。

还有另外一点,工具也不是万能的,就算把反汇编出来的汇编语句交给ChatGPT和DeepSeek这些AI工具,它也只能转换少量的语句对应C语言,而且没有把最重要的逻辑呈现出来,甚至不一定比自己理解的好。最重要的是其实反汇编研究虽然难度大且"痛苦",但其实就我本人而言我是乐在其中的,感受到自己在其中分析研究后的收获很大,不只是三国志英杰传和反汇编的知识积累,解决问题的能力有所提升,也会让自己更有信心面对这样的问题。

即使换了个超级智能的AI软件能把上述问题解决,把整个游戏给分析研究透彻,但是并不能替代人的思考过程,而且要是这个研究简单到不用思考也只会感觉索然无味而已。就假如有个AI软件直接把全兵种战后2999的规划以及每关每回合行动直接草拟出来,月亮这样的大神会放弃自己思考如何规划以及每关的布阵思路吗?其中这是同一个道理。再想想英杰传前辈,当时没有现在这么强的工具,战前2099到2699的战绩以及打法规划的进步还不是前辈们前赴后继一步一个脚印走出来的吗?

[ 本帖最后由 漫漫苦短 于 2025-4-29 20:59 编辑 ]
顶部
性别:未知-离线 漫漫苦短

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 64
编号 545816
注册 2023-12-25


发表于 2025-5-1 19:19 资料 短消息 只看该作者
五、人物数据的重要属性——兵种(4)

本节介绍部队兵种的衍生的方法,计算部队移动力、获取兵种移动类型、获取兵种攻击范围类型这三个方法,之所以称它们为衍生的方法就是因为这三个方法都是先得出部队的兵种,再根据兵种得出对应的类型。

sub_33D26函数是计算部队移动力的方法,arg_0为部队战场数据的首地址。

seg002:6E06                sub_33D26 proc far
seg002:6E06
seg002:6E06                var_A           = word ptr -0Ah
seg002:6E06                var_7           = byte ptr -7
seg002:6E06                var_4           = byte ptr -4
seg002:6E06                var_2           = byte ptr -2
seg002:6E06                var_1           = byte ptr -1
seg002:6E06                arg_0           = word ptr  6
seg002:6E06
seg002:6E06 C8 0A 00 00                    enter   0Ah, 0
seg002:6E0A 56                             push    si
seg002:6E0B 8B 5E 06                       mov     bx, [bp+arg_0]
seg002:6E0E 6B 37 2C                       imul    si, [bx], 44
seg002:6E11 81 C6 16 68                    add     si, 6816h
seg002:6E15 56                             push    si
seg002:6E16 9A CC 6A F6 1C                 call    sub_23A2C                 ; 获取部队兵种代码
seg002:6E1B 8A D8                          mov     bl, al
seg002:6E1D 2A FF                          sub     bh, bh
seg002:6E1F 8A 87 EA 31                    mov     al, [bx+31EAh]                          ; 获取兵种移动力
seg002:6E23 88 46 F9                       mov     [bp+var_7], al

看到了熟悉的[bx+xxxxh]的汇编代码语句,于是到DS:31EA处寻找数据。

dseg:31EA 04                 db    4                             ; 00短兵 4 
dseg:31EB 04                 db    4                             ; 01长兵 4
dseg:31EC 05                 db    5                             ; 02战车 5
dseg:31ED 04                 db    4                             ; 03弓兵 4
dseg:31EE 04                 db    4                             ; 04连弩兵 4
dseg:31EF 03                 db    3                             ; 05投石车 3
dseg:31F0 06                 db    6                             ; 06轻骑兵 6
dseg:31F1 05                 db    5                             ; 07重骑兵 5
dseg:31F2 06                 db    6                             ; 08近卫队 6
dseg:31F3 04                 db    4                             ; 09山贼 4
dseg:31F4 04                 db    4                             ; 0A恶贼 4
dseg:31F5 04                 db    4                             ; 0B义贼 4
dseg:31F6 04                 db    4                             ; 0C军乐队 4
dseg:31F7 04                 db    4                             ; 0D猛兽兵团 4
dseg:31F8 05                 db    5                             ; 0E武术家 5
dseg:31F9 04                 db    4                             ; 0F妖术师 4
dseg:31FA 05                 db    5                             ; 10异民族 5
dseg:31FB 03                 db    3                             ; 11民众 3
dseg:31FC 03                 db    3                             ; 12运输队 3

seg002:6E26-seg002:6E6B是计算道具带来的移动力加成的计算并且保存到var_4中,由于涉及到道具的相关知识,本处省略其中的计算过程。

seg002:6E6D 8A 46 FC                       mov     al, [bp+var_4]                          ; 获取道具加成移动力
seg002:6E70 02 46 F9                       add     al, [bp+var_7]                          ; 与兵种移动力相加即为部队最终的移动力
seg002:6E73 5E                             pop     si
seg002:6E74 C9                             leave
seg002:6E75 CA 02 00                       retf    2
seg002:6E75                sub_33D26 endp

兵种移动类型,这个在番外1也出现过,现在正式介绍sub_342B4这个函数,arg_0为部队战场数据的首地址。

seg002:BBEF 56                             push    si
seg002:BBF0 9A 94 73 F2 2C                 call    sub_342B4
seg002:BBF5 88 46 F4                       mov     [bp+var_C], al



seg002:7394                sub_342B4 proc far
seg002:7394
seg002:7394                arg_0           = word ptr  6
seg002:7394
seg002:7394 55                             push    bp
seg002:7395 8B EC                          mov     bp, sp
seg002:7397 FF 76 06                       push    [bp+arg_0]
seg002:739A 9A 6C 93 F2 2C                 call    sub_3628C                ; 获取部队兵种代码
seg002:739F 8A D8                          mov     bl, al
seg002:73A1 2A FF                          sub     bh, bh
seg002:73A3 8A 87 56 32                    mov     al, [bx+3256h]                          ; 兵种移动类型
seg002:73A7 C9                             leave
seg002:73A8 CA 02 00                       retf    2
seg002:73A8                sub_342B4 endp

还是到DS:3256处寻找数据。

dseg:3256 00                 db    0                             ; 00短兵 
dseg:3257 00                 db    0                             ; 01长兵
dseg:3258 00                 db    0                             ; 02战车
dseg:3259 00                 db    0                             ; 03弓兵
dseg:325A 00                 db    0                             ; 04连弩兵
dseg:325B 00                 db    0                             ; 05投石车
dseg:325C 01                 db    1                             ; 06轻骑兵
dseg:325D 01                 db    1                             ; 07重骑兵
dseg:325E 01                 db    1                             ; 08近卫队
dseg:325F 03                 db    3                             ; 09山贼
dseg:3260 03                 db    3                             ; 0A恶贼
dseg:3261 03                 db    3                             ; 0B义贼
dseg:3262 02                 db    2                             ; 0C军乐队
dseg:3263 03                 db    3                             ; 0D猛兽兵团
dseg:3264 03                 db    3                             ; 0E武术家
dseg:3265 00                 db    0                             ; 0F妖术师
dseg:3266 03                 db    3                             ; 10异民族
dseg:3267 00                 db    0                             ; 11民众
dseg:3268 02                 db    2                             ; 12运输队

0类型的兵种有00短兵01长兵02战车03弓兵04连弩兵05投石车0F妖术师11民众,1类型的兵种有06轻骑兵07重骑兵08近卫队,2类型的兵种有0C军乐队12运输队,3类型的兵种有09山贼0A恶贼0B义贼0D猛兽兵团0E武术家10异民族,根据英杰传的常识可以分析出,0类型的兵种移动类型为无法进入山地等等特点,1类型的兵种移动类型为无法进入森林山地以及荒地移动为2等等,2类型的兵种移动类型为无法进入山地以及森林荒地移动为2等等,3类型的兵种移动类型为就比012全能,可以进入山地等等。

获取兵种攻击范围类型这个方法都是融合在函数中的,也可以理解为程序员在定义函数的时候设置为inline函数,于是在转成汇编后就没有单独成为一个函数(这只是本人的脑补),好在使用到的地方不多,修改的话也没有那么麻烦。

seg002:B4B4                sub_383D4       proc far                                        ; CODE XREF: sub_38F66+8A↓P
seg002:B4B4
seg002:B4B4                var_A= word ptr -0Ah
seg002:B4B4                var_8           = byte ptr -8
seg002:B4B4                var_7           = byte ptr -7
seg002:B4B4                var_6           = word ptr -6
seg002:B4B4                var_4           = byte ptr -4
seg002:B4B4                var_2           = byte ptr -2
seg002:B4B4                var_1= byte ptr -1
seg002:B4B4                arg_0= word ptr  6
seg002:B4B4
seg002:B4B4 C8 0A 00 00                    enter   0Ah, 0
seg002:B4B8 57                             push    di
seg002:B4B9 56                             push    si
seg002:B4BA 8B 76 06                       mov     si, [bp+arg_0]
seg002:B4BD 56                             push    si
seg002:B4BE 9A 6C 93 F2 2C                 call    sub_3628C                ; 获取部队兵种代码
seg002:B4C3 8A D8                          mov     bl, al
seg002:B4C5 2A FF                          sub     bh, bh
seg002:B4C7 8A 87 42 32                    mov     al, [bx+3242h]                          ; 兵种攻击范围类型

顶部
性别:男-离线 神雕小侠

Rank: 3Rank: 3Rank: 3
组别 士兵
级别 忠义校尉
功绩 3
帖子 251
编号 44352
注册 2005-7-27


发表于 2025-5-1 22:40 资料 短消息 只看该作者
回复 #9 漫漫苦短 的帖子

嗯嗯,16位程序无法转换成C语言,这个是关键问题
顶部
性别:未知-离线 漫漫苦短

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 64
编号 545816
注册 2023-12-25


发表于 2025-5-5 19:19 资料 短消息 只看该作者
六、人物数据的兵力等级和经验值(1)

前几节一直介绍了兵种代码以及与其相关的代码,虽然它仅在每个人物数据的偏移+020处占用的1个字节,意义却非同小可,而花费大量的篇幅也不意味内容的完结,因为它还与其他属性有着千丝万缕的联系。从本节开始,继续介绍人物数据的的三个属性兵力、等级、经验值。

先简要概述一下三者的数据位置,兵力在偏移+01E处,占用2字节;等级在偏移+021处,占用1字节;经验值在偏移+022处,占用1字节。从这里就看出第一个巧合了,兵力恰好在兵种代码偏移+020处的左边,等级和经验值在兵种代码位置的右边,三者正好夹住兵种代码这一属性。

先介绍这三个属性的Get方法,以下的代码中,arg_0都是人物数据的首地址。

seg001:C5A4                sub_29504 proc far
seg001:C5A4
seg001:C5A4                arg_0           = word ptr  6
seg001:C5A4
seg001:C5A4 55                             push    bp
seg001:C5A5 8B EC                          mov     bp, sp
seg001:C5A7 8B 5E 06                       mov     bx, [bp+arg_0]
seg001:C5AA 8B 47 1E                       mov     ax, [bx+1Eh]                            ; 获取部队兵力
seg001:C5AD C9                             leave
seg001:C5AE CA 02 00                       retf    2
seg001:C5AE                sub_29504 endp



seg001:6ADA                sub_23A3A proc far
seg001:6ADA
seg001:6ADA                arg_0           = word ptr  6
seg001:6ADA
seg001:6ADA 55                             push    bp
seg001:6ADB 8B EC                          mov     bp, sp
seg001:6ADD 8B 5E 06                       mov     bx, [bp+arg_0]
seg001:6AE0 8A 47 21                       mov     al, [bx+21h]                            ; 获取部队等级
seg001:6AE3 C9                             leave
seg001:6AE4 CA 02 00                       retf    2
seg001:6AE4                sub_23A3A endp



seg001:C5D0                _sub_29530 proc far
seg001:C5D0
seg001:C5D0                arg_0           = word ptr  6
seg001:C5D0
seg001:C5D0 55                             push    bp
seg001:C5D1 8B EC                          mov     bp, sp
seg001:C5D3 8B 5E 06                       mov     bx, [bp+arg_0]
seg001:C5D6 8A 47 22                       mov     al, [bx+22h]                            ; 获取部队经验值
seg001:C5D9 C9                             leave
seg001:C5DA CA 02 00                       retf    2
seg001:C5DA                sub_29530 endp

Get兵种代码这个方法的位置在seg001:6ACC-seg001:6AD6,而seg001:6ADA-seg001:6AE4正好是Get等级的方法的位置,这两个函数是挨着的,在我第一次发现这两处函数的我还对英杰传的代码不太熟悉,突然在此处发现两个如此简单的函数,而且实际上在这两个函数旁边也存在大量类似的代码,那时有种顿悟的感觉,虽然大部分代码都很枯燥无味,但是看到一大串无比简单的代码还是让我感到心情舒适。

仔细观察Get兵力的位置seg001:C5A4-seg001:C5AE和Get经验值的位置seg001:C5D0-seg001:C5DA也很接近,而这其中也只夹杂着两个简单的函数。

而三者的Set方法就稍微有点复杂了,比如说Set兵力,因为兵力其实是在战场上的实际兵力而不是满兵力,Set兵力不能超过最大值。

seg001:6490                sub_233F0 proc far
seg001:6490
seg001:6490                arg_0           = word ptr  6
seg001:6490                arg_2           = word ptr  8
seg001:6490
seg001:6490 55                             push    bp
seg001:6491 8B EC                          mov     bp, sp
seg001:6493 57                             push    di
seg001:6494 56                             push    si
seg001:6495 8B 76 06                       mov     si, [bp+arg_0]
seg001:6498 56                             push    si
seg001:6499 9A 20 69 F6 1C                 call    sub_23880                 ; 获取部队兵力上限
seg001:649E 8B F8                          mov     di, ax
seg001:64A0 3B 7E 08                       cmp     di, [bp+arg_2]                          ; 比较部队兵力上限与新部队兵力
seg001:64A3 72 03                          jb      short loc_23408                         ; 无符号小于跳转
seg001:64A5 8B 46 08                       mov     ax, [bp+arg_2]                          ; 修改后兵力不得到超过兵力上限
seg001:64A8
seg001:64A8                loc_23408:                                                      ; CODE XREF: sub_233F0+13↑j
seg001:64A8 89 44 1E                       mov     [si+1Eh], ax                            ; 修改部队兵力
seg001:64AB 5E                             pop     si
seg001:64AC 5F                             pop     di
seg001:64AD C9                             leave
seg001:64AE CA 04 00                       retf    4
seg001:64AE                sub_233F0 endp

sub_23880函数正式计算部队的最大兵力,这两个方法到下一节再解释。
顶部
性别:未知-离线 漫漫苦短

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 64
编号 545816
注册 2023-12-25


发表于 2025-5-9 19:19 资料 短消息 只看该作者
七、人物数据的兵力等级和经验值(2)

上一节的最后留下了如何计算兵力上限的疑问,关于这个问题,最早重阳老前辈就已经整理过,在英杰传兵种、策略与移动力攻防
中就已经提到了,不过龙吟前辈已经给出了详细的数据以及算法:

QUOTE:
最大兵力=兵种基本兵力+兵种兵力增幅×(等级-1)
【兵种基本兵力与兵力增幅表】
兵种   基本兵力  兵力增幅  99级时兵力
----------------------
步兵系   500     50     5400
骑兵系   500     60     6380
弓兵系   500     40     4420
贼兵系   800     40     4720
军乐队   300     40     4220
猛兽兵团  400     50     5300
武术家   600     50     5500
妖术师   300     50     5200
异民族   700     60     6580
民众    500     50     5400
运输队   300     40     4220

封剑尘和孝直前辈都分别兵种顺序(及代码)、DOS版英杰传兵种攻防、兵力、移动力、策略修改(上文有链接)提到过以下内容:

QUOTE:
037F58开始,按兵种顺序的19个字节代表19个兵种的初始兵力,数值为初始兵力/100
037F6C开始,按兵种顺序的19个字节代表19个兵种的兵力增幅,数值为兵力增幅/10

接下来就介绍计算兵力上限的函数sub_23880,arg_0为人物数据的首地址。

seg001:6920                sub_23880 proc far
seg001:6920
seg001:6920                arg_0           = word ptr  6
seg001:6920
seg001:6920 55                             push    bp
seg001:6921 8B EC                          mov     bp, sp
seg001:6923 56                             push    si
seg001:6924 8B 76 06                       mov     si, [bp+arg_0]
seg001:6927 B0 0A                          mov     al, 10
seg001:6929 8A 5C 20                       mov     bl, [si+20h]                            ; 获取部队兵种代码
seg001:692C 2A FF                          sub     bh, bh
seg001:692E F6 A7 88 0D                    mul     byte ptr [bx+0D88h]                     ; 获取兵种基本兵力
seg001:6932 8B C8                          mov     cx, ax
seg001:6934 8A 87 9C 0D                    mov     al, [bx+0D9Ch]                          ; 获取兵种每等级兵力增幅
seg001:6938 2A E4                          sub     ah, ah
seg001:693A 8A 5C 21                       mov     bl, [si+21h]                            ; 获取部队等级
seg001:693D 4B                             dec     bx
seg001:693E F7 E3                          mul     bx
seg001:6940 03 C8                          add     cx, ax
seg001:6942 6B C1 0A                       imul    ax, cx, 10                              ; 兵力上限=(兵种基本兵力×10+兵种每等级兵力增幅×(等级-1))×10
seg001:6945 5E                             pop     si
seg001:6946 C9                             leave
seg001:6947 CA 02 00                       retf    2
seg001:6947                sub_23880 endp

接着到DS:0D88处和DS:0D9C寻找数据,也就是对应着上面提到的MAIN.EXE文件位置0x037F58和0x037F6C,可以看到两块数据是挨着一起的。

dseg:0D88                兵种基本兵力 MAIN.EXE文件位置0x037F58
dseg:0D88 05                             db    5                                         ; 00短兵 500
dseg:0D89 05                             db    5                                         ; 01長兵 500
dseg:0D8A 05                             db    5                                         ; 02戰車 500
dseg:0D8B 05                             db    5                                         ; 03弓兵 500
dseg:0D8C 05                             db    5                                         ; 04連弩兵 500
dseg:0D8D 05                             db    5                                         ; 05發石車 500
dseg:0D8E 05                             db    5                                         ; 06輕騎兵 500
dseg:0D8F 05                             db    5                                         ; 07重騎兵 500
dseg:0D90 05                             db    5                                         ; 08近衛隊 500
dseg:0D91 08                             db    8                                         ; 09山賊 800
dseg:0D92 08                             db    8                                         ; 0A惡賊 800
dseg:0D93 08                             db    8                                         ; 0B義賊 800
dseg:0D94 03                             db    3                                         ; 0C軍樂隊 300
dseg:0D95 04                             db    4                                         ; 0D猛獸兵團 400
dseg:0D96 06                             db    6                                         ; 0E武術家隊 600
dseg:0D97 03                             db    3                                         ; 0F妖術師 300
dseg:0D98 07                             db    7                                         ; 10異民族 700
dseg:0D99 05                             db    5                                         ; 11民眾 500
dseg:0D9A 03                             db    3                                         ; 12運輸隊 300
dseg:0D9B 00                             db    0



dseg:0D9C                兵种每等级兵力增幅 MAIN.EXE文件位置0x037F6C
dseg:0D9C 05                             db    5                                         ; 00短兵 50
dseg:0D9D 05                             db    5                                         ; 01長兵 50
dseg:0D9E 05                             db    5                                         ; 02戰車 50
dseg:0D9F 04                             db    4                                         ; 03弓兵 40
dseg:0DA0 04                             db    4                                         ; 04連弩兵 40
dseg:0DA1 04                             db    4                                         ; 05發石車 40
dseg:0DA2 06                             db    6                                         ; 06輕騎兵 60
dseg:0DA3 06                             db    6                                         ; 07重騎兵 60
dseg:0DA4 06                             db    6                                         ; 08近衛隊 60
dseg:0DA5 04                             db    4                                         ; 09山賊 40
dseg:0DA6 04                             db    4                                         ; 0A惡賊 40
dseg:0DA7 04                             db    4                                         ; 0B義賊 40
dseg:0DA8 04                             db    4                                         ; 0C軍樂隊 40
dseg:0DA9 05                             db    5                                         ; 0D猛獸兵團 50
dseg:0DAA 05                             db    5                                         ; 0E武術家隊 50
dseg:0DAB 05                             db    5                                         ; 0F妖術師 50
dseg:0DAC 06                             db    6                                         ; 10異民族 60
dseg:0DAD 05                             db    5                                         ; 11民眾 50
dseg:0DAE 04                             db    4                                         ; 12運輸隊 40
dseg:0DAF 00                             db    0

从上面的程序以及汇编分析的结果来提炼出兵力上限的计算方法:

  1. seg001:6929,根据每个人物数据的首地址偏移+020获取部队兵种代码。
  2. seg001:692E,在DS:0D88处,根据部队兵种代码偏移计算数据地址,获取兵种基本兵力的初始值×10。
  3. seg001:6932,将兵种基本兵力的初始值保存在CX寄存器中。
  4. seg001:6934,在DS:0D9C处,根据部队兵种代码偏移计算数据地址,获取兵种每等级兵力增幅。
  5. seg001:693A-693D,根据每个人物数据的首地址偏移+021获取部队等级-1。
  6. seg001:693E,将第4步的结果与第5步的结果相乘。
  7. seg001:6940,将第3步的结果与第6步的结果相加。
  8. seg001:6942,将第7步的结果乘10,保存在AX寄存器中作为函数的返回结果。


可以看到兵种兵力上限的计算并不难,哪怕不知道公式,也可以通过观察英杰传中的游戏界面的数据得出结论,运用公式算也不复杂,但是如何在此基础上进行修改就是另一门学问了。如果只是用UE这样的工具在MAIN.EXE中查找500\50这样的数据,由于程序中的计算方法是无法直接找到的,而借助反汇编分析代码的方式,可以从细节中看出其中每一步的算法以及思路。

而且不只是修改DS:0D88到DS:0DAF的数据,seg001:6927到seg001:6942的代码逻辑也是可以修改的,比如说可以修改第2步和第8步的乘10,可以将两个数分别修改为其他数,或者也可以改其中的乘法为其他运算,甚至把整段的代码逻辑都改了,这就取决于个人的汇编编程能力了。

[ 本帖最后由 漫漫苦短 于 2025-5-9 19:40 编辑 ]
顶部
性别:未知-离线 漫漫苦短

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 64
编号 545816
注册 2023-12-25


发表于 2025-5-12 19:19 资料 短消息 只看该作者
八、人物数据的兵力等级和经验值(3)

在龙吟前辈的分析结果中,有两处提到了判断部队兵力小于最大兵力的40%,九、敌军、友军行动准则全分析的以下两处。

1.友军和敌军的行动顺序
    行动顺序的判定以自动补给后的数据为准。
    最优先行动的是处于恢复性地形(村庄、兵营、鹿砦)中的部队,若有数只部队处于恢复性地形上,则以其在屏幕右上方的敌军列表中的顺序排列。
    第二优先行动的是兵力小于最大兵力的40%或士气低于40的部队,若右数只部门处于该情形下,则以其在屏幕右上方的敌军列表中的顺序进行排列。
    最后余下的部队按屏幕右上方的敌军列表中的顺序进行排列。



QUOTE:
4.行动价值
    每只部队的行动方式由行动价值决定,当部队行动时,会计算战场上每一处坐标的行动价值,然后移动到行动价值最高的地方(当然,部队的行动力必须能移动到该处)。部队在某个地方可能有几种行动方式:物理攻击、策略攻击、休息等,每种行动方式都有其行动价值,在几个行动价值中,最高的为该坐标的行动价值。
    行动价值的计算方法如下:
    1)如果部队的兵力不足(即兵力小于最大兵力的40%或士气小于40,下同),则战场上存在可恢复地形的坐标,行动价值加50。

这两处都有部队是否存在兵力不足或士气值不足的情况,显然都需要先求出部队的兵力上限来进行计算,那么这两处是否会调用同一个函数来进行判断?

sub_38396就是这个判断函数,这个判断函数也只在这两种情况下使用,我把该函数命名为判断部队是否战力不足,毕竟一支部队需要既有足够的兵力还有足够的士气值才有战力。其中arg_0为部队战场数据的首地址,士气值的部分可以回顾上一章。

seg002:B476                sub_38396 proc far                             ; CODE XREF: sub_2FED6+B3↑P sub_383D4+AB↓P
seg002:B476
seg002:B476                arg_0= word ptr  6
seg002:B476
seg002:B476 55                             push    bp
seg002:B477 8B EC                          mov     bp, sp
seg002:B479 57                             push    di
seg002:B47A 56                             push    si
seg002:B47B 8B 7E 06                       mov     di, [bp+arg_0]
seg002:B47E 6B 35 2C                       imul    si, [di], 44
seg002:B481 81 C6 16 68                    add     si, 6816h
seg002:B485 56                             push    si                                      ; 人物数据的首地址
seg002:B486 9A 20 69 F6 1C                 call    sub_23880                 ; 获取部队兵力上限
seg002:B48B B9 05 00                       mov     cx, 5
seg002:B48E 2B D2                          sub     dx, dx
seg002:B490 F7 F1                          div     cx
seg002:B492 03 C0                          add     ax, ax                                  ; 临界兵力=兵力上限/5*2
seg002:B494 56                             push    si
seg002:B495 8B F0                          mov     si, ax
seg002:B497 9A A4 C5 F6 1C                 call    sub_29504                      ; 获取部队兵力
seg002:B49C 3B F0                          cmp     si, ax                                  ; 比较临界兵力与当前兵力
seg002:B49E 73 0A                          jnb     short loc_383CA                         ; 无符号不小于跳转
seg002:B4A0 80 7D 0C 28                    cmp     byte ptr [di+0Ch], 40                   ; 比较部队士气值与40
seg002:B4A4 72 04                          jb      short loc_383CA
seg002:B4A6 33 C0                          xor     ax, ax                                  ; 返回0
seg002:B4A8 EB 03                          jmp     short loc_383CD
seg002:B4AA                loc_383CA:                                                      ; CODE XREF: sub_38396+28↑j sub_38396+2E↑j
seg002:B4AA B8 01 00                       mov     ax, 1                                   ; 返回1
seg002:B4AD                loc_383CD:                                                      ; CODE XREF: sub_38396+32↑j
seg002:B4AD 5E                             pop     si
seg002:B4AE 5F                             pop     di
seg002:B4AF C9                             leave
seg002:B4B0 CA 02 00                       retf    2
seg002:B4B0                sub_38396 endp

判断部队是否战力不足的计算方法:

  1. seg002:B47E-B481 获取人物数据的首地址(具体方法见本章上一节)。
  2. seg002:B485-B486 获取部队兵力上限(具体方法见本章上一节)。
  3. seg002:B48B-B492 计算临界兵力,seg002:B48B-seg002:B490 先用部队兵力上限除以5,再翻倍,即为兵力上限的40%,seg002:B495保存结果到SI寄存器。
  4. seg002:B495 获取部队当前兵力。
  5. seg002:B49C 比较临界兵力与当前兵力,seg002:B49E 如果临界兵力不小于当前兵力(当前兵力小于等于临界兵力)跳到第8步。
  6. seg002:B4A0 比较部队士气值与40,seg002:B4A4 如果部队士气值小于40跳到第8步。
  7. seg002:B4A6 AX寄存器改为0作为函数的返回结果。
  8. seg002:B4AA AX寄存器改为1作为函数的返回结果。
因此该函数只要部队满足兵力小于等于兵力上限的40%或士气值小于40都会返回1,即判断该部队战力不足。而在判断条件上到底是小于还是小于等于上龙吟前辈还是有点小瑕疵的,兵力是判断小于等于40%而士气值是判断小于40,两者是不同的,举例,在第一关汜水关中的2级550兵力上限的李肃在216血的时候就不会出鹿寨射张飞关羽,而信都之战士气值等于40的军乐队不会进村而是有可能跑到森林,这也可以证实兵力的判断确实是小于等于,所以在分析中需要严谨的精神,不至于因为一小处的失误造成遗留问题。

最后再对比一下前面的Set兵力方法,相同点都是先获取部队兵力上限,再分别根据兵力上限或临界兵力与当前兵力进行判断。

总结一下兵力的部分,部队的兵力不能超出上限,这个在Set兵力这个方法就有体现,然后兵力上限的计算方法是根据兵种获取兵种基本兵力和兵种每等级兵力增幅,运用公式兵力上限=兵种基本兵力+兵种兵力增幅×(等级-1)计算,并且在判断部队是否战力不足这个函数会判断部队是否满足当前兵力小于等于兵力上限的40%或士气值小于40的条件。当然兵力上限也有其他用处,比如在军团状态栏中就显示兵力和兵力上限。

[ 本帖最后由 漫漫苦短 于 2025-5-12 19:59 编辑 ]
顶部
性别:未知-离线 漫漫苦短

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 64
编号 545816
注册 2023-12-25


发表于 2025-5-19 19:19 资料 短消息 只看该作者
九、人物数据的兵力等级和经验值(4)

介绍完了兵力,继续介绍等级和经验值。关于Set等级和经验值方法一共有3个,接下来就来简单看这三者之间的区别。

第1个和第2个是修改等级,第3个方法是修改经验值,可能修改等级。以下3个方法,arg_0都为人物数据的首地址,但是arg_2有所不同,在sub_23A48中,arg_2指的是直接修改的等级,在sub_2306A是根据部队的已有等级修改,arg_2指的是上升的等级,sub_23412是根据部队升级的经验值,arg_2指的是上升的经验值

seg001:6AE8                sub_23A48 proc far
seg001:6AE8
seg001:6AE8                arg_0           = word ptr  6
seg001:6AE8                arg_2           = byte ptr  8
seg001:6AE8
seg001:6AE8 55                             push    bp
seg001:6AE9 8B EC                          mov     bp, sp
seg001:6AEB 8B 5E 06                       mov     bx, [bp+arg_0]
seg001:6AEE 8A 46 08                       mov     al, [bp+arg_2]
seg001:6AF1 88 47 21                       mov     [bx+21h], al                            ; 修改部队等级
seg001:6AF4 C9                             leave
seg001:6AF5 CA 04 00                       retf    4
seg001:6AF5                sub_23A48 endp



seg001:610A                sub_2306A proc far                                     ; CODE XREF: sub_24C46+F45↓P
seg001:610A
seg001:610A                arg_0= word ptr  6
seg001:610A                arg_2= byte ptr  8
seg001:610A
seg001:610A 55                             push    bp
seg001:610B 8B EC                          mov     bp, sp
seg001:610D 56                             push    si
seg001:610E 8B 76 06                       mov     si, [bp+arg_0]
seg001:6111 8A 46 08                       mov     al, [bp+arg_2]
seg001:6114 2A E4                          sub     ah, ah
seg001:6116 2D 63 00                       sub     ax, 99
seg001:6119 F7 D8                          neg     ax
seg001:611B 8A 4C 21                       mov     cl, [si+21h]                            ; 部队等级
seg001:611E 2A ED                          sub     ch, ch
seg001:6120 3B C1                          cmp     ax, cx                                  ; 比较99-升等级与部队等级
seg001:6122 7D 06                          jge     short loc_2308A                         ; 有符号大于等于跳转
seg001:6124 C6 44 21 63                    mov     byte ptr [si+21h], 99                   ; 升级后的等级不超过99
seg001:6128 EB 06                          jmp     short loc_23090
seg001:612A                loc_2308A:                                                      ; CODE XREF: sub_2306A+18↑j
seg001:612A 8A 46 08                       mov     al, [bp+arg_2]
seg001:612D 00 44 21                       add     [si+21h], al                            ; 修改部队等级
seg001:6130                loc_23090:                                                      ; CODE XREF: sub_2306A+1E↑j
seg001:6130 8A 44 21                       mov     al, [si+21h]                            ; 返回修改后的等级
seg001:6133 5E                             pop     si
seg001:6134 C9                             leave
seg001:6135 CA 04 00                       retf    4
seg001:6135                sub_2306A endp



seg001:64B2                sub_23412 proc far
seg001:64B2
seg001:64B2                var_88   =  byte ptr -88h
seg001:64B2                var_8           = word ptr -8
seg001:64B2                var_4           = word ptr -4
seg001:64B2                var_2           = word ptr -2
seg001:64B2                arg_0           = word ptr  6
seg001:64B2                arg_2   = word ptr  8
seg001:64B2
seg001:64B2 C8 88 00 00                    enter   88h, 0
seg001:64B6 57                             push    di
seg001:64B7 56                             push    si
seg001:64B8 8B 7E 06                       mov     di, [bp+arg_0]
seg001:64BB C7 46 FE 00 00                 mov     [bp+var_2], 0
seg001:64C0 8B 15                          mov     dx, [di]
seg001:64C2 80 E6 F1                       and     dh, 0F1h                                ; 获取部队人物代码
seg001:64C5 81 FA 00 01                    cmp     dx, 100h
seg001:64C9 72 09                          jb      short loc_23434
seg001:64CB 81 FA 5F 01                    cmp     dx, 15Fh
seg001:64CF 77 03                          ja      short loc_23434
seg001:64D1 E9 06 01                       jmp     loc_2353A                          ; 无名部队不能升级
seg001:64D4               loc_23434:                                              ; CODE XREF: sub_23412+17↑j sub_23412+1D↑j
seg001:64D4 57                             push    di
seg001:64D5 9A DA 6A F6 1C                 call    sub_23A3A                      ; 获取部队等级
seg001:64DA 3C 63                          cmp     al, 99                                  ; 判断部队等级是否到99级
seg001:64DC 72 03                          jb      short loc_23441
seg001:64DE E9 F9 00                       jmp    loc_2353A
seg001:64E1                loc_23441:                                                      ; CODE XREF: sub_23412+2A↑j
seg001:64E1 8B 5E 08                       mov     bx, [bp+arg_2]
seg001:64E4 83 FB 64                       cmp     bx, 100                                 ; 比较新经验值与100
seg001:64E7 73 03                          jnb     short loc_2344C
seg001:64E9 E9 E8 00                       jmp     loc_23534
seg001:64EC                loc_2344C:                                                      ; CODE XREF: sub_23412+35↑j
seg001:64EC 8B C3                          mov     ax, bx
seg001:64EE B9 64 00                       mov     cx, 100
seg001:64F1 2B D2                          sub     dx, dx
seg001:64F3 F7 F1                          div     cx                                      ; 获取经验上升的等级
seg001:64F5 89 46 F8                       mov     [bp+var_8], ax
seg001:64F8 6B C0 9C                       imul    ax, -100
seg001:64FB 03 D8                          add     bx, ax
seg001:64FD 89 5E 08                       mov     [bp+arg_2], bx                  ; 获取新的经验值
...
seg001:65D1 8B 5E 08                       mov     bx, [bp+arg_2]
seg001:65D4                loc_23534:                                                      ; CODE XREF: sub_23412+37↑j
seg001:65D4 8B 76 06                       mov     si, [bp+arg_0]
seg001:65D7 88 5C 22                       mov     [si+22h], bl                            ; 修改新的经验
seg001:65DA                loc_2353A:
seg001:65DA 33 C0                          xor     ax, ax
seg001:65DC 5E                             pop     si
seg001:65DD 5F                             pop     di
seg001:65DE C9                             leave
seg001:65DF CA 04 00                       retf    4
seg001:65DF                sub_23412 endp

中间省略的是提升等级的一些其余代码,这个方法显而易见和我们实际游戏体验差不多,经验值不超过100就只增加经验值,经验值累计到了100或以上,就同时修改等级和经验值,升级的100以上的经验值扣掉,然后提高等级。

而我们在其中看到的三者最大的不同之处在于sub_2306A和sub_23412是不允许部队升级超过99级的,而sub_23A48并没有这个限制,好像sub_23A48传入的参数arg_2保证不超过99,但其实并非如此,具体内容见下节的分析。

其中限制部队升级超过99级是可以修改其中的数字,likelove兄在在前段时间的帖子等级超99设置的修改,对照他的原文,在此也正好解释等级上限的修改方式。

QUOTE:


[mXX]
caption=等级超99设置1
s=2d6300f7d88a4c212aed3bc17d06c6442163
r=2d9600f7d88a4c212aed3bc17d06c6442196
offset=[sg02]6116
mode=normal

[mXXX]
caption=等级超99设置2
s=3c637203
r=3c967203
offset=[sg02]64da
mode=normal


对照原文,这个修改将0x63(99) 共修改了3处,可以看出有两处在sub_2306A中,对比【caption=等级超99设置1 s=2d6300f7d88a4c212aed3bc17d06c6442163】和seg001:6116-seg001:6124。有一处在sub_23412中,对比【caption=等级超99设置2 s=3c637203】和seg001:64DA-seg001:64DC,修改这3个可以让最高等级提升到150级,当然也可以提升到255以下的整数,只要将r中修改的3处替换即可。
顶部
性别:未知-离线 漫漫苦短

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 64
编号 545816
注册 2023-12-25


发表于 2025-5-24 19:19 资料 短消息 只看该作者
十、人物数据的兵力等级和经验值(5)

上节提到了对于同样两个只改变等级的方法sub_2306A和sub_23A48,sub_2306A是根据部队上升的等级改变部队等级,不允许部队升级超过99级的,而sub_23A48是直接根据传入的等级值修改,也就是说如果sub_23A48中的arg_2是大于99,会直接将部队修改超过99级,请看下面这个例子。


图片附件: 升级前.png (2025-5-24 19:19, 90.13 K)




图片附件: 升级后.png (2025-5-24 19:19, 90.05 K)


打开DEBUG菜单,点击等级加5,由于在此之前已经进入了野i状态(就是点了刘备头像N次,再点击左上角的秘籍),此时刘备等级会从99级修改为104级,我并没有使用likelove的突破99级限制的方法却依然突破了99级,可以说这里就是游戏设计里不严谨的地方之一。

按照常理,等级加5,是可以使用sub_2306A进行等级修改,将arg_2设为5即可,如果使用这个方法,那么在没有突破99级限制之前,肯定没法将99级修改为104级,那么,其实答案已经呼之欲出了,这个修改等级的方法是sub_23A48,而要修改等级arg_2是在函数外算出部队当前的等级+5传入的,见下面的代码,部分内容可以参考上一章的介绍。

seg002:3373                loc_30293:
seg002:3373 BE 76 D0                       mov     si, 0D076h
seg002:3376                loc_30296:                                                      ; CODE XREF: sub_30136+18D↓j
seg002:3376 56                             push    si
seg002:3377 9A E8 40 F2 2C                 call    sub_31008                     ; 获取部队撤退标志
seg002:337C 3C 02                          cmp     al, 2                     ; 判断部队撤退标志是不是02正常
seg002:337E 75 1C                          jnz     short loc_302BC
seg002:3380 56                             push    si
seg002:3381 9A 62 40 F2 2C                 call    sub_30F82                   ; 获取部队人物代码
seg002:3386 6B F8 2C                       imul    di, ax, 44
seg002:3389 81 C7 16 68                    add     di, 6816h                   ; 计算部队人物数据的首地址
seg002:338D 57                             push    di
seg002:338E 9A DA 6A F6 1C                 call    sub_23A3A                   ; 获取部队等级
seg002:3393 04 05                          add     al, 5
seg002:3395 50                             push    ax
seg002:3396 57                             push    di
seg002:3397 9A E8 6A F6 1C                 call    sub_23A48                   ; 修改部队等级+5,非常不严谨
seg002:339C                loc_302BC:                                                      ; CODE XREF: sub_30136+168↑j
seg002:339C 83 C6 0E                       add     si, 0Eh
seg002:339F 81 FE 48 D1                    cmp     si, 0D148h
seg002:33A3 72 D1                          jb      short loc_30296

实际上看完整个流程就会发现,这个方法实在是有点多此一举,而且没有判断等级超过99级,而且一直使用这个DEBUG等级+5的功能,会出现当部队等级加到251-255时,再使用一次会将部队等级重新改为0-4,因为单字节显示的无符号整数范围就是0-255,这里就不再展示图片了。sub_2306A也是根据相似的方法计算出将要修改的等级,简单修改代码就能实现调用sub_2306A修改部队等级,并且还不会出现上述错误。这个只修改等级的方法sub_2306A整个程序中只有一处调用,这实在是程序设计的一大失误。

另外说个题外话,有没有人觉得界面中等级超2位数后会导致等级显示溢出,进而影响界面美观,地貌图界面显示的部队临时信息条溢出的等级个位数突出显示,会导致临时信息条消失后会留下溢出的个位数,而部队信息界面数据显示的位置也可稍微调整。

看一下贴吧的前辈parallelucky的修改方法:【英傑伝】等级破99的补丁!但显示起来有瑕疵,强迫症慎用。


图片附件: [借用parallelucky图片] 等级超限补丁.jpg (2025-5-24 19:25, 471.17 K)




图片附件: [借用parallelucky图片] 等级超限补丁-修改瑕疵.jpg (2025-5-24 19:25, 480.28 K)


这个方法是将“等级”两字替换为“LV.”,那么如果就想显示“等级”这个字样,并且调整部队信息界面数据显示的位置,有没有更好的方法?这里先在此留下课题。

那么兵力等级和经验值的部分就先讲到这里了,下个部分就是本章最精彩的部分,是三个与部队能力相关的数据项,如果对英杰传足够了解,相信讲解的内容已经呼之欲出了。

[ 本帖最后由 漫漫苦短 于 2025-5-24 21:49 编辑 ]
顶部
性别:男-离线 阿尔法孝直
(雀力日进)

闽国公
遂安军节度使
★★★★★★

Rank: 19Rank: 19Rank: 19Rank: 19
柱国(正二品) 轩辕春秋年度最佳(游戏人生区)
组别 节度使
级别 卫将军
好贴 2
功绩 1796
帖子 6043
编号 19070
注册 2004-10-16
家族 轩辕雀党


发表于 2025-5-24 20:23 资料 个人空间 短消息 只看该作者 QQ
回复 #16 漫漫苦短 的帖子

麻烦确认两个事情:
1、出现自动加等级的剧情(比如说麦之战战前关羽加10级),如果先前的等级已经超过99级,那么加完等级之后会不会回到99级?
2、超过99级的武将单挑升级,会不会回到99级?
顶部
性别:未知-离线 漫漫苦短

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 64
编号 545816
注册 2023-12-25


发表于 2025-5-24 22:22 资料 短消息 只看该作者
回复 #17 阿尔法孝直 的帖子

其实这两个是同一个事情,两个升级方式都是调用指令39人物等级上升,参考龙吟前辈的三国志英杰传剧本初步解析
,这个从龙吟的剧本编辑器中查看也能看到很清楚。

QUOTE:
【指令 39】
指令格式: 39 XX XX YY
人物等级上升
XX XX 人物代码
YY 上升等级数

指令对应的汇编代码:

seg001:8C15                loc_25B75:                                               ; CODE XREF: sub_24C46+256↑j
seg001:8C15 56                             push    si
seg001:8C16 9A 66 95 F6 1C                 call    sub_264C6                                      ; 获取剧本指令中的人物代码
seg001:8C1B 8B F8                          mov     di, ax
seg001:8C1D 56                             push    si
seg001:8C1E 9A 40 95 F6 1C                 call    sub_264A0                                      ; 上升的等级
seg001:8C23 50                             push    ax
seg001:8C24 6B C7 2C                       imul    ax, di, 44
seg001:8C27 05 16 68                       add     ax, 6816h
seg001:8C2A 50                             push    ax
seg001:8C2B 9A 0A 61 F6 1C                 call    sub_2306A                                      ; 修改部队的等级
seg001:8C30 EB 71                          jmp     short def_254AE

再对照sub_2306A:

seg001:610E 8B 76 06                       mov     si, [bp+arg_0]
seg001:6111 8A 46 08                       mov     al, [bp+arg_2]
seg001:6114 2A E4                          sub     ah, ah
seg001:6116 2D 63 00                       sub     ax, 99
seg001:6119 F7 D8                          neg     ax                            ; 获取升等级-99的相反数,即99-升等级
seg001:611B 8A 4C 21                       mov     cl, [si+21h]                            ; 部队等级
seg001:611E 2A ED                          sub     ch, ch
seg001:6120 3B C1                          cmp     ax, cx                                  ; 比较99-升等级与部队等级
seg001:6122 7D 06                          jge     short loc_2308A                         ; 有符号大于等于跳转
seg001:6124 C6 44 21 63                    mov     byte ptr [si+21h], 99                   ; 升级后的等级不超过99
seg001:6128 EB 06                          jmp     short loc_23090
seg001:612A                loc_2308A:                                                      ; CODE XREF: sub_2306A+18↑j
seg001:612A 8A 46 08                       mov     al, [bp+arg_2]
seg001:612D 00 44 21                       add     [si+21h], al                            ; 修改部队等级
seg001:6130                loc_23090:                                                      ; CODE XREF: sub_2306A+1E↑j
seg001:6130 8A 44 21                       mov     al, [si+21h]

如果[si+21h]大于99,而升等级为正数,即99-升等级小于99,那么seg001:6120在这个比较中ax肯定小于cx,因此seg001:6122不会发生跳转,就会执行seg001:6124修改等级为99级。根据汇编代码,高出99级的部队强行升级也是会跳回99级,但是另一个修改等级和经验值的方法sub_23412就不太一样了。

seg001:64D4 57                             push    di
seg001:64D5 9A DA 6A F6 1C                 call    sub_23A3A                      ; 获取部队等级
seg001:64DA 3C 63                          cmp     al, 99                                  ; 判断是否到99级
seg001:64DC 72 03                          jb      short loc_23441
seg001:64DE E9 F9 00                       jmp     loc_2353A

这个方法只判断了未获得经验值前的部队等级是否超出了99级,也就是说如果部队为98.99级,一次性获得101点或以上经验值就可以直接升到100级以上,当然在原始的游戏中,一只部队是无法一次性获得这么多的经验值(大策略是依次结算的),不过还是有办法证明这是可行的。

感谢孝直的提问,我也正好重新审视了代码,发现了其中的遗漏的情形,另外以上说法我只是简单测试过,并没有考虑到所有的情况,如果有疏漏或错误还希望各位指正。
顶部
性别:未知-离线 漫漫苦短

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 64
编号 545816
注册 2023-12-25


发表于 2025-6-4 19:19 资料 短消息 只看该作者
十一、人物数据的统御力武力智力(1)

人物数据的这三个属性,也有称之为三围的说法,一直是很多玩家津津乐道的话题,哪个人物的武力低了,智力高了,或者该培养哪个武将,该让哪个武将打决战,都是时常可见的话题,当然兵种这一属性也很重要,不过由于有转职道具的存在,更多被讨论的还是三围属性。以下还是从程序中的代码来讨论这三个属性。

三围与其它诸多属性都一样,也是有Get方法和Set方法的,按照惯例还是先给出其中的Get方法,arg_0都为人物数据的首地址

人物统御力的Get方法:

seg001:6A7C                sub_239DC proc far
seg001:6A7C
seg001:6A7C                arg_0           = word ptr  6
seg001:6A7C
seg001:6A7C 55                             push    bp
seg001:6A7D 8B EC                          mov     bp, sp
seg001:6A7F 8B 46 06                       mov     ax, [bp+arg_0]
seg001:6A82 05 18 00                       add     ax, 18h
seg001:6A85 50                             push    ax
seg001:6A86 9A 24 6A F6 1C                 call   sub_23984
seg001:6A8B C9                             leave
seg001:6A8C CA 02 00                       retf    2
seg001:6A8C                sub_239DC endp

人物武力的Get方法:

seg001:6A90                sub_239F0 proc far
seg001:6A90
seg001:6A90                arg_0           = word ptr  6
seg001:6A90
seg001:6A90 55                             push    bp
seg001:6A91 8B EC                          mov     bp, sp
seg001:6A93 8B 46 06                       mov     ax, [bp+arg_0]
seg001:6A96 05 19 00                       add     ax, 19h
seg001:6A99 50                             push    ax
seg001:6A9A 9A 24 6A F6 1C                 call   sub_23984
seg001:6A9F C9                             leave
seg001:6AA0 CA 02 00                       retf    2
seg001:6AA0                sub_239F0 endp

人物智力的Get方法:

seg001:C590                sub_294F0 proc far
seg001:C590
seg001:C590                arg_0           = word ptr  6
seg001:C590
seg001:C590 55                             push    bp
seg001:C591 8B EC                          mov     bp, sp
seg001:C593 8B 46 06                       mov     ax, [bp+arg_0]
seg001:C596 05 1A 00                       add     ax, 1Ah
seg001:C599 50                             push    ax
seg001:C59A 9A 24 6A F6 1C                 call    sub_23984
seg001:C59F C9                             leave
seg001:C5A0 CA 02 00                       retf    2
seg001:C5A0                sub_294F0 endp

可以看到这三个方法都是先获取其中的偏移地址(相对人物数据的首地址而言),比如说统御力的sub_239DC这个方法,就是将seg001:6A7F这AX寄存器获取的arg_0保存的人物数据的首地址,再seg001:6A82加上一个十六进制数0x18,获取到偏移地址,再seg001:6A85传入AX寄存器的数据,再调用sub_23984方法,武力的sub_239F0和智力的sub_294F0,也是这个道理。

那么接下来就是就是看sub_23984这个方法的具体代码。

seg001:6A24                sub_23984 proc far
seg001:6A24
seg001:6A24                arg_0= word ptr  6
seg001:6A24
seg001:6A24 55                             push    bp
seg001:6A25 8B EC                          mov     bp, sp
seg001:6A27 8B 5E 06                       mov     bx, [bp+arg_0]
seg001:6A2A 8A 07                          mov     al, [bx]
seg001:6A2C C9                             leave
seg001:6A2D CA 02 00                       retf    2
seg001:6A2D                sub_23984 endp

可以看到sub_23984这个方法也没有什么特殊所在,甚至这好像就是有点多此一举,如果说将武力的Get方法sub_294F0和sub_23984合并成一个方法,那么代码大概是下面这样:

push    bp
mov     bp, sp
mov     bx, [bp+arg_0]
add     bx, 19h
mov     al, [bx]
leave
retf    2

而且add     bx, 19h和mov     al, [bx]这两句代码还可以合并,用mov     al, [bx+19h]就可以替代了,而[bx+19h]这种形式的代码也再熟悉不过了,大部分的数据属性的Get方法和Set方法都用这样的代码表达式。

以上就是这三个属性的Get方法,看起来这样的代码形式有点多余,那么下一节再从它们的Set方法来看,这样的代码形式到底有没有必要?

[ 本帖最后由 漫漫苦短 于 2025-6-4 19:39 编辑 ]
顶部
性别:未知-离线 漫漫苦短

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 64
编号 545816
注册 2023-12-25


发表于 2025-6-9 19:19 资料 短消息 只看该作者
十二、人物数据的统御力武力智力(2)

这节的内容就是三围的Set方法的代码,以下三个arg_0都为人物数据的首地址,其实根据前面的内容大致可以推出,Set方法相对于Get方法只是多了一个arg_2,以下的arg_2就是要设置的属性值。

人物统御力的Set方法

seg002:2856                sub_2F776 proc far
seg002:2856
seg002:2856                arg_0           = word ptr  6
seg002:2856                arg_2           = byte ptr  8
seg002:2856
seg002:2856 55                             push    bp
seg002:2857 8B EC                          mov     bp, sp
seg002:2859 2A E4                          sub     ah, ah
seg002:285B 8A 46 08                       mov     al, [bp+arg_2]
seg002:285E 50                             push    ax
seg002:285F 8B 46 06                       mov     ax, [bp+arg_0]
seg002:2862 05 18 00                       add     ax, 18h
seg002:2865 50                             push    ax
seg002:2866 9A 30 6A F6 1C                 call    sub_23990
seg002:286B C9                             leave
seg002:286C CA 04 00                       retf    4
seg002:286C                sub_2F776 endp

人物武力的Set方法

seg002:2870                sub_2F790 proc far
seg002:2870
seg002:2870                arg_0           = word ptr  6
seg002:2870                arg_2           = byte ptr  8
seg002:2870
seg002:2870 55                             push    bp
seg002:2871 8B EC                          mov     bp, sp
seg002:2873 2A E4                          sub     ah, ah
seg002:2875 8A 46 08                       mov     al, [bp+arg_2]
seg002:2878 50                             push    ax
seg002:2879 8B 46 06                       mov     ax, [bp+arg_0]
seg002:287C 05 19 00                       add     ax, 19h
seg002:287F 50                             push    ax
seg002:2880 9A 30 6A F6 1C                 call    sub_23990
seg002:2885 C9                             leave
seg002:2886 CA 04 00                       retf    4
seg002:2886                sub_2F790 endp

人物智力的Set方法

seg002:288A                sub_2F7AA proc far
seg002:288A
seg002:288A                arg_0           = word ptr  6
seg002:288A                arg_2           = byte ptr  8
seg002:288A
seg002:288A 55                             push    bp
seg002:288B 8B EC                          mov     bp, sp
seg002:288D 2A E4                          sub     ah, ah
seg002:288F 8A 46 08                       mov     al, [bp+arg_2]
seg002:2892 50                             push    ax
seg002:2893 8B 46 06                       mov     ax, [bp+arg_0]
seg002:2896 05 1A 00                       add     ax, 1Ah
seg002:2899 50                             push    ax
seg002:289A 9A 30 6A F6 1C                 call    sub_23990
seg002:289F C9                             leave
seg002:28A0 CA 04 00                       retf    4
seg002:28A0                sub_2F7AA endp

这三个Set方法也是高度相似,其中seg002:285B-seg002:285E/seg002:2875-seg002:2878/seg002:288F-seg002:2892都是同样的内容,就是将此函数的arg_2的值,再传给sub_23990函数作为它的arg_2的值,和Get方法类似,例如智力的Get方法seg001:C596-seg001:C599对照Set方法seg002:2893-seg002:2896,都需要获取其中的偏移地址(相对人物数据的首地址而言),保存在AX寄存器中,并且作为传给sub_23990函数作为它的arg_0的值。

举个例子,这三个Set方法就像3个人,都是要存一件物品,只是它们存的物品的保险箱的编号不同,而它们都只是告诉职员它们所存的物品的保险箱的编号,店员就可以将不同的物品存到对应得的保险箱,这里的sub_23990函数无疑充当着职员的角色,那么接下来就是看看“职员”sub_23990函数的代码。

seg001:6A30               sub_23990 proc far
seg001:6A30
seg001:6A30                arg_0= word ptr  6
seg001:6A30                arg_2 = word ptr  8
seg001:6A30
seg001:6A30 55                             push    bp
seg001:6A31 8B EC                          mov     bp, sp
seg001:6A33 8B 56 08                       mov     dx, [bp+arg_2]
seg001:6A36 0B D2                          or      dx, dx
seg001:6A38 7D 02                          jge     short loc_2399C                         ; 有符号大于等于跳转
seg001:6A3A 33 D2                          xor     dx, dx
seg001:6A3C                loc_2399C:                                                      ; CODE XREF: sub_23990+8↑j
seg001:6A3C 83 FA 64                       cmp     dx, 64h
seg001:6A3F 7E 03                          jle     short loc_239A4                         ; 有符号小于等于跳转
seg001:6A41 BA 64 00                       mov     dx, 64h                                 ; arg_2不得超过100
seg001:6A44                loc_239A4:                                                      ; CODE XREF: sub_23990+F↑j
seg001:6A44 8B 5E 06                       mov     bx, [bp+arg_0]
seg001:6A47 88 17                          mov     [bx], dl
seg001:6A49 8B C3                          mov     ax, bx
seg001:6A4B C9                             leave
seg001:6A4C CA 04 00                       retf    4
seg001:6A4C                sub_23990 endp

在seg001:6A3C有个比较,就是将seg001:6A33中DX寄存器保存的arg_2的值与100(0x64)进行比较,如果DX小于等于100,那么就跳到seg001:6A44处,将DL寄存器的值保存到传入的arg_0的值所在的地址的位置,否则就先在seg001:6A41修改DX的值为100再继续执行seg001:6A44的代码。

可以看到三围的属性,与之前看到的等级的属性的一个差别,就是等级的直接Set方法是没有检测等级的传入值是不是大于99,而三围的Set方法对应的sub_23990方法,就是直接判断传入的arg_2的值是否是大于100,大于100就修正为100,这也就导致了如果不修改MAIN.EXE,而是用修改器或者直接修改BAKDATA.R3中人物的三围数据大于100,在游戏中的实际值依然只有100。

这同样能解答上节提出的这样的代码是否多余,可以看到三围由于都限制了最大属性不大于100,就可以通过sub_23990一个函数实现这样的限制,那么如果说三围都各自写一个限制函数,就有可能会导致写的三围的限制函数部分出现重复,而且如果要修改三围的限制值,比如说将100修改为120,就要修改三处代码,而这样就只用修改sub_23990的seg001:6A3C和seg001:6A41的两处0x64字节值,这样就不容易出现错误。

从下一节开始讲统御力武力智力的相关方法,而这个相关方法也就能印证三围对应的人物数据地址的偏移位置,统御力在偏移+018处武力在偏移+019处智力在偏移+01A处了。

[ 本帖最后由 漫漫苦短 于 2025-6-18 19:19 编辑 ]
顶部
性别:未知-离线 漫漫苦短

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 64
编号 545816
注册 2023-12-25


发表于 2025-6-18 19:19 资料 短消息 只看该作者
十三、人物数据的智力——计算策略值上限(1)

策略值算是第叁章的内容,在那一章只是简单提到了策略值是部队数据中的一个属性,而这一节就是讲如何计算策略值上限,如果对龙吟前辈的《三国志英杰传 分析结果》比较熟悉,就能想到那篇原文的以下两处:

QUOTE:
偏移+0D  占1字节  部队策略值



QUOTE:
4.最大策略值:
    最大策略值=(等级+10)×智力×5÷200

可以看到计算策略值上限运用了几个简单四则运算,sub_35296就是计算策略值上限的方法,其中arg_0为部队战场数据的首地址,对照下面的代码可以看到策略值计算中的四则运算。

seg002:8376                sub_35296 proc far
seg002:8376
seg002:8376                arg_0           = word ptr  6
seg002:8376
seg002:8376 55                             push    bp
seg002:8377 8B EC                          mov     bp, sp
seg002:8379 57                             push    di
seg002:837A 56                             push    si
seg002:837B 8B 7E 06                       mov     di, [bp+arg_0]
seg002:837E 6B 05 2C                       imul    ax, [di], 2Ch
seg002:8381 05 16 68                       add     ax, 6816h
seg002:8384 50                             push    ax
seg002:8385 9A DA 6A F6 1C                 call   sub_23A3A                      ; 获取人物等级
seg002:838A 2A E4                          sub     ah, ah
seg002:838C 05 0A 00                       add     ax, 0Ah
seg002:838F 6B F0 05                       imul    si, ax, 5
seg002:8392 6B 05 2C                       imul    ax, [di], 2Ch
seg002:8395 05 16 68                       add     ax, 6816h
seg002:8398 50                             push    ax
seg002:8399 9A 90 C5 F6 1C                 call   sub_294F0                      ; 获取人物智力
seg002:839E 2A E4                          sub     ah, ah
seg002:83A0 F7 E6                          mul     si
seg002:83A2 B9 C8 00                       mov     cx, 0C8h
seg002:83A5 2B D2                          sub     dx, dx
seg002:83A7 F7 F1                          div     cx                                      ; 策略值上限=(等级+10)×5×智力÷200
seg002:83A9 8B F0                          mov     si, ax
seg002:83AB 81 FE 00 01                    cmp     si, 100h                                 ; 比较策略值上限与256
seg002:83AF 76 03                          jbe     short loc_352D4                         ; 无符号小于等于跳转
seg002:83B1 BE FF 00                       mov     si, 0FFh
seg002:83B4
seg002:83B4                loc_352D4:                                                      ; CODE XREF: sub_35296+39↑j
seg002:83B4 8B C6                          mov     ax, si
seg002:83B6 5E                             pop     si
seg002:83B7 5F                             pop     di
seg002:83B8 C9                             leave
seg002:83B9 CA 02 00                       retf    2
seg002:83B9                sub_35296 endp

先观察seg002:837B-83A9,seg002:8384-838C这部分就是(计算等级+10)保存到AX寄存器中,seg002:838F把AX寄存器的数值×5保存到SI寄存器中,之所以要保存到SI寄存器中是因为接下来seg002:8398-8399求人物智力会改变AX寄存器的值,但SI寄存器的值不会变,seg002:83A0就是AX寄存器的值(人物智力)×SI寄存器的值并保存到AX寄存器中,seg002:83A2-seg002:83A7就是将上述的积再÷200并且得到策略值上限,这就是这个方法的第一步。

然后第二步seg002:83AB-83B4,seg002:83AB是个比较策略值上限与0x100(256),这个比较本身没有问题,问题是seg002:83AF的跳转的条件,意味着策略值上限计算结果小于等于256,都会跳过seg002:83B1这一步,而如果计算结果大于256,则会seg002:83B1将SI寄存器的值修改为0xFF(255)。

那么这一步有什么问题?策略值只占1字节,也就意味着策略值只能0-0xFF(255)这中间的整数取值,这里就先在此留下谜题,下一节具体分析这第二步计算的问题。

[ 本帖最后由 漫漫苦短 于 2025-6-18 20:09 编辑 ]
顶部
性别:未知-离线 漫漫苦短

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 64
编号 545816
注册 2023-12-25


发表于 2025-6-26 19:19 资料 短消息 只看该作者
十四、人物数据的智力——计算策略值上限(2)

导致计算策略值上限这个方法第二步出错的关键,就是seg002:83AB比较策略值上限与256,很多玩家都知道一个策略值计算的BUG,94智力的部队在98级时的策略值为253,而99级时策略值却变为0。发现的方式不能,有可能是将94智力的曹操等级修改为99级或让94智力的姜维升级的时候发现的,可以按照计算公式算出计算策略值上限第一步的结果:

QUOTE:
策略值上限=(99+10)×5×94÷200=256

可以看到计算的结果刚好是256并保存在SI寄存器中,那么在seg002:83AB这个比较中,就跳过seg002:83B1这条而不会修改SI寄存器的值为0xFF(255)。

但是seg002:83B4这条语句可能又让人迷惑了,虽然并没有将SI寄存器的值由0x100(256)修改为正确的0xFF(255),但是最后返回到AX寄存器的值还是0x100(256),那么为什么最后策略值得上限会变为0。要解决这个问题,就要继续研究调用sub_35296函数也就是计算策略值上限的其他函数对返回的AX寄存器的值进行了怎样的处理?例如下面的函数,就是第叁章提到的较为复杂的Set策略值方法sub_3618C。

seg002:926C                sub_3618C proc far                                      ; CODE XREF: sub_33DD0+1A9↑P
seg002:926C
seg002:926C                arg_0           = word ptr  6
seg002:926C                arg_2           = word ptr  8
seg002:926C
seg002:926C 55                             push    bp
seg002:926D 8B EC                          mov     bp, sp
seg002:926F 57                             push    di
seg002:9270 56                             push    si
seg002:9271 8B 7E 06                       mov     di, [bp+arg_0]
seg002:9274 57                             push    di
seg002:9275 9A 76 83 F2 2C                 call    sub_35296                     ; 获取部队策略值上限
seg002:927A 2A E4                          sub     ah, ah
seg002:927C 8B F0                          mov     si, ax
seg002:927E 8A 45 0D                       mov     al, [di+0Dh]                            ; 部队策略值
seg002:9281 03 46 08                       add     ax, [bp+arg_2]
seg002:9284 3B C6                          cmp     ax, si                                  ; 比较补充后策略值和策略值上限
seg002:9286 7E 07                          jle     short loc_361AF                         ; 有符号小于等于跳转
seg002:9288 8B C6                          mov     ax, si
seg002:928A 88 45 0D                       mov     [di+0Dh], al                            ; 修改部队策略值为上限
seg002:928D EB 06                          jmp     short loc_361B5
seg002:928F                loc_361AF:                                                      ; CODE XREF: sub_3618C+1A↑j
seg002:928F 8A 46 08                       mov     al, byte ptr [bp+arg_2]
seg002:9292 00 45 0D                       add     [di+0Dh], al                            ; 修改补充后策略值
seg002:9295
seg002:9295                loc_361B5:                                                      ; CODE XREF: sub_3618C+21↑j
seg002:9295 5E                             pop     si
seg002:9296 5F                             pop     di
seg002:9297 C9                             leave
seg002:9298 CA 04 00                       retf    4
seg002:9298                sub_3618C endp

seg002:9275就是调用计算策略值上限的方法,紧接着seg002:927A这步就是这个问题的核心了。255和256这两个数值虽然相差不大,但是AX寄存器拆成的AH寄存器和AL寄存器的值却完全不一样。255的16进制为0x00FF,所以AH寄存器的值为0,AL寄存器的值为FF;256的16进制为0x0100,所以AH寄存器的值为1,AL寄存器的值为0。因此在这步中,如果AX寄存器的值为0到0xFF(255),sub ah, ah并不会改变AX寄存器的值,而AX寄存器的值为0x100(256)到0x1FF(511),sub ah, ah会让AX寄存器的值减少256,以此类推。可以将sub ah, ah简单总结为让AX寄存器取除以0x100(256)的余数,因此在seg002:927C中,如果sub_35296返回的AX寄存器的值为0x100(256),那么SI寄存器的值就是0,从而让seg002:9284与策略值上限比较中,AX寄存器的值会大于SI寄存器的值,然后执行seg002:9288将AX寄存器的值的值修改为0,最后导致seg002:928A中部队策略值也被修改为0。

上面就是大致的对计算策略值上限变为0的大致分析,最要紧的问题还是如何修改能正确计算策略值上限,刚好也在其他贴中找到了一个修改方法

QUOTE:
原帖由 lightlife 于 2021-4-4 16:43 发表
补充一些修改,以供大家自娱自乐
4: 修改策略值的bug. 对于部分武将,比如陆逊、姜维,达到99级,导致最大策略为0.
02BCCF: 76 -> 72

这个回复也估计也没多少人看到,以至于在不同游戏讨论的地方都能看到对于策略值变为0的好奇以及求问如何这个BUG的方法。那么看到这个修改方法,就要研究一下这个修改的原理以及如何实现的。这个MAIN.EXE的地址,转换到IDAPro中的反汇编代码的位置,就是将seg002:83AF处的76,修改为72,那么这样修改后,这段代码会变成以下语句:

seg002:83AF 72 03                          jb     short loc_352D4                         ; 无符号小于跳转

通过这个修改虽然seg002:83AB这个比较没有变动,还是与0x100(256)比较,但是比较的跳转结果seg002:83AF却变了,由无符号小于等于跳转变为无符号小于跳转,那么当SI寄存器的值还是0x100(256)时,就不会跳转,就会正确执行seg002:83B1这步,将SI寄存器的值修改为0xFF(25)。

根据这个修改方法,也可以想到修改seg002:83AB这个比较条件,可以将seg002:83AB 81 FE 00 01 的00 01,修改为seg002:83AB 81 FE FF 00,这样这条语句就变成了cmp si, 0FFh,一样可以实现这个修改策略值的bug,还可以调整策略值上限的值(不超过255),比如说240,具体的修改方法就不多叙述了。

结合之前的构想,等级上限突破99以及智力上限突破100,那么实现这两个突破后,计算策略值上限是否还有其它BUG,这个修改方法也解决了其他BUG吗?下一节将探讨如何进一步完善这个sub_35296计算策略值上限方法。

[ 本帖最后由 漫漫苦短 于 2025-6-26 23:23 编辑 ]
顶部
性别:未知-离线 漫漫苦短

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 64
编号 545816
注册 2023-12-25


发表于 2025-7-5 19:19 资料 短消息 只看该作者
十五、人物数据的智力——计算策略值上限(3)

上节解决了策略值上限临界值为256计算出错的问题,当等级上限突破99,也不会在出现像99级94智力这样的问题了,但是策略值上限的计算就真的没有问题了吗?

其实从原始的计算公式可以看到,在做计算策略值上限的最后一步之前也就是seg002:83A0(等级+10)×5×智力得出的结果是保存在AX寄存器中的,而AX寄存器的大小只有2字节,也就是说,如果(等级+10)×5×智力这步的计算结果如果大于0xFFFF(65535,2字节整数的最大值),是无法完整保存在AX寄存器的,按照计算机CPU运算原理,一般情况下是会保存整数的低位2字节部分,“丢弃”整数的高位部分,这个后续会再说明。

当并没有实现这两个突破前,即使是99级智力100的诸葛亮,在seg002:83A0这步计算得出的结果也没超过65536,然而一旦等级突破99之后,121级时智力100的诸葛亮策略值上限还是255,再升1级到122级时,智力100的诸葛亮策略值上限突然变为2。这仿佛像高楼跳水一般,策略值上限突然大翻转。这究竟是怎么回事,其实只需要计算两个等级下seg002:83A0这步的计算结果就能看出问题所在,以下就是计算结果:

QUOTE:
=(121+10)*5*100=65500<65535
=(122+10)*5*100=66000>65535

从这里可以看出对于100智力诸葛亮,121级时,策略值上限的计算还没有问题,因为结果小于65535,而122级时,结果大于65535后,由于上述提到的AX寄存器无法完整保存,这个结果的高位部分被“丢弃”了。

那么究竟AX寄存器保存的结果是什么导致计算策略值上限为2,先分析66000的十六进制数0x101D0。由于AX寄存器只能保存这个结果的低位2字节,所以AX寄存器只保存这个结果的0x1D0(464),而它高位的部分0x10000并没有保存下来,于是只能按照seg002:83A0计算结果为0x1D0(464)继续计算最后一步,也就是计算的最后一步的结果为464÷200=2。

我们再回头看整个计算公式:(等级+10)×5×智力÷200,似乎×5和÷200是可以抵消的,也就是说这个公式可以转化为(等级+10)×1×智力÷40,有些同学可能认为如果这样改不就会将原来的结果变了,请自行验证一下,看看这样改动后,相同等级智力计算的结果是否一样。

那么(等级+10)×1×智力的计算结果在大部分情况下是没有问题了(计算结果不大于65535),由于等级智力也只是1字节大小(不大于255),也只有在等级和智力同时接近255时才有可能出现问题,大部分时候时无关大雅了。然而还是一个同样的问题,对于策略值上限的计算有没有更好的改动?
顶部
性别:未知-离线 漫漫苦短

Rank: 2Rank: 2
组别 百姓
级别 破贼校尉
功绩 1
帖子 64
编号 545816
注册 2023-12-25


发表于 2025-7-19 19:19 资料 短消息 只看该作者
十六、人物数据的统御力武力——计算防御力攻击力(1)

暂时介绍完智力以及计算策略值上限的方法,接下来继续介绍统御力和武力,而这两个属性各种最常用的方法自然是计算防御力和攻击力的方法了,当然龙吟前辈也早就给出了两者的计算公式了。

QUOTE:
一、部队的各项属性的计算方法:

  1.攻击力:

    攻击力=((4000÷(140-武力)+兵种基本攻击力×2+士气)×(等级+10)÷10)×(100+宝物攻击加成)÷100

2.防御力:

    防御力=((4000÷(140-统御力)+兵种基本防御力×2+士气)×(等级+10)÷10×(100+宝物防御加成)÷100

可以看出来两者的计算公式极度类似,似乎相似同一个磨子了刻出来的,那么它们两者的代码是否也很类似,以下这两个方法将同步介绍,这样就能清楚看到其中的相似之处了。

计算??力的外层方法

seg002:6E78                sub_33D98 proc far
seg002:6E78
seg002:6E78                arg_0           = word ptr  6
seg002:6E78
seg002:6E78 55                             push    bp
seg002:6E79 8B EC                          mov     bp, sp
seg002:6E7B 56                             push    si
seg002:6E7C 8B 76 06                       mov     si, [bp+arg_0]
seg002:6E7F 8A 44 0C                       mov     al, [si+0Ch]
seg002:6E82 50                             push    ax                                      ; 部队士气值
seg002:6E83 6B 04 2C                       imul    ax, [si], 2Ch
seg002:6E86 05 16 68                       add     ax, 6816h
seg002:6E89 50                             push    ax                                      ; 人物数据的首地址
seg002:6E8A 9A 0A 60 F6 1C                 call    sub_22F6A                     ; 获取人物??力
seg002:6E8F 5E                             pop     si
seg002:6E90 C9                             leave
seg002:6E91 CA 02 00                       retf    2
seg002:6E91                sub_33D98 endp

计算??力的外层方法

seg002:6E94                sub_33DB4 proc far
seg002:6E94
seg002:6E94                arg_0           = word ptr  6
seg002:6E94
seg002:6E94 55                             push    bp
seg002:6E95 8B EC                          mov     bp, sp
seg002:6E97 56                             push    si
seg002:6E98 8B 76 06                       mov     si, [bp+arg_0]
seg002:6E9B 8A 44 0C                       mov     al, [si+0Ch]                    ; 部队士气值
seg002:6E9E 50                             push    ax
seg002:6E9F 6B 04 2C                       imul    ax, [si], 2Ch
seg002:6EA2 05 16 68                       add     ax, 6816h
seg002:6EA5 50                             push    ax                                      ; 人物数据的首地址
seg002:6EA6 9A 38 61 F6 1C                 call    sub_23098                     ; 获取人物??力
seg002:6EAB 5E                             pop     si
seg002:6EAC C9                             leave
seg002:6EAD CA 02 00                       retf    2
seg002:6EAD                sub_33DB4 endp

仅仅看这两个函数,还无法看出两个方法的各自的作用,那么其实真正能决定这两个方法的核心作用就是sub_22F6A和sub_23098的函数了,于是继续挖掘这两个函数的具体代码,先给出函数的开头部分。

seg001:600A                sub_22F6A proc far                                      ; CODE XREF: sub_2B8F0+307↓P sub_33D98+12↓P
seg001:600A
seg001:600A                var_A           = word ptr -0Ah
seg001:600A                var_7           = byte ptr -7
seg001:600A                var_6           = word ptr -6
seg001:600A                var_4           = word ptr -4
seg001:600A                var_2           = byte ptr -2
seg001:600A                var_1           = byte ptr -1
seg001:600A                arg_0           = word ptr  6
seg001:600A                arg_2           = byte ptr  8
seg001:600A
seg001:600A C8 0A 00 00                    enter   0Ah, 0
seg001:600E 56                             push    si
seg001:600F 8B 76 06                       mov     si, [bp+arg_0]
seg001:6012 56                             push    si
seg001:6013 9A 90 6A F6 1C                 call    sub_239F0                      ; 获取人物武力
seg001:6018 2A E4                          sub     ah, ah
seg001:601A B9 8C 00                       mov     cx, 8Ch
seg001:601D 33 D2                          xor     dx, dx
seg001:601F 2B C8                          sub     cx, ax                      ; 140-武力
seg001:6021 1B D2                          sbb     dx, dx



seg001:6138                sub_23098 proc far                                      ; CODE XREF: sub_2B8F0+32B↓P sub_33DB4+12↓P
seg001:6138
seg001:6138                var_A           = word ptr -0Ah
seg001:6138                var_7           = byte ptr -7
seg001:6138                var_6           = word ptr -6
seg001:6138                var_4           = word ptr -4
seg001:6138                var_2           = byte ptr -2
seg001:6138                var_1           = byte ptr -1
seg001:6138                arg_0           = word ptr  6
seg001:6138                arg_2           = byte ptr  8
seg001:6138
seg001:6138 C8 0A 00 00                    enter   0Ah, 0
seg001:613C 56                             push    si
seg001:613D 8B 76 06                       mov     si, [bp+arg_0]
seg001:6140 56                             push    si
seg001:6141 9A 7C 6A F6 1C                 call   sub_239DC                     ; 获取人物统御力
seg001:6146 2A E4                          sub     ah, ah
seg001:6148 B9 8C 00                       mov     cx, 8Ch
seg001:614B 33 D2                          xor     dx, dx
seg001:614D 2B C8                          sub     cx, ax                      ; 140-统御力
seg001:614F 1B D2                          sbb     dx, dx

看到seg001:6013的调用sub_239F0获取人物武力/seg001:6141的调用sub_239DC获取人物统御力,已经能大概从龙吟前辈的公式里确定sub_22F6A是计算攻击力/sub_23098是计算防御力的方法了,但是这只是分析的第一步。可以看到这两个方法的除了此处不一致,其余都很相似,seg001:601A/seg001:6148的8Ch对应的十进制就是140,seg001:601A-6021就是对应着计算攻击力公式的140-攻击力,seg001:6148-614F就是对应着计算防御力公式的140-防御力。

但这里的有处奇特的代码,seg001:6021/seg001:614F的sbb dx, dx,光看sbb这个操作码,还以为是个骂人的,而且好像seg001:601F/seg001:614D的sub cx, ax就已经实现了140-攻击力和140-防御力的功能,那么还需要DX寄存器做什么?
不少修改过人物属性的玩家都知道不能将人物的统御力或武力修改为140原因就是无法除0,但为什么将属性修改为大于140,计算结果会难以琢磨,从公式的角度来看,修改为140以上就会导致出现负数,而seg001:601F/614D的结果保存在AX中,似乎并没有出现负数的符号,那么代码是如何识别出这是个负数的,下一节继续分析sub_22F6A/sub_23098的后续代码。

[ 本帖最后由 漫漫苦短 于 2025-7-19 19:44 编辑 ]
顶部

正在浏览此帖的会员 - 共 1 人在线




当前时区 GMT+8, 现在时间是 2025-8-1 19:19
京ICP备2023018092号 轩辕春秋 2003-2023 www.xycq.org.cn

Powered by Discuz! 5.0.0 2001-2006 Comsenz Inc.
Processed in 0.029873 second(s), 10 queries , Gzip enabled

清除 Cookies - 联系我们 - 轩辕春秋 - Archiver - WAP