软件字体修改

前 言

    谈起软件字体的修改,大家一定会联想到汉化新世纪梁利锋,他所写的文章解决了我们汉化中碰到的一些难题,开拓了汉化界的新天地,在此,我们向他表示感谢。今天,我们写这篇文章目的只是为了让大家对软件字体的修改有更深、更进一步的了解,并带领大家分析、理解反汇编代码,找字体修改的突破口,详细讲解修改方法。这篇文章从对沈晔骏汉化的 WinZip Self-Extractor 2.2 Build 4003 这个软件字体变小的问题出发,先提出一种危险的字体修改方法,从而引深出三种不同的安全正确的字体修改方法,并从中引发出很多汉化的技巧。文章的第一部分是由飞鹰编写,第二部分是由Ronnier编写,第三部分是由飞鹰和Ronnier共同编写完成。对于这篇文章我们力求写的清楚、易读,所以,文章的篇幅就多了些,还请大家谅解。

    最后,衷心希望这篇文章对你能有所帮助。

第一篇:误入歧途

    今天,在网上下载了沈晔骏汉化的 WinZip Self-Extractor 2.2 Build 4003,下载安装后发现该汉化版中有几处的字体非常难看(如图一所示),没有改成我们希望看见的“宋体,9号”,一时心血来潮想帮沈晔骏兄修改一下这个问题。

    先用FI测试可知该软件是用 MS Visual C++ 编写的,后用 exescope 打开看它里面的资源(如图二所示)。大家可以看到虽然该软件是用 MS Visual C++ 编写,但里面却有 RCData 资源,奇怪!而且查看Import表中的 GID32.DLL 内确实有 CreateFontIndirectA 函数存在,但在 exescope 中查看该软件字体设置有问题的窗口,在这里显示却是正常的(如图三所示)。所以初步猜想可能软件在运行时动态的用CreateFontIndirectA函数改变了我们原先设置的字体及大小,而且该软件内有RCData资源,我就想修改方法可能与DEPHI程序字体的修改(请参看梁利锋兄写的《Delphi 字体修改一例 》)类似。

    大概了解情况之后,就可以动手修改了!第一步是把它调入uedit中查找是否有“MS Sans Serif”字体存在(注意:这里查找的是ASCII字串,而不是UNICODE字串,一般修改这种资源查找的都是ASCII字串),每找到一处时就改为“System”存盘返回到软件中看效果,如果没有变化就把这一处改回原状,后再修改下一处,直到软件的字体起了变化之后,就可以在uedit中记下这个偏移地址为进行下一步打下基础。在该汉化版中,当我修改了开头的两处字体后软件的界面就起了变化(如图四所示),这时我们就不必再往后查找了,因为我们已经找到了问题的根源。可是,象这样修改后字体变的有一些大、不美观,如果你怕麻烦的话,就这样修改好了,不必再把这篇文章往后看下去了;如果你是汉化DIY的话,我希望你能继续往下看。(废话!)为了把它改成我们希望看到的“宋体,9号”,先把这两处的字体改回原状“MS Sans Serif”,后记下这两处的偏移地址,第1处是2540cH,第2处是2541cH(如图五所示),这些地址在第二步中将会被用到。

     在第二步中我们将要用到软件W32DASM,对该汉化版进行反汇编。在继续修改之前,必须先知道所需字串的RVA值(即MS Sans Serif的相对虚拟地址),这就要用到上面我们记下的偏移地址来计算RVA值了。实际操作中我们不必套用公式来算这个值,可以让梁利锋兄编写的“点睛偏移量转换器”这个软件来帮忙,目前该软件最新的版本是0.94B。关于该软件的具体使用方法,大家可以参看大宇兄写的《RVA字串乾坤大挪移》这篇文章,这里我只简单介绍:先点击“...”浏览按钮,选择需要载入的文件-->在“实偏移”文本框中输入字串的偏移地址(即MS Sans Serif的偏移地址)--> 之后,字串的RVA值可以从“虚偏移”文本框中看到(如图六所示)。在这里我先计算出了第2处的RAV值是426c1cH,因为1、2处的字体相隔非常近(如图五所示),所以只需知道第2处的RAV值后,反汇编出来查找到这个值再往上找几行就是第1处的RAV值了。启动W32DASM(我用的是W32DASM 8.93 汉化版),在“反汇编”莱单中选择“打开文件反汇编”选项来载入要反汇编的文件,这里当然是载入 WinZip Self-Extractor 2.2 Build 4003 汉化版了,不用多说!之后,在“查找”莱单中选择“查找文本”选项,在“查找内容”文本框中输入426c1c(注意:不要把它后面的'H'也输进去,'H'只是表示该值是16进制数)按“查找下一个”按钮进行查找,在该汉化版中只查找到一个,那么我们百分之百的肯定就是它,找对了!先往上移几行,就可以看到有“MS Sans Serif”的字样,后往下移几行,就可以看到有“GDI32.CreateFontIndirectA”的字样,说明该软件的字体设置就在这里,这里就是我们要修改的地方了。根据上面的'加粗'字体段的说明,再往上移几行,很容易就找到需要修改的另一处地方。要修改的这两处的具体代码摘抄如下:

