/*	
 *	supp.c
 *
 *	シンプルライブラリサポートルーチン集
 *	Copyright (C) 2002-2006 Naoyuki Sawa
 *
 *	* Sat Sep  7 18:26:00 JST 2002 Naoyuki Sawa
 *	- 作成開始。
 *	* Sun Dec  1 19:12:00 JST 2002 Naoyuki Sawa
 *	- bgdisp()を追加しました。
 *	* Sun Dec  1 21:22:00 JST 2002 Naoyuki Sawa
 *	- symbolf()を追加しました。
 *	* Sun Dec  2 07:11:00 JST 2002 Naoyuki Sawa
 *	- fxput()/fxputstr()/fxprintf()を追加しました。
 *	  追記:->grfont()/grputstr()/grprintf()に名称変更。
 *	* Sun Dec  2 19:00:00 JST 2002 Naoyuki Sawa
 *	- スプライト系関数のプレフィクスをsp*、ch*に統一。
 *	  ＢＧ系関数のプレフィクスをbg*に統一。
 *	  グラフィック系関数のプレフィクスをgr*に統一。
 *	* Tue Dec  3 05:54:00 JST 2002 Naoyuki Sawa
 *	- die()を追加。
 *	* Wed Dec  4 05:52:00 JST 2002 Naoyuki Sawa
 *	- bgcls()/bgput()を追加。
 *	* Fri Dec 27 12:15:00 JST 2002 Naoyuki Sawa
 *	- grfont()からフォント初期化を分離し、fntadr()を追加。
 *	- pausedisp()を追加。
 *	* Wed Jan  1 11:02:00 JST 2003 Naoyuki Sawa
 *	- grfont()に背景色指定を追加。
 *	  grputstr()/grprintf()の色指定も同じ仕様となります。
 *	* Mon May 24 19:00:00 JST 2004 Naoyuki Sawa
 *	- ドラム音色分離キットをv1.00→v2.10に変更しました。
 *	- die()関数のコメントに、コンパイラ警告に関するコメントを追記しました。
 *	- CLiPライブラリのvsprintf()がリンクされると、他にもいろいろ必要になってしまうので、
 *	  P/ECE内蔵のpcevsprintf()が選択されるよう、関数名を明示しました。
 *	* Sat Dec 18 17:00:00 JST 2004 Naoyuki Sawa
 *	- die()関数のコンパイル時警告に関するコメントを追加しました。
 *	- turbo()関数の実装を追加しました。
 *	  pceAppNotify()にて、終了時に確実にturboモードを解除するようにしました。
 *	* Wed Mar 29 04:35:00 JST 2006 Naoyuki Sawa
 *	- turbo()関数を、CLiPライブラリ本体に合わせて更新しました。
 *	- errno変数の定義を、各アプリケーション個別から、本モジュールへ移動しました。
 *	* Sat Apr 01 04:15:00 JST 2006 Naoyuki Sawa
 *	- BG面のスクロール位置を設定する関数を bgscrl()→bghome() に改名しました。
 *	- BG面のスクロール位置を取得する関数 bghomex()、bghomey() を追加しました。
 *	* Sat Apr 22 19:24:00 JST 2006 Naoyuki Sawa
 *	- rnd_range()を追加しました。
 *	* Wed Aug 24 21:05:06 JST 2016 Naoyuki Sawa
 *	- しばらくの間suppフォルダをメンテナンスしていなくて、他のモジュールと整合性が取れなくなっていました。
 *	  またsuppフォルダを使えるように、メンテナンスを行いました。
 *	  具体的には、以下の通りです。
 *	- die2()関数を、/clip/clipdie.cから/clip/supp/supp.cへ、コピーペーストしました。
 *	  2012/01/14のclipmisc.hの変更により、DIEマクロはdie()を直接呼び出さず、die2()を経由してdie()を呼び出すようになっていたからです。
 *	- EnterCS(),LeaveCS()関数を、/clip/clipmisc.cから/clip/supp/supp.cへ、コピーペーストしました。
 *	  2016/06/13のclipmisc.hの変更により、ENTER_CS,LEAVE_CSマクロはpceSyncEnterCriticalSection(),pceSyncLeaveCriticalSection()を直接呼び出さず、
 *	  EnterCS(),LeaveCS()を経由してpceSyncEnterCriticalSection(),pceSyncLeaveCriticalSection()を呼び出すようになっていたからです。
 */
#include "supp.h"

/*****************************************************************************
 *	グローバル変数
 *****************************************************************************/

/* S1C33 Family Cコンパイラパッケージでは、errnoはアプリケーション側で定義する必要があります。
 * [S5U1C33000C_J.pdf p.140]
 */
int errno;

/****************************************************************************
 *	die
 ****************************************************************************/

/* CLiPのdie()と同じ機能を持った関数の、シンプルライブラリ版の実装です。
 * 次のような警告が出ますが、無視してください。
 *
 *	supp.c: In function `die':
 *	supp.c:51: warning: `noreturn' function does return
 */
void
die(const char* fmt, ...)
{
extern void simple_error(char* fmt, ...); /* シンプルライブラリの非公開API */

	va_list ap;

	va_start(ap, fmt);
	pcevsprintf(_def_vbuff, fmt, ap); /* システム用仮想画面を利用!! */
	va_end(ap);

	simple_error(_def_vbuff);
	thread_return();

	/* * Sat Dec 18 17:00:00 JST 2004 Naoyuki Sawa
	 * - thread_return()の宣言に__attribute__((noreturn))が無いため、
	 *   コンパイラが警告"warning: `noreturn' function does return"を表示します。
	 *   実際にはthread_return()は処理を返さないので、動作上の問題はありません。
	 *   コンパイラの警告は無視してください。
	 */
}

//{{2016/08/24追加:die2()関数を、/clip/clipdie.cから/clip/supp/supp.cへ、コピーペーストしました。
/*__declspec(noreturn)*/ void die2(const char* file, int line) /*__attribute__((noreturn))*/ {
	die("%s(%d)", file, line);
}
//}}2016/08/24追加:die2()関数を、/clip/clipdie.cから/clip/supp/supp.cへ、コピーペーストしました。

/*****************************************************************************
 *	EnterCS,LeaveCS
 *****************************************************************************/

//{{2016/08/24追加:EnterCS(),LeaveCS()関数を、/clip/clipmisc.cから/clip/supp/supp.cへ、コピーペーストしました。
CRITICAL_SECTION EnterCS() {
	CRITICAL_SECTION cs;
	pceSyncEnterCriticalSection(&cs);
	return cs;
}
void LeaveCS(CRITICAL_SECTION cs) {
	pceSyncLeaveCriticalSection(&cs);
}
//}}2016/08/24追加:EnterCS(),LeaveCS()関数を、/clip/clipmisc.cから/clip/supp/supp.cへ、コピーペーストしました。

/****************************************************************************
 *	fntadr
 ****************************************************************************/

