Noise Ninja 汉化遇到乱码问题。此软件由 QT 开发,以下修改方法可能通用。
首先要做的事是找到导致乱码的原因,缩小范围,对症下药。
试过修改字体的语系以及要翻译字符串的编码方式等等常见的解决乱码方法,都没有效果。
那么只好对字符串的处理过程进行跟踪,看看是在哪个环节上出了问题。
由于其需翻译内容全是非标,那么就以程序显示界面上的 &File 这个字符串进行跟踪。
用 Ollydbg 载入程序后先查找到 &File 下断,记下字符串所在地址 006852D8。
[attachmentid=26146]
然后用步过 F8 和步入 F7 相互配合进行跟踪。这个过程需要极大的耐心。操作过程中注意寄存器的变化,
特别是出现 ASCII "&File" 的时候。原则是先用 F8 步过大体看看,再用 F7 步入进行跟踪。在可疑的地
方下断以便跟踪。在经过 004D74A8 CALL NoiseNin.004D58E0 后我们发现,寄存器中的 ASCII "&File"
消失了,这个地址很可疑,进入看看。
[attachmentid=26147]
小心按 F8 步过,发现一个循环对字符串进行了处理,看来十有八九找到地方了。
[attachmentid=26148]
既然我们怀疑这是处理字符串的地方,那么肯定不单单处理 &File 这一个。那么就在这个调用的末尾下断,
然后 F9 执行看看寄存器有没有什么变化。
[attachmentid=26149]
如上图中所示,Unicode ,我们的猜想基本已经被证实,而且就是那个循环进行的处理,那么问题就出在这个循环上。
为了最终证实,对字符串的处理结果手动修改一下看看运行结果。EAX 中的 Unicode 字符串是由 EBX 传过来的,
那么就在地址 004D593F 8BC3 MOV EAX,EBX 下断。重新加载程序,临时禁止 004D593F 断点,启用 00492075
断点。
[attachmentid=26150]
按 F9 执行,再启用 004D593F 断点,按 F9 执行。程序被断下来了,在 EBX 上鼠标右击,查看地址的数据。
[attachmentid=26151]
数据中显示的是 Unicode ,但由于英文 UTF-8 和 Unicode 是一样的,在这里我们还需要进行排除。
&File Unicode 字节长度是 10 ,那么需要 5 个中文字。就用“文文文文文”来试试。用编码查询工具查出
“文文文文文”Unicode 编码是 87658765876587658765 ,选择 &File 并编辑二进制。
[attachmentid=26152]
[attachmentid=26153]
[attachmentid=26154]
临时禁用所有断点,按 F9 执行。
[attachmentid=26155]
很幸运,就是 Unicode 。但是为什么在程序中直接修改 &File 运行则显示乱码呢?
[attachmentid=26156]
[attachmentid=26157]
这一次再来跟踪一遍 &File 已经汉化为 文件(&F) 的程序。先运行到 00492075 ,再开启 004D593F 断点并执行到
那里。
[attachmentid=26158]
查看 EBX 的数据,看到了吧,问题很明显,Noise Ninja 在这里转码出现了错误。本应该转成
CEC4 BCFE 2800 2600 4600 2900 而程序却转成 CE00 C400 BC00 FE00 2800 2600 4600 2900 ,也就是双字节字符
转错了。那个转码循环只是在 ASCI 码字符串的每个字节后面插入 00 来转的,英文字母这样转不会出错,但中文
就完全不对了。
问题找到了,那就来着手解决,要实现正确的转码,需要用到 MultiByteToWideChar 这个函数,先说说此函数各个
参数的含义。
[attachmentid=26159]
第一个参数 CodePage :代码页,简体中文取 3A8 ,也就是 936 。
第二个参数 Options :选项,一般取 0 值。
第三个参数 StringToMap :ANSI 字符串的地址。
第四个参数 StringSize :ANSI 字符串的长度,如果用 -1, 就表示是用 0 作为结束符的字符串。
第五个参数 WideCharBuf :转码后的字符串保存地址。取字符串长度时为 Null 。
第六个个参数 WideBufSize :注意是字符串的长度,也就是一个中文字是 1 ,而不是字符串的字节长度。用于
取得字符串长度时为零。在这里我曾经走过弯路,刚开始用的是软件本身提供的长度,后来才发现,软件提供的
长度是字节长度,而不是字符长度,导致显示的中文后面有乱码。
在修改代码过程中,共用了两次 MultiByteToWideChar ,第一次用它来获得字符串的字符长度,第二次则是转码。
由于是在程序代码间隙里找的空间,一个间隙往往不能一次放下所有代码,所以我的代码看上去有好多跳转,但那
并不是必须的。因此下面我只写出必要的代码,你可以根据实际情况灵活运用。
下图是修改前的代码:
[attachmentid=26160]
下图是修改后的两次调用 MultiByteToWideChar 代码,之所以用 Call 是为了省却维护堆栈平衡的麻烦。
[attachmentid=26161]
使用第一次 MultiByteToWideChar 获得字符串字符长度,其长度值保存到 EAX 中,其中寄存器要根据实际情况
来设定,比如说我这里字符串地址是在 EDI ,而你的程序可能是在 EBX :
PUSH 0
PUSH 0
PUSH -1
PUSH EDI
PUSH 0
PUSH 3A8
CALL MultiByteToWideChar
RETN
第二次使用 MultiByteToWideChar 进行转码,寄存器的使用也是根据实际情况而定,转换后的字符串保存在 EBX:
PUSH ESI
PUSH EBX
PUSH -1
PUSH EDI
PUSH 0
PUSH 3A8
CALL MultiByteToWideChar
RETN
再来看看最后一张图,两个 Call 之间怎么有一个 DEC EAX ,这是因为获得的字符串长度比实际长度长 1 ,所以
这里减 1 。
看看实际运行效果。
[attachmentid=26162]
文章中错误之处还请指正。