* Possible StringData Ref from Data Obj ->"MS Sans Serif"
                                  |
:0040D830 680C6C4200 push 00426C0C
:0040D835 8D45E0 lea eax, dword ptr [ebp-20]
:0040D838 50 push eax
:0040D839 E8828B0000 call 004163C0
:0040D83E 59 pop ecx
:0040D83F 59 pop ecx
:0040D840 6A5A push 0000005A

:0040D842 FF75C0 push [ebp-40]

* Reference To: GDI32.GetDeviceCaps, Ord:00C7h
                                  |
:0040D845 FF159C204200 Call dword ptr [0042209C]
:0040D84B C1E003 shl eax, 03
:0040D84E 99 cdq
:0040D84F 6A48 push 00000048
:0040D851 59 pop ecx
:0040D852 F7F9 idiv ecx
:0040D854 6BC0FF imul eax, FFFFFFFF
:0040D857 8945C4 mov dword ptr [ebp-3C], eax
:0040D85A C745D4BC020000 mov [ebp-2C], 000002BC
:0040D861 8D45C4 lea eax, dword ptr [ebp-3C]
:0040D864 50 push eax

* Reference To: GDI32.CreateFontIndirectA, Ord:002Ch
                                  |
:0040D865 FF15A0204200 Call dword ptr [004220A0]
:0040D86B A388DE4300 mov dword ptr [0043DE88], eax
:0040D870 6A3C push 0000003C
:0040D872 6A00 push 00000000
:0040D874 8D45C4 lea eax, dword ptr [ebp-3C]
:0040D877 50 push eax
:0040D878 E8E38A0000 call 00416360
:0040D87D 83C40C add esp, 0000000C

----------------------------------------------------------------------------------------------

* Possible StringData Ref from Data Obj ->"MS Sans Serif"
                                  |
:0040D880 681C6C4200 push 00426C1C
:0040D885 8D45E0 lea eax, dword ptr [ebp-20]
:0040D888 50 push eax
:0040D889 E8328B0000 call 004163C0
:0040D88E 59 pop ecx
:0040D88F 59 pop ecx
:0040D890 6A5A push 0000005A
:0040D892 FF75C0 push [ebp-40]

* Reference To: GDI32.GetDeviceCaps, Ord:00C7h
                                  |
:0040D895 FF159C204200 Call dword ptr [0042209C]
:0040D89B C1E003 shl eax, 03
:0040D89E 99 cdq
:0040D89F 6A48 push 00000048
:0040D8A1 59 pop ecx
:0040D8A2 F7F9 idiv ecx
:0040D8A4 6BC0FF imul eax, FFFFFFFF
:0040D8A7 8945C4 mov dword ptr [ebp-3C], eax

* Possible Reference to Dialog: DialogID_0190
                                  |
:0040D8AA C745D490010000 mov [ebp-2C], 00000190
:0040D8B1 8D45C4 lea eax, dword ptr [ebp-3C]
:0040D8B4 50 push eax

* Reference To: GDI32.CreateFontIndirectA, Ord:002Ch
                                  |
