あの丘で

上周末去了横滨一趟,到清水ヶ丘公园看了一眼。这是君望/Muv-Luv系列中某传说的后山的原型地。严格来说,是最接近作品设定氛围的地方。根据君望fanbook的说法,当时各背景图都没有直接按实景画,而是做了改动,严格意义上的原型地其实并不存在。比如某常见的港口公园图,虽然图中的桥和实物一样,但是现实中距离桥这么近的地方没有公园,只有货物码头。

“あの丘”设定是白陵柊高校的后山,特点是有青草有树的山坡,视野开阔能总览柊町(横滨)。虽然不像心跳系列传说之树那样有确切的说法,却是很多关键情节的舞台。君望中如第一章遥的告白,第三章水月的结局等;Muv-Luv中如純夏的结局;Muv-Luv Alternative中如和霞的约定(好吧那不是我们这个世界)。即使到了The Day After,日本已经沉了,还在西雅图设置了一个类似的场景。

从google street view和网上fans的记录来看,位于横滨市南太田站附近的清水ヶ丘公园似乎是和“あの丘”最符合的地方。(君望fanbook给出的举例是野毛山公园,但是山坡的配合度似乎差点。)公园地势较高,西南侧有一个小坡,有草有树,视野开阔。虽然细节上仍有一些对不上,大的要素都凑齐了。下面就是到实地拍到的几张照片。

近期rUGP方面进展的若干备忘

进入六月后搞了三周rUGP,还是有点收获的。一方面搞定了立绘,也做了个粗略的文本提取功能;另一方面开始跟踪调试Nagato的补丁,对钩子怎么做有了个很初步的概念。不过又要开始忙了。老样子,写备忘,收工……

一 关于资源的静态提取

从接触到的几部分材料看,最关键的参考资料还是hikobae的alterdec。RIODecode所做的限于脚本中文本的提取和打包,以及.rio.ici文件的解码和编码;而WESTSIDE社的工具是alterdec的继承和扩展——从字符串看,其中很大一部分可能直接就用的是alterdec的代码。要想完整正确地提取文本,要想提取更多类型的资源,恐怕只能回归hikobae的ReadObject(),乖乖地把一路上遇到的对象都解出来。在RioX上,下一步可以考虑去做的是增加遍历资源树、完整正确提取文本的模式——这也是alterdec原本就具备的功能——不过,想要支持MLA以外的作品仍是一个颇为困难的事情。

最近在想的另一个方向是能否调用rUGP各dll中的函数提取资源。alterdec很伟大,但是相应的算法分析过程还是太艰苦。为了提取资源,我们并不是真的需要确切的算法。能正确找出dll中的函数并正确调用的话,也一样可以提取。当然这两个“正确”是另一个难题,需要不少跟踪调试的努力。而且age社也在不断更新dll,基本每一作出来,dll都和上一作有些不同。总之,现在的我连Olly都不熟,还做不到这些,留作悬案吧。

二 关于Nagato的钩子

最近开始用IDA+Hex-Ray和OllyDbg研究Nagato的mlhookv2.rpo。初步的跟踪发现,该补丁动态修改了rvmm.dll、UnivUI.dll、Vm60.dll和GMfc.dll,替换了14处资源/字符串/代码,并安装了19个inline hook。字符串和代码的替换比较明了,.rsrc段的替换应该也不难查明白,比较麻烦的是那19个钩子。每个钩子都是覆盖10个字节,先把被钩位置所在模块的基地址压栈,再跳转一个远地址到mlhookv2.rpo里,相应处理做完后跳到原址后面某处继续执行。

麻烦的是,这个事情似乎是在汇编层面上做的。一方面,所钩的位置在函数内,跟上下文关系密切,比如有一个就装在push/push/push/call序列的第二个push语句上;另一方面,跳转到的位置不是一般的C函数,更像C/汇编混合编的代码段,栈都不一定平衡。这样一来,要搞明白发生了什么,就得把两边的汇编都看明白。当然,调试mlhookv2相比调试rUGP找函数省事,不过还是需要一些努力。目前暂时就做到这里,后面的工作有空再说吧。

总之收工忙正事去…起码文本替换钩子是要做的,不过也要看时间了…早几年都没摸破解这摊事(毕竟我不是爱好技术的人),现在有兴趣了却没那么闲了……如果把当年花在博得和无冬上的时间拿过来……当然,现在再谈这样的如果什么用都没有……

