用实际的例子进行说明。
这次我们的目的比较简单,在人物状态选单上增加一个显示品德指数的项。
这是原来的状态选单的第一页,考虑一下加到哪里合适:
经过简单分析,认为把所有选项全部左移,在右面增加一列比较美观。但是在这个演示例子里不做那么多,我们仅把头像和第一个字符串左移。
在show_PersonStatus子程中可以找到:
dseg02:00022A9C push 3
dseg02:00022A9E push offset array_VRAM
dseg02:00022AA3 push 0
dseg02:00022AA5 push 0FFh
dseg02:00022AAA push 0C8h ; 200
dseg02:00022AAF push 0D2h ; 210
dseg02:00022AB4 push 0
dseg02:00022AB6 push 37h ; 55
dseg02:00022AB8 call sub_2CEBF
dseg02:00022ABD add esp, 20h ; 32
dseg02:00022AC0 push offset array_VRAM
dseg02:00022AC5 imul ebx, edi, 0B6h ; 182
dseg02:00022ACB movsx eax, word_9014E[ebx] ; 头像代号
dseg02:00022AD2 push eax
dseg02:00022AD3 push 44h ; 头像显示位置X坐标
dseg02:00022AD5 push 4Eh ; 头像显示位置Y坐标
dseg02:00022AD7 call sub_2D590
dseg02:00022ADC add esp, 10h
注释得这么清楚,看来不用多解释了。但是注意这个注释有些错误,在计算机绘图时往往y轴是先被提到的,所以这个头像坐标的注释实际上写反了。
上面的200和210是你看到的那个矩形框的高度和宽度,0和55是框的起始坐标位置。需要注意y轴是向下的。改成:
dseg02:00022AAA push 0C8h ; 200
dseg02:00022AAF push 12ch ; 300
dseg02:00022AB4 push 0
dseg02:00022AB6 push 0ah ; 10
头像位置的x坐标改小一些,比如把4eh改成21h,左移2dh个像素。
而显示攻击力的位置也同样左移:
dseg02:00022FC7 push 10h
dseg02:00022FC9 push 6663h
dseg02:00022FCE push offset array_VRAM
dseg02:00022FD3 push offset aZDo ; 攻擊力
dseg02:00022FD8 push 5
dseg02:00022FDA push 0A0h
这个改为73h,上面的两个是该字符串的纵横坐标。
dseg02:00022FDF call draw_string ; 显示字符串 “攻击力”3个字
dseg02:00022FE4 add esp, 18h
dseg02:00022FE7 push 10h
dseg02:00022FE9 push 705h
dseg02:00022FEE push offset array_VRAM
dseg02:00022FF3 push offset byte_C07C4
dseg02:00022FF8 push 5
dseg02:00022FFA push 0E6h
这个改为b9h
dseg02:00022FFF call draw_string ; 显示字符串 显示数字
dseg02:00023004 add esp, 18h
dseg02:00023007 imul edx, edi, 0B6h 这个后面会被改写为call
先看看阶段结果:
看起来乱了些,但是可以一一修改全部的项,这样在右侧就有了空间。这里就不做了。
分析一下原版中显示一个数字用了些什么代码,大致是先调用sprintf生成字符串,再输出。我们先找一个能改写成call语句的地方。在调用显示数字的函数之后是清栈,后面是一个乘指令,占用6字节,可以改写为call指令(我并不是建议你改写这里,这只是一个例子)。
在z.dat里面找一个连续空白的地方,这里我们用5b800的位置。
23007开始的69 D7 B6 00 00 00 改为 E8 F4 87 03 00 90 (5b800h-2300Ch=387f4h,即从call指令结束的地方到目标地点所隔的字节数)。
看看原版怎样调用攻击力的值的:
dseg02:00022F61 imul edx, edi, 0B6h
dseg02:00022F67 movsx eax, word_901A2[edx] ; 攻击力
以下用来将数字转成字符串:
dseg02:00022FB4 push eax
dseg02:00022FB5 push offset a3d ; "%3d"
dseg02:00022FBA push offset byte_C07C4
dseg02:00022FBF call sprintf??
dseg02:00022FC4 add esp, 0Ch
我们看到是利用edi进行一个乘法。那么edi里面的值在整个子程中应该不会有变化,观察一下确实如此。这个子程中利用edi保存人物id,简化了过程。如果是用栈中的数据表示的,需要先修改栈顶(因为call指令就会修改栈顶,经常我们需要在自己的子程前面加上pushad,也会修改栈顶),计算之后再改回去。我们照着葫芦画瓢,写自己的一段程序:
pushad
imul edx, edi, 0b6h
movsx eax, word_pinde[edx]
push eax
push offset a3d
push offset byte_C07C4
call sprintf??
add esp, 0ch
push 10h
push 705h
push offset array_VRAM
push offset byte_C07C4
push 5
push 118h
call draw_string
add esp, 18h
后面在这里会插上一段
popad
imul edx, edi, 0b6
retn
pushad 和 popad 是为了保存现场和恢复现场,避免原有的程序出错。最后一个乘法指令是重复原有的。
这段程序有5处是要修改重定位表的,我们先把这段程序译成机器码,写入5b8000h的位置:
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
0005B800 60 69 D7 B6 00 00 00 0F BF 82 FF FF FF FF 50 68
0005B810 FF FF FF FF 68 FF FF FF FF E8 2C 37 FE FF 83 C4
0005B820 0C 6A 10 68 05 07 00 00 68 FF FF FF FF 68 FF FF |