/*	
 *	clipio.c
 *
 *	S1C33 Family Cコンパイラパッケージの io.lib 関数を置き換えます。
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2002 Naoyuki Sawa
 *
 *	* Mon Nov 25 20:37:00 JST 2002 Naoyuki Sawa
 *	- 作成開始。
 *	* Mon Nov 30 08:06:00 JST 2002 Naoyuki Sawa
 *	- stderr_open()/close()/write()を追加。
 *	* Wed Dec 11 12:30:00 JST 2002 Naoyuki Sawa
 *	- キャッシュ用メンバ変数(cache_sct/len/ptr)と、キャッシュを介したシーク関数(io_seek())を追加。
 *	  連続したfread()呼び出しでセクタが変化しない場合は、pceFileReadSct()を使わずキャッシュ情報を使います。
 *	  この改良により、fgetc()やfgets()のような細かな単位で読み込みを行う処理が、2倍以上速くなりました。
 *	* Sat Nov 22 06:00:00 JST 2003 Naoyuki Sawa
 *	- stderr_open()はアプリケーションから明示的に呼ぶようにしたので、(→clippce.c参照)
 *	  stderr_open()が呼ばれた場合は常に出力モニタの接続を待つように変更しました。
 *	* Wed Jun 26 20:03:00 JST 2005 Naoyuki Sawa
 *	- stderr_open()内の不要なpceLCDDispStop()呼び出しを削除しました。
 *	* Tue Dec 13 04:35:00 JST 2005 Naoyuki Sawa
 *	- 標準出力への書き込みも、標準エラー出力と同様に、標準エラー出力モニタへ出力するように変更しました。
 *	  これまでは、標準出力(stdout)への書き込みは、エラー終了としていました。
 *	  この変更により、fprintf(stderr, fmt, ...)だけでなく、printf(fmt, ...)もモニタできるようになりました。
 *	* Sun Jan 15 04:02:00 JST 2005 Naoyuki Sawa
 *	- ungetc()にて、EOFが指定されたら、エラーとして扱い、何もせずにEOFを返す処理を追加しました。
 *	  これまでは、EOFが指定された場合にも、エラーとせずに、ストリームへ戻す処理を行っていました。
 */
#include "clip.h"

/* ※2002/11/26現在の実装では、いずれの関数もエラー時にerrnoを設定しません。
 *   errnoを見てエラーを判断しているプログラムは、正しく動かない可能\性があります。
 * →TODO:errnoを設定するよう修正せよ。
 */

/****************************************************************************
 *	関数プロトタイプ
 ****************************************************************************/

/*  『S5U1C33000C Manual (S1C33 Family Cコンパイラパッケージ) (Ver.4)』(S5U1C33000C_J.pdf)
 * p.97 8.2.1 入出力関数(io.lib) 表\8.2.1.1 入出力関数一覧 の並びと同じ順番です。
 * 各々の関数を実装したら、コメントアウトして行ってください。
 */
