/*
 *	dkscan - カーネルサービス検索ユーティリティー
 *
 *	Copyright (C) 2004 Naoyuki Sawa
 *
 *	* Wed Sep 27 06:00:00 JST 2004 Naoyuki Sawa
 *	- 作成開始。
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <zlib.h>

#define VERSION		"20041027"

/* [NOTE]
 * * コマンドライン引数のワイルドカードを展開するために、
 *   [プロジェクトの設定]->[リンク]->[オブジェクト/ライブラリモジュール]に
 *   「setargv.obj」を追加しています。
 *   ただし、本プログラムは複数の入力ファイルを受け付けないので、
 *   ワイルドカード展開はコマンド入力の手間を省く程度の目的に過ぎません。
 */

/****************************************************************************
 *
 ****************************************************************************/

/* エラーメッセージを表示して終了します。
 */
void
err(const char* fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	 fprintf(stderr, "### ");
	vfprintf(stderr, fmt, ap);
	 fprintf(stderr, "\n");
	va_end(ap);

	exit(-1);
}

/****************************************************************************
 *
 ****************************************************************************/

/* 使い方を表示して、終了します。
 */
void
usage()
{
	//		01234567890123456789012345678901234567890123456789012345678901234567890123456789
	fprintf(stderr,"dkscan - カーネルサービス検索ユーティリティ (%s)\n", VERSION);
	fprintf(stderr,"Copyright (C) 2004 Naoyuki Sawa\n");
	fprintf(stderr,"\n");
	fprintf(stderr,"使い方: dkscan filename[.pex]\n");
	fprintf(stderr,"  filename[.pex] 検索対象の、pexファイル名を指定してください。\n");
	fprintf(stderr,"                 拡張子.pexは省略可能です。\n");
	fprintf(stderr,"\n");
	fprintf(stderr,"オプション:\n");
	fprintf(stderr,"  -d[dump[.bin]] pex展開イメージを、指定ファイルへ出力します。\n");
	fprintf(stderr,"                 ファイル指定を省略すると、filename.binへ出力します。\n");
	fprintf(stderr,"\n");
	fprintf(stderr,"検索結果の出力先は常に標準出力です。必要に応じてリダイレクトしてください。\n");
	exit(-1);
}

/****************************************************************************
 *
 ****************************************************************************/

#define KSVECTORTOP	0x20		/* カーネルサービステーブルの先頭アドレス */
#define KSMAX		248		/* カーネルサービスが使用するベクタの最大数 */

const char* kstbl[KSMAX];		/* カーネルサービス名称テーブル */

/* カーネルサービス名称テーブルを初期化します。
 */
