雷电2的经典毋庸置疑,由于街机模拟的不完善,其PC版仍旧是体验这款游戏的最佳方式。在遥远的1997年,限于当时的软硬件环境,PC版采用CD音轨作为背景音乐,运行时需要光盘或虚拟光驱,既不方便又占用大量存储空间。因此我一直希望有一个方便的版本,不依赖光盘但保留背景音乐以获取完整的游戏体验。经过一番努力,终于成功使用Ogg音乐文件代替CD音轨回放,完美解除了雷电2对光盘的依赖。本文是此次逆向工程的记录,仅限于技术研究,勿用于破解及盗版用途。
以下记录基于雷电2英文版,可执行文件Raidenii.exe,文件日期1997-12-1 23:12,CRC值B14D0DE9。
分析
反汇编主程序Raidenii.exe,根据其导入的API函数以及字符串资源等信息,可以得知雷电2使用DirectSound实现音效,而使用MCI实现背景音乐的回放,两者没有共用代码。
搜索代码中所有对MCI函数的调用,发现只有mciSendCommand及mciGetErrorString,并进一步分析MCI命令代码及函数的输入参数和返回值,整理如下(音量控制使用auxSetVolume,勿遗漏)。
int proc_00402E70()
获取音量控制设备,保存设备ID到某全局变量中。int proc_00402F20()
初始化CD回放设备,同样保存设备ID到某全局变量中。int proc_00402FA0()
关闭CD回放设备。int proc_00402FF0()
停止播放。int proc_00403040(unsigned char)
播放音轨,参数为音轨编号。int proc_00403100()
暂停播放。int proc_00403140()
恢复播放。int proc_00403180(unsigned char, int, int, int)
获取CD音轨的参数如音轨长度等,第一个参数为音轨编号,其余参数为输出参数,用于记录音轨长度等数据。int proc_004031F0(int)
获取MCI错误的文字描述,参数为错误代码(MCIERROR)。int proc_00403260(int, int, int)
MCI消息处理函数,三个参数为设备ID、WPARAM及LPARAM,用于在一条音轨播放结束时切换到新的音轨。int proc_004032C0(int)
设置音量,参数为0~100。0040692D ~ 00406AF6
位于WinMain函数,光盘检测的相关代码。
由上面的分析不难看出,雷电2的背景音乐回放并不复杂,并且模块化相当好,编写一个新的模块,实现以上这些功能进行替换是可行的。整理初步的方案如下。
采用Ogg作为新的背景音乐,并使用Audiere音频库实现回放,编写一个DLL文件实现以上接口。
由于部分函数如获取音量控制设备、获取音轨参数等不再使用,无需实现。
Audiere的回调方式不同于MCI,并非使用消息系统,因此在初始化时将消息处理函数作为参数输入并记录下来,在Audiere回调事件发生时进行调用。
将新编写的DLL注入到Raidenii.exe,并修改以上函数的实现,改为调用DLL中的相关代码。
准备工作
提取CD音轨
压缩为Ogg文件,保存到游戏所在目录的MUSIC子目录中,依次命名为 BGM01.OGG ~ BGM11.OGG。
编写fakecd.dll
动手术
使用LordPE编辑Raidenii.exe,在其导入表中添加fakecd.dll导出的接口函数,并记录下所有新导入函数的地址。
修改Raidenii.exe,调用fakecd.dll实现背景音乐回放。
收工!
补充
另一种方案:Hook并重新实现mciSendCommand及mciSendString(部分,覆盖游戏使用的范围),这种方案不需要对游戏本身进行太多分析,只要搜集游戏对MCI函数的调用即可,通用性也强得多。