:0040D8B5 FF15A0204200 Call dword ptr [004220A0]
:0040D8BB A38CDE4300 mov dword ptr [0043DE8C], eax
:0040D8C0 FF75C0 push [ebp-40]

    其中,蓝色字表示找到的第1处,红色字表示找到的第2处,绿色字是用来判断是否找对地方的标志,经过我的分析、测试,发现只要把紫色字表示的地方8D45C450修改成为68909090即可,68表示 Push 压入栈,90表示 Nop 空。虽然,68 和 6A 同时都是 Push 指令(68 压入的是四字节整数,6A 压入的是单字节整数),但这里不能改为6A909090,如果这样改,字体会比“宋体,9号”大一倍,不知道为什么。关于修改方法就是把它调入到uedit中查找C745D4900100008D45C450,找到后修改为C745D49001000068909090(如图七所示),修改完成后运行该汉化版一看效果,成功!已经有一部分难看的字体被修改成“宋体,9号”了,这里我只是修改了在反汇编中找到的符合条件的第2处,至于另一处我想大家也应该知道如何修改了,就是在uedit中查找C745D4BC0200008D45C450,找到后修改为C745D49001000068909090即可。全改完了,运行软件制作一个自解压安装程序测试一下软件的运行状况,一切正常,收工,休息了!修改后该汉化版软件的界面如图八所示

    为了进一步研究、测试该汉化版是否修改的成功,我又把它调入到了exescope中修改以前存在字体设置问题的窗口,并把该窗口中字体及大小改变成“楷体,8号”,结果显示如图九所示,大家可以看到这里字体及大小已经按照我们所需要的设置改变了,这说明CreateFontIndirectA函数已经不能动态的改变我们原先设置的字体及大小了。

    我本想这样修改就完事了,但事实并非如此,经过同行Ronnier兄对我的修改方法的检查,发现我这种修改方法是极其危险的。Ronnier兄说:“飞鹰兄之所以这样修改能成功,也是很侥幸的,这样修改大有可能会造成软件无法运行,大多数情况会非法操作,也有可能不非法操作,但会有潜在问题。”哦!看来我飞鹰的汇编功力还差点,误入了邪道,下面还是请Ronnier兄帮忙拉兄弟一把,让我能“改邪归正”。

第二篇:改邪归正

    首先,飞鹰兄找到的字体调用位置,字体名的位置等等完全正确,但是他对机器码的意义理解完全错了。机器码在执行的时候是非常严格的,比如飞鹰兄说的 68 和 6A 代表 push 指令没错,但单单 68 和 6A 什么也不是。只有 68XXXXXXXX 和 6AXX 才是完整的 push 指令,也就是说,68 必须和之后的四个字节(32 位模式下,16 位则是两个字节)一起,6A 必须和之后的一个字节一起构成 push 指令。当指令译码器读到 68 时,它就自动把其后的四个字节数(32 位模式下,16 位则是两个字节数)做为源操作数压入堆栈了,而不会再去读那四个字节是否是指令。比如:6890505859 执行起来是:
        push 59585090
        而不是这样的:
        push
        nop
        push eax
        pop eax
        pop ecx

    所以,飞鹰兄所改的68909090这样的语句,从本质上说就是错的。但是,飞鹰兄说这样改成功了,为什么会如此呢?要回到这个软件本身来看了。正像他所说的那样,这个对话框本身就有字体定义“宋体,9pt”了,而这里的 CreateFontIndirectA 函数确实是想忽略字体定义而动态的再给它改成别的字体。刚才说过了,68 这个 push 语句是由五个字节组成的。也就是说,68909090 还不是完整的语句,必须跟上后面的 FF 才是完整的 push FF909090 这样一条指令。但是这个 FF 是原来 FF15A0204200 也就是 call GDI32.CreateFontIndirectA 指令的一部分,那么,整个函数调用已经被完全破坏了,而其后的指令也会随之改变。由于不再调用 GDI32.CreateFontIndirectA 函数了,所以此处的字体就保持了原来对话框中定义的“宋体,9pt”。

    为了验证我说的,我们比较一下用 W32Dsm 反汇编这样修改前后的文件,注意用“——————”包括起来的部分:

这是原来的文件:

* Reference To: GDI32.GetDeviceCaps, Ord:00C7h
                                 |
:0040D845 FF159C204200 Call dword ptr [0042209C]
:0040D84B C1E003 shl eax, 03
:0040D84E 99 cdq
:0040D84F 6A48 push 00000048
:0040D851 59 pop ecx
:0040D852 F7F9 idiv ecx
:0040D854 6BC0FF imul eax, FFFFFFFF
:0040D857 8945C4 mov dword ptr [ebp-3C], eax
:0040D85A C745D4BC020000 mov [ebp-2C], 000002BC
——————————————————————————————————
:0040D861 8D45C4 lea eax, dword ptr [ebp-3C]
:0040D864 50 push eax
* Reference To: GDI32.CreateFontIndirectA, Ord:002Ch
                                 |
