チュンソフト製ゲームのSPCの音抜けを解消する方法

さもまとまった方法を紹介する風情でありながら、ほとんど説明になっていませんし、ある意味でやや不完全です(うまくいかない場合もある)。参考メモ程度に残しておきます。

適当にやっていたら音抜けを直せたので、某所のかまいたちの夜を直そうとしています。ただ、元のdumperの方は過去に修正方法を見つけていたようで(そうだろうなとは思ってたけど)、じつは本人の手元には完全に修正されたバージョンがあるのではないかと思っていたりします。無駄骨率高そう。

さて本題ですが、上記「かまいたちの夜」をはじめとした、チュンソフトのミュージックエンジンを用いているゲームでは、楽曲の音色がすべてそろう前に演奏を開始して、演奏しつつ残りをそろえるような仕組みになっています。曲の頭の時点でSPCをダンプすると、残りの音色がそろわない状態で保存されてしまうため、楽曲の演奏が不完全なものになってしまいます。

これを直すためのアプローチはいろいろとあるかもしれませんが、ここでは音色の欠けたSPCに追加音色を移植することにします。

誰でもできる作業だと言い切るには記事が説明不足ですが、内容自体はそう難しいものでもありません。

という内容でありましたが、あすかさんがコメントで簡単確実そうな方法を寄せてくださったので、そちらを以下に掲載しておきます。

  1. 頭から吸い出したSPCファイル(以下SPC1)
  2. 全ての情報が読み込まれたSPCファイル(出来るだけ頭に近いデータ)(以下SPC2)
  3. バイナリーエディタ(範囲選択してコピー/貼り付けができるもの)

これらを用意したのち、以下の作業をやればokです。(+100hは不要; かまいたちの夜の場合)

  1. SPC2から、0x003a0〜0x100ffの範囲をコピーし、SPC1の0x003a0〜0x100ffに貼り付ける
  2. SPC2から、0x00280〜0x0029fの範囲をコピーし、SPC1の0x00280〜0x0029fに貼り付ける
  3. 一部音が鳴らない場合は、黒猫SPCなど鍵盤が見れるソフトで再生し、音が鳴っていないトラックを調べます。
    • Trk1の場合:SPC2の0x10102〜0x10103をSPC1の0x10102〜0x10103に貼り付る
    • Trk2の場合:SPC2の0x10112〜0x10113をSPC1の0x10112〜0x10113に貼り付る
    • Trk3の場合:SPC2の0x10122〜0x10123をSPC1の0x10122〜0x10123に貼り付る
    • Trk4の場合:SPC2の0x10132〜0x10133をSPC1の0x10132〜0x10133に貼り付る
    • Trk5の場合:SPC2の0x10142〜0x10143をSPC1の0x10142〜0x10143に貼り付る
    • Trk6の場合:SPC2の0x10152〜0x10153をSPC1の0x10152〜0x10153に貼り付る
    • Trk7の場合:SPC2の0x10162〜0x10163をSPC1の0x10162〜0x10163に貼り付る
    • Trk8の場合:SPC2の0x10172〜0x10173をSPC1の0x10172〜0x10173に貼り付る

Cソースにするとこんな感じでしょうか。

// Kamaitachi no Yoru SPC fix
// Method contributed by Asuka Rangray

#include <stdio.h>
#include <stdlib.h>

void fileCopyBytes(FILE *dst, FILE *src, int start, int end)
{
	int i;
	int c;

	if (start > end)
		return;

	for (i = start; i <= end; i++) {
		fseek(src, i, SEEK_SET);
		c = fgetc(src);
		fseek(dst, i, SEEK_SET);
		fputc(c, dst);
	}
}

int main(int argc, char *argv[])
{
	FILE *fBase = NULL;
	FILE *fPatch = NULL;
	int result = EXIT_FAILURE;

	if (argc < 2) {
		fprintf(stderr, "Error: too few input files\n");
		fprintf(stderr, "Arguments: <base spc> <patch spc>\n");
		return EXIT_FAILURE;
	}

	// file 1:
	// spc file which starts from the beginning of a song,
	// but lacks some instruments. (Chunsoft's music engine
	// delayloads some samples just after the start of a song)
	fBase = fopen(argv[1], "r+b");
	if (fBase == NULL)
		goto onexit;

	// file 2:
	// spc file which has fully loaded necessary instruments.
	// If it's closer to the beginning of a song, it's better.
	fPatch = fopen(argv[2], "rb");
	if (fPatch == NULL)
		goto onexit;

	// step 1: copy $02a0-ffff of ARAM
	fileCopyBytes(fBase, fPatch, 0x003a0, 0x100ff);
	// step 2: copy $0180-019f of ARAM
	fileCopyBytes(fBase, fPatch, 0x00280, 0x0029f);
	// step 3: copy pitch registers if needed
	#if 0
	fileCopyBytes(fBase, fPatch, 0x10102, 0x10103); // CH#0
	fileCopyBytes(fBase, fPatch, 0x10112, 0x10113); // CH#1
	fileCopyBytes(fBase, fPatch, 0x10122, 0x10123); // CH#2
	fileCopyBytes(fBase, fPatch, 0x10132, 0x10133); // CH#3
	fileCopyBytes(fBase, fPatch, 0x10142, 0x10143); // CH#4
	fileCopyBytes(fBase, fPatch, 0x10152, 0x10153); // CH#5
	fileCopyBytes(fBase, fPatch, 0x10162, 0x10163); // CH#6
	fileCopyBytes(fBase, fPatch, 0x10172, 0x10173); // CH#7
	#endif

	result = EXIT_SUCCESS;

onexit:
	if (fPatch)
		fclose(fPatch);
	if (fBase)
		fclose(fBase);
	return result;
}