//FILE* fopen(const char* filename, const char* mode) { DIE(); return 0; }
//FILE* freopen(const char* filename, const char* mode, FILE* stream) { DIE(); return 0; }
//int fclose(FILE* stream) { DIE(); return 0; }
//int fflush(FILE* stream) { DIE(); return 0; }
//int fseek(FILE* stream, long offset, int origin) { DIE(); return 0; }
//long ftell(FILE* stream) { DIE(); return 0; }
//void rewind(FILE* stream) { DIE(); }
//int fgetpos(FILE* stream, fpos_t* pos) { DIE(); return 0; }
//int fsetpos(FILE* stream, const fpos_t* pos) { DIE(); return 0; }
//size_t fread(void* buffer, size_t size, size_t count, FILE* stream) { DIE(); return 0; }
//size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream) { DIE(); return 0; }
//int fgetc(FILE* stream) { DIE(); return 0; }
//int getc(FILE* stream) { DIE(); return 0; }
//int getchar() { DIE(); return 0; }
//int ungetc(int c, FILE* stream) { DIE(); return 0; }
//char* fgets(char* string, int n, FILE* stream) { DIE(); return 0; }
//char* gets(char* buffer) { DIE(); return 0; }
//int fputc(int c, FILE* stream) { DIE(); return 0; }
//int putc(int c, FILE* stream) { DIE(); return 0; }
//int putchar(int c) { DIE(); return 0; }
//int fputs(const char* string, FILE* stream) { DIE(); return 0; }
//int puts(const char* string) { DIE(); return 0; }
//int remove(const char* filename) { DIE(); return 0; }
int rename(const char* oldname, const char* newname) { DIE(); return 0; }
void setbuf(FILE* stream, char* buffer) { DIE(); }
int setvbuf(FILE* stream, char* buffer, int mode, size_t size) { DIE(); return 0; }
FILE* tmpfile() { DIE(); return 0; }
char* tmpnam(char* string) { DIE(); return 0; }
//int feof(FILE* stream) { DIE(); return 0; }
//int ferror(FILE* stream) { DIE(); return 0; }
//void clearerr(FILE* stream) { DIE(); }
//void perror(const char* string) { DIE(); }
int fscanf(FILE* stream, const char* format, ...) { DIE(); return 0; }
int scanf(const char* format, ...) { DIE(); return 0; }
//int sscanf(char* buffer, const char* format, ...); -> io.lib(そのまま使用)
//int fprintf(FILE* stream, const char* format, ...) { DIE(); return 0; }
//int printf(const char* format, ...) { DIE(); return 0; }
//int sprintf(char* buffer, const char* format, ...); -> P/ECE Kernel(pcesprintf) -> 2003/05/20 clipprnt.c
//int vfprintf(FILE* stream, const char* format, va_list argptr) { DIE(); return 0; }
//int vprintf(const char* format, va_list argptr) { DIE(); return 0; }
//int vsprintf(char* buffer, const char* format, va_list argptr); -> P/ECE Kernel(pcevsprintf) -> 2003/05/20 clipprnt.c

/****************************************************************************
 *	ローカル変数・関数
 ****************************************************************************/

#define MAX_IO		4		/* 同時に開ける最大ファイル数 */
#define IO_SIG		((unsigned short)(('I'<<0)|('O'<<8)))	/* ->IO.sig */
#define IO_EOF		(1 << 0)	/* -> IO.flags */
#define IO_ERROR	(1 << 1)	/* -> IO.flags */
typedef struct _IO {
	unsigned short sig;		/* 0:空き/IO_SIG:使用中 */
	unsigned short flags;		/* IO_EOF,IO_ERRORの組み合わせ */
	int pos;			/* 次回の読み込み/書き込み位置 */
	FILEACC fa;			/* ファイルアクセス構\造体 */
	/*{{キャッシュ用メンバ変数:io_seek()にて更新*/
	int cache_sct;			/* 最後にアクセスしたセクタ(-1:キャッシュ無効) */
	int cache_len;			/* そのセクタの有効バイト数 */
	const void* cache_ptr;		/* そのセクタの先頭アドレス */
	/*}}キャッシュ用メンバ変数:io_seek()にて更新*/
} IO;
static IO ios[MAX_IO];			/* ファイルI/O構\造体バッファ  */

/* 一時バッファとして、システム用仮想画面を利用。
 * システム用仮想画面は128x88=11KBあるので、同時に使用する領域が重ならないよう割り当てます。
 */
#define sctbuf (&_def_vbuff[        0])	/* 0〜BLOCKSIZE(=4KB) : fwrite()用セクタバッファ */
#define strbuf (&_def_vbuff[BLOCKSIZE])	/* BLOCKSIZE〜最後まで: vsprintf用文字列バッファ */

/* ダミーの標準入出力用アドレス定義。
 * 偶数アドレスにしておかないと、io_file()でエラーを検出する前にアドレスエラーで落ちるので注意。
 */
#define FILE_STDIN	(FILE*)(- 4)	/* ダミー。io_file()でエラー扱い。 */
#define FILE_STDOUT	(FILE*)(- 8)	/* ダミー。io_file()でエラー扱い。fwrite()のみ特別処理 */
#define FILE_STDERR	(FILE*)(-12)	/* ダミー。io_file()でエラー扱い。fwrite()のみ特別処理 */