/* フォントデータアドレスを取得します。
 * [in]
 *	code		文字コード(0〜255、bit31-8は無視します)
 * [out]
 *	戻り値		フォントデータアドレス
 * [note]
 *	* フォントデータは、1ライン1バイトで、1〜8ラインの順に並んでいます。
 */
const unsigned char*
fntadr(int code)
{
extern unsigned char FONT[];
	static unsigned char* font; /* ＳＴＡＴＩＣです！ */

	PBMP_FILEHEADER* header;
	unsigned char tmp[128]; /* ※少し大きいので、もしかしたらスタック上に取るのは危険かも？ */
	unsigned char* dst;
	unsigned char a, b;
	int row, col, line, bit;

	/* 初回のみ、フォントデータを初期化します。 */
	if(font == NULL) {
		/* フォントグラフィックデータを取得。 */
		header = (PBMP_FILEHEADER*)FONT;
		if((strncmp((char*)&header->head, "PMBP", 4) != 0) ||	/* シグネチャ検査 */
		   (header->bpp != 1 || header->mask != 0) ||		/* 1bitマスクなし？ */
		   (header->w != 128 || header->h != 128)) DIE();	/* 128x128？ */
		font = (unsigned char*)(header + 1);
		/* グラフィックデータ(ライン単位)をキャラクタデータ(文字単位)に並び替え。 */
		dst = font;
		for(row = 0; row < 16; row++) {				/* 16行分ループ */
			memcpy(tmp, dst, 128);				/* 現在行のグラフィックデータを一時バッファにコピー */
			for(col = 0; col < 16; col++) {			/* 16文字分ループ */
				for(line = 0; line < 8; line++) {	/* 8ライン分ループ */
					a = tmp[line * 16 + col];	/* 現在文字のラインデータ取得 */
					b = 0;
					for(bit = 0; bit < 8; bit++) {
						b <<= 1;		/* 左右ビット反転(Draw系->スプライト系変換) */
						b |= a & 1;
						a >>= 1;
					}
					*dst++ = b;			/* 現在文字のラインデータ格納 */
				}
			}
		}
	}

	/* 文字のフォントアドレス取得。(フォントデータは、文字単位並びで、一文字8バイトです) */
	return font + (code & 0xff) * 8;
}

/****************************************************************************
 *	spcls
 ****************************************************************************/

void
spcls(int obj)
{
	while(obj < N_OBJS) sppos(obj++, -8, -8);
}

/****************************************************************************
 *	chdisp
 ****************************************************************************/

/* 複数のスプライトから構成される、キャラを表示します。
 * [in]
 *	obj		最初のスプライトプレーン番号
 *	x, y		キャラの中心座標
 *	chr		キャラ構造体へのポインタ
 * [out]
 *	戻り値		最後のスプライトプレーン番号＋１
 * [note]
 *	* spdisp()への引数で64を越えるスプライトプレーン番号を指定した場合、
 *	  spdisp()が何もせずに処理を返す仕様に依存しています。
 *	  chdisp()関数自身はスプライトプレーン番号の範囲を考慮していません。
 */
int
chdisp(int obj, int x, int y, const CHR* chr)
{
	int col, row, pno;

	/* 中心座標へのオフセット分、原点座標をずらします。 */
	x -= chr->x;
	y -= chr->y;

	/* キャラを構成する、各スプライトを表示します。 */
	for(row = 0; row < chr->h; row++) {
		for(col = 0; col < chr->w; col++) {
			pno = chr->pno + 0x10 * row + col;
			spdisp(obj++, x + col * 8, y + row * 8, pno);
		}
	}

	return obj;
}

/****************************************************************************
 *	sphit
 ****************************************************************************/

/* スプライト同士のピクセル単位の当たり判定を行います。
 * [in]
 *	(x1,y1),pno1	スプライト1の座標とパターン番号
 *	(x2,y2),pno2	スプライト2の座標とパターン番号
 * [out]
 *	戻り値		当たりなら1、外れなら0を返します。
 * [note]
 *	* 当たり判定は、スプライトパターンのマスク成分だけを使って行います。
 *	  両方のスプライトパターンのマスク成分が1ピクセルでも重なっていれば、
 *	  当たりと見なします。
 */
int
sphit(int x1, int y1, int pno1, int x2, int y2, int pno2)
{
	extern unsigned char PAT[];

	int i;
	unsigned char *p1, *p2;
	unsigned short mask1, mask2;

	/* 大まかな判定。 */
	if(x1 + 8 - 1 < x2) return 0; /* スプライト1がスプライト2より左 */
	if(y1 + 8 - 1 < y2) return 0; /* スプライト1がスプライト2より上 */
	if(x2 + 8 - 1 < x1) return 0; /* スプライト1がスプライト2より右 */
	if(y2 + 8 - 1 < y1) return 0; /* スプライト1がスプライト2より下 */

	/* スプライトパターンアドレスを計算。 */
	p1 = &PAT[(pno1 & 0xff/*念のため*/) * 8 * 3/*bit1,bit0,mask*/];
	p2 = &PAT[(pno2 & 0xff/*念のため*/) * 8 * 3/*bit1,bit0,mask*/];

	/* 各ラインでのピクセル単位の当たり判定。 */
	if(y1 <= y2) {
		i = y2 - y1;
		p1 += i * 3;
	} else {
		i = y1 - y2;
		p2 += i * 3;
	}
	if(x1 <= x2) {
		x2 -= x1;
		      x1 = 0;
	} else {
		x1 -= x2;
		      x2 = 0;
	}
	do {
		/* 注意：スプライトデータは0:不透明/1:透明 */
		mask1 = (p1[2/*mask*/] ^ 0xff) << x1;
		mask2 = (p2[2/*mask*/] ^ 0xff) << x2;
		if(mask1 & mask2) return 1;
		p1 += 3;
		p2 += 3;
	} while(++i < 8);

	return 0;
}

/****************************************************************************
 *	chhit
 ****************************************************************************/

/* キャラ同士のピクセル単位の当たり判定を行います。
 * [in]
 *	(x1,y1),chr1	キャラ1の座標とパターン番号
 *	(x2,y2),chr1	キャラ2の座標とパターン番号
 * [out]
 *	戻り値		当たりなら1、外れなら0を返します。
 * [note]
 *	* sphit()の[note]を参照してください。
 */
