汉化程序的时候,如果修改字体资源?
声明
个人可以自由转载本文,不过应保持原文的完整性,并通知我;商业转载先请和我联系。
本文没有任何明确或不明确地提示说本文完全正确,阅读和使用本文的内容是您自己的选择,本人不负任何责任。
如果您发现本文有错漏的地方,请您给我指出;如果有什么不理解的,请您给我提出。
意见、建议和提出的问题最好写在我的主页 http://llf.126.com 的留言版上。
前言
汉化人大宇在汉化 CTris2000 这个 Delphi 程序的时候,修改了所有 RCData 中的字体设置,但仍然有一个窗体的字体很难看,我帮他做了修改,在这里就以它为例说一下 Delphi 程序字体字号的修改。
本来,我以为要详细的说才能说清,不过现在看来,也未必,所以在这里还是简单的说一下算了。
Delphi 程序的字体
关于 Delphi 程序的字体,以我想大概有三种情况:
一、RCData 格式。这种格式的字体字号的问题有很多工具,诸如 eXeScope 、ResHacker 等工具都可以非常方便的修改,而且这也是 Delphi 程序中的字体设置使用最多的方法,而且大多数 Delphi 程序都只使用这一种方式,而其修改方法相对成熟的多,所以我不讨论这种方式。
二、SDK 格式。有些 Delphi 程序为了追求最小的独立可执行文件尺寸,不使用 Delphi 缺省的 VCL 运行库,而是自行使用 SDK 的编程方式,不过这种程序不多,如果有,个头也很小,一般是几K、十几K的居多,如果在这些程序里出现字体不协调的问题,可以使用和修改 C 字体字号相同的方法修改。
三、VCL 内部格式。VCL 是 Delphi 使用的函数库,其内部很可能使用了不是我们所希望的"宋体,9"的窗体,不过我想来想去,也只有类似 VB 函数的 InputBox 有这种可能,也就是 CTris2000 中要求输入姓名的那个窗体,故我们要谈的就是这种方式的程序的字体的修改,而且,几乎可以确定的固定在 InputBox 上,当然,即使是这一种情况,也比较类似 C 字体的修改,而不是 VB 。(我不知道 Delphi 中相同功能的函数的函数名,所以暂称其为 InputBox)。
修改过程
我修改的过程比较繁琐,所以在这里剔除复杂的部分,简单的说。
首先,使用 W32dasm 反汇编汉化后的 CTris2000.exe ,存盘,打开 CTris2000.alf 文件,查找 CreateFont ,发现只有 CreateFontIndirectA ,而没有 CreateFontA ,这真是一个烦人的开始。继续查找,发现 CreateFontIndirectA 被三个地方调用,地址分别是 4124EC 、420460 和 42A7F3 。
运行 Trw2000 ,调入 CTris2000.exe ,然后键入"bpx 4124EC"、"bpx 420460" 和 "bpx 42A7F3"设置断点,按"F5"运行,中断时键入"dd *esp"查看堆栈的栈顶指针所指的地址的内容,发现大多数情况此地址的开头都是"FFFFFFF4",也就是"-12",是正常的,不过有一次,它是"FFFFFFF5",也就是"-11",在它下面一点,我们见到了这种字体的名称"MS Sans Serif"。
用 ResHacker 检查 CTris2000.exe ,发现并不是所有的"MS Sans Serif,8"都被改成了"宋体,9",所以首先把这些没有改成"宋体,9"的项都改成"宋体,9",存盘。
用 UEdit 打开 CTris2000.exe ,查找"MS Sans Serif",只有一个,在 0x5b0b8 处,为了验证,把这个"MS Sans Serif"改成"System",运行程序,发现那个输入窗体的字体确实如我们所想的变成"宋体,12"了。
0x5b0b8 在数据段,所以,数据基偏移 = 基地址 + 数据RVA - 数据Offset = 400000h + 5c000h - 5ae00 = 401200h ,所以,0x5b0b8 的在代码中为 401200h + 5b0b8 = 45C2B8h 。在 CTris2000.alf 查找"0045C2B8",没找到,那么,查找"0045C2B"吧,找到几处,如"0045C2B7"、"0045C2B6"和"0045C2B0",很是奇怪。
运行 Trw2000 ,调入 CTris2000.exe ,像上面一样设断点,运行到有问题的 CreateFontA 的时候,键入"dd 0045C2B0",看看是什么?原来是"FFFFFFF5"!好吧,把"0045C2B0"当作突破口。
打开 CTris2000.alf ,查找"0045C2B0",发现两个地方,代码如下:
* Referenced by a CALL at Address:
|:0041979B
|
:00419604 53 push ebx
:00419605 56 push esi
:00419606 57 push edi
:00419607 6A48 push 00000048
:00419609 A1D0E54500 mov eax, dword ptr [0045E5D0]
:0041960E 50 push eax
:0041960F 6A08 push 00000008
* Reference To: kernel32.MulDiv, Ord:0000h
|
:00419611 E836BEFEFF Call 0040544C
:00419616 F7D8 neg eax
:00419618 A3B0C24500 mov dword ptr [0045C2B0], eax
:0041961D A13CD64500 mov eax, dword ptr [0045D63C]
:00419622 80780800 cmp byte ptr [eax+08], 00
:00419626 743A je 00419662
:00419628 E893FFFFFF call 004195C0
:0041962D 8BD8 mov ebx, eax
:0041962F 8BC3 mov eax, ebx
:00419631 2C80 sub al, 80
:00419633 752D jne 00419662
:00419635 BE68964100 mov esi, 00419668
:0041963A BFB7C24500 mov edi, 0045C2B7
:0041963F B904000000 mov ecx, 00000004
:00419644 F3 repz
:00419645 A5 movsd
:00419646 6A48 push 00000048
:00419648 A1D0E54500 mov eax, dword ptr [0045E5D0]
:0041964D 50 push eax
:0041964E 6A09 push 00000009
* Reference To: kernel32.MulDiv, Ord:0000h
|
:00419650 E8F7BDFEFF Call 0040544C
:00419655 F7D8 neg eax
:00419657 A3B0C24500 mov dword ptr [0045C2B0], eax
:0041965C 881DB6C24500 mov byte ptr [0045C2B6], bl
* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:00419626(C), :00419633(C)
|
:00419662 5F pop edi
:00419663 5E pop esi
:00419664 5B pop ebx
:00419665 C3 ret
不知道大家是否能大概看懂上面的代码,不过它和我在《C 程序字号的修改》里摘抄的 MSDN 里建议的设置字号的方式是基本一样的:
nHeight = -MulDiv(PointSize, GetDeviceCaps(hDC, LOGPIXELSY), 72);
我们可以看到,"0041960F"处的"6A08"压入的就是磅值,我们需要的是 9 磅的字体,所以把"6A08"改成"6A09"就可以了。代码基偏移 = 基地址 + 代码RVA - 代码Offset = 400000h + 1000h - 400h = 400c00h ,41960fh - 400c00h = 18a0fh ,也就是说"6a08"在 0x18a0f 处。
所以,最后的修改是这样的:把 0x5b0b8 处的 "MS Sans Serif" 改成 "宋体";把 0x18a0f 处的 6a08 改成 6a09 。
虽然我已经简化了过程,不过好像还是很麻烦,那么有没有什么简单的方法呢?有的。
一、如果实在不会这样的修改方法,也不要留着,把"MS Sans Serif"改成"System"是一种简单有效的方法,遗憾的是字体显得比较大。
二、先把"MS Sans Serif"改成"宋体",然后用 W32dasm 反编译汉化后的程序并存盘后,用 EmEditor 打开 *.alf 文件,按"Ctrl+F",出现查找对话框,选择"使用表达式",在要查找的文本中输入"push 00000008\n\n* Reference To: kernel32.MulDiv,",找到的不会很多,把"08"修改成"09"试一试,直到得到正确的结果。
需要注意,第二种方法只适用于 Delphi 程序的 InputBox 的字体修改,而第一种方法适用于所有程序。另外,我因为没有见到其它类似的 Delphi 程序,也不能肯定第二种方法就是对的,如果各位有见到,不妨一试。
Delphi 字号修改之二
声明
个人可以自由转载本文,不过应保持原文的完整性,并通知我;商业转载先请和我联系。
本文没有任何明确或不明确地提示说本文完全正确,阅读和使用本文的内容是您自己的选择,本人不负任何责任。
如果您发现本文有错漏的地方,请您给我指出;如果有什么不理解的,请您给我提出。
意见、建议和提出的问题最好写在我的主页 http://llf.126.com 的留言版上。
Hint
在上次的 Delphi 消息框字体问题之后,又有汉化人提出 Hint 字体修正的问题,在说这一个问题之前,我们要再熟悉一下微软推荐的字体设置方法:
nHeight = -MulDiv(PointSize, GetDeviceCaps(hDC, LOGPIXELSY), 72);
大家也看到了,最后一个参数是 72 ,也就是十六进制的 0x48 ,而且它是一个常数,所以这样的语句编译到可执行文件中的时候就会出现一个明显的 push 00000048 的语句,而且,因为 72 是最后一个参数,所以这个 push 语句是这一行程序的第一个 push 语句,这样,上次我们介绍的修改消息框的方法就可以用 push 00000048 作为辅助判断方法,据大宇说,用这种方法判断,迄今为止是百发百中的。 :)
而这一次的 Hint 的问题,因为在反编译出来的文件中找不到"push 00000008\n\n* Reference To: kernel32.MulDiv,"或上面几行没有"push 00000048",所以无法找到需要修改的地方,我通过查找代码发现,对于 Hint 的字体设置,其中并不是直接压入 8 ,也就是说没有明显的 push 00000008 ,但是仍然是可以修改的。
以前 ChinEase 曾问过 iProtect SoHo 的字体问题,当时我并不熟悉,以为主界面是使用了枚举的方式设置的字体,觉得不能解决,不过现在才发现其实其主界面字体是可以自己设置的,所以所剩的字体问题仍然是 Hint 的问题,在这里就以 iProtect SoHo 为例说一下。
用 W32dasm 反编译 iprotect.exe ,生成 iprotect.alf ,打开它,查找 push 00000048 ,发现四个,其中后两个一个因为在 call MulDiv 之上的语句是 push 00000009 ,所以已经是 9 磅的字体,另一个后面根本没有 call MulDiv ,所以更不是我们所要找的语句了,剩下的两个如下:
* Referenced by a CALL at Addresses:
|:00412279 , :00416EAA , :0041D93C , :00425408 , :004BAFCA
|:004BB710
|
:0041DB38 53 push ebx
:0041DB39 8BD8 mov ebx, eax
:0041DB3B 8B4314 mov eax, dword ptr [ebx+14]
:0041DB3E 50 push eax
:0041DB3F 6A48 push 00000048
:0041DB41 8BC3 mov eax, ebx
:0041DB43 E864FFFFFF call 0041DAAC
:0041DB48 50 push eax
* Reference To: kernel32.MulDiv, Ord:0000h
|
:0041DB49 E8A67BFEFF Call 004056F4
:0041DB4E F7D8 neg eax
:0041DB50 5B pop ebx
:0041DB51 C3 ret
:0041DB52 8BC0 mov eax, eax
* Referenced by a CALL at Addresses:
|:00412298 , :00416EB9 , :0041AE0F , :0041D945 , :00425417
|:0043CC63 , :0043D62A , :00471F31 , :004914F4 , :0049CB09
|:004B6212 , :004BA4DA , :004BA51E , :004BB720 , :004D4955
|
:0041DB54 53 push ebx
:0041DB55 56 push esi
:0041DB56 8BF2 mov esi, edx
:0041DB58 8BD8 mov ebx, eax
:0041DB5A 6A48 push 00000048
:0041DB5C 8B4314 mov eax, dword ptr [ebx+14]
:0041DB5F 50 push eax
:0041DB60 56 push esi
* Reference To: kernel32.MulDiv, Ord:0000h
|
:0041DB61 E88E7BFEFF Call 004056F4
:0041DB66 8BD0 mov edx, eax
:0041DB68 F7DA neg edx
:0041DB6A 8BC3 mov eax, ebx
:0041DB6C E843FFFFFF call 0041DAB4
:0041DB71 5E pop esi
:0041DB72 5B pop ebx
:0041DB73 C3 ret
可以看到,在 call MulDiv 之上并不是压入的常数,而是寄存器值,而这两段代码其实是函数,所以它是通过别人调用来设置的,这需要跟踪才能知道究竟是谁在调用,我最近发现其实 W32dasm 也可以进行动态跟踪,因为不会中止其它进程,所以我现在比较喜欢用 W32dasm 来跟踪 -- 可以同时听 mp3 。 :) 不过 W32dasm 的跟踪界面有些零乱,我就不介绍了。在这里,跟踪到调用者:
:0041ADFA BA38AE4100 mov edx, 0041AE38
:0041ADFF 8B460C mov eax, dword ptr [esi+0C]
:0041AE02 E8ED2C0000 call 0041DAF4
:0041AE07 BA08000000 mov edx, 00000008
:0041AE0C 8B460C mov eax, dword ptr [esi+0C]
:0041AE0F E8402D0000 call 0041DB54
就是最后一个 call 0041DB54 调用了上面的设置字体的代码,我们发现很明显的上面有一个 mov edx, 00000008 ,我们尝试把它修改成 mov edx, 0000000F ,运行程序,发现 Hint 的字体真的变得很大了,也就是说,改对地方了。
另外,第一句的 mov edx, 0041AE38 其实压入的是字体名,所以最后的修改是:把 0x1a239 处的"MS Sans Serif"改成"宋体",把 0x1a208 处的 08 改成 09 。(0x1a238 处是字体名的长度计数器,最好也修改成 04)
简单方法
上面的介绍只是为了让大家知道为什么要这样修改,其实是有一种简单的方法找到要修改的地方的。我说过,Delphi 程序员一般不会自己创建字体,所以这里的仍然是 VCL 的问题,而 VCL 编译出来的代码不会因为不同的程序而不同,所以仍然有规律可循。
不知道大家注意到没有,上面修改的地方相差不大,一个 0x1a239 ,一个 0x1a208 ,只相差 0x31 ,对于 UEdit 来说,就是三行多一个字符,其实这就是它的规律,我们可以先找到究竟是哪一个字体名的问题,再向上三行,找到一个"BA 08",把它改成"BA 09"就可以了,我修改的几个程序用这种方法的成功率目前也是百分之百的。:)
至于怎么找到是哪一个字体名的问题,我就不说了 -- 顶多一个一个的改成"System"进行测试,一定可以找到的。 :)