最終更新日:

PS2 PARよもやま話

PlayStation2用のPARが発売されてから、かなりの時間が経過しました。
改造コードのサーチ講座はいろいろなHPで解説されているので、これに関しては任せます。
しかし、そもそも何故ゲーム改造がPS2で可能なのか、PAR自体はどういう仕組みで動作しているのか。
こういった技術的なことに関して書かれているHPは存在していませんでした。
このことに関して、たまに雑誌などで解説される場合がありますが、どれも全くのデタラメとしか思えませんでした。
そこで、ここではPARからゲーム起動までのプロセスに関して解説することにします。
EE kernelに関わる部分まで含めて、なるべく詳細に書くことを目標にします。

EE kernelのフック

まず、簡単にプロセスの概略を説明します。
ゲームディスクに入れ替えて、ゲームを起動させるには、SYSTEM.CNF内に 記述されているブートファイル名を指定して、LoadExecPS2システムコールを 呼びます。 LoadExecPS2内で各種初期化とブートファイルをメモリに読み込みます。 その後、ExecPS2システムコールを呼んで、プログラムが実行されます。 しかしながら、PARはExecPS2にパッチを当てています。つまり、ゲームプログラム 実行直前にPARに処理が回ってきます。 この処理で、マスターコード処理やAコード処理が行われた後、ゲームが起動するわけです。
以上がごくごく簡単な説明です。
以下では、次のステップに分けて、それぞれ詳細に説明したいと思います。

ExecPS2へのパッチ

何らかのシステムコールを呼ぶと、kernelに制御が移ります。具体的には、 $80000280 からの処理に移行します。この部分のプログラムは以下のように なっています。

80000280:
	bltzl	v1, $8000032c	#v1はベクタ番号
	subu	v1, zero, v1
	addiu	k0, zero, $007c
	bne	k0, v1, $8000029c
	nop
	j	$80013e7c
	nop
8000029c:
	lui	k0, $8000
	sq	sp, $10c0(k0)
	sq	ra, $10d0(k0)
	sq	at, $10e0(k0)
	mfc0	at, Status
	addiu	k0, zero, $ffe4
	and	at, at, k0
	mtc0	at, Status
	sync.p
	daddu	k0, sp, zero
	lui	sp, $8002
	addiu	sp, sp, $8b00
	addiu	sp, sp, $fff0
	sw	ra, $0000(sp)
	sw	k0, $0004(sp)
	mfc0	k0, EPC
	addiu	k0, k0, $0004
	sw	k0, $0008(sp)
	mtc0	k0, EPC
	sync.p
800002ec:
	sll	v1, v1, 2
	lui	k0, $8001
	addu	k0, k0, v1
	lw	k0, $4bc0(k0)	#ベクタテーブル($80014bc0)を参照し各処理に移る
	jalr	k0
80000302:
	nop
	lw	k0, $0008(sp)
	lw	ra, $0000(sp)
	lw	sp, $0004(sp)
	mtc0	k0, EPC
	sync.p
	mfc0	k0, Status
8000031f:
	ori	k0, k0, $0013
	mtc0	k0, Status
	sync.p
	eret
ベクタテーブル($80014bc0)の先頭部分を少し抜き出してみると、次のようになっています。
80014bc0 : 80001564	#0: Undefined
80014bc4 : 8000d638	#1: ResetEE
80014bc8 : 8000bfe8	#2: SetGsCrt
80014bcc : 80001564	#3: Undefined
80014bd0 : 80000d80	#4: Exit
80014bd4 : 80002880	#5: ?
80014bd8 : 800055a0	#6: LoadExecPS2
80014bdc : 80002f80	#7: ExecPS2
v1にはベクタ番号が入ってますので、 ベクタテーブルを参照して各処理が実行されるわけです。 さて、PARではExecPS2にパッチを当てています。手順としては、ベクタテーブルの 先頭($80014bc0)を探し、そこからのオフセットでExecPS2が定義されるアドレス($80014bdc)を取得します。 まず、ベクタテーブルがどこに配置されているのか探さねばなりません。 もちろんPS2のBIOSによってkernelの内容が違うため、アドレスが変わる可能性があります。 ではどうやってベクタテーブルの位置を探すかということですが、PARはうまくやっています。 kernel領域、$80000000から$8007FFFFまでは、ユーザモードの場合アクセスできませんので、 この領域を読み書きする関数をベクタテーブルに追加して、独自のシステムコールを作ります。 この関数のアドレスがベクタテーブルに入っているので、kernel内でこのアドレスを探します。 この関数のベクタ番号は知っているので、そこから逆算してExecPS2の位置を割り出しています。 そういうわけで、上手い具合にしてExecPS2の位置が求まります。
ExecPS2内では各種処理の後、eretが実行されます。eretにより、ゲームプログラム に制御が移ります。従って、eretより前の部分にパッチを当てると、ゲームプログラムが実行される直前の状態に割り込むことが可能になります。 まず、eret付近のプログラムを示しておきます。
80002fa8:
	di
	mfc0	k0, Status	#PARはこの位置からパッチを当てる
	ori	k0, k0, $0013
	mtc0	k0, Status
	sync.p
	eret			#eretによりゲームプログラムに制御が移る
mfc0の命令部分からPARはパッチを当てます。j命令とnopの2つを書き込んでいます。 j命令で飛んだ先で、マスターコードとAコード処理が行われます。この処理の後、 mfc0〜eretまでの命令を実行し、ゲームプログラムに制御を移します。
蛇足ですが、diの次の行からパッチを当てていると不都合が生じる場合があります。 Tales Of RebirthがPARで起動できない原因だったりします。詳しいことはブログの方に書いてあります。


現在執筆中…