void
init_kstbl()
{
	kstbl[  0] = "pceVectorSetTrap";
	kstbl[  1] = "pceVectorSetKs";
	kstbl[  2] = "pceSystemGetInfo";
	kstbl[  3] = "pceDebugSetMon";

	kstbl[  4] = "pceAppSetProcPeriod";
	kstbl[  5] = "pceAppReqExit";
	kstbl[  6] = "pceAppExecFile";
	kstbl[  7] = "pceAppActiveResponse";

	kstbl[  8] = "pceUSBDisconnect";
	kstbl[  9] = "pceUSBReconnect";
	kstbl[ 10] = "pceUSBSetupMode";

	kstbl[ 12] = "pcePadGetDirect";
	kstbl[ 13] = "pcePadGetProc";
	kstbl[ 14] = "pcePadGet";
	kstbl[ 15] = "pcePadSetTrigMode";

	kstbl[ 16] = "pceLCDDispStart";
	kstbl[ 17] = "pceLCDDispStop";
	kstbl[ 18] = "pceLCDTrans";
	kstbl[ 19] = "pceLCDSetBuffer";
	kstbl[ 20] = "pceLCDSetOrientation";
	kstbl[ 21] = "pceLCDSetBright";
	kstbl[ 22] = "pceLCDTransDirect";
	kstbl[ 23] = "pceLCDTransRange";

	kstbl[ 24] = "pceFontGetAdrs";
	kstbl[ 25] = "pceFontPut";
	kstbl[ 26] = "pceFontSetType";
	kstbl[ 27] = "pceFontSetPos";
	kstbl[ 28] = "pceFontPutStr";
	kstbl[ 29] = "pceFontPrintf";
	kstbl[ 30] = "pceFontSetTxColor";
	kstbl[ 31] = "pceFontSetBkColor";

	kstbl[ 32] = "pcesprintf";
	kstbl[ 33] = "pcevsprintf";
	kstbl[ 34] = "pceCRC32";

	kstbl[ 36] = "pceCPUSetSpeed";

	kstbl[ 40] = "pceWaveCheckBuffs";
	kstbl[ 41] = "pceWaveDataOut";
	kstbl[ 42] = "pceWaveSetChAtt";
	kstbl[ 43] = "pceWaveSetMasterAtt";
	kstbl[ 44] = "pceWaveStop";
	kstbl[ 45] = "pceWaveAbort";

	kstbl[ 48] = "pceTimeSet";
	kstbl[ 49] = "pceTimeGet";
	kstbl[ 50] = "pceTimeSetAlarm";
	kstbl[ 51] = "pceTimeGetAlarm";

	kstbl[ 64] = "pceFlashErase";
	kstbl[ 65] = "pceFlashWrite";

	kstbl[ 68] = "pceTimerSetContextSwitcher";
	kstbl[ 69] = "pceTimerAdjustPrecisionCount";
	kstbl[ 70] = "pceTimerSetCallback";
	kstbl[ 71] = "pceTimerGetCount";

	kstbl[ 72] = "pceFileFindOpen";
	kstbl[ 73] = "pceFileFindNext";
	kstbl[ 74] = "pceFileFindClose";
	kstbl[ 75] = "pceFileLoad";
	kstbl[ 76] = "pceFileOpen";
	kstbl[ 77] = "pceFileReadSct";
	kstbl[ 78] = "pceFileWriteSct";
	kstbl[ 79] = "pceFileClose";

	kstbl[ 80] = "pcePowerSetReport";
	kstbl[ 81] = "pcePowerGetStatus";
	kstbl[ 82] = "pcePowerForceBatt";
	kstbl[ 83] = "pcePowerEnterStandby";

	kstbl[ 88] = "pceIRStartRx";
	kstbl[ 89] = "pceIRStartTx";
	kstbl[ 90] = "pceIRGetStat";
	kstbl[ 91] = "pceIRStop";
	kstbl[ 92] = "pceIRStartRxEx";
	kstbl[ 93] = "pceIRStartTxEx";
	kstbl[ 94] = "pceIRStartRxPulse";
	kstbl[ 95] = "pceIRStartTxPulse";

	kstbl[ 96] = "pceLCDPoint";
	kstbl[ 97] = "pceLCDLine";
	kstbl[ 98] = "pceLCDPaint";
	kstbl[ 99] = "pceLCDSetObject";
	kstbl[100] = "pceLCDDrawObject";

	kstbl[104] = "pceUSBCOMSetup";
	kstbl[105] = "pceUSBCOMStartRx";
	kstbl[106] = "pceUSBCOMStartTx";
	kstbl[107] = "pceUSBCOMStop";
	kstbl[108] = "pceUSBCOMGetStat";

	kstbl[112] = "pceHeapAlloc";
	kstbl[113] = "pceHeapFree";
	kstbl[114] = "pceHeapRealloc";
	kstbl[115] = "pceHeapGetMaxFreeSize";

	kstbl[120] = "pceFileCreate";
	kstbl[121] = "pceFileDelete";
	kstbl[122] = "pceFileSetBufferMode";

	/* 192〜223 は同期用特殊サービス */

	/* ※TODO: 同期用特殊サービスは呼び出し手順が違うので、2004/10/27現在は検出できません。 */
	kstbl[192] = "pceSyncEnterCriticalSection";
	kstbl[193] = "pceSyncLeaveCriticalSection";
	kstbl[194] = "pceSyncSafetyAddition";
	kstbl[195] = "pceSyncSafetySetIL";
	kstbl[196] = "pceTimerGetPrecisionCount";

	/* 224〜247 はユーザ拡張用 */

	/* 以下224〜235はMMC制御用APIとして利用 2004/05/02 Add by Madoka */
	kstbl[224] = "mmcAppExecFile";
	kstbl[225] = "mmcExit";
	kstbl[226] = "mmcFileClose";
	kstbl[227] = "mmcFileFindClose";
	kstbl[228] = "mmcFileFindNext";
	kstbl[229] = "mmcFileFindOpen";
	kstbl[230] = "mmcFileOpen";
	kstbl[231] = "mmcFileReadMMCSct";
	kstbl[232] = "mmcFileReadSct";
	kstbl[233] = "mmcGetCIDInfo";
	kstbl[234] = "mmcGetCSDInfo";
	kstbl[235] = "mmcInit";
}