int
chhit(int x1, int y1, const CHR* chr1, int x2, int y2, const CHR* chr2)
{
	int col1, row1, pno1, col2, row2, pno2;

	/* 中心座標へのオフセット分、原点座標をずらします。 */
	x1 -= chr1->x;
	y1 -= chr1->y;
	x2 -= chr2->x;
	y2 -= chr2->y;

	/* 大まかな判定。 */
	if(x1 + chr1->w * 8 - 1 < x2) return 0; /* キャラ1がキャラ2より左 */
	if(y1 + chr1->h * 8 - 1 < y2) return 0; /* キャラ1がキャラ2より上 */
	if(x2 + chr2->w * 8 - 1 < x1) return 0; /* キャラ1がキャラ2より右 */
	if(y2 + chr2->h * 8 - 1 < y1) return 0; /* キャラ1がキャラ2より下 */

	/* キャラ1とキャラ2の全スプライトの総当り判定。 */
	for(row1 = 0; row1 < chr1->h; row1++) {
		for(col1 = 0; col1 < chr1->w; col1++) {
			pno1 = chr1->pno + 0x10 * row1 + col1;
			for(row2 = 0; row2 < chr2->h; row2++) {
				for(col2 = 0; col2 < chr2->w; col2++) {
					pno2 = chr2->pno + 0x10 * row2 + col2;
					if(sphit(x1 + col1 * 8, y1 + row1 * 8, pno1,
						 x2 + col2 * 8, y2 + row2 * 8, pno2)) return 1;
				}
			}
		}
	}

	return 0;
}

/****************************************************************************
 *	bgcls
 ****************************************************************************/

/* BGをキャラクタ0でクリアします。
 * [note]
 *	* シンプルライブラリのcls()を使ったのでは、16x11の範囲しかクリアできません。
 *	  cls()が、簡易コンソールpclSpriteBGClear()を利用しているからです。
 *	  そこで、32x32の範囲全てをクリアする関数を用意しました。
 */
void
bgcls()
{
	memset(pclSpriteBGGetAdr(0), 0, sizeof(short) * BG_CX * BG_CY);
}

/****************************************************************************
 *	bgput
 ****************************************************************************/

/* BGに1キャラクタ置きます。
 * [in]
 *	x, y		転送先の左上位置(0〜31)
 *	ch		キャラクタコード。
 * [note]
 *	* シンプルライブラリのlocate()とprintstr()/siprintf()等を使ったのでは、
 *	  16x11の範囲外にキャラクタを置けません。(ピクセルスクロールに対応できない)
 *	  また、(15,10)の位置にキャラクタを置くと、キャラクタスクロールしてしまいます。
 *	  printstr()等が、簡易コンソール関数pclSpriteBGPutCharacter()を利用しているからです。
 *	  そこで、pclSpriteBGSetCharacter()相当の関数を用意しました。
 */
void
bgput(int x, int y, int code)
{
	pclSpriteBGSetCharacter(0, x & 31, y & 31, code);
}

/****************************************************************************
 *	bgdisp
 ****************************************************************************/

/* 複数のスプライトから構成されるキャラを、BGに表示します。
 * [in]
 *	x, y		転送先の左上位置(0〜31)
 *	chr		キャラ構造体へのポインタ
 * [note]
 *	* キャラ構造体の中心オフセット(x,y)は参照しません。
 */
void
bgdisp(int x, int y, const CHR* chr)
{
	int col, row, pno;

	/* キャラを構成する、各スプライトを表示します。 */
	for(row = 0; row < chr->h; row++) {
		for(col = 0; col < chr->w; col++) {
			pno = chr->pno + 0x10 * row + col;
			pclSpriteBGSetCharacter(0, (x + col) & 31, (y + row) & 31, pno);
		}
	}

}

/****************************************************************************
 *	bghome
 ****************************************************************************/

/* BG面のスクロール位置を設定します。
 * [in]
 *	x, y		スクロール位置
 * [note]
 *	* シンプルライブラリには、グラフィック面のスクロール位置を設定する
 *	  home()関数はあるのですが、BG面のスクロール位置を設定する関数が
 *	  なかったので、自前で用意しました。
 */
void
bghome(int x, int y)
{
	pclSpriteBGSetPosition(0, x, y);
}

/****************************************************************************
 *	bghomex
 ****************************************************************************/

/* BG面の水平スクロール位置を取得します。
 * [out]
 *	戻り値		水平スクロール位置
 * [note]
 *	* シンプルライブラリには、グラフィック面の水平スクロール位置を取得する
 *	  homex()関数はあるのですが、BG面の水平スクロール位置を取得する関数が
 *	  なかったので、自前で用意しました。
 */
int
bghomex()
{
	return pclSpriteBGGetX(0);
}

/****************************************************************************
 *	bghomey
 ****************************************************************************/

/* BG面の垂直スクロール位置を取得します。
 * [out]
 *	戻り値		垂直スクロール位置
 * [note]
 *	* シンプルライブラリには、グラフィック面の垂直スクロール位置を取得する
 *	  homey()関数はあるのですが、BG面の垂直スクロール位置を取得する関数が
 *	  なかったので、自前で用意しました。
 */
int
bghomey()
{
	return pclSpriteBGGetY(0);
}

/****************************************************************************
 *	grcls
 ****************************************************************************/

void
grcls(int color)
{
	unsigned char mask, bit1, bit0;
	unsigned char* p;
	int y;

	switch(color) {
	case  0: bit1 = 0x00; bit0 = 0x00; mask = 0x00; break;
	case  1: bit1 = 0x00; bit0 = 0xff; mask = 0x00; break;
	case  2: bit1 = 0xff; bit0 = 0x00; mask = 0x00; break;
	case  3: bit1 = 0xff; bit0 = 0xff; mask = 0x00; break;
	default: bit1 = 0x00; bit0 = 0x00; mask = 0xff; break;
	}
	p = pclSpriteBGGetFrameAdr(1);
	for(y = 0; y < 256; y++) {
		memset(p, bit1, 256 / 8); p += 256 / 8;
		memset(p, bit0, 256 / 8); p += 256 / 8;
		memset(p, mask, 256 / 8); p += 256 / 8;
	}
}

/****************************************************************************
 *	paint
 ****************************************************************************/

/* * Sat Jul 20 08:18:00 JST 2002 Naoyuki Sawa
 * - 上記タイムスタンプの時点でのrender_paint()をベースに、シンプルライブラリ用に移植しました。
 *   移植に際してコメントは修正していませんので、一部、API使用を想定したコメントが残っています。
 * - 塗りつぶされる色を保持する変数「c」の型に注意！
 *   API用render_paint()ではunsigned charでしたが、シンプルライブラリではintです。
 *   point()は透明値として-1を返すので、intで保持しておかないと透明部分の塗りつぶしができません。
 */

/* 全く高速化していません。遅いです。
 * ゲームでは、高速塗りつぶしが要求される場面は少ないと思うので、
 * 現状のままでも特に問題はないと思います。
 */
