サクラノ詩全平台移植笔记

本文共1,966字,阅读完需要约7分钟。
Copyright: 署名-非商业性使用-相同方式共享 | CC BY-NC-SA 2.5 CN

声明:
游戏破解和移植行为会损害游戏厂商的利益
本文仅讨论一种可能的方法,不会公开任何文件的下载

背景

由于这个长长的假期,我接触到了サクラノ詩这样一部优秀的作品。遇到了好的作品,就会不由自主地想要把它推荐给别人。但是,サクラノ詩只有Windows版本,如何将他推荐给Linux、macOS、Android甚至Chrome OS平台的同学呢?

因此我们需要移植。移植即为将一个平台上运行的程序修改以在另一个平台上运行。对于一个普通的二进制可执行文件,移植几乎等同于反编译+重写程序,再考虑到可能存在的壳,移植的可能性几乎为零。

但是galgame的游戏剧本和资源一般与游戏主程序独立(这也是galgame汉化的前提),我们可以将剧本和资源提取出来,经过处理,再用另一个程序进行解析,实现接近的效果。由于没有追求和源程序完全等价,因此工作量减少了许多。

本文的目标是将サクラノ詩由BGI引擎移植至RenPy,实现全平台通用。

参考资料

Galgame 汉化破解初级教程:以 BGI 为例,从解包到测试

在前年处理素晴日的时候就参考了这篇文章很多。BGI引擎没有大变,这篇文章一直没有过时。文章里的资源处理很有用。


《素晴日》移植笔记

这篇文章是讲BGI引擎的ONS移植的,本文没有用ONS是因为 ONS难用 RenPy的全平台通用性更好。素晴日ONS版文件里留下了许多注释,对于这次移植起到了很大作用。


Ren’Py的参考文档

移植需要将剧本翻译为RenPy可以解析的语言。能实现多少演出效果也取决于对RenPy的熟悉程度。因此RenPy的参考文档要反反复复看上好多遍。


CROSS†CHANNEL 中文化(汉化)项目

C+C的全平台化使用了RenPy引擎,并且该项目将其RenPy移植的成果公开发布在了Github上。其源码里对于一些情况的处理有很大的参考价值。

拆包

BGI引擎万年不加密,用fxckBGI、AnimED、GARbro或是其他工具或是手动拆包都可以。

BGI引擎剧本文件一般打包在游戏目录下的data01xxx.arc里,用工具提取可得到一堆无扩展名、以章节名为文件名的文件,这就是剧本文件。

其他的文件也用类似的方法提取,根据文件格式放到对应的目录。

剧本处理

剧本文件的结构在澄空那个教程上已经讲得很清楚了,这里直接用前人留下的工具Bgi_asdis.zip将剧本文件转换成类汇编语言。

得到的.bsd文件内容大概长这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
line("00_op_01.bss", 416);
push_dword(1);
push_dword(1);
push_dword(0);
push_string("直哉");
push_string("「少し……気持ちが悪いんだ……」");
msg_::f_140();

line("00_op_01.bss", 418);
push_string("ked_000005");
snd_::f_1a9();

line("00_op_01.bss", 420);
push_dword(1);
push_dword(1);
push_dword(0);
push_string("圭");
push_string("「どうした? 気分が悪いのか? 直哉?」");
msg_::f_140();

line("00_op_01.bss", 423);
push_dword(1);
push_dword(1);
push_dword(0);
push_string("直哉");
push_string("「ああ……そうかもしれないな……」");
msg_::f_140();

line("00_op_01.bss", 427);
push_dword(1000);
push_string("evb000b");
grp_::f_280();

line("00_op_01.bss", 432);
push_dword(1);
push_dword(1);
push_dword(0);
push_dword(0);
push_string(" 俺は空を仰ぎ……舞い散る桜を見つめる。");
msg_::f_140();

有点编程的同学大概能看懂这段程序在干什么。压栈压栈再调用函数,函数的参数即为前面入栈的参数。

写一个大概这样的脚本把这段剧本变成正常的调用函数

