prxモジュールを作ってAPIをフックしたい(1)

イヤホンジャックの基盤と繋がっていた端子が剥がれて以来ずっと音が鳴らないPSPだけど、RemoteJoyみたいな感じで音をUSB経由でPCに渡せば聞けるんじゃないだろうか、ってことで久しぶりのPSPプログラミング。
PSP SDKのドキュメントを眺めてみると、sceAudio系のAPIをフック出来ればよさそう。デコード系のAPIは、デコードしてWAVにするようなものばかりなので、最終的にはsceAudioを呼ぶんだろう。pspAudio系はPSP SDKの独自実装だから内部でsceAudioを呼んでるはず。XMBでの動作は作ってみるまでわからないけど、ゲームだけなら大丈夫そう。
とりあえず、prxモジュールのHello worldを作る。PSPカーネルにはKprintfっていうデバッグ用のAPIがあるんだけど、この出力先はシリアルポート、つまりリモコン端子。このPSPはリモコン端子も基盤から剥がれてるのでつなげることが出来ない。少し前ならPSPLink使ってUSB経由で出力する、って手もあったけど、最近は動かなくなっている。ということなので、テキストファイルに書き出して動作確認することにする。prxのサンプルから引っ張ってきて、main.cだけ弄る。

/* main.c */
#include <pspkernel.h>
#include <pspiofilemgr.h>

PSP_MODULE_INFO("RemoteSpk", PSP_MODULE_KERNEL, 1, 1);

SceUID log;
/* エントリポイント */
int module_start(SceSize args, void *argp)
{
	log=sceIoOpen("ms0:/log.txt",PSP_O_CREAT|PSP_O_WRONLY|PSP_O_TRUNC,0777);
	sceIoWrite(log,"Hello,world!\n",13);
	sceIoClose(log);
	
	return 0;
}

/* 終了時に呼ばれてなさそう。いつ呼ばれるんだ? */
int module_stop(SceSize args, void *argp)
{
	return 0;
}

こんな感じでmakeすると、remotespk.prxが出来てるのでメモリースティックのルートの下のsepluginsディレクトリに放り込んで、vsh.txtとかに追記して、リカバリモードでプラグインを有効にしてあげて、PSPを起動するとメモリースティックのルートのlog.txtが出来てちゃんとHello,worldって書いてある。よしよし。次に、sceAudio系のフックをする。とりあえずRemoteJoyのコードを読んで参考にするか。

/* psplinkusb/tools/remotejoy/main.c  抜粋*/
#define GET_JUMP_TARGET(x) (0x80000000 | (((x) & 0x03FFFFFF) << 2))
int hook_ctrl_function(unsigned int* jump)
{
	unsigned int target;
	unsigned int func;
	int inst;

	target = GET_JUMP_TARGET(*jump);
	inst = _lw(target+8);
	if((inst & ~0x03FFFFFF) != 0x0C000000)
	{
		return 1;
	}

	g_ctrl_common = (void*) GET_JUMP_TARGET(inst);

	func = (unsigned int) ctrl_hook_func;
	func = (func & 0x0FFFFFFF) >> 2;
	_sw(0x0C000000 | func, target+8);

	return 0;
}

_swってなんだよ、とおもってgrepかけたらpsptypes.hに書いてあった。ちなみに、psptypes.hはpspkernel.hから間接的にincludeされてるから、ほとんどのコードで定義されてる。

static __inline__ void _sw(u32 val, u32 addr) { *(vu32 *)addr = val; }

あー、SaveStore wordね。MIPSアセンブラで散々つかったわ。つーか、これくらい関数にするんじゃねーよ、突然ニーモニック出てきてもわかんねーよ。で、コードの中身をみる。これは、外からhook_ctrl_function((unsigned int*)sceCtrlHogehoge);って感じで使う。なるほど、関数ポインタをunsigned intのポインタにキャストして関数を書き換えるのか。つーか、このコードって動くんだ。
MIPSのジャンプ系の命令は、上位6ビットが命令をあらわすビット列、残り26ビットにジャンプ先のアドレスを2右シフトされたものが入ってる。なので、GET_JUMP_TARGETマクロを使うとジャンプ先のアドレスを取得できる、と。同様に、ctrl_hook_func関数へのアドレスを2ビット右にシフトして、上に000011を加えてjal命令にして書き込んでる、ってわけか。教科書に簡単なMIPS機械語が載ってて助かったぜ。これを参考にsceAudioのフックを作ればよさそうだな。