void
paint(int x0, int y0, int color)
{
/* 塗りつぶし点スタックサイズ：
 * 実験したところ、かなり複雑な塗りつぶしでも24程度でしたが、
 * 念のため、余裕を見込んで、大きめに取っておくことにします。
 * あまり大きすぎると、P/ECEではスタックがあふれるので、ご注意ください。
 */
#define RENDER_PAINT_STACK_SIZE	64

/* ビューポートサイズ：
 * 画面に見えている部分(DISP_X×DISP_Y)ではなく、
 * グラフィック面全体のサイズ(256×256)とします。
 */
#define VP_LEFT		  0
#define VP_RIGHT	256
#define VP_TOP		  0
#define VP_BOTTOM	256

	short stack[RENDER_PAINT_STACK_SIZE][2]; /* メモリ節約のためshortで */
	int i_stack, x, y, x1, x2;
	int c; /* APIではunsigned charですが、シンプルライブラリではintです！上のコメント参照 */

	/* 塗りつぶし開始点がビューポート外なら、何もしません。 */
	if(x0 < VP_LEFT || VP_RIGHT  - 1 < x0) return;
	if(y0 < VP_TOP  || VP_BOTTOM - 1 < y0) return;

	/* 塗りつぶされる色を取得します。 */
	c = point(x0, y0);

	/* 塗りつぶし開始点が既に塗りつぶす色なら、何もしません。 */
	/* if(c == color) return; */
	/* ->よく考えたら、このチェックは不要でした。
	 *   もしも塗りつぶし開始点が既に塗りつぶす色なら、
	 *   1. スタックに最初の(x0,y0)を積む。
	 *   2. while()ループの中で(x0,y0)を取り出す。
	 *   3. 既に塗られているので、continueで飛ばされる。
	 *   4. スタックは空なので、終了する。
	 *   となり、何もせずに終了します。
	 */

	/* 塗りつぶし開始点をスタックに積みます。 */
	stack[0][0] = x0;
	stack[0][1] = y0;
	i_stack = 1;

	/* 塗りつぶし点がなくなるまで... */
	while(i_stack != 0) {
		/* 塗りつぶし点を取り出します。 */
		i_stack--;
		x0 = stack[i_stack][0];
		y0 = stack[i_stack][1];

		/* 既に塗られていたら、飛ばします。 */
		if(point(x0, y0) == color) continue;

		/* 塗られる色の左端を探します。 */
		x1 = x0;
		for(;;) {
			x1--;
			if(x1 < VP_LEFT) break; /* ビューポート外 */
			if(point(x1, y0) != c) break; /* 別色 */
		}
		x1++; /* 一つ行き過ぎているので戻す */

		/* 塗られる色の右端を探します。 */
		x2 = x0;
		for(;;) {
			x2++;
			if(x2 > VP_RIGHT - 1) break; /* ビューポート外 */
			if(point(x2, y0) != c) break; /* 別色 */
		}
		x2--; /* 一つ行き過ぎているので戻す */

		/* 左右の間を塗りつぶします。 */
		for(x = x1; x <= x2; x++) {
			pset(x, y0, color);
			/* ここでpceLCDTrans()を呼ぶと、塗りつぶしの様子を1ピクセル単位で観察できます。 */
			/* pceLCDTrans() */
		}
		/* ここでpceLCDTrans()を呼ぶと、塗りつぶしの様子を1ライン単位で観察できます。 */
		/* pceLCDTrans() */

		/* 上のラインの調査。 */
		if(y0 != VP_TOP) {
			y = y0 - 1;
			x = x1;
			for(;;) {
				/* 塗られる色の始まりを探します。 */
				for(;;) {
					if(x > x2) break;
					if(point(x, y) == c) break;
					x++;
				}
				if(x > x2) break; /* もうない */

				/* 塗られる色の終わりを探します。 */
				for(;;) {
					if(x > x2) break;
					if(point(x, y) != c) break;
					x++;
				}

				/* 塗られる色の終わりの位置をスタックに積みます。 */
				if(i_stack < RENDER_PAINT_STACK_SIZE) {
					stack[i_stack][0] = x - 1; /* 一つ行き過ぎているので戻す */
					stack[i_stack][1] = y;
					i_stack++;
				}
			}
		}

		/* 下のラインの調査。 */
		if(y0 != VP_BOTTOM - 1) {
			y = y0 + 1;
			x = x1;
			for(;;) {
				/* 塗られる色の始まりを探します。 */
				for(;;) {
					if(x > x2) break;
					if(point(x, y) == c) break;
					x++;
				}
				if(x > x2) break; /* もうない */

				/* 塗られる色の終わりを探します。 */
				for(;;) {
					if(x > x2) break;
					if(point(x, y) != c) break;
					x++;
				}

				/* 塗られる色の終わりの位置をスタックに積みます。 */
				if(i_stack < RENDER_PAINT_STACK_SIZE) {
					stack[i_stack][0] = x - 1; /* 一つ行き過ぎているので戻す */
					stack[i_stack][1] = y;
					i_stack++;
				}
			}
		}
	}

#undef RENDER_PAINT_STACK_SIZE
#undef VP_LEFT
#undef VP_RIGHT
#undef VP_TOP
#undef VP_BOTTOM
}

/****************************************************************************
 *	symbolf
 ****************************************************************************/

/* グラフィック面に書式付き文字列を描画します。
 * [in]
 *	x, y		文字列の左上座標(ピクセル単位、0〜255)
 *	type		表示フォント(0〜2)
 *	color		色(-1〜3)
 *	fmt		書式文字列
 *	...		パラメータ
 * [note]
 *	* この関数は、文字列展開のために、システム用仮想画面領域を利用します。
 */
void
symbolf(int x, int y, int type, int color, char* fmt, ...)
{
	va_list ap;

	/* 書式付文字列を展開します。 */
	va_start(ap, fmt);
	pcevsprintf(_def_vbuff, fmt, ap); /* システム用仮想画面を利用!! */
	va_end(ap);

	/* 展開した文字列を描画します。 */
	symbol(x, y, _def_vbuff, type, color);
}

/****************************************************************************
 *	grfont
 ****************************************************************************/

/* グラフィック面に8×8ピクセルフォントを描きます。
 * [in]
 *	x, y		文字列の左上座標(キャラクタ単位、0〜31)
 *	code		文字コード(0〜255)
 *	color		bit3〜0: 前景色(0:白/1:明/2:暗/3:黒/4〜15:透明)
 *			bit7〜4: 背景色(0:白/1:明/2:暗/3:黒/4〜15:透明)
 * [note]
 *	* 前景色・背景色に透明を指定しても、既存のピクセルが残るわけではありません。
 *	  透明色で描画されるだけなので、いずれにせよ既存のピクセルは上書きされます。
 */
