首页
社区
课程
招聘
[原创]PEDIY:解除雷电2对光盘的依赖
发表于: 2014-8-10 15:20 11316

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

2014-8-10 15:20
11316

雷电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的背景音乐回放并不复杂,并且模块化相当好,编写一个新的模块,实现以上这些功能进行替换是可行的。整理初步的方案如下。


#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;
}

}
;跳过光盘检测
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
  • 采用Ogg作为新的背景音乐,并使用Audiere音频库实现回放,编写一个DLL文件实现以上接口。

  • [培训]科锐逆向工程师培训第53期2025年7月8日开班!

    收藏
    免费 3
    支持
    分享
    最新回复 (16)
    雪    币: 468
    活跃值: (52)
    能力值: ( LV2,RANK:10 )
    在线值:
    发帖
    回帖
    粉丝
    2
    http://bbs.pediy.com/showthread.php?t=177667
    呵呵
    【分享】经典打飞机游戏
       
    看到大家都玩微信打飞机,本人发一个经典打飞机游戏雷电2。经过 本人修改打补丁,可以1人控制双机重叠发出雷电火力。
    13bK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4m8S2L8W2)9J5k6h3u0S2K9h3c8#2i4K6u0W2j5$3!0E0i4K6u0r3M7$3S2S2M7X3g2Q4x3V1k6D9K9h3&6C8i4K6y4r3M7$3S2S2M7X3g2A6k6q4)9K6c8o6p5K6x3e0b7$3y4U0M7J5x3W2)9J5y4Y4g2C8i4K6y4p5x3U0V1^5z5e0p5I4y4e0j5^5z5l9`.`.
    2014-8-10 16:13
    0
    雪    币: 2575
    活跃值: (502)
    能力值: ( LV6,RANK:85 )
    在线值:
    发帖
    回帖
    粉丝
    3
    掌握技术就是好,可以自己解决问题。
    2014-8-10 20:56
    0
    雪    币: 47
    活跃值: (169)
    能力值: ( LV2,RANK:10 )
    在线值:
    发帖
    回帖
    粉丝
    4
    前排出售广告位
    2014-8-11 20:06
    0
    雪    币: 23
    活跃值: (10)
    能力值: ( LV2,RANK:10 )
    在线值:
    发帖
    回帖
    粉丝
    5
    分享是种快乐。回帖也是美德
    2014-8-11 22:23
    0
    雪    币: 39261
    活跃值: (7505)
    能力值: ( LV3,RANK:20 )
    在线值:
    发帖
    回帖
    粉丝
    6
    有点意思的。。
    2014-8-12 09:34
    0
    雪    币: 71
    活跃值: (10)
    能力值: ( LV2,RANK:10 )
    在线值:
    发帖
    回帖
    粉丝
    7
    不错啊

    借鉴一下
    2014-8-12 12:40
    0
    雪    币: 49
    活跃值: (10)
    能力值: ( LV2,RANK:10 )
    在线值:
    发帖
    回帖
    粉丝
    8
    谢谢分享,借鉴一下
    2014-8-12 13:49
    0
    雪    币: 12023
    活跃值: (4964)
    能力值: ( LV2,RANK:10 )
    在线值:
    发帖
    回帖
    粉丝
    9
    厉害额,只能观望了,感谢分享了
    2014-8-12 19:35
    0
    雪    币: 559
    活跃值: (349)
    能力值: ( LV2,RANK:10 )
    在线值:
    发帖
    回帖
    粉丝
    10
    好方法 学习了
    2014-8-12 22:07
    0
    雪    币: 1
    活跃值: (10)
    能力值: ( LV2,RANK:10 )
    在线值:
    发帖
    回帖
    粉丝
    11
    学习学习了
    2014-8-13 09:26
    0
    雪    币: 10
    活跃值: (10)
    能力值: ( LV2,RANK:10 )
    在线值:
    发帖
    回帖
    粉丝
    12
    好帖子好帖子
    2014-8-13 10:35
    0
    雪    币: 261
    活跃值: (83)
    能力值: ( LV3,RANK:20 )
    在线值:
    发帖
    回帖
    粉丝
    13
    我下载了你的DIY版本,可是这是日文版本的,一个字都不认识,导致游戏没法儿玩呀,不知道按哪个键子去开枪。
    在你的readme中也看不懂你是设置了哪个具体的按键来实现双机分开,并该如何控制?
    2014-8-13 11:06
    0
    雪    币: 261
    活跃值: (83)
    能力值: ( LV3,RANK:20 )
    在线值:
    发帖
    回帖
    粉丝
    14
    1、可否将修改好的文件上传一份,以体验一下具体的效果。
    2、在2#有一个网友也分享了一份他DIY的作品,但他不是DIY的音效方面,也就是说这款游戏PC版的原型应该是这样的(是日文的)。从文件夹中看SOUND文件夹中就应该是所有的音乐文件了,是wav格式的。这款游戏运行起来窗口小,画面质感差,音效更烂,那么你DIY成OGG格式后会是什么样呢?
    3、你文章中提到光盘,难道你现在某时在玩该软件的时候,还用着光盘?
    2014-8-13 11:11
    0
    雪    币: 468
    活跃值: (52)
    能力值: ( LV2,RANK:10 )
    在线值:
    发帖
    回帖
    粉丝
    15
    如图所示
    上传的附件:
    • a.png (108.63kb,4次下载)
    2014-8-13 13:49
    0
    雪    币: 195
    活跃值: (123)
    能力值: ( LV2,RANK:10 )
    在线值:
    发帖
    回帖
    粉丝
    16
    可惜了雷电,怎么不做成手游。感觉qq的雷霆战机,整个升级系统还有....,都可以参考来做!
    2014-8-14 10:47
    0
    雪    币: 35
    活跃值: (10)
    能力值: ( LV2,RANK:10 )
    在线值:
    发帖
    回帖
    粉丝
    17
    掌握技术就是好,可以自己解决问题。
    2015-3-17 11:20
    0
    游客
    登录 | 注册 方可回帖
    返回