RioX 1.1完工,及各种杂谈

一周前写文说重开rUGP方面的事情,当时也没想到进展如此之快。到昨晚为止,解决了立绘的提取,实现了粗略提取文本的通用方法,RioX 1.1版的界面也写好了。虽然提取的内容在CG/背景的基础上增加了立绘和文本,毕竟仍是在复现和通用化有人实现过的东西,所以只增加了副版本号。如果有一天搞出了前人没搞出来的东西——说句放卫星的话,搞出视频/音频的话——再叫2.0吧。

首先是文本。前面也说过RIODecode和alterdec的局限性,遗憾的是,我也没能改进太多。我的提取算法类似于RIODecode,在二进制数据中识别字符串然后提取出来。经测试,我的算法可以对应君望DVD版以来的各游戏(君いたFV等早期游戏中脚本未加密,需要跳过一个步骤),但是不保证不错不漏。感觉上每解出一千个字符串,就要有一到两个有问题:或者是二进制数据被误认为字符串,解出没法读的东西;或者是有字符串没解出来。读这些文本了解剧情足够了,要进行翻译等精密工作的话还应该进行进一步的人工校对。

我在文本提取中用的思路是识别shift-JIS编码和利用Vm对象的结构信息,后者基于hikobae的解析结果。shift-JIS编码的识别是个很强的约束,而且age社的文本也有规律,在字符串比较长时(比如多于12字节),识别几乎万无一失。对于长度较短的字符串,就很困难了,需要结合一些结构信息,也没能完全避免错误。不过相比RIODecode,我输出成了一种容易人工校对的形式,这也是个有意义的进步吧。RIODecode的输出文本中结构控制信息太多,不学习下程序的话看不懂,也难以人工修改,甚至程序本身也是靠修改不知所云的输入参数来适应不同的脚本对象。我的输出至少可以让外行也能立即上手吧:输出文本仅包含字符串内容和字符串起始位置tag。误识别的字符串直接删除即可;前后文之间缺东西的话,可以用二进制编辑器去解开的脚本文件里找相应的位置,手工添加一条记录即可。同样地,把这些误判漏判的位置记录到文件里,由程序读取处理也是很容易的。相比RIODecode,这应该是一个对翻译人员来说更方便的解决方案。当然,学习Nagato用钩子+Ctrl可能更好。

文本部分花了三天搞定,没想到接下来的立绘只花了两天。原因很简单:我3月的时候已经走到距离真理很近的地方了。那时我通过对比实例,发现立绘对象开头的位流相当于一个剪影,而现在我发现,边界信息之后的部分和CG/背景图片的提取是相通的,最大的不同无非是立绘中只处理剪影内部的部分。然后就是找立绘对象数据特征即可。能做到这些多亏有WESTSIDE的工具:它让我能确认到一些立绘在rio文件中的位置,从而可以通过比对/猜想找出提取算法。

其实,前面一直拿“CG/背景”和“立绘”描述我解出的图片对象,是很不准确的。只能说相应类型的对象“主要用作CG/背景”和“主要用作立绘”。比如君望DVD版,绝大多数立绘用的就是“CG/背景”用的存储类型;而在较近的游戏中,“立绘”对象用来存储各种差分图像,不仅包括近似CG的差分,还包括一些把立绘分解为身体、头发、眼、嘴等元素再合成的实例。总之,“CG/背景”和“立绘”只是一个简化的描述方式,不过为了方便,今后我还会继续这样描述……

到此为止,RioX已经可以从rio文件中提取绝大多数图片和绝大多数文本。不过两部分都不完美:对比WESTSIDE的工具,还有一小部分图片没有提取;文本也还存在错漏。不过进一步的解析边际效益就太低了,暂时没有投身其中的打算。

作为一个到现在还不具备破解必要技术的新手来说,我觉得做出RioX来还不错了。在整件事中,我所做的整理远多于发现,更多地是在已有信息的基础上合理猜想并加以验证。之所以能把rio2png/alterdec代码中没有的图片解出来,除了WESTSIDE社工具提供的各种线索,更重要的是我有这样一个假设:“从开发者的角度讲,底层的数据流编码应该会保持一致”。而事实证明确实如此:虽然hikobae和WESTSIDE都对不同对象类型区别对待,并写出了很多底层的解码函数,我却成功地把这些函数统一了起来。立绘的成功提取也要归功于此。hikobae和WESTSIDE所做的是正常的破解:跟踪,辨别,然后记录。而我所做的,则是揣测开发的方式,并基于前人已有的结果,提出假设并验证,让有限的信息发挥出更大的作用。当然,我这个方式局限性很大,现在也基本走到头了。

