在汉化以及开发国际化软件的过程中很难避免各种字符编码转换的问题。相信各位都在Windows98上玩过以前的轩辕剑、仙剑等台湾出品的游戏,而且对于那不用南极星打开游戏就会乱码的印象一定非常深刻。
在计算机诞生的时候,由于没考虑到计算机会被这么广泛地使用,于是就采用只能表示127个字符的ASCII码。而到了后来,各个国家就相继开发出各自的编码标准。而对于咱们中国人来说,最熟悉的莫过于是GBK编码以及BIG-5编码了,当然还有日文的Shift-JIS编码。这些编码方式都称为双字节字符编码(double-byte character set)。
这篇东西不打算细述各种编码的历史跟编码方法,对此有兴趣的可以自行上网搜索。我在这里就简单说说产生乱码的原因。
拿Shift-JIS编码来说,对于以932代码页为默认代码页的系统来说,当我输入了“あのそら”这句句子保存后,用WinHex等东西打开可以看到各个字符的编码分别是82 A0 82 CC 82 BB 82 E7,这些字符就是Shift-JIS编码,在日文的系统中能识别出正确的字符,但到了中文的系统就将其当成是GBK编码的东西来解析,于是就变成了“偁偺偦傜”这样的东西了。
为了解决这个问题,微软提供了一个叫APP的东西,用来转换区域让程序不会显示乱码。而这个东西的原理十分简单,就是将__COMPAT_LAYER 和 ApplocaleID这两个环境变量改成目标区域的代码再运行程序。
而另一个著名的转区软件NTLEA则是采用了HOOK相关字符串处理的函数来实现区域转换(实际上这个软件的早期版本是开源的,但现在在网上都找不到源代码,只好拿IDA来逆向那个关键的DLL了)。NTLEA几乎挂钩了所有跟字符串有关的API,包括CreateWindowExA等创建窗口的函数甚至是DialogBoxA都不放过,这些挂钩的函数大多数都是将传入的字符串用MultiByteToWideChar强行按照设定的代码页转换成宽字符再调用对样函数的宽字符版本。另外所有跟区域代码页等有关的函数也全部挂钩,更改其返回值。这种很黄很暴力的方法可是比APP有效得多。但这有一个缺点,当用NTLEA加载一些加了壳的游戏的时候,会被当成是用调试器加载直接退出。
上面的解决方法都是治标不治本,最根本的方法还是利用Unicode编码来开发程序。(但对于我这种懒人来说在每个字符串前面加个“L”可是一件很大工作量的事情……)
有了上面的东西现在就来说一下游戏汉化中遇到的字符边界检查的问题。
现在很多GALGAME输出文字都是使用GetGlyphOutline函数来实现的。这个函数每次传入一个字符的参数,将其转换成矩阵,再利用BitBlt等函数来绘图输出文字。然而,每一种编码方式都是有编码范围的,例如日语的编码范围就是从0x80~0xA0以及0xE0~0xFC(双字中的低字节)。为了避免输入无效的字符造成程序出错,一般在调用GetGlyphOutline函数前都会有如下一段检查的代码:
[quote]cmp al, 80
jbe short 0040676F
cmp al, 0A0
jb short 00406777
cmp al, 0E0
jb short 004067D0
cmp al, 0FC
ja short 004067D0[/quote]
当传入的参数是0xA0~0xE0之间的值的时候就代表这是无效的编码,函数直接跳走处理。
然而,对于中文来说,GBK编码的范围比日文大得多,假如汉化的时候不修改上述的判断的时候会让程序误以为传入的是无效的字符而将原来的字符编码打乱,造成乱码。解决这个方法只需将判断边界的代码中0xA0以及0xFC改成GBK编码的上界0xFE即可。
在计算机诞生的时候,由于没考虑到计算机会被这么广泛地使用,于是就采用只能表示127个字符的ASCII码。而到了后来,各个国家就相继开发出各自的编码标准。而对于咱们中国人来说,最熟悉的莫过于是GBK编码以及BIG-5编码了,当然还有日文的Shift-JIS编码。这些编码方式都称为双字节字符编码(double-byte character set)。
这篇东西不打算细述各种编码的历史跟编码方法,对此有兴趣的可以自行上网搜索。我在这里就简单说说产生乱码的原因。
拿Shift-JIS编码来说,对于以932代码页为默认代码页的系统来说,当我输入了“あのそら”这句句子保存后,用WinHex等东西打开可以看到各个字符的编码分别是82 A0 82 CC 82 BB 82 E7,这些字符就是Shift-JIS编码,在日文的系统中能识别出正确的字符,但到了中文的系统就将其当成是GBK编码的东西来解析,于是就变成了“偁偺偦傜”这样的东西了。
为了解决这个问题,微软提供了一个叫APP的东西,用来转换区域让程序不会显示乱码。而这个东西的原理十分简单,就是将__COMPAT_LAYER 和 ApplocaleID这两个环境变量改成目标区域的代码再运行程序。
而另一个著名的转区软件NTLEA则是采用了HOOK相关字符串处理的函数来实现区域转换(实际上这个软件的早期版本是开源的,但现在在网上都找不到源代码,只好拿IDA来逆向那个关键的DLL了)。NTLEA几乎挂钩了所有跟字符串有关的API,包括CreateWindowExA等创建窗口的函数甚至是DialogBoxA都不放过,这些挂钩的函数大多数都是将传入的字符串用MultiByteToWideChar强行按照设定的代码页转换成宽字符再调用对样函数的宽字符版本。另外所有跟区域代码页等有关的函数也全部挂钩,更改其返回值。这种很黄很暴力的方法可是比APP有效得多。但这有一个缺点,当用NTLEA加载一些加了壳的游戏的时候,会被当成是用调试器加载直接退出。
上面的解决方法都是治标不治本,最根本的方法还是利用Unicode编码来开发程序。(但对于我这种懒人来说在每个字符串前面加个“L”可是一件很大工作量的事情……)
有了上面的东西现在就来说一下游戏汉化中遇到的字符边界检查的问题。
现在很多GALGAME输出文字都是使用GetGlyphOutline函数来实现的。这个函数每次传入一个字符的参数,将其转换成矩阵,再利用BitBlt等函数来绘图输出文字。然而,每一种编码方式都是有编码范围的,例如日语的编码范围就是从0x80~0xA0以及0xE0~0xFC(双字中的低字节)。为了避免输入无效的字符造成程序出错,一般在调用GetGlyphOutline函数前都会有如下一段检查的代码:
[quote]cmp al, 80
jbe short 0040676F
cmp al, 0A0
jb short 00406777
cmp al, 0E0
jb short 004067D0
cmp al, 0FC
ja short 004067D0[/quote]
当传入的参数是0xA0~0xE0之间的值的时候就代表这是无效的编码,函数直接跳走处理。
然而,对于中文来说,GBK编码的范围比日文大得多,假如汉化的时候不修改上述的判断的时候会让程序误以为传入的是无效的字符而将原来的字符编码打乱,造成乱码。解决这个方法只需将判断边界的代码中0xA0以及0xFC改成GBK编码的上界0xFE即可。