2023/05/30

好きなBGMを任意のタイミングで再生するみたいなやつのやり方

内臓音源のBGMとかって基本的に音声抽出ツールで吸い出せないことが多くて困りますよね。
ゲーム自体にBGM鑑賞機能があるならまだしも、ない場合は該当のBGMが流れる場面まで行って録音することになると思うのですが、他の効果音が入り込んだりしてしまうこともあるかと思います。
そこで、任意のBGMを好きなタイミングで再生できれば解決するんじゃねえかってことで、そのやり方を超適当に紹介していこうと思います。
ただし全てのゲームに使えるわけではないし(今の所自分が挑戦したゲームでは全部成功してますが)、MIPS採用ハード(PS、PS2、PSP、N64など)のみ対象です。

必要なアドレス
① パッドアドレス
・任意のタイミングで再生するうえで必須になります。

② ゲーム起動中、常時通過する関数のjr ra
・パッドアドレスやフレーム数をカウントしてるアドレスにストアしてる関数のを使うといいでしょう。
後述の理由により、遅延スロットがnopだと楽になるかも。

③ BGM再生関係の関数の先頭アドレス
・見つけ方は、まず変動サーチするなどして、BGM再生に使われるBGM番号がストアされるアドレスを見つけます。
そのアドレスをエミュのデバッガでReadでブレークポイントを仕掛け、そのBGMが再生されるタイミングで引っかかった箇所で
大抵a0~a3にロードされるので(何らかの処理をした後にa0~a3に格納されることも)、その直後にあるjal命令のジャンプ先がBGM再生関係の関数の可能性が高いです。
とりあえずそのjalを潰してみて再生されなくなれば当たりだと思います。

④ 空きメモリ(場合によっては必要なし)
・PSは8000あたり、PS2はF0000~FFFFF、N64はゲームによりけり(メモリダンプしてps2disなどで調べるとよい)、PSPは知りません。

やり方
1. BGM再生に必要なレジスタを調べる
例えばa0にBGM番号のほかに、他レジスタにループの有無や音量を格納する必要があったりするので、③を調べる過程で他にそれっぽい値が格納されてないか調べておきましょう。
もし必要なのがBGM番号だけ且つ、②の遅延スロットがnopであれば④は必要なくなります。

2. 必要な命令を数値化
少なくとも以下の2つの命令は必要になります。
・③に飛ぶj命令
・再生したいBGMの番号を代入するli命令
ゲームによっては④に飛ぶj命令と1.のループの有無や音量を代入する命令が必要になってきます。
数値化にはps2disを使うとよいでしょう。

3. ①と②のアドレスと2.で用意した命令でコードを作る
PS2のPAR(複合化状態)で、例えば①が200000、②が110000、③が120000、a0レジスタにBGM番号を入れるとすると、
②の遅延スロットがnopで且つ、必要なのがBGM番号だけであれば
E002FEFF 00200000 L2だけが押されているときのみ下の2行を実行
20110000 08048000 j命令で120000にジャンプ
20110004 2404xxxx 遅延スロットでli命令でa0にBGM番号を代入
E002FEFF 10200000 L2が押されていなければ下の2行で元の値を書き込む
20110000 03E00008
20110004 00000000
もし②の遅延スロットが使われてる且つループの有無や音量など他に値の代入が必要であれば、
D0200000 0000FEFF L2だけが押されているときのみ下の1行を実行
20110000 0803C000 j命令で空きメモリのF0000にジャンプ
200F0000 2404xxxx li命令でa0にBGM番号を代入
200F0004 2405000y a1にループ判定を代入
200F0008 3C08zzzz f12に格納するためにt0にluiで音量を代入
200F000C 08048000 j命令で120000にジャンプ
200F0010 44886000 遅延スロットでt0に代入した音量をf12に移動
D0200000 0010FEFF L2が押されていなければ下の1行で元の値を書き込む
20110000 03E00008
こんな感じになります。

4. 実際に作ったコードを適用して、指定したボタンを押して好きなBGMが流れれば成功。

GT4で実際にやってみるとこんな感じ