1
2
3
4
5
6
7
8
9
stk=[]
for line in lines:
if line[0:4]=='push':
stk.append(something)
elif line.find('f_xx')!=-1:
something=[stk.pop(),stk.pop(),...,stk.pop()]
print('f_xx(' + ','.join(something) + ')')
elif ...:
...

类似这样,可以把之前提取到的类汇编剧本变成这样

1
2
3
4
5
6
f_140("「少し……気持ちが悪いんだ……」","直哉",0,1,1)
f_1a9('ked_000005')
f_140("「どうした? 気分が悪いのか? 直哉?」","圭",0,1,1)
f_140("「ああ……そうかもしれないな……」","直哉",0,1,1)
f_280('evb000b', 1000)
f_140(" 俺は空を仰ぎ……舞い散る桜を見つめる。",0,0,1,1)

这下就看起来直观多了

把输出文本的地方稍微改一下(主要是f_140()这个函数)以符合RenPy的格式

1
2
3
4
5
6
直哉 "「少し……気持ちが悪いんだ……」"
f_1a9('ked_000005')
"「どうした? 気分が悪いのか? 直哉?」"
直哉 "「ああ……そうかもしれないな……」"
f_280('evb000b', 1000)
" 俺は空を仰ぎ……舞い散る桜を見つめる。"

这段剧本已经符合了RenPy的格式。

函数完善

运行这段代码没有BGM没有语音没有立绘没有CG,换句话说只能显示文本,其他的一切效果都没有(因为还没做)。

统计剧本文件中出现的函数,一共有120个。如果没有正确的定义这些函数,函数对应的功能就不能实现。

素晴日ONS移植版中有一部分函数的ons实现,可以用作参考。其他的函数需要先确定功能再用RenPy实现类似的效果。

此外,类汇编剧本中的line()表示的是行号,和通过修改文件开启的DEBUG模式里显示的行号是一致的(利用这个可以比较方便地找到)。通过这个特性也可以方便地定位演出效果和函数的对应关系。

动画相关的函数像实现文本放大居中等演出效果的函数的参数的确定需要反复观察对比才能确定。

和立绘相关的函数的处理也是个大坑。

界面处理

RenPy推荐使用frame、vbox、hbox等界面语言描述界面,但是为了使移植版和原版表现效果一致(尽可能使用上原版的素材),最终选择直接用绝对pos来确定图片和按钮的位置。先截图用ps确定按钮的位置,再直接把按钮固定到对应的位置上。简单粗暴。

1
2
3
4
5
6
7
8
imagebutton:
pos(116,514)
idle im.Crop("gui/button/SGTitle000000.png",(0,0,114,63))
hover im.Crop("gui/button/SGTitle000000.png",(114,0,114,63))
selected_hover im.Crop("gui/button/SGTitle000000.png",(228,0,114,63))
hover_sound "music/sse000000.ogg"
activate_sound "music/sse000001.ogg"
action [Stop("music"),Start()]

需要处理的界面有主界面、游戏界面、设置界面、存档读档界面、对话历史界面、鉴赏界面。处理方法相同。

资源压缩

BGI引擎的压缩算法很玄学,不到3Garc文件全部提取出来占用了11G的空间。体积这么大打包也成了困难的事。所以需要对资源文件进行压缩。

RenPy支持的图片格式有png、jpg、webp。经过实际测试,将图片压缩为webp不仅不会丢透明图层、画质损失低,还达到了10~20%的压缩率,效果好到惊人。

压缩使用了pythonPIL库,遍历源文件目录用PIL打开文件再在输出目录用webp格式保存,代码很好写。

语音文件占1.5G左右,格式为ogg。通过ffmpeg将码率压缩到48kbps,文件体积缩小了一半。

最终将11G的资源文件压缩到了2.5G。

打包

通过RenPy自带的工具可以方便地进行打包。

Windows、Linux、macOS打包都很顺利,一键全自动(其实只是把python环境、RenPy库和游戏文件加到同一个压缩包里)。

在打包apk时遇到了内存不足的问题,开了16g的虚拟内存问题解决。

调试

因为是边做边试的,打包完成运行时没有遇到什么问题。