最后,虽然RioX 1.1写出来了,关键的conan同学却不在,暂时没法放到riox.thisillusion.org上面去。不过相关工具已经空白了几年了,也不差这几天吧……

rUGP资源提取工作重开

搞thesis搞了快俩月,上周总算交上去了。休整了几天,重开rUGP这边的事情。虽说是重开,后面还能走多远,自个儿也说不清楚。一方面,容易取得的资料基本上都消化了,却发现前人只解决了一小部分问题;另一方面,摸了下Ollydbg却发现用起来并不容易,我想要自己走更远也很困难。总之闲时摸索着做一点吧。目前的状态是:吸收了alterdec的CG/背景/文本解析算法,吸收了WESTSIDE社部分解CG/背景的算法,并统合了CG/背景解码部分。目前想做的课题:1.通用的文本提取算法;2.较新立绘类型的提取算法;3.替换文本/图片的程序钩子。正在做的是第一项。

实际做起来发现,从.rio文件中提取文本,并没有预想的简单。现在找到的资料只有君望英化项目的RIODecode(强烈感谢blovewind提供资源,连代码都有!),和alterdec中的文本提取部分。Amaterasu的英化版也提取了原始文本,但是据Nagato说用的是钩子+ctrl的方式,而不是从.rio文件中静态提取的。

遗憾的是,RIODecode和alterdec的文本提取方式都不够一般化。RIODecode是君望DVD版的专用文本提取工具(其实还能解.rio.ici文件,但是还没搞明白解出来做什么用),似乎是成功解出了全部文本。该工具只找出了较少的数据规律,解文本的算法比较tricky,需要人为设置5个控制参数。而这些控制参数,甚至对君望DVD版这个专门针对的对象都不是统一的。在全部229个脚本中,用默认参数可以提取的只有194个,另外的35个需要分别设置独自的控制参数。

从原理上讲,RIODecode不需要解析脚本数据中种类繁多的对象,除字符串之外,其他部分都被当作“不明的二进制数据”跳过了。这个方式本身是适合于通用化的。然而,5个人为的控制参数是跳过这些“不明数据”的关键,寻找文本的难度转化成了设置参数的难度。这是一个重大的进步,但是仍然很不好用,连解君望DVD版都有15%的脚本需要单独设置参数。我暂时还没有尝试用调整参数的方式去解其他作品的文本。

alterdec的文本提取针对Muv-Luv Alternative 18X版,方法很优美,但是也有局限。alterdec在解析多种对象的基础上,正确维护了对象类型记录的cache,进而能正确识别对象并跳过不需要的部分。hikobae给出了近300种OM对象的读取方法,做到了在MLA的脚本解析中正确界定每一个局部对象的类型和数据长度,进而完整正确地提取文本。这是一个伟大的工作。

然而这个方法的问题也同样显著。当脚本中出现新的对象类型时,就必须去把这些新的对象类型也解析了,才能保证alterdec的方法可以继续使用。否则对象类型记录的cache可能会漏掉一部分内容并导致对象的识别和界定出错。要保证能够用识别对象的方法提取文本,需要解析相应.rio文件中的大部分数据对象类型(事实上hikobae就是解析了MLA中出现的大部分对象类型);而随着时间推移,age社在不断加入新的对象类型,也在舍弃较旧的对象类型。用alterdec的算法去解MLA之前和之后的作品,都只能解出其中一部分脚本,而且解很新的作品时几乎就解不了含有文字的较长脚本。

RIODecode和alterdec提供了两个思路,然而都不容易通用化。我现在试图做的,就是结合两方面的参考材料,争取靠粗线条地界定VM对象,做一个更通用的解决方案。看起来不容易,能不能做出来全无把握,总之试一试吧。

当然,做文本的静态提取可能不是很必要。搞定钩子的话,完全可以像Nagato那样一路ctrl过去取文本。甚至这样还更便于文本替换——只需要挂上同一个钩子即可。即使从.rio文件中解出了文本,也还存在如何把翻译文本静态或动态地替换回去的问题。