:0040D865 FF15A0204200 Call dword ptr [004220A0]
——————————————————————————————————
:0040D86B A388DE4300 mov dword ptr [0043DE88], eax
:0040D870 6A3C push 0000003C
:0040D872 6A00 push 00000000
:0040D874 8D45C4 lea eax, dword ptr [ebp-3C]
:0040D877 50 push eax :0040D878 E8E38A0000 call 00416360
:0040D87D 83C40C add esp, 0000000C

这是改过的文件:

* Reference To: GDI32.GetDeviceCaps, Ord:00C7h
                                 |
:0040D845 FF159C204200 Call dword ptr [0042209C]
:0040D84B C1E003 shl eax, 03
:0040D84E 99 cdq
:0040D84F 6A48 push 00000048
:0040D851 59 pop ecx
:0040D852 F7F9 idiv ecx
:0040D854 6BC0FF imul eax, FFFFFFFF
:0040D857 8945C4 mov dword ptr [ebp-3C], eax
:0040D85A C745D4BC020000 mov [ebp-2C], 000002BC
——————————————————————————————————
:0040D861 68909090FF push FF909090
:0040D866 15A0204200 adc eax, 004220A0
——————————————————————————————————
:0040D86B A388DE4300 mov dword ptr [0043DE88], eax
:0040D870 6A3C push 0000003C
:0040D872 6A00 push 00000000
:0040D874 8D45C4 lea eax, dword ptr [ebp-3C]
:0040D877 50 push eax
:0040D878 E8E38A0000 call 00416360
:0040D87D 83C40C add esp, 0000000C

    看到了吧,原来的装入地址、压栈和函数调用指令变成了一个压栈和加法指令。可以说,在这个软件里这么做没有问题那是很侥幸的。这么改是及其危险的,因为我们没法预料随后的多少条指令会受牵连。一般来说这么改后程序是无法执行的,大多数情况会非法操作,也有可能不非法操作,但会有潜在问题。

     那么,这个软件的字体要怎么改才是正确的呢?当然,用梁利锋兄的《野蛮人战记》中我提出来的把整个函数调用脱出来的方法是绝对可以的,因为那是通法。不过对于这个软件不必要这么麻烦,因为它有它自己的特点。有两种简单的方法可以做到:

    第一种,很简单,刚才说过了,鉴于这个软件的情况比较特殊,它原来有字体定义,只是调用了 CreateFontIndirectA 函数想给它改掉,所以,索性把整个 50FF15A0204200 改成 90909090909090,把整个函数调用废了就成。这样修改后该汉化版软件的界面如图八所示。对于别的软件这种方法好不好用就不敢说了,想过去对于原先对话框就有了字体定义的软件也许都可以这么试试。

    第二种应该说是比较“正解”的方法吧,有兴趣的朋友可以看看。因为这个软件装入 LogFont 结构的过程比较清晰,我们完全可以让 CreateFontIndirectA 去创建一个“宋体,9pt”出来。

先看看用 FontKey 监视出的 GDI32.CreateFontIndirectA 调用情况:

CreateFontIndirectA(LPLOGFONT:006BFD0C:FFFFFFF6_00000000_00000000_00000000_000002BC_00_00_00_00_00_00_00_00:"MS Sans Serif")
CreateFontIndirectA returns: A20

CreateFontIndirectA(LPLOGFONT:006BFD0C:FFFFFFF6_00000000_00000000_00000000_00000190_00_00_00_00_00_00_00_00:"MS Sans Serif")
CreateFontIndirectA returns: A40

再来看看文件里这两个 GDI32.CreateFontIndirectA 的调用过程:

* Possible StringData Ref from Data Obj ->"MS Sans Serif"
                                 |
:0040D830 680C6C4200 push 00426C0C
:0040D835 8D45E0 lea eax, dword ptr [ebp-20]
:0040D838 50 push eax
:0040D839 E8828B0000 call 004163C0
:0040D83E 59 pop ecx
:0040D83F 59 pop ecx
:0040D840 6A5A push 0000005A
:0040D842 FF75C0 push [ebp-40]
* Reference To: GDI32.GetDeviceCaps, Ord:00C7h
                                 |