static IO*
io_file(FILE* stream)
{
	IO* io = (IO*)stream;

	/* ファイルが開かれていることを確認します。 */
	if(io->sig != IO_SIG) DIE();

	return io;
}

static IO*
io_open(IO* io, const char* filename, const char* mode)
{
	/* まずクリアしておきます。 */
	memset(io, 0, sizeof(IO));

	/* ファイルを開きます。 */
	switch(*mode) { /* 一文字目だけで判定 */
	case 'r':
		if(pceFileOpen(&io->fa, filename, FOMD_RD) != 0) return NULL;
		break;
	case 'w':
		if(pceFileOpen(&io->fa, filename, FOMD_WR) != 0) return NULL;
		break;
	default:
		return NULL;
	}

	/* このエントリが使用中であることをマークします。 */
	io->sig = IO_SIG;

	/* キャッシュを無効化しておきます。 */
	io->cache_sct = -1;

	return io;
}

static int
io_seek(IO* io, int sct)
{
	/* 前回と違うセクタなら、有効バイト数とセクタアドレスを取得し、キャッシュします。 */
	if(sct != io->cache_sct) {
		io->cache_sct = sct;
		io->cache_len = pceFileReadSct(&io->fa, NULL, sct, BLOCKSIZE);
		io->cache_ptr = io->fa.aptr;
	}

	return io->cache_len; /* pceFileReadSct()互換の戻り値として、有効バイト数を返します */
}

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

FILE* stdin = FILE_STDIN;
FILE* stdout = FILE_STDOUT;
FILE* stderr = FILE_STDERR;

FILE*
fopen(const char* filename, const char* mode)
{
	IO* io;
	int i;

	/* ファイルI/O構\造体バッファから、空きエントリを探します。 */
	for(io = ios, i = 0; i < MAX_IO; i++, io++) {
		if(!io->sig) break; /* 空きエントリ発見？ */
	}
	if(i == MAX_IO) return NULL;

	/* ファイルを開きます。 */
	return (FILE*)io_open(io, filename, mode);
}

FILE*
freopen(const char* filename, const char* mode, FILE* stream)
{
	IO* io = io_file(stream);

	/* いったん閉じます。 */
	pceFileClose(&io->fa);

	/* ファイルを開きます。 */
	return (FILE*)io_open(io, filename, mode); /* この中でキャッシュも無効化されます */
}

int
fclose(FILE* stream)
{
	IO* io = io_file(stream);

	/* ファイルを閉じます。 */
	pceFileClose(&io->fa);

	/* このエントリが空きであることをマークします。 */
	memset(io, 0, sizeof(IO));

	return 0;
}

int
fflush(FILE* stream)
{
	return 0; /* 何もしません */
}

int
fseek(FILE* stream, long offset, int origin)
{
	IO* io = io_file(stream);

	clearerr(stream); /* EOF・エラーインジケータをクリアします */

	switch(origin) {
	case SEEK_SET:
		io->pos = offset;
		break;
	case SEEK_CUR:
		io->pos += offset;
		break;
	case SEEK_END:
		io->pos = io->fa.fsize - offset;
		break;
	default:
		return -1;
	}

	return 0;
}

long
ftell(FILE* stream)
{
	IO* io = io_file(stream);

	return io->pos;
}

void
rewind(FILE* stream)
{
	fseek(stream, 0, SEEK_SET); /* fseek()の中でEOF・エラーインジケータがクリアされます */
}

int
fgetpos(FILE* stream, fpos_t* pos)
{
	*pos = ftell(stream);

	return 0;
}

int
fsetpos(FILE* stream, const fpos_t* pos)
{
	return fseek(stream, *pos, SEEK_SET); /* fseek()の中でEOF・エラーインジケータがクリアされます */
}