void
grfont(int x, int y, int code, int color)
{
	const unsigned char* src;
	unsigned char *dst, mask, t1, t0, tm, b1, b0, bm;
	int line;

	/* 転送元フォントアドレス・転送先フレームバッファアドレスを取得します。 */
	src = fntadr(code);
	dst = pclSpriteBGGetFrameAdr(1)
	    + (256/*[pixel]*/ / 8/*[bit]*/ * 3/*[plane]*/ * 8/*[line]*/) * (y & 31) + (x & 31);

	/* 前景色・背景色を設定します。 */
	switch(color & 0x0f) {
	case 0x00: t1 = 0x00; t0 = 0x00; tm = 0x00; break;
	case 0x01: t1 = 0x00; t0 = 0xff; tm = 0x00; break;
	case 0x02: t1 = 0xff; t0 = 0x00; tm = 0x00; break;
	case 0x03: t1 = 0xff; t0 = 0xff; tm = 0x00; break;
	default  : t1 = 0x00; t0 = 0x00; tm = 0xff; break;
	}
	switch(color & 0xf0) {
	case 0x00: b1 = 0x00; b0 = 0x00; bm = 0x00; break;
	case 0x10: b1 = 0x00; b0 = 0xff; bm = 0x00; break;
	case 0x20: b1 = 0xff; b0 = 0x00; bm = 0x00; break;
	case 0x30: b1 = 0xff; b0 = 0xff; bm = 0x00; break;
	default  : b1 = 0x00; b0 = 0x00; bm = 0xff; break;
	}

	for(line = 0; line < 8; line++) {
		mask = *src++; /* fntadr()でのフォントデータ初期化時に、文字単位に並び替え済み */
		dst[256/*[pixe]*/ / 8/*[bit]*/ * 0] = (t1 & mask) | (b1 & ~mask); /* bit1 */ 
		dst[256/*[pixe]*/ / 8/*[bit]*/ * 1] = (t0 & mask) | (b0 & ~mask); /* bit0 */ 
		dst[256/*[pixe]*/ / 8/*[bit]*/ * 2] = (tm & mask) | (bm & ~mask); /* mask */ 
		dst += 256/*[pixe]*/ / 8/*[bit]*/ * 3/*[plane]*/;
	}
}

/****************************************************************************
 *	grputstr
 ****************************************************************************/

/* グラフィック面に8×8ピクセルフォントで文字列を描きます。
 * [in]
 *	x, y		文字列の左上座標(キャラクタ単位、0〜31)
 *	str		文字列
 *	color		bit3〜0: 前景色(0:白/1:明/2:暗/3:黒/4〜15:透明)
 *			bit7〜4: 背景色(0:白/1:明/2:暗/3:黒/4〜15:透明)
 * [note]
 *	* 文字列中にSO(\x0e)を置くと、SI(\x0f)が現れるまでの間、
 *	  文字列中の半角カタカナを半角ひらがなとして表示します。
 *	  例えば、
 *		str = "ｱｲｳｴｵｶｷｸｹｺｻｼｽｾｿ"
 *	  は、
 *		アイウエオかきくけこサシスセソ
 *	  と表示されます。
 *	* 秀丸エディタなら、SOは「Ctrl+P→Ctrl+N」で入力できます。
 *	  また、SIは「Ctrl+P→Ctrl+O」で入力できます。
 *	* シフトアウト状態は、改行では解除されません。
 *	  関数が処理を返すとシフトアウト状態は解除され、次回は必ずシフトイン状態から始まります。
 *	* SO/SIの解釈は独自ですが、SOで特殊モード・SIで通常モードというのは本来の意味と同じです。
 *	  なんとなく、「シフトインで特殊モードに*入り*」「シフトアウトで特殊モードから*抜ける*」
 *	  だと思っていましたが、インとアウトの意味は逆だったようです。
 *	  「シフトアウトで特殊モードに*入り*」「シフトインで特殊モードから*抜ける*」が正しいようです。
 *	  SIが文字コード14、SOが文字コード15なので、順番から言ってもたぶんそうなのでしょう。
 *	  ※詳しくは未確認なので、要確認です。そもそも独自解釈だから、どっちでも問題ないのですけど(^^;
 */
void
grputstr(int x, int y, const char* str, int color)
{
	int x0, so, c;

	x0 = x; /* 改行時にこの桁へ戻ります */
	so = 0; /* 0:カタカナ/1:ひらがな */

	while((c = *str++) != '\0') {
		/* 制御コードの処理。(表示しません) */
		switch(c) {
		case '\n': /* LF(改行) */
			x = x0;
			y++;
			continue;
		case '\x0e': /* SO(シフトアウト) */
			so = 1;
			continue;
		case '\x0f': /* SI(シフトイン) */
			so = 0;
			continue;
		}

		/* カタカナ→ひらがな変換。(font.bmp参照) */
		if(so) {
			if('ｦ' <= c && c <= 'ｿ') {
				c -= 0x20;
			} else if('ﾀ' <= c && c <= 'ﾝ') {
				c += 0x20;
			}
		}

		/* 一文字表示。 */
		grfont(x, y, c, color);
		x++;
	}
}

/****************************************************************************
 *	grrintf
 ****************************************************************************/

/* グラフィック面に8×8ピクセルフォントで書式付き文字列を描きます。
 * [in]
 *	x, y		文字列の左上座標(キャラクタ単位、0〜31)
 *	color		bit3〜0: 前景色(0:白/1:明/2:暗/3:黒/4〜15:透明)
 *			bit7〜4: 背景色(0:白/1:明/2:暗/3:黒/4〜15:透明)
 *	fmt		書式文字列
 *	...		パラメータ
 * [note]
 *	* この関数は、文字列展開のために、システム用仮想画面領域を利用します。
 */
void
grprintf(int x, int y, int color, const char* fmt, ...)
{
	va_list ap;

	/* 書式付文字列を展開します。 */
	va_start(ap, fmt);
	pcevsprintf(_def_vbuff, fmt, ap); /* システム用仮想画面を利用!! */
	va_end(ap);

	/* 展開した文字列を描画します。 */
	grputstr(x, y, _def_vbuff, color);
}

/****************************************************************************
 *	pausedisp
 ****************************************************************************/

static void (*old_LCDTransDirect)(const unsigned char* lcd);

/* 画面転送の直前をフックして、「PAUSE」を重ね描きします。
 * シンプルライブラリは毎回全画面転送を行うので、重ね描き部分の退避・復元は不要です。
 */
static void
new_LCDTransDirect(const unsigned char* lcd)
{
	static const char msg[] = "PAUSE";
	const unsigned char* src;
	unsigned char* dst;
	int i, j;

	/* 画面のほぼ中央に「PAUSE」を表示。 */
	for(i = 0; i < 5; i++) {
		src = fntadr(msg[i]);
		dst = (unsigned char*)lcd + (2 * 88) * (5 + i) + (2 * 40);
		for(j = 0; j < 8; j++) {
			*dst++ = *src; /* bit1 */
			*dst++ = *src; /* bit0 */
			src++;
		}
	}

	/* 本物の画面転送処理へ流します。 */
	old_LCDTransDirect(lcd);
}

/* ポーズをかけたままSTART+SELECT長押しで終了すると、pceLCDTransDirect()のフックが解除されません。
 * この問題を防ぐため、終了通知をハンドリングし、確実にpceLCDTransDirect()のフック解除を行います。
 */
