欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]PEDIY:解除雷电2对光盘的依赖

[原创]PEDIY:解除雷电2对光盘的依赖

雷电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
      
    #include <windows.h>
    
    #include <string.h>
    
    #include <stdio.h>
    
    #include "audiere.h"
    
    using namespace audiere;
    
    #define FAKECD_API __declspec(dllexport)
    
    AudioDevicePtr g_device = 0;
    
    OutputStreamPtr g_stream = 0;
    
    StopCallbackPtr g_callback = 0;
    
    unsigned int g_volume = 100;
    
    signed int (*g_stop_callback)(int, int, int) = 0;
    
    #ifdef NDEBUG
    
        #define dbgprint(format, args...)
    
    #else
    
        #define dbgprint(format, args...) \
    
        { \
    
            char buffer[1024]; \
    
            sprintf(buffer, format, ##args); \
    
            OutputDebugString(buffer); \
    
        }
    
    #endif
    
    class fakecd_stop_callback : public RefImplementation<StopCallback>
    
    {
    
    public:
    
        void ADR_CALL streamStopped(StopEvent* event)
    
        {
    
            if (event->getReason() == StopEvent::STREAM_ENDED)
    
            {
    
                dbgprint("FAKECD_CALLBACK");
    
                if (g_stop_callback)
    
                    g_stop_callback(0, 0, 0);
    
            }
    
        }
    
    };
    
    extern "C"
    
    {
    
    FAKECD_API DWORD fakecd_init(signed int (*stop_callback)(int, int, int))
    
    {
    
        dbgprint("FAKECD_INIT: %d", (int)stop_callback);
    
        g_device = OpenDevice();
    
        if (!g_device)
    
            return EXIT_FAILURE;
    
        g_callback = new fakecd_stop_callback();
    
        g_device->registerCallback(g_callback.get());
    
        g_stop_callback = stop_callback;
    
        return EXIT_SUCCESS;
    
    }
    
    FAKECD_API DWORD fakecd_play(unsigned char track)
    
    {
    
        dbgprint("FAKECD_PLAY: %d", (int)track);
    
        if (!g_device)
    
            return EXIT_FAILURE;
    
        char base_path[MAX_PATH];
    
        HMODULE happ = GetModuleHandle(0);
    
        GetModuleFileName(happ, base_path, MAX_PATH);
    
        int i = strlen(base_path) - 1;
    
        while (i > 0 && base_path[i] != '\\') i--;
    
        base_path[i] = 0;
    
        char ogg_path[MAX_PATH];
    
        sprintf(ogg_path, "%s\\MUSIC\\BGM%02d.OGG", base_path, track);
    
        dbgprint("FILE: %s", ogg_path);
    
        g_stream = OpenSound(g_device, ogg_path, true, FF_OGG);
    
        if (!g_stream)
    
            return EXIT_FAILURE;
    
        g_stream->setVolume(g_volume / 100.0);
    
        g_stream->play();
    
        return EXIT_SUCCESS;
    
    }
    
    FAKECD_API DWORD fakecd_pause()
    
    {
    
        dbgprint("FAKECD_PAUSE");
    
        if (g_stream)
    
            g_stream->stop();
    
        return EXIT_SUCCESS;
    
    }
    
    FAKECD_API DWORD fakecd_resume()
    
    {
    
        dbgprint("FAKECD_RESUME");
    
        if (g_stream)
    
            g_stream->play();
    
        return EXIT_SUCCESS;
    
    }
    
    FAKECD_API DWORD fakecd_stop()
    
    {
    
        dbgprint("FAKECD_STOP");
    
        fakecd_pause();
    
        g_stream = 0;
    
        return EXIT_SUCCESS;
    
    }
    
    FAKECD_API DWORD fakecd_set_volume(unsigned int volume)
    
    {
    
        dbgprint("FAKECD_VOLUME: %d", (int)volume);
    
        g_volume = volume;
    
        if (g_volume > 100)
    
            g_volume = 100;
    
        if (g_stream)
    
            g_stream->setVolume(g_volume / 100.0);
    
        return EXIT_SUCCESS;
    
    }
    
    FAKECD_API DWORD fakecd_uninit()
    
    {
    
        dbgprint("FAKECD_UNINIT");
    
        fakecd_stop();
    
        g_device = 0;
    
        g_callback = 0;
    
        return EXIT_SUCCESS;
    
    }
    
    BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
    
    {
    
        if (fdwReason == DLL_PROCESS_DETACH)
    
            fakecd_uninit();
    
        return TRUE;
    
    }
    
    }

      
    动手术
    * 使用LordPE编辑Raidenii.exe,在其导入表中添加fakecd.dll导出的接口函数,并记录下所有新导入函数的地址。
    * 修改Raidenii.exe,调用fakecd.dll实现背景音乐回放。
      
    ;跳过光盘检测
    
    0040692D    E9 C5010000      JMP 00406AF7
    
    ;初始化设备
    
    00402F20    60               PUSHAD                          ;保留作案现场
    
    00402F21    68 60324000      PUSH 00403260                   ;回调函数
    
    00402F26    FF15 74E0CB00    CALL DWORD PTR DS:[fakecd_init] ;偷梁换柱
    
    00402F2C    83C4 04          ADD ESP,4                       ;平衡堆栈
    
    00402F2F    61               POPAD                           ;恢复现场
    
    00402F30    33C0             XOR EAX,EAX                     ;返回值
    
    00402F32    C3               RETN
    
    ;暂停播放
    
    00403100    60               PUSHAD
    
    00403101    FF15 78E0CB00    CALL DWORD PTR DS:[fakecd_pause]
    
    00403107    61               POPAD
    
    00403108    33C0             XOR EAX,EAX
    
    0040310A    C3               RETN
    
    ;播放
    
    00403040    33DB             XOR EBX,EBX
    
    00403042    8A5C24 04        MOV BL,BYTE PTR SS:[ESP+4] ;音轨编号
    
    00403046    4B               DEC EBX                    ;校正,因为第一条轨道是数据轨
    
    00403047    60               PUSHAD
    
    00403048    53               PUSH EBX
    
    00403049    FF15 7CE0CB00    CALL DWORD PTR DS:[fakecd_play]
    
    0040304F    83C4 04          ADD ESP,4
    
    00403052    61               POPAD
    
    00403053    33C0             XOR EAX,EAX
    
    00403055    C3               RETN
    
    ;恢复播放
    
    00403140    60               PUSHAD
    
    00403141    FF15 80E0CB00    CALL DWORD PTR DS:[fakecd_resume]
    
    00403147    61               POPAD
    
    00403148    33C0             XOR EAX,EAX
    
    0040314A    C3               RETN
    
    ;设置音量
    
    004032C0    8B4C24 04        MOV ECX,DWORD PTR SS:[ESP+4] ;音量参数
    
    004032C4    60               PUSHAD
    
    004032C5    51               PUSH ECX
    
    004032C6    FF15 84E0CB00    CALL DWORD PTR DS:[fakecd_set_volume]
    
    004032CC    83C4 04          ADD ESP,4
    
    004032CF    61               POPAD
    
    004032D0    33C0             XOR EAX,EAX
    
    004032D2    C3               RETN
    
    ;停止播放
    
    00402FF0    60               PUSHAD
    
    00402FF1    FF15 88E0CB00    CALL DWORD PTR DS:[fakecd_stop]
    
    00402FF7    61               POPAD
    
    00402FF8    33C0             XOR EAX,EAX
    
    00402FFA    A3 24174D00      MOV DWORD PTR DS:[4D1724],EAX ;下一条音轨,原有逻辑,下同
    
    00402FFF    A3 20174D00      MOV DWORD PTR DS:[4D1720],EAX ;播放状态
    
    00403004    C3               RETN
    
    00403005    90               NOP                           ;填充
    
    00403006    90               NOP                           ;防止破坏其它指令的正常显示
    
    00403007    90               NOP
    
    00403008    90               NOP
    
    ;关闭设备
    
    00402FA0    60               PUSHAD
    
    00402FA1    FF15 8CE0CB00    CALL DWORD PTR DS:[fakecd_uninit]
    
    00402FA7    61               POPAD
    
    00402FA8    33C0             XOR EAX,EAX
    
    00402FAA    C3               RETN
    
    ;回调函数(一首音乐播放完毕后触发),此函数基本未修改,只是去除了对消息参数的检测
    
    00403260    A1 20174D00      MOV EAX,DWORD PTR DS:[4D1720]
    
    00403265    85C0             TEST EAX,EAX
    
    00403267    74 10            JE SHORT 00403279
    
    00403269    60               PUSHAD
    
    0040326A    A1 24174D00      MOV EAX,DWORD PTR DS:[4D1724]
    
    0040326F    50               PUSH EAX
    
    00403270    E8 CBFDFFFF      CALL 00403040
    
    00403275    83C4 04          ADD ESP,4
    
    00403278    61               POPAD
    
    00403279    33C0             XOR EAX,EAX
    
    0040327B    C3               RETN

      
    收工!

    注意:上传附件及图片大小不得大于30M。

    ⚠️ 版权声明:
    本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
    未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

    0 0 0 举报
    复制成功