XPCEをWindows XPで動かしTai!

移植ベースとなるXPCEは、軽いし、いいエミュレータなんですけど、
どうもWindows XPでは動かないようなんです。


Windows2000がインストールされているマシンでは正常に動作するのですが、
XPのマシンだと、実行後、ROMを読み込んだところで強制終了してしまいます。


まあ、XPで動かせなくても移植作業自体には支障がないかなと思っていたので、気にしていなかったのです。
しかし、移植前のXPCEを動かしながら動作を比較したりして動作検証したい場合も出てきました。


そんなわけで、XPのマシンでも動かないと何かと不便です。


Visual C++のデバッガで動作を追いかけていたのですが、デバッグビルドだと、強制終了する現象が
再現しないようです。
リリースビルドしたバイナリだけが、強制終了するようです。
これだと強制終了の原因を探るのがやっかいです。


リリースビルドしたバイナリを、デバッグするのはどうやればいいんでしょう…
やったことないなあー
できなくはないんでしょうけど。
やり方がわかりません。


.NETとJavaの産湯みたいな世界にどっぷり浸かっちゃってましたからねー
ゴリゴリのC言語なんてむりよ無理ー
あぁっ、ま、マクロ!?マクロ多すぎますぅ!!きょ、共用体!!?共用しちゃぁらめぇぇぇぇ!!!


また、デバッグビルドしたバイナリの場合、ROMを読み込んだ後、ハングアップするように見えるので
動いてないと思ってたのですが違いました。
コードを追いかけていったら、デバッグ用のコマンド入力待ちになっていたようです。
ロムを読み込んだあと、Gキーを押して、ENTERキーを押すと、エミュレーションが開始するようです。


そんな感じで、一応は動くようになったわけですが…


できれば、リリースビルドでも動くようにソースを直したいところです。



リリースビルドの場合だけ例外が発生していたので、デバッグするにはどうすればいいのだろう?
とちょっと悩んだんですけど、
それは意外と単純に解決しました。


Visual C++のリンク設定で、「デバッグ情報を生成する」というチェックボックスをONにすることで、
リリースビルドでもソースコードレベルでデバッグできることが分かりました。


それで、例外が発生する直前までの動作をデバッガで追いかけて行ったんですけど…


XPCEのソースコード、「Pce.cpp」内の、RefreshScreen関数内でAccess Violationが起きますね。
以下のコードです。

void RefreshScreen(void)
{
	int dispmin,dispmax;

	dispmin = (MaxLine-MinLine>MAXDISP ? MinLine+((MaxLine-MinLine-MAXDISP+1)>>1) : MinLine);
	dispmax = (MaxLine-MinLine>MAXDISP ? MaxLine-((MaxLine-MinLine-MAXDISP+1)>>1) : MaxLine);
	PutImage((WIDTH-FC_W)/2,(HEIGHT-FC_H)/2+MinLine+dispmin,FC_W,dispmax-dispmin+1);
	memset(XBuf+MinLine*WIDTH,Pal[0],(MaxLine-MinLine)*WIDTH);
	memset(SPM+MinLine*WIDTH,0,(MaxLine-MinLine)*WIDTH);
}

最初のmemsetで変なアドレスに書き込みに行ってしまっているのですが、
MinLineという変数がとんでもなく大きな値になっているのが原因のようです。


MinLineが変な値なのは、どうも変数が初期化されていないためっぽいです。


MinLineは、マクロで

#define MinLine	io.minline

と定義されています。なので実体はio.minlineという変数です。


では、この変数はどこで初期化されるのかな?ということをgrepしてみると、

void ResetPCE(M6502 *M)
{
	memset(M,0,sizeof(*M));
	TimerCount = TimerPeriod;
	M->IPeriod = IPeriod;
	M->TrapBadOps = 1;
	memset(&io, 0, sizeof(IO));
	scanline = 0;
	io.vdc_status=0;//VDC_InVBlank;
	io.vdc_inc = 1;
	io.minline = 0;
	io.maxline = 255;
	io.irq_mask = 0;
	io.psg_volume = 0;
	io.psg_ch = 0;
	for (int i = 0; i < 6; i++)
	{
		io.PSG[i][4] = 0x80;
	}
	CycleOld = 0;
	Reset6502(M);
	//printf("reset PC=%04x ",M->PC.W);
}

このResetPCE関数内で0が設定されるはずなんですが…


この関数にブレークポイントを仕掛けても、引っかかる様子がないです。


ソースのロジックを追いかけると、

int RunPCE(void)
{
//	M6502 M;
	ResetPCE(&M);
	Run6502(&M);
	return 1;
}

このRunPCE関数から、ResetPCE関数が呼ばれるはずなのに!


なんで?
というわけで、ソースを頭からステップ実行していくと、
なぜか
ResetPCE関数をすっ飛ばして
Run6502関数が実行されました。
意味不明です。


ソースレベルではおかしくないので、コンパイラ側を疑ってみることに。
コンパイラの最適化設定が、かなりの強さで最適化をかける設定になっていました。


これが原因なんでしょうか?
最適化を「デフォルト」に戻してみます。


実行!!


おー、R-TYPEが動いたー!!


どうやら、コンパイラの最適化によって不具合が発生していたようです。
Windows2000以前のOSで動いていたのも、たまたまだったのかもしれません。


せっかくなので、ビルドしなおしたXPCEをうpしておきます。
http://fcchome.web.fc2.com/
ここに置いておきます。


普通にXPCEを動かしながら動作検証できるようになったので、ちょっと作業がはかどりますね。


そんなわけで、ちまちまiアプリに移植していきます。