古いPCゲームソフトとMIDI ファイルの読み込み

ソフト開発としてつまっていることはないがスランプになってしまってコードが見たくなり別件が入ったことで停滞中... 作業量が少ない対応として Windows 98 時代のゲームソフトの修正をしてみた.

BGM として MIDI (.mid) ファイルを利用するのだが、ファイル再生前に5秒間ぐらいソフトが止まる. 原因を探った. (Windows ソフトの hack はほとんど経験無し)

Win32 API として当時の midi の再生手段は mci を利用して mciSendCommand() か mciSendString() を利用するらしい. 該当のゲームソフトを逆アセンブルしてみた、 UsaMimi32.exe から実行中のプログラムを逆アセンブルしたり、ブレークをかけることができる. (すごい)

解析手順

  • モジュールを逆アセンブル → 逆アセンブル実行 → 検索:APIコールで mci を入れる
  • 検索結果で mciSendCommand() を利用した数か所でてくる
  • mciSendCommand() の実装前に引数の確認. ABI はちゃんとよんでないが引数を逆から push するらしい.
    • 第2引数の uMSG は immediate を push していたのでその値の意味を調べる. mingw では /mingw32/include/mmsystem.h に定義があった.
    • mciSendCommand() を呼び出してる箇所で引数が MCI_OPEN の場所は2個に絞り込めた
  • ブレークポイントの挿入/削除でコード実行ブレークポイント(INT3) で該当の2つの PC (=EIP) をいれる (ここで書き写しが発生するのがちょっと...)
    • ゲーム側で音を鳴らしてみて2つの PC は midi と wave で別れていることが判明した.

API をみる

midi を利用するための関数の逆アセンブルされた命令*1を眺めてみる.
http://www13.plala.or.jp/kymats/study/MULTIMEDIA/mciCommand_play.html このサイトの説明通りの手順らしい. そこで第3引数の lpOpen のもととなる変数をみたところ, ここの説明通りで下記のようだった.

static MCI_OPEN_PARMS mop;
mop.lpstrDeviceType= "sequencer"; //.exe ファイルの(たぶん) rodata section にある
mop.lpstrElementName= xx; //ini ファイルから読んだ文字列をわたしていたので動的確保された領域のようだ

これの原因は上記で言う変数 lpstrDeviceType が "sequencer" の場合は Windows NT 系(2000,xp,それ以降) だと遅く "MPEGVideo" だと早いらしい. ただしなぜそうなるかはちゃんとした文書が見当たらなかった. そしてこの議論は2005年にされているがおそらく 2000 年の時点で明らかになり20年間以上放置されているらしい.
https://www.activebasic.com/forum/viewtopic.php?t=99

以上より .exe ファイルのなかをバイナリエディタで探り "sequencer" を "MPEGVideo" に書き換えることで問題は解消された*2. あとは ElementName の参照先が記載された ini ファイルのファイル名を書き換えると MPEGVideo で開けるファイルの種類がたくさんあるので mp3 も使える.

レジストリでは aiff も "MPEGVideo" で使えるらしいのでやってみたい. 噂で aiff はループ再生ができると聞いており、仕様書では loop の記述を確認したが、編集ソフトでそれに対応しているものは今はないだろうし、 mci でそれが動くかはわからない.

他のソフト

同様の現象があったがこのソフトでは関数は mciWndCreate() で動いているようでそこには DeviceType を明示する方法が分からなかった.

*1:私は 80386 はほとんど知識なしなので addressing mode がよくわからない

*2:注: "" で囲んでいるのは \0 終端文字列である