int
pceAppNotify(int type, int param)
{
extern unsigned char _def_vbuff[];

	switch(type) {
	/*{{C:\usr\PIECE\lib\src\defnotify.cより*/
	case APPNF_SMSTART:
		return APPNR_ACCEPT;
	case APPNF_SMREQVBUF:
		pceLCDSetBuffer(_def_vbuff);
		return APPNR_ACCEPT;
	/*}}C:\usr\PIECE\lib\src\defnotify.cより*/
	case APPNF_EXITREQ:
		pausedisp(0); /* 確実にpceLCDTransDirect()のフックを解除 */
		turbo(0); /* 確実にturboモードを解除 */
		return APPNR_ACCEPT;
	}
	return APPNR_IGNORE;
}

void
pausedisp(int pause)
{
	thread_lock();
	if(pause) {
		if(old_LCDTransDirect == NULL) {
			old_LCDTransDirect = pceVectorSetKs(KSNO_LCDTransDirect, new_LCDTransDirect);
		}
	} else {
		if(old_LCDTransDirect != NULL) {
			pceVectorSetKs(KSNO_LCDTransDirect, old_LCDTransDirect);
			old_LCDTransDirect = NULL; /* フックされていないことをマーク */
		}
	}
	thread_unlock();
}

/****************************************************************************
 *	rnd_range
 ****************************************************************************/

int
rnd_range(int min_value, int max_plus1)
{
	return rnd(max_plus1 - min_value) + min_value;
}

/*{{2006/03/29時点のclippce.cからコピーしました*/
/****************************************************************************
 *	24MHz/48Mhzモード切り替え
 ****************************************************************************/