size_t
fread(void* buffer, size_t size, size_t count, FILE* stream)
{
	IO* io = io_file(stream);
	char* p = (char*)buffer;
	int len = size * count;
	int ofs, sct, sof, sln;

	/* * 現在の実装では、最後の項目が中途半端に読み込める場合、その分もバッファに読み込んでしまいます。
	 *   例えば、150バイトのファイルの前半100バイトをバッファに読み込む場合、本当のfread()ならば：
	 *	char buf[100];
	 *	retval = fread(buf, 100, 2, fp); -> 戻り値は1で、buf[0〜99]までにデータが読み込まれます
	 *   となりますが、この実装では：
	 *	char buf[100];
	 *	retval = fread(buf, 100, 2, fp); -> 戻り値は1ですが、buf[0〜149]までデータが読み込まれます
	 *   となり、バッファオーバーランを起こしてしまいます。
	 *   そもそもこのような危険な呼び出し方をすることはあまりないと思いますが、念のためご注意ください。
	 */

	ofs = 0;
	while(ofs < len) {
		/* セクタのフラッシュメモリアドレスを取得します。 */
		sct = (io->pos + ofs) / BLOCKSIZE;			/* sct = 現在の読み込みオフセットを含むセクタ番号                         */
		sln = io_seek(io, sct);					/* sln = このセクタのデータサイズ(最終セクタのみsln<=BLOCKSIZEになります) */
		if(sln == 0) break;					/* たぶんオフセット超過                                                   */

		/* このセクタから読み込むサイズを決定します。 */
		sof = (io->pos + ofs) % BLOCKSIZE;			/* セクタ内オフセット                                                       */
		sln -= sof;						/* セクタ内オフセット以降から読み込み可能\な最大データサイズ                 */
		if(sln > len - ofs) sln = len - ofs;			/* 読み込み可能\な最大データサイズが残り読み込みサイズ以上なら、切り詰めます */
		if(sln <= 0) break;					/* たぶんオフセット超過、または読み込みサイズに0以下が指定された(len<=0)    */

		/* セクタからバッファへ読み込みます。 */
		memcpy(p + ofs, io->cache_ptr + sof, sln);
		ofs += sln;
	}
	count = ofs / size;						/* 完全に読み込めた項目数                                             */
	io->pos += count * size;					/* 完全に読み込めた項目の分だけ、ファイルポインタの現在位置を進めます */
	if(count * size < len) io->flags |= IO_EOF;			/* 完全に読み込めたバイト数が、要求サイズ*未満*だったら、EOFをマーク  */

	return count;							/* 完全に読み込めた項目数を返します */
}

size_t
fwrite(const void* buffer, size_t size, size_t count, FILE* stream)
{
	if((stream == FILE_STDOUT) || (stream == FILE_STDERR)) { /* 標準出力と標準エラー出力は特別処理で、標準エラー出力モニタへ出力します。 */
		stderr_write(buffer, size * count);
		return count;
	} else {
#if 0
//		IO* io = io_file(stream);
//		char* p = (char*)buffer;
//		int len = size * count;
//		int ofs, sct, sof, sln;
//
//		/* * fread()と同じく、最後の項目を中途半端に書き込んでしまう可能\性があります。ご注意ください。 */
//		/* * v1.18現在のP/ECEカーネルは、pceFileOpen()に指定されたmodeを無視します。
//		 *   FOMD_RDで開いたファイルに対してもpceFileWriteSct()できますし、FOMD_WRで開いたファイルに対してもpceFileReadSct()できます。
//		 *   fwrite()関数に渡されるファイルはFOMD_WRで開かれているはずですが、上記の特性を利用してpceFileReadSct()を使ってしまいます。
//		/* * 書き込み処理では、キャッシュ用メンバ変数(cache_sct/ptr/len)を参照せず、変更もしません。 */
//
//		ofs = 0;
//		while(ofs < len) {
//			/* セクタのフラッシュメモリアドレスを取得します。 */
//			sct = (io->pos + ofs) / BLOCKSIZE;			/* sct = 現在の書き込みオフセットを含むセクタ番号                             */
//			sln = pceFileReadSct(&io->fa, sctbuf, sct, BLOCKSIZE);	/* このセクタの全データを取得します。(最終セクタのみsln<=BLOCKSIZEになります) */
//			if(sln == 0) break;					/* たぶんオフセット超過                                                       */
//
//			/* このセクタに書き込むサイズを決定します。 */
//			sof = (io->pos + ofs) % BLOCKSIZE;			/* セクタ内オフセット                                                       */
//			sln -= sof;						/* セクタ内オフセット以降に書き込み可能\な最大データサイズ                   */
//			if(sln > len - ofs) sln = len - ofs;			/* 書き込み可能\な最大データサイズが残り書き込みサイズ以上なら、切り詰めます */
//			if(sln <= 0) break;					/* たぶんオフセット超過、または書き込みサイズに0以下が指定された(len<=0)    */
//
//			/* セクタの全データの一部を書き換えます。 */
//			memcpy(sctbuf + sof, p + ofs, sln);
//			ofs += sln;
//
//			/* セクタの全データをフラッシュメモリに書き戻します。 */
//			pceFileWriteSct(&io->fa, sctbuf, sct, BLOCKSIZE);	/* 最終セクタの末尾超過分は、pceFileWriteSct()によって切り詰められます  */
//										/* ここで失敗することはありえないはずなので、エラーチェックは行いません */
//		}
//		count = ofs / size;						/* 完全に書き込めた項目数                                             */
//		io->pos += count * size;					/* 完全に書き込めた項目の分だけ、ファイルポインタの現在位置を進めます */
//		if(count * size < len) io->flags |= IO_EOF;			/* 完全に書き込めたバイト数が、要求サイズ*未満*だったら、EOFをマーク  */
//
//		return count;							/* 完全に書き込めた項目数を返します */
#else
		/* フラッシュメモリ上の通常ファイルへの細かな書き込みは、フラッシュメモリを痛める恐れがあるので、現在のところエラー扱いとしておきます。 */
		DIE(); return 0;
#endif
	}
}