/****************************************************************************
 *
 ****************************************************************************/

typedef struct _pffsFileHEADER {
	unsigned char mark;			/* + 0: マーク */
	unsigned char type;			/* + 1: ファイルタイプ */
	unsigned short ofs_data;		/* + 2: 実データへのオフセット */
	unsigned short ofs_name;		/* + 4: キャプションへのオフセット */
	unsigned short ofs_icon;		/* + 6: アイコンへのオフセット */
	unsigned long resv2;			/* + 8: 予約 */
	unsigned long top_adrs;			/* +12: 先頭のアドレス */
	unsigned long length;			/* +16: 長さ */
	unsigned long crc32;			/* +20: CRC32 */
} pffsFileHEADER;


#define MAX_PROG_SIZE	(256 * 1024)				/* P/ECEプログラム最大サイズ */
unsigned  char  pex[MAX_PROG_SIZE / sizeof(unsigned  char)];	/* pexファイル読み込みバッファ */
unsigned short prog[MAX_PROG_SIZE / sizeof(unsigned short)];	/* pexプログラム展開バッファ */
int prog_size; /* ハーフワード単位じゃないよ、バイト単位です!! */

void
load_pex(const char* path)
{
	int retval;
	FILE* fp;
	int pex_size;
	pffsFileHEADER* fh;

	/* pexファイルを読み込みます。 */
	fp = fopen(path, "rb");
	if(!fp) {
		err("%s: ファイルが開けません。", path);
	}
	pex_size = fread(pex, 1, sizeof pex, fp);
	fclose(fp);

	/* pexファイルを検査します。 */
	if(pex_size < sizeof(pffsFileHEADER)) {
		err("%s: ファイルサイズが不正です。", path);
	}
	fh = (pffsFileHEADER*)pex;
	fh->ofs_data += 8; /* 実データの先頭にも{len,crc}があります。仕様!! */
	fh->length   -= 8; /* 実データの先頭にも{len,crc}があります。仕様!! */
	if(fh->mark != 'X') {
		err("%s: マークが不正です。", path);
	}
	if(fh->ofs_data > pex_size - 1) {
		err("%s: 実データへのオフセットが不正です。", path);
	}
	if(fh->ofs_data + fh->length > (unsigned)pex_size) {
		err("%s: 実データへのオフセットまたは長さが不正です。", path);
	}

	/* pexプログラムを展開します。 */
	prog_size = sizeof prog;
	retval = uncompress((unsigned char*)prog, &prog_size, &pex[fh->ofs_data], fh->length);
	if(retval != Z_OK) {
		err("%s: 展開できません。", path);
	}
}

/****************************************************************************
 *
 ****************************************************************************/

