PlayStation2用のPARが発売されてから、かなりの時間が経過しました。
改造コードのサーチ講座はいろいろなHPで解説されているので、これに関しては任せます。
しかし、そもそも何故ゲーム改造がPS2で可能なのか、PAR自体はどういう仕組みで動作しているのか。
こういった技術的なことに関して書かれているHPは存在していませんでした。
このことに関して、たまに雑誌などで解説される場合がありますが、どれも全くのデタラメとしか思えませんでした。
そこで、ここではPARからゲーム起動までのプロセスに関して解説することにします。
EE kernelに関わる部分まで含めて、なるべく詳細に書くことを目標にします。
まず、簡単にプロセスの概略を説明します。
ゲームディスクに入れ替えて、ゲームを起動させるには、SYSTEM.CNF内に
記述されているブートファイル名を指定して、LoadExecPS2システムコールを
呼びます。
LoadExecPS2内で各種初期化とブートファイルをメモリに読み込みます。
その後、ExecPS2システムコールを呼んで、プログラムが実行されます。
しかしながら、PARはExecPS2にパッチを当てています。つまり、ゲームプログラム
実行直前にPARに処理が回ってきます。
この処理で、マスターコード処理やAコード処理が行われた後、ゲームが起動するわけです。
以上がごくごく簡単な説明です。
以下では、次のステップに分けて、それぞれ詳細に説明したいと思います。
何らかのシステムコールを呼ぶと、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: ExecPS2v1にはベクタ番号が入ってますので、 ベクタテーブルを参照して各処理が実行されるわけです。 さて、PARではExecPS2にパッチを当てています。手順としては、ベクタテーブルの 先頭($80014bc0)を探し、そこからのオフセットでExecPS2が定義されるアドレス($80014bdc)を取得します。 まず、ベクタテーブルがどこに配置されているのか探さねばなりません。 もちろんPS2のBIOSによってkernelの内容が違うため、アドレスが変わる可能性があります。 ではどうやってベクタテーブルの位置を探すかということですが、PARはうまくやっています。 kernel領域、$80000000から$8007FFFFまでは、ユーザモードの場合アクセスできませんので、 この領域を読み書きする関数をベクタテーブルに追加して、独自のシステムコールを作ります。 この関数のアドレスがベクタテーブルに入っているので、kernel内でこのアドレスを探します。 この関数のベクタ番号は知っているので、そこから逆算してExecPS2の位置を割り出しています。 そういうわけで、上手い具合にしてExecPS2の位置が求まります。
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までの命令を実行し、ゲームプログラムに制御を移します。