int
fgetc(FILE* stream)
{
	char c;

	if(fread(&c, 1, 1, stream) != 1) return -1;

	return c & 0xff; /* '\x80'〜'\xff'を符号拡張してはいけない */
}

int
getc(FILE* stream)
{
	return fgetc(stream);
}

int
getchar()
{
	return getc(stdin);
}

int
ungetc(int c, FILE* stream)
{
	IO* io = io_file(stream);

	/* EOFを返そうとしたら、ストリームへ戻す処理を行わずに、エラーを示すEOFを返します。
	 * それ以外ならば、ストリームへ戻す処理を行い、戻した文字コードをそのまま返します。
	 */
	if(c != EOF) {

		io->pos--; /* ※適当な実装！本当のungetc()とはぜんぜん違います */

		/* もしも呼び出し元が、'\x80'〜'\xff'を符号拡張して、'\xff80'〜'\xffff'を渡した場合でも、
		 * ungetc()の戻り値は、'\x80'〜'\xff'となります。
		 * (実際には、上位8bitが\xffかどうかにかかわらず、常に上位8bitをクリアします。)
		 * この仕様は、Visual C++ 6.0 SP6の、ランタイムライブラリのソ\ースの実装に合わせませした。
		 */
		return c & 0xff; /* '\x80'〜'\xff'を符号拡張してはいけない */
	}

	return c;
}

char*
fgets(char* string, int n, FILE* stream)
{
	char* p = string;

	if(n <= 0) return NULL;

	while(--n) { /* n-1文字までしか読み込まないので、プリデクリメント */
		if(fread(p, 1, 1, stream) != 1) {
			if(p == string) return NULL; /* 一文字も読めなかった */
			break;
		}
		if(*p++ == '\n') break; /* 改行を\0に置き換えないので、必ずp++を行う */
	}
	*p = '\0';

	return string;
}

char*
gets(char* buffer)
{
	char* p = buffer;

	for(;;) {
		if(fread(p, 1, 1, stdin) != 1) {
			if(p == buffer) return NULL; /* 一文字も読めなかった */
			break;
		}
		if(*p == '\n') break; /* 改行を\0に置き換えるので、\nなら++を行わない */
		p++;
	}
	*p = '\0';

	return buffer;
}

int
fputc(int c, FILE* stream)
{
	if(fwrite(&c, 1, 1, stream) != 1) return -1;

	return c & 0xff; /* '\x80'〜'\xff'を符号拡張してはいけない */
}

int
putc(int c, FILE* stream)
{
	return fputc(c, stream);
}

int
putchar(int c)
{
	return putc(c, stdout);
}

