C/C++による非公式PlayStationプログラミング
ネットやろうぜは、最新のゲームマシンのソフトをアマチュアが開発できるという点では非常に魅力的で、出版社と組んでコンテストを開催したり、プレプレ(体験版CD)で作品を公開するなど一定の成果は残したものの、(開発環境としては安いものの)学生等が気軽に出すには高い費用、所定のサーバで会員間でしかソフトを公開できないといった閉鎖性、情報不足などにより、全体としては低調で、予定の2万人の会員を集めることなく終了しました。
しかしながら、世界は広いもので、こうした公式な開発環境に頼ることなく、独自に解析してPlayStationの技術情報を公開したり、ソフトを作っている人もいます。 NAPALMにいくつかのソフトや情報が集積されています。
ここでは、合法的に入手できる情報/開発環境でのPlayStationのプログラミングについて解説していきます。最終的にPS用のライブラリを構築し、C/C++によるフリーの開発環境を目指します。
開発環境を入手する
ハードウェア
第一に、開発用のパソコン(ISAスロットのあるPC-AT互換機)と、実行用のPlayStation本体が必要です。Free Wingのパラレル転送ケーブルと転送ソフトを自作又は購入すれば、ISAスロットのないノートパソコンや他のマシン(PC-98)でも可能かも入れません。
Pro Action Replay for PlayStation
Pro Action Replay for PlayStation(PS-PAR)は、英DATEL社のゲーム改造用ツールです。PlayStationの拡張端子に差すことで動作し、もともとはゲームにパッチを当てて改造し、体力が減らないようにしたり、アイテムを入手したりといったcheat(ずる)をするためのものです。
Pro Comms Link
Pro Comms Link(PC-LINK)は、同じくDATLEL社の、PS-PARとパソコン(PC-AT互換機)を繋いで通信できるようにするパソコン用ISAボードです。PC-98用の同様のものも存在するようです。
caetla
caetlaは、PS-PARの内蔵ソフトウェアを置換し、ユーザーの作成したプログラムをPC-LINKで転送して実行するなどの機能を追加し、PS-PARを開発環境に変身させるものです。k-comm.氏が作成され、Deaf Dumb & Blindで公開されています。機能や詳しい使用法はこちらを参照してください。
まず、PS-PARとPC-LINKを秋葉原の専門店やゲームラボの広告やWeb上で通販している輸入販売業者等から購入します。両方で1〜2万円くらいです。次に、PS-PARのフラッシュROMをcaetlaで置き換え、パソコン上のプログラムをPlayStationに転送して実行できる環境を整えます。
ソフトウェア
開発は基本的にDOSまたはWindows95のDOS窓のコマンドラインで行いますが、
コンパイラや転送ツールを再構築すれば、他のOS上でも可能です。
PlayStation development with BSDでは、BSD用のPAR+caetlaデバイスドライバ、gcc等のパッチ(いわゆるports)が公開されています。
GNU C/C++ Compiler
GNU C/C++ Compiler(gcc)は、多くのプラットフォームに対応したフリーのC/C++コンパイラです。PlayStationのCPUであるMIPS R3000にも対応しています。ソース配布されているので、開発を行うマシン(host)と、実行マシン(target)を指定してやればクロスコンパイラとして構築することができます。
GNU binutils
GNU binutilsは、同じく多くのプラットフォームに対応したアセンブラ(as)・リンカ(ld)・ライブラリアン(ar)等のパッケージです。アセンブラでの開発なら、こちらだけでかまいません。
一般的にバイナリオブジェクト形式にはa.out,coff,elfなどいくつかありますが、ネットやろうぜでは"ecoff"というのが使われています。また、最終的なCD-ROMのファイルでは、俗にPSX-EXEと呼ばれるPlayStation独自形式が使われています。caetlaは両方とも対応しています。ecoffからPSX-EXEの変換には、hitmenのeco2exeといったツールがあります。
GCCやbinutilsは、各地のftpサイトからソースを入手して自分で構築することもできますが、Win32/X68k用にコンパイル済みのものが非公式CO HOMEPAGEで公開されています。
ソフトに添付されているドキュメントは英文ですが、これらはもともとUnix上のソフトで、Linux,FreeBSD,NetBSD等のUnix系のOSのページでUnix関連の各種ドキュメントが日本語に翻訳されているので、これらに関するドキュメントもあります。また、日本語の書籍もいくつかあるようです。
最近のWindowsのビジュアル開発環境しか知らない方はとっつきにくいでしょうが、X68kやMS-DOS(DJGPP)でコマンドラインでの開発に慣れている方は、特に新しいことを覚える必要はありません。
GCCには多くのオプションがありますが、よく使うのは以下のオプションです。
必須ではありませんが、便利なことと、公開されたソースにMakefileが添付されていることも多いので、入手しておくほうが良いでしょう。MicrosoftのNMAKEやwatcomのWMAKE等、既存のコンパイラに付属しているものもありますが、微妙に書式が違うので、GNU Makeを入手しておくのが無難です。
MAKEは処理系依存しないので、djgpp用やmingw32用などのコンパイル済のものをそのまま利用できます。
ただ一つ足りないのはライブラリです。 GCCは、一般的なC標準関数については、対応するlibcというものがフリーで公開されていますが、パッドの入出力やポリゴンの描画といった、PlayStationに依存したライブラリはありません。 公式の開発環境ではPlayStationのハードウェアを制御するライブラリが付属していますが、一般にはそんなものは公開されていません。この部分は自作する必要があります。
そのためには、PlayStationのハードウェアがどうなっているかを独自に知る必要がありますが、現在では、世界中で解析がなされ、多くの情報が公開されているので、これらを利用することができます。
PlayStationの情報を入手する
CPUの情報
PlayStationに使われているCPUのMIPS R3000は、ワークステーション等で一般的なCPUなので、開発元のMIPSのサイト(今はSGIに買収されたのでSGIの中)で英文資料を入手できるほか、「MIPS RISCアーキテクチャ R2000/R3000」(共立出版 ISBN4-320-02598-9 C3041)通称「青本」(表紙が青い)という和訳書籍もあります。また、極楽プレイステーションでR3000の命令について解説されています。
PlayStation固有の情報
PlayStationのハードウェアの概要については、PlayStation FAQでも触れられていますが、SCEIからもネットやろうぜ用のものが一部公開されています。用語等についてはこちらを参照してください。
ネットやろうぜで入手できる情報は、以前は専用サーバで会員内にのみ公開されていたのですが、ネットやろうぜの終了と専用サーバの停止に伴い、一部は一般公開可になったらしく、ネットやろうぜライブラリのリファレンスや実行環境がWickedBeATで公開されています。 また、C Magazine(ソフトバンク)96年10月号でネットやろうぜとサンプルプログラムが紹介され、11月号から数ヶ月間プログラミングの連載がありました。これらは直接ネットやろうぜと同じ環境が手に入ったり、ハードウェア内部の機能がわかる訳ではありませんが、どんな機能があるかを知るための手がかりになります。
より詳しい情報に関しては、英語ですがBlackbagやCREATURE - PSXやbTmASTER's PSX-Development-Pageで解析された資料が公開されています。
前述のDeaf Dumb & BlindのPS開発ノート(仮)では、アセンブラでのプログラムの日本語の情報といくつかのソースが公開されています。 また、各種コントローラの信号やメモリカードのデータ形式がNiftyの藤田氏によって解析されています。
以下はBlackbagの資料の一部を翻訳・整形・補足したものです。
以下はその他の資料です。この引数の渡し方と返り値は、mips-gccの関数呼び出し規約と全く同じです。 したがって、引数($a0-3やスタックの状態)をそのままにして、$t1($9)を設定し、システムコールのアドレスにジャンプするラッパ関数を導入すれば、Cの関数として簡単に呼び出すことができます。
ラッパ関数の例: プロトタイプ宣言: int open(char *name,int mode); 実体:(アセンブラ) open: li $8,0xa0 #システムコールアドレス .set noreorder jr $8 li $9,0 #システムコール番号 .set reorderここでは、$8をジャンプのためのテンポラリレジスタに使用していますが、関数内で保存する必要のあるレジスタ以外であれば何でもかまいません。mipsの遅延分岐のためちょっとややこしいですが、平易に書けば
li $8,0xa0 #システムコールアドレス li $9,0 #システムコール番号 jr $8 #nopとなります。
open関数が呼ばれるとき、通常のCの関数のように$a0,$a1に引数が渡され、戻りアドレスを$raに入れてopenを呼び出します。 openでは$a0,$a1,$raはそのまま、$t1にシステムコール番号を入れてシステムコールにジャンプします。 システムコールでは、渡された$a0,$a1からシステムコールの処理を行い、$raのアドレス、すなわちopen関数が呼ばれた場所に戻ります。
この辺りは若干アセンブラの知識が必要ですが、アセンブラやGCCのインラインアセンブラで一度ラッパ関数を書いてしまえば、あとは楽です。
ハードウェアの制御
ハードウェアの制御は、0x1f801000-0x1f80????のメモリマップドI/Oを通じて行います。この資料はBlackbagに有ります。こちらが翻訳したものです。
GPU
GPU(グラフィックプロセッサユニット)は、PCの3Dアクセラレータボードに相当するもので、ポリゴン描画など、描画/表示全般を受け持つチップです。GPUを制御するには、フラット三角ポリゴン、グローテクスチャ四角ポリゴン等、個々のプリミティブ(基本図形)で特定の構造をもったコマンドパケットを送ります。これは、Direct3D Immidiate ModeのExecute Bufferと同様のものです。
GPUにコマンドを送るには、メモリマップドI/Oを使用してCPUで1ワード(32ビット)ずつ送る方法と、DMAを使用して複数のパケットのリンクリストを一気に送る方法の2つがあり、両方ともシステムコールにあります。 GPUのコマンドは前述のBlackbagで公開されています。こちらは翻訳したものです。
リンクリストは、通常のパケットの先頭に、パケットサイズと、次のパケットへのポインタが付いています。
8bit 24bit | size | next ptr | | pkt[0] | | pkt[1] | | pkt[2] | . . | pkt[size-1] |下位24ビットは、次のパケットのアドレスの下位24ビットです。PlayStationはメモリが2Mしかないので、24ビット(16Mまで表現できる)で十分表現できます。 上位8ビットは、そのパケットのワード(32ビット)でのサイズです。0の場合は、パケットの実体がない空のパケットです。 リストの終了は、恐らく-1(0xffffffff)です。
SPUの解析が進み、既に音を出せるようになっているようです。
bITmASTER's PSX-Development-PageやCREATURE - PSXに、SPUのレジスタ情報や波形コンバータ、サンプルコード等が公開されています。
こちらはCREATUREの資料の翻訳とbITmASTERの資料の翻訳です。
MDEC
MDECは動画の再生(圧縮されたデータの展開)を行うもので、MPEG再生ボードに相当します(最近はCPUの性能が上がったのでソフトウェアの再生の方が一般的ですが)。caetlaは動画再生機能を既に実装しているので、制御方法は既に解析済みのようです。
CD-ROM
これもPSエミュレータで既に実装されているので、解析済みのようです。
低レベルの制御法もあるようですが、open("cdrom:filename;1",1);とすることで、open/_lseek/read/close/firstfile/nextfile等のファイル関係のシステムコールから使えます。
ただし、PlayStationのCD-ROMにはハードウェアとソフトウェアのプロテクトがかかっているので、普通のCD-ROMやCD-Rのデータを通常のPlayStationで読むことはできません。
メモリカード
メモリカードは、open("bu00:filename",1);(スロット2は"bu10:〜")とすることで、ファイル関係のシステムコールから使えますが、使う前に初期化が必要なようです。具体的な手順は、Deaf Dumb & Blindの「簡易メモリカード管理ツール」のアセンブラソースが参考になります。
コントローラ
標準コントローラからの入力はシステムコールにあります。初期化やパッド入力の具体的な方法はBlackbagで公開されています。
アナログコントローラやマウスは、こちらの解析が参考になります。システムコールでは3バイト目(0x5a)が読み捨てられて、以降がバッファに格納されるようです。震動パッドについては不明です。
ライブラリの作成
スタートアップコード
GCCでは_startから実行されるので、main()を呼ぶようにスタートアップコードを書く必要があります。また、main()の先頭で__mainが呼ばれます。
ここでは_startでbss領域の初期化とgpレジスタの設定を行い、__mainは何もしないようにします。
extern long _fbss[];
extern long _end[];
extern long _gp[];
register long *gp asm("gp");
_start()
{
long *adr;
for(adr = _fbss;adr!=_end;*adr++=0);
gp = _gp;
main();
}
__main()
{
}
_start.cをコンパイルして_start.oを作っておきます
mipsgcc -O2 -c _start.c
syscall.sをアセンブルしてsyscall.oを作っておきます
mipsgcc -c syscall.s
matrix.lzh
高レベルライブラリ
続く・・・かもしれない
プログラミング
細かいライブラリの作成は後にして、システムコールだけを使って実際のプログラミングに入ります。
Hello world
とりあえずお約束のHello worldプログラムです。
#include "syscall.h"
int main(int argc,char **argv)
{
printf("Hello,world\n");
return 0;
}
hello.cでセーブして
mipsgcc -O2 -Xlinker -mpsx -o hello.psx hello.c syscall.o _start.oでコンパイル、
psexe hello.psxで実行します。
ところで、PlayStationの標準入出力はどこでしょう?
実機には存在しませんが(nullデバイス?)、caetlaではホストマシンのコンソールになるように拡張されています。いわゆる「printfデバッグ」のためのものでしょう。
2Dポリゴンの表示
次にPlayStationらしい(?)プログラムとして、ポリゴンを表示してみます。
まだ3D演算部分がないので、とりあえずは2Dです。
void Clear_(void)
{
}
void gpuInit(void)
{
SendGPU(0); /* GPUリセット ? */
Clear_();
SendGPU(0x03000000); /* 表示マスク (表示有効) */
Clear_();
SendGPU(0x06c60260); /* スクリーン水平開始/終了 (0/256) */
Clear_();
SendGPU(0x07040010); /* スクリーン垂直開始/終了 (0/240) */
Clear_();
GPU_cw(0xe1000400); /* 描画モード(表示領域に描画 ディザなし tpage0)*/
Clear_();
GPU_cw(0xe3000000); /* 描画領域始点 (0,0) */
Clear_();
GPU_cw(0xe407ffff); /* 描画領域終点 (1023,511) */
Clear_();
GPU_cw(0xe5000000); /* 描画オフセット (0,0) */
Clear_();
SendGPU(0x08000000); /* 表示モード 256x240/NTSC/ノンインターレース */
}
Clear_()はGPUフラッシュかキャッシュフラッシュか終了待ちか何かと思いますが、とりあえず無くても動作するようなので無視して空の関数にしておきます。
void test(void)
{
struct {
unsigned char r0,g0,b0,code;
short x0,y0;
short w,h;
} boxf = {
0,0,0,0x02, //黒
0,0,
256,240
};
struct {
unsigned char r0,g0,b0,code;
short x0,y0;
short x1,y1;
short x2,y2;
} polyF3 = {
255,0,0,0x20, //赤
50,0,
0,100,
100,100
};
struct {
unsigned char r0,g0,b0,code;
short x0,y0;
unsigned char r1,g1,b1,pad1;
short x1,y1;
unsigned char r2,g2,b2,pad2;
short x2,y2;
} polyG3 = {
255,0,0,0x30, //赤
150,0,
0,255,0,0, //緑
100,100,
0,0,255,0, //青
200,100
};
GPU_cwb(&boxf,sizeof(boxf)/sizeof(long)); /* 背景塗りつぶし */
GPU_cwb(&polyF3,sizeof(polyF3)/sizeof(long)); /* フラット三角形 */
GPU_cwb(&polyG3,sizeof(polyG3)/sizeof(long)); /* グロー三角形 */
}
int main(int argc,char **argv)
{
gpuInit();
test();
for(;;) ;
}
そのままmainから抜けるとcaetlaに戻った時に画面が消えるので、戻らないようにfor(;;);で無限ループにしています。
poly2d.c
ファイルの入出力
ファイルの入出力は、open/read/write/lseek/close等の、Cの低水準入出力関数に相当するシステムコールで統一的に扱うことができます。read/write/lseekは、デバイス固有のブロック単位で行う必要があります。CD-ROMは2048バイト、メモリカードは128バイトです。これらを使うに先立ち、ExitCriticalSection();で割り込みを許可する必要があるようです。
openに渡すファイル名は"デバイス名:ファイル名"の形で渡します。
デバイス名 デバイス cdrom: CD-ROM bu00: スロット1のメモリカード bu10: スロット2のメモリカード pcdrv: PCのハードディスク(caetla拡張)CD-ROMのファイル名はISOのレベル1に従い、英大文字8.3形式の末尾に";1"が付きます。当然ながらCD-ROMにはwriteできません。
メモリカードを使うには、InitCARD(1);StartCARD();_bu_init();を最初に行う必要があるようです。また、メモリカードのファイル名は、各ソフトのセーブデータのファイル名が重ならないように、何らかの法則性を持った長いものが使われているようです。
ファイルの情報(ファイルサイズ等)やディレクトリを見るには、firstfile/nextfileのシステムコールを使います。これはDOSやWindowsの扱いと似ています。 lseekがブロック単位でしか行えないので、ファイル末尾にlseekしてその位置のファイルポインタをファイルサイズとする手法は使えません。
以下はDeaf Dumb & BlindのアセンブラのサンプルをCで書き直したもので、CD-ROMからpcdrvにファイルのコピーを行います。
通常、ネットやろうぜ環境でプログラムを作成すると、libpsを利用したものになります。 libpsはlibps.aとlibps.exeから成り、libps.aはプログラムにリンクされ、libps.exeはネットやろうぜのシステムCD-ROMから読み込まれます。
libps.exeとlibps.aの関係は、MS-Windowsのダイナミックリンクライブラリ(.DLL)と、DLLを利用するためのインポートライブラリの関係に似ています。プログラムにリンクされるlibps.aには、ライブラリ自体ではなく、libps.exe内にあるライブラリ本体へのジャンプテーブルのみが記録されています。
したがって、ネットやろうぜで作成したプログラムは、プログラムだけでなく、libps.exeがないと動作しません。 caetlaでは、libps.exeを事前に転送することで、ネットやろうぜのプログラムも実行可能です。
libps.exeは、いくつかのネットやろうぜ会員が自作プログラムとともに公開しています。 libps.aやヘッダファイルは、ネットやろうぜ会員以外は入手できませんが、 Mark Heath's Home Pageで ジャンプテーブルが公開されているので、同様のものを作成することは可能です。
libpsインポートライブラリとヘッダファイル
Win32用のgccであるcygwin32やmingw32では、Win32 DLLのための独自のインポートライブラリやヘッダファイルが使われているので、同様にlibps.exeのための独自のインポートライブラリやヘッダファイルを作ることは合法だと思いますが、私は法律家ではないので、あなたの判断で使用してください。
testsuits
Hitmenでネットやろうぜ用のサンプルプログラム(YA.ZIP)が公開されているので、これをコンパイルしてみます。これにはlibps.exeも含まれています。
ネットやろうぜ用のソースは他にもPSX Developer's ConnectionやPlaystation.Netなどにあります。
MakefileをCO's gcc用に書き換えます。
[OLD]
LINKER = -Xlinker -Ttext -Xlinker 801b0000 -Xlinker $(PROG): $(OBJS) $(CC) $(LINKER) -o $@ $?[NEW]
CC = mipsgcc LINKER = -Xlinker -Ttext -Xlinker 801b0000 -Xlinker -meco $(PROG): $(OBJS) $(CC) $(LINKER) -o $@ $? -lps
バッチファイル(A.BAT)はez-o-ray用なので、caetla用に書き換えます。
[OLD]
del main del main.o make eco2exe -p main ez load libps.exe 8000f800 ez load picture.tim 80180000 ez run main[NEW]
del main del main.o make rem eco2exe -p main psexe -X libps.exe psexe picture.tim -d80180000 psexe mainこれでコンパイルと実行できるはずです。
海外版のソフトを一度イメージにして、日本のエリアコードに差し替え、CD-Rで焼くことで、海外版のソフトをプレイしている人もいるようです。
自作CD-Rでは当然このエリアコードは書き込まれていませんが、CD-Rイメージを作成後、エリアコードの部分を本物のCDから持ってくることでパスできます。
BOOT = cdrom:\POLY2D.PSX;1 TCB = 4 EVENT = 10 STACK = 801FFFF0BOOT = は、起動するファイル名です。