:0040D845 FF159C204200 Call dword ptr [0042209C]
:0040D84B C1E003 shl eax, 03
:0040D84E 99 cdq
:0040D84F 6A48 push 00000048
:0040D851 59 pop ecx
:0040D852 F7F9 idiv ecx
:0040D854 6BC0FF imul eax, FFFFFFFF
:0040D857 8945C4 mov dword ptr [ebp-3C], eax
:0040D85A C745D4BC020000 mov [ebp-2C], 000002BC
:0040D861 8D45C4 lea eax, dword ptr [ebp-3C]
:0040D864 50 push eax
* Reference To: GDI32.CreateFontIndirectA, Ord:002Ch
                                 |
:0040D865 FF15A0204200 Call dword ptr [004220A0]
:0040D86B A388DE4300 mov dword ptr [0043DE88], eax
:0040D870 6A3C push 0000003C
:0040D872 6A00 push 00000000
:0040D874 8D45C4 lea eax, dword ptr [ebp-3C]
:0040D877 50 push eax
:0040D878 E8E38A0000 call 00416360
:0040D87D 83C40C add esp, 0000000C
* Possible StringData Ref from Data Obj ->"MS Sans Serif"
                                 |
:0040D880 681C6C4200 push 00426C1C
:0040D885 8D45E0 lea eax, dword ptr [ebp-20]
:0040D888 50 push eax
:0040D889 E8328B0000 call 004163C0
:0040D88E 59 pop ecx
:0040D88F 59 pop ecx
:0040D890 6A5A push 0000005A
:0040D892 FF75C0 push [ebp-40]
* Reference To: GDI32.GetDeviceCaps, Ord:00C7h
                                 |
:0040D895 FF159C204200 Call dword ptr [0042209C]
:0040D89B C1E003 shl eax, 03
:0040D89E 99 cdq
:0040D89F 6A48 push 00000048
:0040D8A1 59 pop ecx
:0040D8A2 F7F9 idiv ecx
:0040D8A4 6BC0FF imul eax, FFFFFFFF
:0040D8A7 8945C4 mov dword ptr [ebp-3C], eax
* Possible Reference to Dialog: DialogID_0190
                                 |
:0040D8AA C745D490010000 mov [ebp-2C], 00000190
:0040D8B1 8D45C4 lea eax, dword ptr [ebp-3C]
:0040D8B4 50 push eax
* Reference To: GDI32.CreateFontIndirectA, Ord:002Ch
                                 |
:0040D8B5 FF15A0204200 Call dword ptr [004220A0]
:0040D8BB A38CDE4300 mov dword ptr [0043DE8C], eax
:0040D8C0 FF75C0 push [ebp-40]

    很明显,CreateFontIndirectA 的 LogFont 结构两次调用都是存放在 EBP-3C 中的值所指向的内存单元中,也就是 FontKey 监视出来的 006BFD0C 这里。我们仔细的读一下这段程序,可以发现字体名 MS Sans Serif 是由这几条指令写入 LogFont 结构中的(一个 LogFont 结构最低位 4 个字节是字体大小,往高位间隔 28 个字节存放的是字体名称,此例中 3C-20=1C,就是十进制的 28。)

:0040D830 680C6C4200 push 00426C0C
:0040D835 8D45E0 lea eax, dword ptr [ebp-20]
:0040D838 50 push eax
:0040D839 E8828B0000 call 004163C0

    这里,把字体名所在地址,LogFont 结构中的对应地址做为参数传给了一个子程序,让这个子程序去完成装入字体名称的工作。所以,把飞鹰兄找到的两个 MS Sans Serif 改成宋体,字体名称就改过来了。然后,接着往下看,字体大小是由这几句定义的:

:0040D84B C1E003 shl eax, 03
:0040D84E 99 cdq
:0040D84F 6A48 push 00000048
:0040D851 59 pop ecx
:0040D852 F7F9 idiv ecx
:0040D854 6BC0FF imul eax, FFFFFFFF
:0040D857 8945C4 mov dword ptr [ebp-3C], eax

    这里,鬼知道它想算什么,先把 EAX 中的值左移三位,再怎么除以一个 00000048,然后还乘以 FFFFFFFF,不过看起来结果应该就是 FontKey 记录的 FFFFFFF6 了,然后送入到 LogFont 结构的首部。非常高兴哦,这里我们有这么多字节可以动手,于是把 C1E003996A4859F7F96BC0FF8945C4 改为 C645DB86C745C4F4FFFFFF90909090,结果就是这几句变成了:

:0040D84B C645DB86 mov [ebp-25], 86
:0040D84F C745C4F4FFFFFF mov [ebp-3C], FFFFFFF4
:0040D856 90 nop :0040D857 90 nop
:0040D858 90 nop :0040D859 90 nop

    把两个调用 CreateFontIndirectA 的地方都这样改过。好了,存盘一看,没问题了,再用 FontKey 监视一下,结果如下:

CreateFontIndirectA(LPLOGFONT:006BFD0C:FFFFFFF4_00000000_00000000_00000000_000002BC_00_00_00
_86_00_00_00_00:"宋体")
CreateFontIndirectA returns: A5C

CreateFontIndirectA(LPLOGFONT:006BFD0C:FFFFFFF4_00000000_00000000_00000000_00000190_00_00_00
_86_00_00_00_00:"宋体")
CreateFontIndirectA returns: 568

    标准的“宋体,12px”的 LogFont 结构就这样创建成功了,一切 OK。这样修改后该汉化版软件的界面如图十所示

    想必大家从图中可以看出“下一步”按钮的字体被加粗了,而其它按钮的字体却是规则的,那么,上面的反汇编代码中那一段是改变字重的呢?大家只要根据上面监视出来 LogFont 结构就可以知道:0040D85A C745D4BC020000 mov [ebp-2C], 000002BC 与 :0040D8AA C745D490010000 mov [ebp-2C], 00000190 这两段代码就是改变按钮中字体字重的语句。我看了看微软关于这两个字体函数(一个是 CreateFont,一个是 CreateFontIndirect)的说明,其中它这样写到:“weight: This specifies the font's weight as a number between 0 and 900. The value 0 selects the default, 400 is normal, and 700 is bold.”其实这个参数就是字重 0 - 默认,400 - 普通,700 - 粗体。因为,400 的16进制数为 190,700 的16进制数为 2BC,所以,那个 :0040D85A C745D4BC020000 mov [ebp-2C], 000002BC 就显示了粗体字,而:0040D8AA C745D490010000 mov [ebp-2C], 00000190 就显示了规则字。

第三篇:疑问解答

    看完上面的内容之后,可能大家会提出很多问题,下面我们就来解答一些在字体修改中可能会碰到的问题。

    一开始的问题我们相信大家都会说,上面的文章中Ronnier兄提到的通过把整个函数调用脱出来的修改字体设置的方法是怎么一回事?其实,这种修改方法在梁利锋兄写的《野蛮人战记》这篇文章中讲的也比较简单,不容易理解,这里我们就请Ronnier兄把详细的修改方法传授给我们吧!在看下面内容之前,我们建议大家先看一看梁利锋兄写的《野蛮人战记》这篇文章,后再继续往下看。

    Ronnier兄说:其实这种修改方法说到底也很简单,就是自己构建两个东西:

    第一,“宋体,12px”的 LogFont 结构,一个标准的“宋体,12px”LogFont 结构,它的十六进制值是 f4ffffff000000000000000000000000900100000000008600000000cbcecce5 而如果要粗体字就把 9001 改为 BC02,如果是大字体环境把 F4FFFFFF 改为 F1FFFFFF,因为大字体里 9pt=15px。

    第二,一个小函数,这个小函数其实就是把我们构建的那个 LogFont 结构做为参数然后调用 CreateFontIndirectA。其内容就是这样三行:

    Push “LogFont 结构的起始地址”
        Call GDI32.CreateFontIndirectA
    Ret

按照梁利锋的文章里说的,可以把它们写在一起,举个例子说明吧,还是拿上面的那个软件:

* Possible StringData Ref from Data Obj ->"MS Sans Serif"
                                 |
:0040D830 680C6C4200 push 00426C0C
:0040D835 8D45E0 lea eax, dword ptr [ebp-20]
:0040D838 50 push eax
:0040D839 E8828B0000 call 004163C0
:0040D83E 59 pop ecx
:0040D83F 59 pop ecx
:0040D840 6A5A push 0000005A
:0040D842 FF75C0 push [ebp-40]
* Reference To: GDI32.GetDeviceCaps, Ord:00C7h
                                 |
:0040D845 FF159C204200 Call dword ptr [0042209C]
:0040D84B C1E003 shl eax, 03
:0040D84E 99 cdq
:0040D84F 6A48 push 00000048
:0040D851 59 pop ecx
:0040D852 F7F9 idiv ecx
:0040D854 6BC0FF imul eax, FFFFFFFF
:0040D857 8945C4 mov dword ptr [ebp-3C], eax
:0040D85A C745D4BC020000 mov [ebp-2C], 000002BC
:0040D861 8D45C4 lea eax, dword ptr [ebp-3C]
:0040D864 50 push eax
* Reference To: GDI32.CreateFontIndirectA, Ord:002Ch
                                 |