int
fputs(const char* string, FILE* stream)
{
	const char* p = string;

	while(*p != '\0') { /* 改行文字も関係なく出力 */
		if(fwrite(p++, 1, 1, stream) != 1) return -1;
	}

	return 0; /* 成功時は書き込んだバイト数ではなく0を返す */
}

int
puts(const char* string)
{
	const char* p = string;

	while(*p != '\0' && *p != '\n') { /* 改行文字は出力せずに終了 */
		if(fwrite(p++, 1, 1, stdout) != 1) return -1;
	}

	return 0; /* 成功時は書き込んだバイト数ではなく0を返す */
}

int
remove(const char* filename)
{
	if(pceFileDelete(filename) != 0) return -1;

	return 0;
}

int
feof(FILE* stream)
{
	IO* io = io_file(stream);

	return io->flags & IO_EOF;
}

int
ferror(FILE* stream)
{
	IO* io = io_file(stream);

	return io->flags & IO_ERROR;
}

void
clearerr(FILE* stream)
{
	IO* io = io_file(stream);

	io->flags = 0;
}

void
perror(const char* string)
{
	if(string && *string) { /* 空文字列時は": "を表\示しない */
		fprintf(stderr, "%s", string);
		fprintf(stderr, ": ");
	}
	fprintf(stderr, "%s", strerror(errno));
	fprintf(stderr, "\n"); /* 要改行 */
}

int
fprintf(FILE* stream, const char* format, ...)
{
	int retval;
	va_list argptr;

	va_start(argptr, format);
	retval = vfprintf(stream, format, argptr);
	va_end(argptr);

	return retval;
}

int
printf(const char* format, ...)
{
	int retval;
	va_list argptr;

	va_start(argptr, format);
	retval = vprintf(format, argptr);
	va_end(argptr);

	return retval;
}

int
vfprintf(FILE* stream, const char* format, va_list argptr)
{
	return fwrite(strbuf, 1, vsprintf(strbuf, format, argptr), stream);
}

int
vprintf(const char* format, va_list argptr)
{
	return vfprintf(stdout, format, argptr);
}

/****************************************************************************
 *	標準エラー出力
 ****************************************************************************/

//void
//stderr_open()
//{
//	USBCOMINFO uci = { "stderr" };
//
//	/* 標準エラー出力を開きます。 */
//	pceUSBCOMSetup(&uci);
//
//	/* Ｂボタンが押されていたら、標準エラー出力モニタの接続を待ちます。 */
//	if(pcePadGetDirect() & PAD_B) {
//		unsigned char* old_vbuff;
//		pceLCDDispStop();
//		old_vbuff = pceLCDSetBuffer(_def_vbuff);
//		pceLCDDispStart();
//		pceFontSetType(0x80);
//		pceFontSetTxColor(3);
//		pceFontSetBkColor(0);
//		pceFontSetPos(0, (DISP_Y - 10 * 5) / 2);
//		/*             ０１２３４５６７８９０１２ */
//		pceFontPutStr(" +----------------------+\n"
//		              " |標準エラー出力モニタの|\n"
//		              " | 接続を待っています… |\n"
//		              " |(Ａボタンでキャンセル)|\n"
//		              " +----------------------+");
//		pceLCDTrans();
//		while(pcePadGetDirect() & PAD_A) { /* 開放待ち*/ }
//		for(;;) {
//			if(pceUSBCOMGetStat() & UCS_PCSTAT) break; /* 接続 */
//			if(pcePadGetDirect() & PAD_A) break; /* キャンセル */
//		}
//		while(pcePadGetDirect() & PAD_A) { /* 開放待ち*/ }
//		pceLCDSetBuffer(old_vbuff);
//		stderr_write("接続完了\n", 9); /* 標準エラー出力モニタに表\示 */
//	}
//}
//↓2003/11/22 stderr_open()はアプリケーションから明示的に呼ぶようにしたので、
//             stderr_open()が呼ばれた場合は常に出力モニタの接続を待つように変更しました。
void
stderr_open(int timeout)
{
	USBCOMINFO uci = { "stderr" };
	//
	unsigned char* old_vbuff;
	int T0;
	int T1;

	/* 標準エラー出力を開きます。 */
	pceUSBCOMSetup(&uci);

	/* 標準エラー出力モニタの接続待ち。 */
	//pceLCDDispStop(); {{2005/06/26 Naoyuki Sawa 不要なので削除しました}}
	old_vbuff = pceLCDSetBuffer(_def_vbuff);
	pceLCDDispStart();
	pceFontSetType(0x80);
	pceFontSetTxColor(3);
	pceFontSetBkColor(0);
	pceFontSetPos(0, (DISP_Y - 10 * 5) / 2);
	/*             ０１２３４５６７８９０１２ */
	pceFontPutStr(" +----------------------+\n"
	              " |標準エラー出力モニタの|\n"
	              " | 接続を待っています… |\n"
	              " |(Ａボタンでキャンセル)|\n"
	              " +----------------------+");
	pceLCDTrans();
	while(pcePadGetDirect() & PAD_A) { /* 開放待ち*/ }
	T0 = pceTimerGetCount();
	for(;;) {
		if(pceUSBCOMGetStat() & UCS_PCSTAT) break; /* 接続 */
		if(pcePadGetDirect() & PAD_A) break; /* キャンセル */
		if(timeout >= 0) {
			T1 = timeout * 1000 - (pceTimerGetCount() - T0);
			if(T1 <= 0) break; /* タイムアウト */
			pceFontSetPos((DISP_X - 5 * 9) / 2, DISP_Y - 16);
			/*             012345678 */
			pceFontPrintf("あと%3d秒", (T1 + 999) / 1000);
			pceLCDTrans();
		}
	}
	while(pcePadGetDirect() & PAD_A) { /* 開放待ち*/ }
	pceLCDSetBuffer(old_vbuff);
	stderr_write("接続完了\n", 9); /* 標準エラー出力モニタに表\示(キャンセル時は/dev/nulへ) */
}