void
scan_ks()
{
	int i;
	int code;
	int ksno;
	int addr;
	const char* ks;

	printf("---------+------+------------------------------\n");
	printf(" Address | Ksno | Name\n");
	printf("---------+------+------------------------------\n");
	//     " $100000 |  100 | pceTimerAdjustPrecisionCount"
#define FMT    " $%06x | %4d | %s\n"

	for(i = 0; i < (int)(prog_size / sizeof(unsigned short) - 3); i++) {
	//                             ~~~~~~~~~~~~~~~~~~~~~~~~忘れないで!!
		/* ext命令で、オペランドが4の倍数? */
		code = prog[i];
		if((code & 0xe003) != 0xc000) {
			continue;
		}

		/* カーネルサービス番号が0〜KSMAX-1? */
		ksno = ((code & ~0xe003) - 0x20) / 4;
		if(ksno < 0 || ksno > KSMAX - 1) {
			continue;
		}

		/* 後続コードが 3089:"ld.w %r9,[%r8]", 0689:"jp %r9" ? */
		if(prog[i + 1] != 0x3089) {
			continue;
		}
		if(prog[i + 2] != 0x0689) {
			continue;
		}

		/* カーネルサービスの呼び出しです。 */
		addr = 0x100000 + i * 2;
		ks = kstbl[ksno];
		if(!ks) ks = "";
		printf(FMT, addr, ksno, ks);
	}
	printf("---------+------+------------------------------\n");

#undef FMT
}

/****************************************************************************
 *
 ****************************************************************************/

int dump; /* -dオプション指定 0:無し/1:有り */

char pex_path[_MAX_PATH];
char bin_path[_MAX_PATH];

char drive[_MAX_DRIVE];
char dir[_MAX_DIR];
char fname[_MAX_FNAME];
char ext[_MAX_EXT];

int
main(int argc, char* argv[])
{
	int retval;
	int i;
	FILE* fp;

	/* カーネルサービス名称テーブルを初期化します。 */
	init_kstbl();

	/* コマンドラインオプションを解析します。 */
	for(i = 1; i < argc; i++) {
		if(argv[i][0] != '-') break;
		switch(argv[i][1]) {
		case 'd':
			if(dump) usage(); /* 二重指定 */
			dump = 1;
			strncpy(bin_path, &argv[i][2], sizeof bin_path - 1);
			break;
		default: /* 不明なコマンドラインオプション */
			usage();
		}
	}

	/* pex,binファイル名を作成します。 */
	if(i != argc - 1) usage(); /* ファイル名指定が不正 */
	strncpy(pex_path, argv[i], sizeof pex_path - 1);
	_splitpath(pex_path, drive, dir, fname, ext);
	if(!*ext) strcpy(ext, "pex");
	_makepath(pex_path, drive, dir, fname, ext);
	printf("scan: %s\n", pex_path);
	if(dump) {
		if(!*bin_path) { /* ダンプファイル指定無し */
			strncpy(bin_path, pex_path, sizeof bin_path);
			_splitpath(bin_path, drive, dir, fname, ext);
			strcpy(ext, "bin"); /* 無条件で拡張子変更 */
			_makepath(bin_path, drive, dir, fname, ext);
		} else { /* ダンプファイル指定有り */
			_splitpath(bin_path, drive, dir, fname, ext);
			if(!*ext) strcpy(ext, "bin");
			_makepath(bin_path, drive, dir, fname, ext);
		}
		printf("dump: %s\n", bin_path);
	}

	/* pexファイルを読み込みます。 */
	load_pex(pex_path);

	/* pex展開イメージをダンプ出力します。 */
	if(dump) {
		fp = fopen(bin_path, "wb");
		if(!fp) {
			err("%s: ファイルが作成できません。", bin_path);
		}
		retval = fwrite(prog, 1, prog_size, fp);
		if(retval != prog_size) {
			err("%s: ファイル書き込みエラーです。", bin_path);
		}
		fclose(fp);
	}

	/* カーネルサービス呼び出しを検索します。 */
	scan_ks();

	return 0;
}