で、やっぱり修正版は手元にあるそうです。うーむ。

一応、わたしが書いた内容も以下に残しておきます。上記の方法の方がおそらく確実です

準備

バイナリの比較表示ができるヘキサエディタを用意してください。私はStirlingを使いました。

楽曲データも準備しましょう。まずは普段通りに演奏開始直前でSPCをダンプ。ある程度演奏を続けて、音色が欠けることなく演奏がされるようなSPCもダンプしましょう。ここまでで、道具と患者とドナーがそろうことになります。あとはオペに取りかかるのみです。

作業

二つのSPCをSNES SPC700 Playerなどの表示を見比べつつ聞いていると、何番の波形が鳴らせないのかわかると思います。これを頭の片隅に覚えておくと、どこの変更が必要で、どこは無視してもよさそうか考えやすくなるかもしれません。

あまりSPCやAPUの仕様について詳しく説明する気はありませんが、SPCにおける波形の一覧はSNES SPC700 PlayerでいうSrcAddr(DSPレジスタ$5Eが示す位置)の領域に、波形開始位置とループ位置のアドレスを書き並べることで記されています。音色が欠けている場合、ここのデータの書き込みも不完全です。よって、SrcAddrの位置にジャンプして(SPCファイル上での作業なので、表示アドレスに+0100hした位置)、足りない音色の分のアドレスを書き換えます。

音色の位置を詐称したものの、実際の波形が存在しないのでは意味がありません。書き込んだデータを見ればどこに波形があるべきなのかわかりますので、その位置までジャンプします。ファイル間の差分を強調して表示してみると、一帯のまとまった領域がごっそり書き換わっていることに気づくでしょう。適当にまとめて選択して音色データを移植してください。

これでハードウェア仕様的には波形が追加されたことになりますが、実際に演奏で使うためにはもう少し書き換えが必要です。その音色の基本的なADSRやピッチの情報などを書き込まなければいけないからです。

さらなる作業

ここから、実際に扱ったかまいたちの夜のSPC(ひとつの推理)を例に話をします。

ドライバは未転送の波形がいくつあるのかを知っているようです。未転送の波形が3つあるSPCのバイナリを見てみると次のようなデータがありました。以下、アドレスはRAM上のものです。

05d0: ff ff 0d 12 11 ff ff ff ff ff ff ff ff ff ff ff

ポートを通じてAPUに飛ばされるバイトデータを見てみるとわかるのですが、05d2の位置にある3つの数字は波形の転送に関わっていて、波形を識別するIDのような役目を果たしているようです。転送が完了するとffになるようなので、念のためにすべてffに変更して、転送済みであることをアピールしておきます。

そのもう少し後ろを覗いてみます。

05e6: 7e 00 ff f1 b8 08 32 09
05ee: d8 1b ff e0 b8 08 50 20
05f6: 8b 11 ff e0 b8 0e 8f 01

8バイトの並びが続いています。波形のピッチやADSRなどの情報を格納している部分です。最初の2バイトが波形サイズ、続く5バイトも波形に関わる情報、最後の1バイトのみ値がどこに由来するのかつかめませんでしたが……少なくともその前の7バイトは波形転送時に付随している情報です。音色のそろったSPCからデータを持ってきて、情報をそろえてしまいましょう(ただこれ、単なる演奏中にも変化しているようにも見えます。気のせい? だとすると、移植元のダンプはタイミングを選ぶかもしれません)。

最後に、利用できる波形を覚えているようなマップがあります。

0410: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff 04
0420: ff 03 ff 00 02 ff ff ff ff ff ff ff ff ff ff ff
0430: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

データを見比べると、音色がそろっている方には、ffの代わりに欠けていた音色番号が書き込まれている箇所があるはずですので、適宜書き換えます。

ほとんど説明になっていませんが、これで作業は完了です。うまくいけば音色が増えた状態になっているはずですが、それでも一部の音色が鳴らないこともあるかもしれません。見直しても書き換え自体に抜けや間違いがないようなら、新たなタイミングで移植先SPCを再ダンプしてみるとよいかもしれません。非常に曖昧な解決策の提示ですが、その程度しか解析していないので仕方がないのです。

曲によってはCPUが関わる条件ジャンプを持っているものもあるので、そちらの解決策も欲しいかもしれませんが……とりあえずは音色の話だけ。

追:作成したSPCは数秒再生してから再ダンプして、波形部分が妙な形で上書きされていないことを確認した方がいいかもです。