void
stderr_close()
{
	USBCOMINFO uci = { "" };

	/* 標準エラー出力を閉じます。
	 * * 単にpceUSBCOMStop()を呼んだだけでは、USBCOMが切断できません。
	 *   PCとの接続が確立していた場合、pceUSBCOMStop()は切断を行わないからです。
	 *   また、シグネチャもクリアされません。(以上、BIOS 1.18で確認)
	 * * この問題を回避するには、pceUSBCOMSetup()を使ってUSBCOMを切断します。
	 *   pceUSBCOMSetup()ならば、無条件にPC側の接続フラグをクリアするので、
	 *   いったんpceUSBCOMSetup()を使って：
	 *	mystat(P/ECE側の接続フラグ) -> 1
	 *	pcstat(PC側の接続フラグ)    -> 0
	 *	シグネチャ                  -> ""
	 *   としておき、あらためてpceUSBCOMStop()を呼んで：
	 *	mystat(P/ECE側の接続フラグ) -> 0
	 *   とします。
	 */
	pceUSBCOMSetup(&uci);
	pceUSBCOMStop();
}

void
stderr_write(const void* ptr, int len)
{
	int ucs;

	/* 0バイトで送信要求を発行すると、絶対にUCS_TXDONEが立たないので、(usbcom.c: txsub() 参照)
	 * あらかじめ除外しておきます。
	 */
	if(len <= 0) return;

	/* PCとの接続が確立していなければ、何もせずに帰ります。
	 * * PISTATが1でなければPCSTATも1になりません。(usbcom.c: usbcom() case13: (USBCOM Open) 参照)
	 、  PCSTATが1である間PISTATは0に戻りません。(usbcom.c: pceUSBCOMStop() 参照)
	 *   従って、PCSTATが1ならPISTATも必ず1です。PISTATを検査する必要はありません。
	 */
	ucs = pceUSBCOMGetStat();
	if(!(ucs & UCS_PCSTAT)) return;

	/* 送信要求を発行します。 */
	pceUSBCOMStartTx(ptr, len);

	/* 送信が完了するまで待ちます。 */
	for(;;) {
		ucs = pceUSBCOMGetStat();
		if(ucs & UCS_TXDONE) break; /* 送信完了 */
		if(!(ucs & UCS_PCSTAT)) break; /* PCとの接続が切れた */
	}
}
