前言

在数个月前我听到了这游戏的OP,感觉挺好听的。
然后找了一下这个游戏,发现并没有汉化版,甚至连开坑的消息都没有~~,个人开坑的不算,谁知道整到什么时候~~。

于是乎,我花了2天时间来进行程序的分析及修改。(初版完成)

后来又花了4天时间,对乱码文本进行修复。(解析完善)

最后,加上写这篇文章的时间,便是一周了。

操作环境

系统:macOS+Windows

软件:
AnimED,
010editor,
LordPE,
Python3.6,
ollydbg,
易语言/C/C++/...(能编译windows动态链接库就行)

软件请自行下载/安装,纯windows亦可操作。

资源汉化

资源汉化过程:

  • 使用AnimED对游戏目录下arc文件解包。

  • 使用AnimED对解包后内容进行解析。(崩溃了)

  • 使用Python3对解包后内容进行解析。

  • 对资源进行汉化处理。

  • 使用AnimED对解包后内容重新打包。(也崩溃了)

  • 使用Python3对解包后内容重新打包。

BGI2_Archive解包

这是汉化文本的第一步,过程如图。

BGI2_Arc解包

解压出来的都是解密后的文件。

对此我曾尝试用Python来编写解包工具,arc文件解析出来了,能正常解包。

但是里面的文件都被DSC算法(可能是buriko的独家算法)加密了,由于我能力不足,对此没辙了。

虽然AnimED是开源的(delphi写的,pascal语言),我去看了下dsc算法的方法,试着用python抄了下来,可是解析失败,细节太多了,于是就放弃了。

BGI2_Script解析

这就是汉化文本的关键了。游戏的所有文本都存放在这个脚本里面。

看到AnimED里有个Script Tool,我天真的以为这玩意能解析脚本,结果一点,然后就没有然后了,TM的单纯是个按钮。

BGI2_Script结构

使用010editor进行分析。

BGI2_Script格式

Freemind文件下载

BGI2_Script指令

还是使用010editor进行分析。

经过一番痛苦的猜测,测试,已知指令如下:

  • keycommand id
  • value['param']参数数量
{
    # body
    0: {'param': 1},  # 00 00 00 00
    1: {'param': 1},  # 01 00 00 00
    2: {'param': 0},  # 02 00 00 00
    3: {'param': 1},  # 03 00 00 00, text
    4: {'param': 0},  # 04 00 00 00
    9: {'param': 0},  # 09 00 00 00
    16: {'param': 0},  # 10 00 00 00
    17: {'param': 0},  # 11 00 00 00
    24: {'param': 0},  # 18 00 00 00
    25: {'param': 1},  # 19 00 00 00
    27: {'param': 1},  # 1b 00 00 00, end
    28: {'param': 0},  # 1c 00 00 00
    32: {'param': 0},  # 20 00 00 00
    33: {'param': 0},  # 21 00 00 00
    48: {'param': 0},  # 30 00 00 00
    56: {'param': 0},  # 38 00 00 00
    63: {'param': 2},  # 3f 00 00 00
    127: {'param': 2},  # 7f 00 00 00
    320: {'param': 0},  # 40 01 00 00
    # foot
    249: {'param': 0},  # f9 00 00 00
    244: {'param': 0},  # f4 00 00 00
}

BGI2_Script数据

BGI2_Script主要包含指令数据以及文本数据(包含BSB)。

指令数据

首先按结构图的结构从开始读到Package List,读完List,取得当前位置为code_pos

读取Command Code - head,参数一为未知,参数二为code_foot_position

然后按照前面的指令字典,从当前位置一直读到code_foot_positioncode_body部分就解析完成了。

code_body部分解析完成时,当前stream的位置必须与code_foot_position相等,否则这个文件也许不是剧本的脚本。

然后开始读code_foot部分,方法和读code_body一样。

顺便读取BSB(~~其实这个没用,~~重新封包加上)。

文本数据