:0040D865 FF15A0204200 Call dword ptr [004220A0]
:0040D86B A388DE4300 mov dword ptr [0043DE88], eax
:0040D870 6A3C push 0000003C
:0040D872 6A00 push 00000000
:0040D874 8D45C4 lea eax, dword ptr [ebp-3C]
:0040D877 50 push eax
:0040D878 E8E38A0000 call 00416360
:0040D87D 83C40C add esp, 0000000C

其中调用 CreateFontIndirectA 函数的就是这两句:

:0040D864 50 push eax
:0040D865 FF15A0204200 Call dword ptr [004220A0]

就改这两句就行了,改为 Call 我们自定义的那个函数的地址,就行了。

    比如已经把 LogFont 结构记录在文件偏移 300 处开始的 32 个字节中,在此之后就应该是我们自己定义的那个函数了,函数这么写:

:00400320 6800034000 push 00400300
:00400325 FF15A0204200 Call dword ptr [004220A0]
:0040032B C3 ret

    注意,因为这个文件里的 GDI32.CreateFontIndirectA 函数入口是在 004220A0 的值所指的地址,所以可以把原来文件里的 Call GDI32.CreateFontIndirectA 语句直接抄过来用,因为它是个直接地址调用,而如果原来的文件里的 Call GDI32.CreateFontIndirectA 语句是形如 Call XXXXXXXX(机器码是 E8XXXXXXXX)这样的间接地址调用,则不能直接抄过来用,而要通过计算相对偏移。计算方法参照下面的内容。

    好了,在 300 开始写了 f4ffffff000000000000000000000000900100000000008600000000cbcecce56800034000FF15A0204200C3 后,就该修改原文件的调用部分了,把 50FF15A0204200 改为 E8B72AFFFF9090,就是改为:

:0040D864 E8B72AFFFF Call 00400320
:0040D869 90 nop
:0040D86A 90 nop

    这里从 0040D864 到 00400320 的相对偏移为 FFFF2AB7 是这样计算的:00400320-0040D864-5=FFFF2AB7,为什么要减 5 是因为 Call 00400320 这个指令占 5 个字节。

    好了,就是这样了,记着从 CreateFontIndirectA 的 push 语句开始改起,才能避免潜在的错误。现在已经修改了存在字体设置问题的一处地方,相信修改另一处对大家来说也已经不是什么难事了吧!修改完后该汉化版软件的界面如图十所示。还有要注意的就是梁利锋兄的《野蛮人战记》中“例外”那一节了。下面这段内容就是摘自《野蛮人战记》中“例外”那一节。

************************************************************************************************

例 外

    不过这样的修改有时候会遇上类似下面的代码:

:00401FC2 56 push esi
* Reference To: GDI32.CreateFontIndirectA, Ord:0053h
                                  |
:00401FC3 8B3D08704000 mov edi, dword ptr [00407008]
:00401FC9 FFD7 call edi

    这样,因为 call 语句呼叫的是上一句 mov 指令放入 edi 的指令,所以这一句 mov 指令也同样是必不可少的,是不能盲目消除的,当然,这样的程序可能有各种变化,我不可能在这里尽列,所以需要修改者自行进行这一方面的的判断。其实这样的问题在 CreateFontA 函数中出现的可能性更大,所以在修改 CreateFontA 函数更要小心。

    另外,需要注意,如下的代码:

* Reference To: GDI32.CreateFontIndirectA, Ord:0053h
                                 |
:00401FC2 8B3D08704000 mov edi, dword ptr [00407008]
:00401FC8 56 push esi :00401FC9 FFD7 call edi

    虽然符合我的这种修改的方法所说的顺序,不过 push 和 call 语句加起来只有 2 个字节,所以还是需要认识到可以把这一句 mov 指令一起移入我们自己的函数才能修改的。