/* * 「P/ECE hacking」さん(http://junkbox.info/piece/)の
 *   『P/ECEを48MHzで動かす(1〜3/3)』(http://junkbox.info/diary/dy200202.htm)を参考にさせて頂きました。
 * * SRAM/FLASHのオーバークロックは行っていません。
 *   今回の目標はあくまで各部品の仕様内で、CPUのみ48MHz動作させることだからです。
 * * 48MHz動作中に利用できないAPIは、次のとおりです。
 *	      pceCPUSetSpeed()			OSC3の倍速設定が想定されていないため
 *	      pcePowerEnterStandby()		 8bitタイマ1を設定しているため
 *	      pceTimerGetPrecisionCount()	後述
 *	      pceTimerAdjustPrecisionCount()	後述
 *	重要! pceFileCreate/Delete/WriteSec()	後述
 *	      pceIR系API全て			16bitタイマ5を設定しているため
 *   これらのAPIを利用する場合は、いったん24MHzモードに戻してから利用してください。
 * * pcePowerEnterStandby()は、アプリケーションから明示的に呼ばれる場合に加えて、
 *   電池駆動・五分間無操作時にシステムから自動的に呼ばれる場合もあります。
 *   本当は、 48MHzへの切り替え時に、pcePowerEnterStandby()だけはフックしておきたかったのですが、
 *   システム内からのpcePowerEnterStandby()呼び出しはフックを経由していないので、できませんでした。
 *   仕方がないので、48MHzモードを使うアプリケーションに次のようなコードを追加して対処してください。
 *	int pceAppNotify(int type, int param) {
 *		int m;
 *		switch(type) {
 *		case APPNF_SMSTART:	return APPNR_ACCEPT;
 *		case APPNF_SMREQVBUF:	pceLCDSetBuffer(_def_vbuff); return APPNR_ACCEPT;
 *		case APPNF_STANDBY:	m = turbo(0); pcePowerEnterStandby(0); turbo(m); return APPNR_REJECT;
 *		}
 *		return APPNR_IGNORE;
 *	}
 *   もっとも、pcePowerEnterStandby()内での8bitタイマ1利用目的は、
 *   スタンバイ復帰時のOSC3発振安定待ちのためであり、さほど重要ではありません。
 *   いや、重要といえば重要なのですが、待ち時間には充分なマージンが取られているはずなので、
 *   待ち時間が半分になった程度では即問題は発生しないと思うのです。
 *   もともと、初期のカーネルでは待ち時間の設定が正しく行われていなかった、にも関らず問題なく動いていたので、
 *   あまり気にしなくてもいいのかもしれません。
 *   というわけで、結論としては、アプリケーション側で上記コード(pceAppNotify)による対策を取ったほうが安全だが、
 *   対策しなくてもまあいいかな？、という結論になります。
 *   ちなみに、なぜCLiPライブラリ内に上記コード(pceAppNotify)を含めてしまわなかったかというと、
 *   pceAppNotify()はもっと多目的に利用されるので、アプリケーション側で定義できなくなってしまうのは問題だと思うからです。
 *   ↓2004/11/05追記
 *   pceAppNotify()をライブラリに含めました。
 *   アプリケーションがNotify処理を必要とする場合は、atnotify()を呼び出して関数を登録してください。
 * * 48MHzモードでは、P/ECEカーネル内のウェイト関数pdwait(\usr\PIECE\sysdev\pcekn\critical.c)のタイミングも
 *   変わってしまうのが心配ですが、いまのところとりあえず大丈夫みたいです。
 *   pdwait()のタイミングの大半は、CPU自身の命令実行時間ではなく、エリア11(0x1000000〜)へのアクセス時間によって決定されます。
 *   エリア11のアクセスタイミングは、最低速に設定されています。(\usr\PIECE\sysdev\pcekn\hard.c 143行)
 *   外部バスへのアクセスなので、24MHz/48MHzの違いには影響されず、エリア11のアクセスはどちらでも同じです。
 *   エリア11のアクセス時間に較べれば、CPU自身の命令実行時間は小さいので、影響は小さいはずです。
 *   確かにpdwait()のタイミングは速くなってしまいますが、ぎりぎりの調整がなされていない限り問題ないと思います。
 *   唯一心配なのは、フラッシュメモリの消去待ちループ内での利用です。(\usr\PIECE\sysdev\pcekn\fmacc1.c 69行)
 *   pceFileCreate/Delete/WriteSec()利用時は、24MHzモードに戻しておいた方が安全でしょう。
 *   (※未検証です。もしかしたら48MHzでも問題なく使えるかもしれません)
 *   →ちょっと検証してみます。P/ECEのフラッシュメモリSST39VF400Aでは、1セクタ消去に要する最大時間は32[ミリ秒]です。
 *   \usr\PIECE\sysdev\pcekn\fmacc1.c 63〜76行は、pdwait(5)×一万回の呼び出し以内に消去が完了しないと、タイムアウトします。
 *   簡単のために、mac命令五万回分の待ち時間と仮定します。(実際はもっとかかります)
 *   mac命令一回毎にエリア11から二回の読み出しを行うので、(1(基本リード)＋7(ウェイト))×2＝16クロックかかります。
 *   外部バスアクセスですので、ここでのクロック単位は、48MHzではなく24MHzです。
 *   1秒/24MHz×16クロック×五万回＝33ミリ秒となり、1セクタ消去に要する時間を満たしています。
 *   実際にはpdwait()の呼び出し時間やmac以外の命令実行時間も含まれるので、もっと多くのウェイトが取られているはずです。
 *   以上の検証より、48MHzモード中にpceFileCreate/Delete/WriteSec()を使ってもたぶん大丈夫だろうと思います。
 *   なお、フラッシュメモリへのワード書き込み(\usr\PIECE\sysdev\pcekn\fmacc2.c)は、
 *   タイムアウトなしで書き込み完了するまで待ち続けますので、pdwait()のタイミングには関係ありません。
 *   →と思ったのですが、実験してみると、48MHzモードではかなりの高確率でpceFileWriteSct()に失敗するようです。←★★★重要★★★
 *   一時的にturbo(0)で24MHzに戻し、直後にpceFileWriteCreate/Open/WriteSct/Close、そしてturbo(1)で48MHzを復元、
 *   とすれば、いまのところ失敗はありません。
 *   やっぱり、48MHzモードでのフラッシュメモリ書き込みはやめたほうがいいようです。
 *   →2002/12/10追記: 48MHzモードでpceFileCreate/WriteSecを使うとハングアップしていた理由がわかりました。
 *   フラッシュメモリへの書き込みが問題なのではなく、上記APIが内部でpceCPUSetSpeed()を呼び出しているのが原因でした。
 *   \usr\PIECE\sysdev\pcekn\file.c内のフラッシュメモリ消去/書き込み用補助関数の中でpceCPUSetSpeed(3)を呼び出し、
 *   いったん3MHzモードにしてフラッシュメモリ消去/書き込みを行っています。
 *   そして、pceCPUSetSpeed()によって、USBCエリアのアクセススピードが既定値に戻ってしまうのが、ハングアップの原因です。
 *   48MHzモードでpceFileCreate/WriteSecを使っても、USB通信が発生するまでは限りハングアップしないようですので、
 *   上記の推測はたぶん間違いないと思います。
 *   結論として、48MHzモードでpceFileCreate/WriteSecが使えないことには変わりありません。
 * * 16bitタイマ0(1msタイマ)の再設定について。
 *   他の16bitタイマ再設定と違って、これだけプリスケーラ分周比ではなくリロード値を再設定しています。
 *   このようにした理由は、P/ECEカーネル(BIOS 1.18)による分周比設定(=4)の倍(=8)の設定がないからです。
 *   止むを得ず、リロード値を倍(5999→11999)にしています。
 *   16bitタイマ0のカウント値は、実時間ベースで24MHz時の倍速で変化することになり、
 *   また、カウント値の最大値も24MHz時の倍の値までカウントアップされます。
 *   このため、pceTimerGetPrecisionCount()/pceTimerAdjustPrecisionCount()は正しい値を返しません。
 *   幸い、P/ECEカーネル内部では、16bitタイマ0のカウント値や上記APIは利用されていません。
 *   アプリケーション側でも、48MHz動作中は16bitタイマ0のカウント値や上記APIを利用しないようご注意ください。
 *   (pceTimerGetPrecisionCount()をフックして戻り値を半分にし、擬似的に24MHz互換の値を返すことは可能ですが、
 *    今回はそこまでやらないことにしました。)
 *   →2004/12/02追記: pceTimerGetPrecisionCount()はそのままで、pceTimerAdjustPrecisionCount()をTurboモード対応に置き換えました。
 *                     このモジュールの下の方にある、pceTimerAdjustPrecisionCount()の実装を参照してください。
 * * USBCのアクセスタイミング設定について。
 *   本来、48MHzモードにしても外部バスへのアクセスタイミングは変わらないので、
 *   USBCのアクセスタイミングを遅くする必要はないはずです。
 *   しかし実際には、現在のP/ECEカーネル(BIOS 1.18)では、USBCへのアクセスタイミングは、
 *   USBCの規定値よりも速く設定してあり、CPU側での処理時間も含めて要求タイミングを満たしています。
 *   (例えば通常の24MHzモードでも、連続してUSBCのポートを読み書きすると正しくアクセスできません。)
 *   48MHzモードではCPU側の処理時間が短くなるので、そのままでは要求タイミングを満たせず、
 *   USB通信ができなくなってしまいます。
 *   この問題を防ぐため、USBCのアクセスタイミングを遅くすることにしました。
 *   設定値は、S1C33209のBCUで可能な一番遅い設定にしてあります。(※設定値の検証はまだ行っていません)
 * * 8bitタイマ5(LCD転送)の再設定は行っていません。
 *   現在のP/ECEカーネル(BIOS 1.18)は、LCDCへのタイミングをかなり遅めに設定しているからです。
 *   P/ECEのLCDC、S6B0741のデータシート(66ページ)によると、SPI接続・2.7V以上で使った場合、
 *   S6B0741は約17MHzまでのSCLKについてくるようです。
 *   P/ECEカーネルは、LCDC転送速度を6MHzに設定しています。(\usr\PIECE\sysdev\pcekn\lcd.c 701〜705行)
 *   倍速になっても12MHzですので、充分許容範囲です。
 *   それに、転送速度を遅くすると、pceLCDTrans()のライン追い越し問題発生する恐れもあります。(※実験まだです)
 *   以上の理由により、8bitタイマ5(LCD転送)の再設定は行わないことにしました。
 * * さて、24MHz→48MHzモードに切り替えることにより、どれぐらい速くなるか、です。
 *   コードを高速RAMに、データとスタックをSRAMに置いた場合を想定します。
 *   具体的には、高速RAM上に置いたnew_LCDDrawObject()の連続呼び出しで、24MHzと48MHz時の実行時間を比較しました。
 *   実験の結果、24MHz時に較べ、約20〜30%程度の高速化となるようです。
 *   アプリケーション全体の実行時間の20〜30%というと、MOD再生時間がまるまる儲かるぐらいの時間です。
 *   即ち、24MHzでBGMなしの場合と、48MHzでMODによるBGM再生を付けた場合の処理の重さがだいたい同じ、ということです。
 *   それなりに使いみちはありそうです。。
 */
