2025-3-13 19:19
漫漫苦短
DOS版三国志英杰传的研究心得——肆
上一章简单分析了部队在战场上的数据,先回顾一下。
[quote]
以D076为首项,D(14)为公差,2D(45)为项数的一个十六进制下的等差数列中的一个地址值
[/quote]
按照上一章的分析这其中记录了人物代码、战场代码、仇人代码、撤退标志、AI类型、士气值、策略值等等信息,但显然,这不能完全概述其中的任一人物的全部信息,例如一个人物的等级经验兵种、还有武力智力统御力这三个一直被玩家拿来比较综合素质的数值,还有这个部队的名字等等信息,这些都没有在部队战场上的数据中,但实际上又不能不在战场上使用,例如部队在战场使用策略获得经验升级,不能不依靠等级经验兵种,部队也需要用武力智力统御力这三者计算在战场上的攻击力防御力和策略值的数据,那么这些数据又是记录在哪里?
可以想象应该是有新的一块数据是记录于此的,这块数据记为人物的固有数据(虽然等级经验兵种等等这些数据都是有变动的),如果依照部队在战场上的数据记录的特点,那么每一个人物的都是有对应的一个的首地址,也就是说这些地址的应该也是以X为首项,Y为公差,Z为项数的一个十六进制下的等差数列中的一个地址值。那么XYZ对应的十六进制值究竟是什么?
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。
[code]seg002:7746 B0 0E mov al, 0Eh
seg002:7748 F6 66 FF mul [bp+var_1] ; 部队战场代码
seg002:774B 05 76 D0 add ax, 0D076h[/code]
在代码找到了这两个部分的汇编代码,相似度很高吧,当然只是举出两个例子,arg_0的值还是以0xD076为首项,0xD(14)为公差,0x2D(45)为项数的一个十六进制下的等差数列中的一个地址值,两个部分的汇编代码sub_22F6A和sub_23098这两个函数的具体内容这章也会大致介绍。
[code]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 [/code]
[code]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[/code]
seg002:6E83-seg002:6E86/seg002:6E9F-seg002:6EA2,与seg002:7746-seg002:774B的地址计算方法很相似吧,seg002:6E7F/seg002:6E9B都是获取战场数据的一个方法,可以见[url=http://xycq.org.cn/forum/viewthread.php?tid=310133#pid4097883]上一章[/url]回忆一下。
所以X对应的十六进制就是0x6816,Y对应的十六进制就是0x2C,这也是龙吟前辈曾经提到的[url=http://www.xycq.org.cn/forum/viewthread.php?tid=239493&#pid3276414]44字节[/url],对比战场数据的Y为0xE,每一个人物都差了44-14=30(0x2C-0xE)字节大小,Z相差更大,用Y×Z就可以算出人物数据占了多大的内存空间,这两块数据占用的内存大小差距相当大。
总结人物数据的首地址为以0x6816为首项,0x2C(44)为公差,0x180(384)为项数的一个十六进制下的等差数列中的一个地址值。
2025-4-14 12:35
神雕小侠
兄弟有没有看过之前龙吟前辈的分析,我感觉您的这些研究成功跟他的应该会有一些交集
2025-4-14 18:18
漫漫苦短
回复 #3 神雕小侠 的帖子
岂止说是有交集,龙吟前辈简直是我的领路人,我一开始就是对着他的《三国志英杰传分析结果》来分析,还有周瑜前辈的全补丁的修改方法等等资料,目前关于他们的这些研究成果大部分都找到了程序中对应的位置,不过还是有很多内容要分析的,其实我汇编分析能力也不强,也只是站在巨人的肩膀上。
2025-4-19 19:19
漫漫苦短
二、人物数据的重要属性——兵种(1)
毫无疑问,人物数据占用的内存空间远大于战场数据的空间,本章分析的内容的篇幅也会比上一章长,分析人物数据中的44字节数据中大部分内容代表的含义。
每个人物的人物代码都是占用2字节的,上一节已经说了战场数据和人物数据的人物代码上的联系,但我想先从其他的数据开始讲起。如果说想从这44字节中挑出最重要的一项属性来说明,我认为这项属性就是每个人物的兵种代码,兵种代码虽然只占用的1个字节,但还是至关重要的,它在偏移+020处,sub_23A2C函数就是它的Get方法,arg_0的值都是以0x6816为首项,0x2C(44)为公差,0x180(384)为项数的一个十六进制下的等差数列中的一个地址值。
[code]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[/code]
这是根据人物数据的首地址来获取对应的兵种代码,那么有没有方法根据部队战场数据的首地址来获取对应的兵种代码,其实根据上一节的计算我们就能看出,通过人物代码,可以将战场数据的首地址转化成人物数据的首地址,于是就有了sub_3628C函数,arg_0的值都是以0xD076为首项,0xD(14)为公差,0x2D(45)为项数的一个十六进制下的等差数列中的一个地址值。
[code]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[/code]
从理论上来说sub_3628C这个函数是不需要调用sub_23A2C函数,因为在seg002:9375这句就已经求出人物数据的首地址,再根据之前的seg001:6ACF-seg001:6AD2的汇编代码稍改就可以求出兵种代码。
但实际上有可能人物数据的内存空间是甲设计结构,而战场数据的内存空间又是乙设计的,数据之间的逻辑是丙等人设计的,如果说甲设计的人物数据的内存空间结构发生变化,对应乙丙等人的汇编代码有可能出现多处改动,这是没有必要的,所以最好的方式就是就是提供一个类似接口的方法,比如一个调用函数(本例的sub_23A2C),这样无论甲设计的人物数据的内存空间结构发生怎样的变化,只要甲给定一个函数,乙丙等人根据这个函数,就是实现直接获取甲设计的的人物数据其中一项属性。
在此之前,封剑尘和孝直前辈都分别提到过——[url=http://xycq.org.cn/forum/viewthread.php?tid=109977&page=3#pid2508753]兵种顺序(及代码)[/url]、[url=http://xycq.org.cn/forum/viewthread.php?tid=172528]DOS版英杰传兵种攻防、兵力、移动力、策略修改[/url],可能大部分人只知道兵种名称,却不知对应的兵种代码,下一节就探究兵种名称和兵种代码的内在逻辑。
2025-4-23 19:19
漫漫苦短
三、人物数据的重要属性——兵种(2)
上节的只知道了可以获取兵种代码,而且内存中很多数据都是与兵种代码相关的,接下来我们就开始研究兵种代码对应的汇编代码,比如说根据兵种代码确定兵种名字。
英杰传中不止一处会显示人物对应的兵种名称,显然这是和兵种代码相关的,显示对应的兵种名称,可以看下面两处的代码,arg_0都是部队战场数据的首地址。
[code]
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
[/code]
[code]
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
[/code]
seg002:7B38-seg002:7B3B/seg002:8E6B-seg002:8E6B都是同样的调用显示文字的函数,显示文字的内容是根据seg002:7B31/seg002:8E61的值来确定,这就是接下来的重点,要想知道显示文字的内容,那么我们需要找到DS段的内存偏移地址0AA2处看里面的内存数据是什么?
[code]
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
[/code]
光看这处内存数据是看不出有兵种代码对应的兵种名称,需要从原来的代码先研究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段内存的偏移地址处查看内存数据。
[code]
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 ; "運輸隊"
[/code]
此时便豁然开朗了,DS:0AA2到DS:0AC6保存的是"兵种名称"的字符串地址,而地址对应的内存空间保存的则是兵种名称的BIG5码,每个兵种名称BIG5码的末尾都是00代表字符串的末尾。
如果想直接在MAIN.EXE中修改兵种名称可以用UE等工具搜索兵种名称对应的BIG码,或者直接查找MAIN.EXE文件位置0x0037BC8处是短兵字符串dseg:0A28的位置,MAIN.EXE文件位置0x0037C72对应dseg:0AA2的位置。
很多修改MOD的爱好者都遵循一个原则,不要改动字符串的长度以免出错,然而事实真是如此吗?在修改兵种名称这块,有没有其他更好的办法?
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運輸隊[/quote]
那么根据这些内容可以有怎样的修改达成改动兵种名称的位置?我总结了以下的几种方法。
[list=1]
[*]修改字符串
这一种就是最基础的修改方法,也就是直接找到MAIN.EXE文件位置0x0037BF8-0x0037C6A,直接将其中的兵种名称BIG5码进行替换即可。注意兵种名称的字符串长度可以改短,比如可以将"猛獸兵團"改为"猛獸團",改动后DS段内存空间数据变化如下所示。
[code]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[/code]
在这个例子中,将字符串长度为4的"猛獸兵團"改为字符串长度为3的"猛獸團"并且在dseg:0A7B也就是字符串的末尾修改为00表示字符串的结尾,不然如果只是将"兵"的BIG5码修改为"團",则会显示"猛獸團團"。同时修改后DS:0A7C和DS:0A7D这两个字节被"浪费"了,不过这两个字节可以重新利用,见下面的几个修改。
[*]修改索引
上文提到了,代码是根据索引对应的兵种名称,那么有没有可能实现改动索引改变兵种代码对应的兵种名称,来试试这个例子。
将DS:0AA2的28改为2D,DS:0AA4的2D改为28,如下所示DS段内存的变动。
[code]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 ; "短兵"[/code]
可以看到DS:0A28的兵种名称依然是"短兵",DS:0A2D的兵种名称依然是"長兵",但是DS:0AA2的字符串索引对象变成了"長兵",DS:0AA4的字符串索引对象变成了"短兵",在游戏中本来兵种代码为00短兵的部队显示的兵种名称却是"長兵",本来兵种代码为01長兵的部队显示的兵种名称却是"短兵",这样的修改只会修改兵种代码对应的兵种名称,虽然游戏中一支显示为"長兵"的部队连火龙都不会用,无法斜向攻击,一支显示为"短兵"的部队却手持长枪,还能可以斜向攻击。
那么这种修改方式是否有其他的尝试,比如说将0C軍樂隊和0E武術家隊的索引对调,如下所示DS段内存的变动。
[code]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 ; "軍樂隊"[/code]
根据上个例子,修改索引只会修改兵种代码对应的兵种名称,并不会修改兵种的性质。在这样的改动下,一支敲鼓的部队却叫"武術家隊",还是一支攻防弱血不多的部队,只能让一群人聚到它的四周回复策略,一支手持大刀的部队对外宣称自己叫"軍樂隊",然而是一支攻防强血厚的部队,砍人反击烧梅花五样样精通,这个场景颇有滑稽的感觉。
这个改动还有另一个特点,就是成功实现了兵种代码为0C軍樂隊这个兵种名称的扩容,普通的修改方式兵种代码为0C的部队修改名称只能保持字符串长度不大于3,如果强行增加字符串长度到4,会导致0D猛獸兵團显示异常,大部分修改者一时间找不到解决的方式只能得出了不能修改字符串长度这条规则,如果知道修改索引的方式,就不会无疾而终了。
修改索引之后,可以修改DS:0A6E和DS:0A7E的文字内容了,比如将DS:0A6E的"軍樂隊"修改为"武術隊",DS:0A7E的"武術家隊"修改为"軍鼓樂隊",就不会出现显示异常了,兵种代码为0C的部队名称修改成功由"軍樂隊"修改为"軍鼓樂隊",兵种代码为0E的部队名称修改成功由"武術家隊"修改为"武術隊",这里就不再演示了。
当然也可以举一反三地尝试其他的索引方式的修改。
[*]修改字符串和索引(字符串长度变化)
上个例子已经实现了字符串长度的变化,但是注意到原始的兵种名称只有0D猛獸兵團和0E武術家隊两个兵种名称的字符串长度为4,上个例子并不能在保留这两个兵种名称的字符串长度不变的情况下实现增加一个兵种名称的字符串长度为4甚至再增加一个,那么就需要本方法来实现了。
[*]一段文字两条索引
这种修改方法需要一定的巧合。
[/list]
当然方法远不止上述的几种,还可以使用MAIN.EXE文件中代码中DS段未使用的文件空间(MAIN.EXE文件位置0x00371D0以后的位置,有多少地方不确定程序是否使用),需要将MAIN.EXE文件中的地址对应DS段位置,这个就比较复杂了。
[color=Silver][[i] 本帖最后由 漫漫苦短 于 2025-4-26 23:51 编辑 [/i]][/color]
2025-4-29 06:22
哆子颜
楼主还是在用反汇编啊,现在有些工具都可以转化成C语言编程了
2025-4-29 20:40
漫漫苦短
回复 #8 哆子颜 的帖子
你说的工具是什么?我目前用的是IDAPro,其实是有反汇编转成C语言功能的,限于32位和64位程序才能使用,不过DOS版三国志英杰传是16位程序,IDAPro并不能将其转成C语言,可能需要其他办法。你如果有其他思路也可以说一下。
还有另外一点,工具也不是万能的,就算把反汇编出来的汇编语句交给ChatGPT和DeepSeek这些AI工具,它也只能转换少量的语句对应C语言,而且没有把最重要的逻辑呈现出来,甚至不一定比自己理解的好。最重要的是其实反汇编研究虽然难度大且"痛苦",但其实就我本人而言我是乐在其中的,感受到自己在其中分析研究后的收获很大,不只是三国志英杰传和反汇编的知识积累,解决问题的能力有所提升,也会让自己更有信心面对这样的问题。
即使换了个超级智能的AI软件能把上述问题解决,把整个游戏给分析研究透彻,但是并不能替代人的思考过程,而且要是这个研究简单到不用思考也只会感觉索然无味而已。就假如有个AI软件直接把全兵种战后2999的规划以及每关每回合行动直接草拟出来,月亮这样的大神会放弃自己思考如何规划以及每关的布阵思路吗?其中这是同一个道理。再想想英杰传前辈,当时没有现在这么强的工具,战前2099到2699的战绩以及打法规划的进步还不是前辈们前赴后继一步一个脚印走出来的吗?
[color=Silver][[i] 本帖最后由 漫漫苦短 于 2025-4-29 20:59 编辑 [/i]][/color]
2025-5-1 19:19
漫漫苦短
五、人物数据的重要属性——兵种(4)
本节介绍部队兵种的衍生的方法,计算部队移动力、获取兵种移动类型、获取兵种攻击范围类型这三个方法,之所以称它们为衍生的方法就是因为这三个方法都是先得出部队的兵种,再根据兵种得出对应的类型。
sub_33D26函数是计算部队移动力的方法,arg_0为部队战场数据的首地址。
[code]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
[/code]
看到了熟悉的[bx+xxxxh]的汇编代码语句,于是到DS:31EA处寻找数据。
[code]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[/code]
seg002:6E26-seg002:6E6B是计算道具带来的移动力加成的计算并且保存到var_4中,由于涉及到道具的相关知识,本处省略其中的计算过程。
[code]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[/code]
兵种移动类型,这个在番外1也出现过,现在正式介绍sub_342B4这个函数,arg_0为部队战场数据的首地址。
[code]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[/code]
[code]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
[/code]
还是到DS:3256处寻找数据。
[code]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运输队[/code]
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函数,于是在转成汇编后就没有单独成为一个函数(这只是本人的脑补),好在使用到的地方不多,修改的话也没有那么麻烦。
[code]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] ; 兵种攻击范围类型[/code]
2025-5-1 22:40
神雕小侠
回复 #9 漫漫苦短 的帖子
嗯嗯,16位程序无法转换成C语言,这个是关键问题
2025-5-5 19:19
漫漫苦短
六、人物数据的兵力等级和经验值(1)
前几节一直介绍了兵种代码以及与其相关的代码,虽然它仅在每个人物数据的偏移+020处占用的1个字节,意义却非同小可,而花费大量的篇幅也不意味内容的完结,因为它还与其他属性有着千丝万缕的联系。从本节开始,继续介绍人物数据的的三个属性兵力、等级、经验值。
先简要概述一下三者的数据位置,兵力在偏移+01E处,占用2字节;等级在偏移+021处,占用1字节;经验值在偏移+022处,占用1字节。从这里就看出第一个巧合了,兵力恰好在兵种代码偏移+020处的左边,等级和经验值在兵种代码位置的右边,三者正好夹住兵种代码这一属性。
先介绍这三个属性的Get方法,以下的代码中,arg_0都是人物数据的首地址。
[code]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[/code]
[code]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[/code]
[code]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
[/code]
Get兵种代码这个方法的位置在seg001:6ACC-seg001:6AD6,而seg001:6ADA-seg001:6AE4正好是Get等级的方法的位置,这两个函数是挨着的,在我第一次发现这两处函数的我还对英杰传的代码不太熟悉,突然在此处发现两个如此简单的函数,而且实际上在这两个函数旁边也存在大量类似的代码,那时有种顿悟的感觉,虽然大部分代码都很枯燥无味,但是看到一大串无比简单的代码还是让我感到心情舒适。
仔细观察Get兵力的位置seg001:C5A4-seg001:C5AE和Get经验值的位置seg001:C5D0-seg001:C5DA也很接近,而这其中也只夹杂着两个简单的函数。
而三者的Set方法就稍微有点复杂了,比如说Set兵力,因为兵力其实是在战场上的实际兵力而不是满兵力,Set兵力不能超过最大值。
[code]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[/code]
sub_23880函数正式计算部队的最大兵力,这两个方法到下一节再解释。
2025-5-9 19:19
漫漫苦短
七、人物数据的兵力等级和经验值(2)
上一节的最后留下了如何计算兵力上限的疑问,关于这个问题,最早重阳老前辈就已经整理过,在[url=https://xycq.org.cn/forum/viewthread.php?tid=1092]英杰传兵种、策略与移动力攻防[/url]
中就已经提到了,不过龙吟前辈已经给出了详细的数据以及算法:
[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[/quote]
封剑尘和孝直前辈都分别兵种顺序(及代码)、DOS版英杰传兵种攻防、兵力、移动力、策略修改(上文有链接)提到过以下内容:
[quote]037F58开始,按兵种顺序的19个字节代表19个兵种的初始兵力,数值为初始兵力/100
037F6C开始,按兵种顺序的19个字节代表19个兵种的兵力增幅,数值为兵力增幅/10[/quote]
接下来就介绍计算兵力上限的函数sub_23880,arg_0为人物数据的首地址。
[code]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[/code]
接着到DS:0D88处和DS:0D9C寻找数据,也就是对应着上面提到的MAIN.EXE文件位置0x037F58和0x037F6C,可以看到两块数据是挨着一起的。
[code]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[/code]
[code]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[/code]
从上面的程序以及汇编分析的结果来提炼出兵力上限的计算方法:
[list=1]
[*]seg001:6929,根据每个人物数据的首地址偏移+020获取部队兵种代码。
[*]seg001:692E,在DS:0D88处,根据部队兵种代码偏移计算数据地址,获取兵种基本兵力的初始值×10。
[*]seg001:6932,将兵种基本兵力的初始值保存在CX寄存器中。
[*]seg001:6934,在DS:0D9C处,根据部队兵种代码偏移计算数据地址,获取兵种每等级兵力增幅。
[*]seg001:693A-693D,根据每个人物数据的首地址偏移+021获取部队等级-1。
[*]seg001:693E,将第4步的结果与第5步的结果相乘。
[*]seg001:6940,将第3步的结果与第6步的结果相加。
[*]seg001:6942,将第7步的结果乘10,保存在AX寄存器中作为函数的返回结果。
[/list]
可以看到兵种兵力上限的计算并不难,哪怕不知道公式,也可以通过观察英杰传中的游戏界面的数据得出结论,运用公式算也不复杂,但是如何在此基础上进行修改就是另一门学问了。如果只是用UE这样的工具在MAIN.EXE中查找500\50这样的数据,由于程序中的计算方法是无法直接找到的,而借助反汇编分析代码的方式,可以从细节中看出其中每一步的算法以及思路。
而且不只是修改DS:0D88到DS:0DAF的数据,seg001:6927到seg001:6942的代码逻辑也是可以修改的,比如说可以修改第2步和第8步的乘10,可以将两个数分别修改为其他数,或者也可以改其中的乘法为其他运算,甚至把整段的代码逻辑都改了,这就取决于个人的汇编编程能力了。
[color=Silver][[i] 本帖最后由 漫漫苦短 于 2025-5-9 19:40 编辑 [/i]][/color]
2025-5-12 19:19
漫漫苦短
八、人物数据的兵力等级和经验值(3)
在龙吟前辈的分析结果中,有两处提到了判断部队兵力小于最大兵力的40%,九、敌军、友军行动准则全分析的以下两处。
[code]1.友军和敌军的行动顺序
行动顺序的判定以自动补给后的数据为准。
最优先行动的是处于恢复性地形(村庄、兵营、鹿砦)中的部队,若有数只部队处于恢复性地形上,则以其在屏幕右上方的敌军列表中的顺序排列。
第二优先行动的是兵力小于最大兵力的40%或士气低于40的部队,若右数只部门处于该情形下,则以其在屏幕右上方的敌军列表中的顺序进行排列。
最后余下的部队按屏幕右上方的敌军列表中的顺序进行排列。[/code]
[quote]4.行动价值
每只部队的行动方式由行动价值决定,当部队行动时,会计算战场上每一处坐标的行动价值,然后移动到行动价值最高的地方(当然,部队的行动力必须能移动到该处)。部队在某个地方可能有几种行动方式:物理攻击、策略攻击、休息等,每种行动方式都有其行动价值,在几个行动价值中,最高的为该坐标的行动价值。
行动价值的计算方法如下:
1)如果部队的兵力不足(即兵力小于最大兵力的40%或士气小于40,下同),则战场上存在可恢复地形的坐标,行动价值加50。[/quote]
这两处都有部队是否存在兵力不足或士气值不足的情况,显然都需要先求出部队的兵力上限来进行计算,那么这两处是否会调用同一个函数来进行判断?
sub_38396就是这个判断函数,这个判断函数也只在这两种情况下使用,我把该函数命名为[b]判断部队是否战力不足[/b],毕竟一支部队需要既有足够的兵力还有足够的士气值才有战力。其中arg_0为[b]部队战场数据[/b]的首地址,士气值的部分可以回顾上一章。
[code]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[/code]
判断部队是否战力不足的计算方法:[list=1]
[*]seg002:B47E-B481 获取人物数据的首地址(具体方法见本章上一节)。
[*]seg002:B485-B486 获取部队兵力上限(具体方法见本章上一节)。
[*]seg002:B48B-B492 计算临界兵力,seg002:B48B-seg002:B490 先用部队兵力上限除以5,再翻倍,即为兵力上限的40%,seg002:B495保存结果到SI寄存器。
[*]seg002:B495 获取部队当前兵力。
[*]seg002:B49C 比较临界兵力与当前兵力,seg002:B49E 如果临界兵力不小于当前兵力(当前兵力小于等于临界兵力)跳到第8步。
[*]seg002:B4A0 比较部队士气值与40,seg002:B4A4 如果部队士气值小于40跳到第8步。
[*]seg002:B4A6 AX寄存器改为0作为函数的返回结果。
[*]seg002:B4AA AX寄存器改为1作为函数的返回结果。
[/list]因此该函数只要部队满足兵力[b]小于等于[/b]兵力上限的40%或士气值[b]小于[/b]40都会返回1,即判断该部队战力不足。而在判断条件上到底是小于还是小于等于上龙吟前辈还是有点小瑕疵的,兵力是判断[b]小于等于[/b]40%而士气值是判断[b]小于[/b]40,两者是不同的,举例,在第一关汜水关中的2级550兵力上限的李肃在216血的时候就不会出鹿寨射张飞关羽,而信都之战士气值等于40的军乐队不会进村而是有可能跑到森林,这也可以证实兵力的判断确实是小于等于,所以在分析中需要严谨的精神,不至于因为一小处的失误造成遗留问题。
最后再对比一下前面的Set兵力方法,相同点都是先获取部队兵力上限,再分别根据兵力上限或临界兵力与当前兵力进行判断。
总结一下兵力的部分,部队的兵力不能超出上限,这个在Set兵力这个方法就有体现,然后兵力上限的计算方法是根据兵种获取兵种基本兵力和兵种每等级兵力增幅,运用公式兵力上限=兵种基本兵力+兵种兵力增幅×(等级-1)计算,并且在判断部队是否战力不足这个函数会判断部队是否满足当前兵力小于等于兵力上限的40%或士气值小于40的条件。当然兵力上限也有其他用处,比如在军团状态栏中就显示兵力和兵力上限。
[color=Silver][[i] 本帖最后由 漫漫苦短 于 2025-5-12 19:59 编辑 [/i]][/color]
页:
[1]
Powered by Discuz! Archiver 5.0.0
© 2001-2006 Comsenz Inc.