************************************************************************************************

    也许有些朋友会问,如果一开始就找不到软件中“MS Sans Serif”字体存在,哪该怎么办?其实很简单,只要先反汇编需要的软件,后查找是否有“CreateFont*”(*表示A或者IndirectA,也可表示空或其它字符)的字样,如有就往上找几行就能看见这个字体的名称,接下来就可以按照上面讲的方法进行修改了。大家是否又会问,如果在反汇编代码中找到不止一个RAV值或者根本就找不到符合的RAV值,又该怎么办?对于第一种情况(找到不止不一个RAV值),大家只有一处一处的分别修改后看软件的运行结果来判断是否改对地方;对于第二种情况(根本就找不到符合的RAV值),可以减少一个字符后再查找,例如:查找426c1c改成查找426c1,这样改了之后再查找又可能找到很多,如何修改呢?方法同上!

    最后我还想说一点,现在我们见到的 WinZip 系列的软件大多数都采用了一些非unicode编码,所以汉化后有部分汉字会变成乱码。在这次修改过程中,如果大家在exescope中打开沈晔骏兄汉化的 WinZip Self-Extractor 2.2 Build 4003查看时,有些对话框显示出来就全是乱码,不过该汉化版沈晔骏兄已经修改了这个问题,只是大家在exescope看见是乱码,实际运行中是正常的。至于如何修改这种非unicode编码,大家可以参看吕达嵘兄写的《关于 Winzip 8.0 beta 版的汉化》这篇文章,也可以参看香港的黄权燊先生写的《使用 CXA 遇到的問題及特别功能》这篇文章,并用他自己开发的软件CXA来进行汉化工作。

    这几天,沈晔骏兄又来信询问是否可以把该汉化版制作成的自解压程序的安装界面给一起汉化掉。我们研究了一下,用 exescope 打开该汉化版仔细查看里面的资源后,发现内在SFXHEADERW32M、SFXHEADERW32MG、SFXPROHEADERW32M、SFXPROHEADERW32MG等资源存在(如图二所示),我们估计可能需要汉化的内容就放在这些里面,但面对这种我们从来没有汉化过的特殊资源应该如何应付呢?于是,Ronnier兄就提出用 Restorator 这个软件来帮助汉化,Ronnier兄说到:用 Restorator 打开文件,看到的那几个叫做 SFXHEADERW32M、SFXHEADERW32MG、SFXHEADERPROW32M、SFXHEADERPROW32MG 的资源分别对应了普通自解压文件英、德文,安装自解压文件英、德文的文件头,修改方法只要用该软件菜单里的“资源->提取为->提取为...”存成 .EXE 文件就可以改了,改完用“资源->回写到”导回即可;汉化新世纪的伟乾兄也提出:可以用 exescope 把他们导出来存盘,不过一般导出的这些资源都加了壳,汉化前需要先脱壳后汉化,汉化完成后再加壳导回去,但是听说这种方式的资源,导出来和导进去的文件大小要一样,如果不一致,通过把文件后面的00增加或删除达到一致后再导回。但通过测试,我们发现用上面写到的这两种方法分别导出该汉化版软件中的这些资源后,从它们里面都无法查找到需要汉化的内容,而且,还发现虽然用 exescope 这个软件可以导出这种特殊资源,但无法把它们再导回去。所以,没办法!飞鹰又再次与伟乾兄联系,寻求其它汉化方法。果真不出多久,伟乾兄又告诉了我们另一种修改方法,他说:可以检查一下注册表或WIN/目录或SYSTEM目录,看看有没有需要汉化的资源存在,或者可以到它的网站看看,是否自定义有SFX的文件下载。经过前面提出的多种修改方法的尝试,我们始终没能找到需要汉化的内容,因此,在这篇文章结稿之时,还是没能把汉化版软件制作成的自解压程序的安装界面给汉化掉,还望广大网友能赐教。虽然,上面写到的这种特殊资源的汉化方法对于该软件无效,不过,这并不代表对其它软件也无效,如果大家在汉化过程中遇到此类问题,大可以用上面说到的这些修改方法一试,难说就成功了也说不定!关于 Restorator 这个软件的使用方法,大家可以参看伟乾兄写的《关于Restorator的快速上手》这篇文章;exescope 这个软件我们相信大家已经用的很熟了,但也可以看看brucez兄写的《ExeScope 4.2Xup 使用中的小秘诀》这篇文章,增点一点功力。

    现在才发现写篇文章真是累,花了我们很多很多的时间才完成。但想想汉化新世纪的伟乾兄、梁利锋兄及其它汉化DIY是那样的无私、伟大,为我们写出了许多技术含量高的汉化教程,培养出了一批又一批的汉化人,我们这点累又算得上什么呢,只要大家快乐就行。

    这篇文章中使用到的全部工具都可以在汉化新世纪网站上找到。由于诸多原因,文章中难免存在一些不足,殷切希望广大汉化爱好者批评指正。
 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值