static int
turbo_CLOCK(int mode)
{
	int old_mode = !bP0_P0D_P07D;		/* 直前のモードを保存(0<->1逆であることに注意!) */

	mode = mode != 0;			/* 新しいモードの指定を確実に0か1にする */
	if(old_mode == mode) return old_mode;	/* 変化なしなら何もしない */

	/* * 48MHzモードに切り替える前に、現在の設定がP/ECEカーネル(BIOS 1.18)の規定値かどうか検査しています。
	 *   P/ECEカーネルの規定値と異なっていた場合は、アサートします。
	 *   もしも今後、カーネルのバージョンアップによって規定値が変化した場合は、修正が必要です。
	 * * DI〜EI内でDIE()した場合、エラー画面は表示されますが、SELECTによる終了はできません。
	 *   終了するには、背面のリセットボタンを押してください。
	 *   DIE()前にEIを発行して対処することは可能ですが、わざとやらないことにしました。
	 *   中途半端な設定状態メニューに戻ってしまうと非常に危険なので、
	 *   それよりもリセットボタンでの完全リセットを強制した方が安全だからです。
	 *   ↓2004/12/18 Naoyuki Sawa 変更
	 *   割り込み禁止/許可の制御は、呼び出し元のturbo()関数内で行うように変更しました。
	 */
	if(mode) {
		/* 16bitタイマ0(1msタイマ) */
		if(!bT16_CTL0_PRUN0) DIE();		bT16_CTL0_PRUN0 = 0;
		if(pT16_CR0B != 5999) DIE();		pT16_CR0B = 11999;
							bT16_CTL0_PRUN0 = 1; /* PRESETなしでも大丈夫かな？ */

		/* 16bitタイマ1(サウンド再生) */
		if(!bT16_CTL1_PRUN1) DIE();		bT16_CTL1_PRUN1 = 0;
		if(bCLKCTL_T16_1_P16TS1 != 0) DIE();	bCLKCTL_T16_1_P16TS1 = 1;
							bT16_CTL1_PRUN1 = 1; /* PRESETなしでも大丈夫かな？ */

		/* 16bitタイマ4(USB 6MHz) */
		if(!bT16_CTL4_PRUN4) DIE();		bT16_CTL4_PRUN4 = 0;
		if(bCLKCTL_T16_4_P16TS4 != 0) DIE();	bCLKCTL_T16_4_P16TS4 = 1;
							bT16_CTL4_PRUN4 = 1; /* PRESETなしでも大丈夫かな？ */

		/* USBコントローラのアクセスタイミング調整 */
		if(bA8_7_A8WT != 4) DIE();		bA8_7_A8WT = 7; /* 6でも一応動くようです */
		if(bA8_7_A8DF != 2) DIE();		bA8_7_A8DF = 3; /* 2でも一応動くようです */

		/* 入出力兼用ポート7(OSC3クロック制御) */
		if(bP0_P0D_P07D != 1) DIE();		bP0_P0D_P07D = 0;

	} else {
		/* 16bitタイマ0(1msタイマ) */
		if(!bT16_CTL0_PRUN0) DIE();		bT16_CTL0_PRUN0 = 0;
		if(pT16_CR0B != 11999) DIE();		pT16_CR0B = 5999;
							bT16_CTL0_PRUN0 = 1; /* PRESETなしでも大丈夫かな？ */

		/* 16bitタイマ1(サウンド再生) */
		if(!bT16_CTL1_PRUN1) DIE();		bT16_CTL1_PRUN1 = 0;
		if(bCLKCTL_T16_1_P16TS1 != 1) DIE();	bCLKCTL_T16_1_P16TS1 = 0;
							bT16_CTL1_PRUN1 = 1; /* PRESETなしでも大丈夫かな？ */

		/* 16bitタイマ4(USB 6MHz) */
		if(!bT16_CTL4_PRUN4) DIE();		bT16_CTL4_PRUN4 = 0;
		if(bCLKCTL_T16_4_P16TS4 != 1) DIE();	bCLKCTL_T16_4_P16TS4 = 0;
							bT16_CTL4_PRUN4 = 1; /* PRESETなしでも大丈夫かな？ */

		/* USBコントローラのアクセスタイミング調整 */
		if(bA8_7_A8WT != 7) DIE();		bA8_7_A8WT = 4;
		if(bA8_7_A8DF != 3) DIE();		bA8_7_A8DF = 2;

		/* 入出力兼用ポート7(OSC3クロック制御) */
		if(bP0_P0D_P07D != 0) DIE();		bP0_P0D_P07D = 1;
	}

	return old_mode;
}

static int
turbo_FLASH_WAIT(int mode)
{
	/* 現在のモードを取得します。 */
	int old_mode = bA10_9_A10WT == 1;	/* 1WAITならモード1、2WAITならモード0。ありえないが、1or2WAIT以外もモード0と見なす */

	/* 新しいモード指定を確実に0か1とします。 */
	mode = mode != 0;

	/* モード変化なしなら、何もしません。 */
	if(old_mode == mode) return old_mode;

	/* 新しいモードに応じたウェイトを設定します。 */
	bA10_9_A10WT = mode ? /*モード1*/1 : /*モード0*/2;

	return old_mode;
}

static int
turbo_SRAM_WAIT(int mode)
{
	/* 現在のモードを取得します。 */
	int old_mode = bA6_4_A5WT == 1;		/* 1WAITならモード1、2WAITならモード0。ありえないが、1or2WAIT以外もモード0と見なす */

	/* 新しいモード指定を確実に0か1とします。 */
	mode = mode != 0;

	/* モード変化なしなら、何もしません。 */
	if(old_mode == mode) return old_mode;

	/* 新しいモードに応じたウェイトを設定します。 */
	bA6_4_A5WT = mode ? /*モード1*/1 : /*モード0*/2;

	return old_mode;
}

static int
turbo_FLASH_OUTPUT_DISABLE(int mode)
{
	/* 現在のモードを取得します。 */
	int old_mode = bA10_9_A10DF == 0;	/* 0.5Cycleならモード1、1.5Cycleならモード0。ありえないが、0.5or1.5Cycle以外もモード0と見なす */

	/* 新しいモード指定を確実に0か1とします。 */
	mode = mode != 0;

	/* モード変化なしなら、何もしません。 */
	if(old_mode == mode) return old_mode;

	/* 新しいモードに応じた出力ディセーブル遅延時間を設定します。 */
	bA10_9_A10DF = mode ? /*モード1*/0 : /*モード0*/1;

	return old_mode;
}

static int
turbo_SRAM_OUTPUT_DISABLE(int mode)
{
	/* 現在のモードを取得します。 */
	int old_mode = bA6_4_A5DF == 0;		/* 0.5Cycleならモード1、1.5Cycleならモード0。ありえないが、0.5or1.5Cycle以外もモード0と見なす */

	/* 新しいモード指定を確実に0か1とします。 */
	mode = mode != 0;

	/* モード変化なしなら、何もしません。 */
	if(old_mode == mode) return old_mode;

	/* 新しいモードに応じた出力ディセーブル遅延時間を設定します。 */
	bA6_4_A5DF = mode ? /*モード1*/0 : /*モード0*/1;

	return old_mode;
}

int
turbo(int mode)
{
	int old_mode = 0;

ENTER_CS;
									/* ---cffss */
	old_mode |= turbo_CLOCK               ((mode >> 4) & 1) << 4;	/* ---c---- */
	old_mode |= turbo_FLASH_OUTPUT_DISABLE((mode >> 3) & 1) << 3;	/* ----f--- */
	old_mode |= turbo_FLASH_WAIT          ((mode >> 2) & 1) << 2;	/* -----f-- */
	old_mode |= turbo_SRAM_OUTPUT_DISABLE ((mode >> 1) & 1) << 1;	/* ------s- */
	old_mode |= turbo_SRAM_WAIT           ((mode >> 0) & 1) << 0;	/* -------s */

LEAVE_CS;

	return old_mode;
}
/*}}2006/03/29時点のclippce.cからコピーしました*/