然后根据之前读取到的command,筛选出command id为3的command(使用文本的命令)。

再在筛选出的内容里筛选出翻译的内容。

这buriko是真的操蛋,文件路径都在里面,否则就不用筛选了。

  • 角色谈话文本的下一位Command id一般为320(40 01 00 00)

  • 角色名称文本的Command在角色谈话文本之前。

  • 角色线路选项文本的下一位Command id一般为9(09 00 00 00),下两位为2(02 00 00 00)

  • 错误文本在code_foot里,下一位Command id一般为249(f9 00 00 00),下两位为244(f4 00 00 00)

符合以上条件的文本都需要翻译(translate)。

我筛选的方法如下:

for i in range(len(code_list)):
    code = code_list[i]
    if code['cmd_id'] == 3:
        p0 = code['param'][0]

        next_cmd_id = code_list[i + 1]['cmd_id']
        next_2_cmd_id = code_list[i + 2]['cmd_id']

        character_text = next_cmd_id == 320
        character_name = next_cmd_id == 3 and next_2_cmd_id == 320
        character_select = next_cmd_id == 9 and next_2_cmd_id == 2
        error_message = next_cmd_id == 249 and next_2_cmd_id == 244
        trans = character_text or character_name or character_select or error_message

BGI2_Archive封包

还是还是使用010editor进行分析。

Archive的结构是十分的简单,如下图所示。

BGI2_Script格式

Freemind文件下载

由于过于简单,不做解释。

程序修改

绕过检测

由于原程序是japan only的,所以实际上我们并不能运行。

方法一

使用ollydbg对原程序进行修改。

按下ctrl+n,在弹出的api列表中寻找GetSystemDefaultLangID,然后按Enter查找调用位置。

OD修改语言区域_1

在简体中文环境下,GetSystemDefaultLangID 返回值是 0x804,而在日语环境下,返回值是 0x411
按空格对代码进行修改,直接返回0x411

修改前:

OD修改语言区域_2

修改后:

OD修改语言区域_3

右键模块窗口,对修改进行保存:

OD修改语言区域_4

方法二

使用apihook对程序进行注入。

用易语言编写一个动态链接库,在启动时把KERNEL32GetSystemDefaultLangID给hook掉,修改返回值为1041(0x411)

当然,也可以用C/C++来写这个动态链接库~~,只是我不会而已~~。

然后使用LordPE进行静态注入,即把该dll添加到程序的入口(随便做个方法就好,比如我这是test),这样程序运行时便会自动被dll把api hook掉。

LordPE注入

修改文本显示

虽然前面的资源汉化了,但是程序本身是日文环境的,读取汉化资源就会导致乱码,所以就要做以下的修改。

修改编码

由于我并不熟悉OD,所以只能以hook的方式来修改。

和之前的hook同理,这次hook的方法是以下两个:

  • GDI32CreateFontA,把fdwCharSet参数修改为134(0x86),同时可以在此处hook字体的参数,做个配置项,就可以随时更换字体了。
  • KERNEL32MultiByteToWideChar,把CharSet参数修改为936(0x3A8)

以上方法任一没有hook,都会导致乱码。

修改字符边界

编码修改了,但是程序还是乱码,这是由于字符边界的关系,把某些字符过滤掉了。

  • gbk字符边界: 0x80-0xFE
  • shift_jis字符边界:0x80-0xA0

这里需要使用OD对程序进行修改。

按下ctrl+f进行搜索,内容为cmp al,0xA0

然后把所有见到的cmp al,0xA0都修改成cmp al,0xFE

这样子程序就能正常加载gbk编码的资源了。

退出崩溃

由于进行了hook,程序退出的时候,hook找不到原来的地址发生了崩溃。

由于能力不足,没法从源头解决。所以我选择把这个问题堵掉。

还是hook,这次把USER32DestroyWindow hook掉,当destroy的句柄为游戏的窗口时,强制把自身的进程杀掉,于是关闭就不会崩溃了~~,嗯,完美~~。