module y4d_aux.filesys;

//	fileにすると、std.fileとかぶる(;´Д`)
private import std.moduleinit;
private import ytl.y4d_result;
private import std.c.stdio;
private import std.string;
private import std.file;
private import std.zlib;
private import std.stream;
private import SDL;
private import y4d_math.rand;
private import y4d_aux.direnumerator;
private import crc32;

//private import yamalib.log.log;
private import yamalib.log.performancelog;

//-----------	テンポラリファイルを作成するためのメソッド
version (Win32) {

extern(Windows) export uint GetTempPathA(
	 uint nBufferLength,
	 char* lpBuffer
	);
extern(Windows) export uint GetTempFileNameA(
  char* lpPathName,
  char* lpPrefixString,
  uint uUnique,
  char* lpTempFileName);

} else version (linux) {
private import y4d_aux.stringconv;
private import std.c.process;	//	getpid

//#include <sys/types.h>
//#include <unistd.h>
//	テンポラリファイルをプロセスIDから作りたいので
//	getpidの定義が必要なのだ(´Д｀)
//	pid_t getpid(void);
//	↑これね。

} else {
private import y4d_aux.stringconv;
private import std.date;
}

///	fileを扱うときに使うと良い。
/**
	ファイルシステムクラス。

	path関連は、multi threadを考慮していない。
	(つまりsetPathしているときに他のスレッドがgetPathするようなことは
	想定していない)

	pathはたいてい起動時に一度設定するだけなので、これでいいと思われる。

<PRE>
	使用例)
	FileSys.addPath("..");
	FileSys.addPath("sdl\src\");
	FileSys.addPath("yanesdk4d/");
	char[] name = FileSys.makeFullName("y4d.d");

	./y4d.d(これはディフォルト) と ../y4d.d と sdl/src/y4d.d と
	yanesdk4d/y4d.d を検索して、存在するファイルの名前が返る。
</PRE>

	また、アーカイバとして、 FileArchiverBase 派生クラスを
	addArchiver で指定できる。その Archiver も read/readRWのときに
	必要ならば自動的に呼び出される。
*/
class FileSys {
	///	ファイル名を二つくっつける。
	/**
	<PRE>
		1. / は std.path.sep(windows環境では'\'。linux環境では'/') に置き換える
		2.左辺の終端の \ は自動補間する
		"a"と"b"をつっつければ、"a\b"が返ります。
		3.駆け上りpathをサポートする
		"dmd/bin/"と"../src/a.d"をつくっければ、"dmd\src\a.d"が返ります。
		4.カレントpathをサポートする
		"./bin/src"と"./a.d"をくっつければ、"bin\src\a.d"が返ります
		"../src/a.d"と"../src.d"をつっつければ"..\src\src.d"が返ります
		5.途中のかけあがりもサポート
		"./src/.././bin/"と"a.c"をつっつければ、"bin\a.c"が返ります。
		6.左辺が ".."で始まるならば、それは除去されない
		"../src/bin"と"../test.c"をつっつければ、"../src/test.c"が返ります。
		char name1[] = r"../../test2\./../test3/test3\";
		char name2[] = ".././test.d";
			=>	r"..\..\test3\test.d"
		7.先頭が'/'or'\'で始まるか、間に':'が含まれる場合は絶対pathと
		みなされる。bが絶対pathである場合、aの内容は無視される。
	</PRE>
	*/
	static char[] concat(char[] a,char[] b)
	{
		//	bが絶対pathならば、そのまま返す。
		if (isAbs(b)) {
			char[] name;
			add(name,b);
			return name;
		}
		char[] name;
		add(name,a);
		//	終端が \ でないなら \ を入れておく。
		if (name.length &&
			name[name.length-1]!='\\' && name[name.length-1]!='/')
			{ name ~= std.path.sep;}
		add(name,b);
		return name;
	}

	///	現在の作業フォルダ + a を concatして返す
	/**
		ただし、aが絶対pathの場合は、作業フォルダは結合されない。
		(concatの仕様)
	*/
	static char[] concatCwd(char[] a){
		return concat(cast(char[]) std.file.getcwd(),a);
	}

	///	絶対pathかどうかを調べる。
	/**
		絶対pathの定義：<BR>
		1.先頭が'\'or'/'である<BR>
		2.途中に':'が含まれる<BR>
	*/
	static bool isAbs(char[] path){
		if (path.length == 0) return false;
		if (path[0] == '\\' || path[0] == '/') return true;
		return cast(bool) (std.string.find(path,':') != -1);
	}

	///	ファイル名に、setPathで指定されているpathを結合する(存在チェック付き)
	/**
		ただし、ファイルが存在していなければ、
		pathは、設定されているものを先頭から順番に調べる。
		ファイルが見つからない場合は、元のファイル名をそのまま返す。<BR>

		filenameが絶対pathの場合は、localpathは無視される。<BR>

		fullname := setPathされているpath + localpath + filename;
	*/
	static char[] makeFullName(char[] localpath,char[] filename)
	{
		return makeFullName(concat(localpath,filename));
	}

	///	ファイル名に、setPathで指定されているpathを結合する(存在チェック付き)
	/**
		ただし、ファイルが存在していなければ、
		pathは、設定されているものを先頭から順番に調べる。
		ファイルが見つからない場合は、元のファイル名をそのまま返す

		fullname := setPathされているpath + filename;
	*/
	static char[] makeFullName(char[] filename)
	{
		foreach(char[] path;pathlist) {
			char [] fullname = concat(path,filename);
			//	くっつけて、これが実在するファイルか調べる
			if (isExist(fullname)) return fullname;
		}
		//	アーカイバを用いて調べてみる
		foreach(FileArchiverBase arc;archiver){
			foreach(char[] path;pathlist) {
				char[] fullname = concat(path,filename);
				if (arc.isExist(fullname)){
					return fullname;
				}
			}
		}
		return filename;	//	not found..
	}

	///	ファイルの生存確認
	/**
		指定したファイル名のファイルが実在するかどうかを調べる
		setPathで指定されているpathは考慮に入れない。
	*/
	static bool isExist(char[] filename){

/*
		FILE* fp = fopen(std.string.toStringz(filename),"r");
		if (fp) { fclose(fp); return true; }
		return false;
*/
	/*	//	Windowsならば↓のほうが早いと思うが．．
		return ( 0xFFFFFFFF != GetFileAttributes(
			std.string.toStringz(filename)) );
		// 属性の取得に失敗したので、ファイルが存在しない可能性が高い。
	*/

		//	isfileは、attributeで調べるので超ﾊﾔｰ(ﾟдﾟ)
		try {
			return cast(bool) (std.file.isfile(filename) != 0);
		} catch (std.file.FileException){
			return false;
		}
	}

	/// ファイルの生存確認(pathも込み)
	/**
		setPathで指定したpathも含めてファイルを探す。
		アーカイバは含まない。
		あくまでファイルが実在するときのみそのファイル名が返る。
	*/
	static char[] isRealExist(char [] filename) {
		foreach(char[] path;pathlist) {
			char [] fullname = concat(path,filename);
//printf("isRealExist : %*s\n", fullname);
			//	くっつけて、これが実在するファイルか調べる
			if (isExist(fullname)) return fullname;
		}
		return null;
	}

	///	readメソッド
	/**
		fileを読み込む。ファイルが実在しないときは、
		setSearchで設定されているアーカイバを利用して読み込めるか試す。
		読み込めないときはnullが返る
	*/
	static void[] read(char[] filename) {
		
		try {
			PerformanceLog.logFileRead(filename, null);

			scope char[] f = isRealExist(filename);
//printf("read method : %*s \n", f);
			if (f) { return readSimple(f); }
			
			//	ここで、アーカイバから読み込む必要がある
			foreach(FileArchiverBase arc;archiver){
				foreach(char[] path;pathlist) {
					scope char[] file = concat(path,filename);
					void[] p = arc.read(file);
					if (p) return p;
				//	アーカイバで、解凍が成功するまで
				//	次の候補(path,アーカイバ)を試していく。
				}
			}

		} catch {
			// エラー
		} finally {
			PerformanceLog.endLog();
		}

		return null;
	}

	///	readRWでの戻り値に使用している
	/*
			実体がある場合は、ファイルから読み込まずにファイル名のみを
			返す。(ファイルからDのランタイムに読ませるとGCからメモリを
			確保して、そのメモリをゼロクリアする分だけオーバーヘッドが
			発生するため、出来ることならDのランタイムを利用せず直接読み
			込むほうが望ましいので)
	*/
	struct RWops {
		/// これが実体。
		/**
			参照しておかないとGCが解放してしまうので構造体のなかに含めてある。
		*/
		void [] data;

		///	これが肝心のデータ。
		/**
			使い終わったらrwops.close()を呼び出すか何かして解放する必要がある
			このメンバは、dataが非nullのときのみ有効である。
		*/
		SDL_RWops* rwops;

		///	filenameのみ
		char[] filename;
	}

	///	fileから読み込む。SDL_RWops*を返す。
	/**
		読み込みに失敗すればこの関数は非0が返る

<PRE>
		使用例)
		FileSys.RWops rw = FileSys.readRW(name);
		if (rw.rwops)
		{
			chunk = Mix_LoadWAV_RW(rw.rwops,1);
			return 0;	// 正常終了
		} else if (rw.filename) {
			chunk = Mix_LoadWAV(std.string.toStringz(rw.filename));
		} else {
			return 1;	// file not found
		}
</PRE>

		※ SDL_RWopsとは、SDLで、メモリ内のデータを扱うためのopearator。
			SDLでRWのついている関数は、これを引数にとる。
	*/
	static RWops readRW(char[] filename)
	{
		RWops rw;

		//	path上に存在するかどうかを確かめる。
		char[] fullname = isRealExist(filename);

		//	見つかったので、このまま返る
		if (fullname) {
			rw.filename = fullname;
			return rw;
		}

		void[] data = read(filename);
		if (data) {
			rw.data = data;
			rw.rwops = SDL_RWFromMem(&data[0],data.length);
		}
		return rw;
	}

	/// ファイルを読み込む(例外は投げない)
	/**
		file.readが例外を投げてうっとおしいときに使います。
		読み込みに失敗するとnullが返ります。
		pathのサーチは行ないません。登録されているarchiverも無視です。
	*/
	static void[] readSimple(char[] filename) {
		void[] rdata;
		try {
			rdata = std.file.read(filename);
		} catch (std.file.FileException) {
			printf("readSimple : %*s", filename);
			return null;
		}
		return rdata;
	}

	/// ファイルを書き込む(例外は投げない)
	/**
		file.readが例外を投げてうっとおしいときに使います。
		読み込みに失敗すると非0が返ります。
	*/
	static y4d_result writeSimple(char[] filename,void[] rdata){
		try {
			std.file.write(filename,rdata);
		} catch (std.file.FileException) {
//Log.printError("File write failure.");
			return y4d_result.file_write_error;
		}
		return y4d_result.no_error;
	}

	///	ファイルの実在性をチェックして、無ければファイルを作成
	/**
	<PRE>
		1.isReadExist()で実存するかチェック。実在するなら、そのファイル
		2.実存しないならばテンポラリファイルを作成して返す。
	</PRE>
	*/
	static TmpFile getTmpFile(char[] filename){
		char[] file = isRealExist(filename);
		if (file) {
			return new TmpFile(file); // 実在するらしい
		} else {
			return new TmpFile(read(filename));
		}
	}
	
	/// アーカイバを指定できるヤツ
	static TmpFile getTmpFile(char[] filename, FileArchiverBase arcReader) {
		char[] file = isRealExist(filename);
		if (file) {
			return new TmpFile(file); // 実在するらしい
		} else {
			return new TmpFile(arcReader.read(filename));
		}
	}

	/// テンポラリのパスを返す
	static char[] getTmpPath() {
version (Win32) {

			char* szTmpPath = cast(char*) new char[256];
			if (GetTempPathA(256, szTmpPath) == 0) {
	//		Err.Out("CFile::CreateTemporary::テンポラリパスが取得できない。");
				return null;
			}
			return cast(char[]) std.string.toString(szTmpPath);

} else version (linux) {

		//	プロセスID
		uint pid = cast(uint)(getpid());
		return StringConv.toHex(pid,8);

} else {

		uint pid = std.date.getUTCtime();
		//	時刻をかわりに使うしかない
		return StringConv.toHex(pid,8);

}
	}

	///	テンポラリファイルを作成して扱うためのクラス
	static class TmpFile {
		///	実在するファイルを設定する
		/**
			実在するファイルなので終了するときに削除はされない。
			削除されるのは、setMemoryでファイルを生成したときのみ。
		*/
		void setFile(char[] file){
			filename = file; 
			bMade = false;
		}
		
		/// 自分で生成したファイルがあるか？
		bool isMade() {
			return bMade;
		}

		///	void[]を渡して、テンポラリファイルを生成する
		/**
			渡されるmemが、nullならば、テンポラリファイルは生成しない。
			生成に失敗したら非0が返る

			@portnote テンポラリファイルの作成は環境依存。移植時に注意が必要。(windows,linuxのみサポート)
		*/
		y4d_result setMemory(void[] mem)
		{
			release();
			if (!mem) {
printf("TmpFile#setMemory: memory data is null\n");
				return y4d_result.invalid_parameter;
			}
			//	データ無いやん..

			// テンポラリファイル名を取得する(環境依存)
version (Win32) {

			char* szTmpPath = cast(char*) new char[256];
			char* szTmpFileName = cast(char*) new char[256];
			if (GetTempPathA(256, szTmpPath) == 0) {
	//		Err.Out("CFile::CreateTemporary::テンポラリパスが取得できない。");
				return y4d_result.win32api_error;
			}
			if (GetTempFileNameA(szTmpPath, cast(char*) "YNE", 0, szTmpFileName) == 0) {
	//		Err.Out("CFile::CreateTemporary::テンポラリファイルが作成できない。");
				return y4d_result.win32api_error;
			}
			filename = cast(char[]) std.string.toString(szTmpFileName);

} else version (linux) {

		//	プロセスID
		uint pid = cast(uint)(getpid());
		filename = std.path.sep ~ "tmp" ~ std.path.sep ~ "YNE";
		static uint fileno = 0;
		synchronized (syncobj) {
			filename ~= StringConv.toHex(pid,8) ~ StringConv.toHex(fileno,8);
			fileno++;
			//	プロセスIDと固有のナンバーでファイル名を生成。
		}

} else {

		uint pid = std.date.getUTCtime();
		//	時刻をかわりに使うしかない

		filename = "YNE";	//	カレントフォルダに作らざるを得ない
		static uint fileno = 0;
		synchronized (syncobj) {
			filename ~= StringConv.toHex(pid,8) ~ StringConv.toHex(fileno,8);
			fileno++;
			//	時刻と固有のナンバーでファイル名を生成。
		}
}

			//	ファイルを生成してみるテスト。
			if (writeSimple(filename,mem)) {
				release();
//Log.printError("TmpFile create error: %s",filename);
				return y4d_result.file_write_error;
				// 書き出しエラー
			}
			bMade = true;
			return y4d_result.no_error;
		}

		///	確保していたテンポラリファイルを解放する(作成していたときのみ)
		/**
			setMemoryでファイルを生成していたときのみファイルを削除。
			setFileで設定していた場合は、ファイルは削除しない。
		*/
		void release() {
			if (bMade) {
				try {
					std.file.remove(filename);
				} catch {
				}
			}
			filename = null; 
			bMade = false;
		}

		///	ファイル名を返す
		/**
			setFileで設定されたファイル名および、setMemoryで
			メモリを渡されたときに生成されたテンポラリファイル名を返します。
		*/
		char[] getFileName() { return filename; }

		///	事前に用意されたファイルをセットする
		this(char[] file) { setFile(file); }

		///	バッファを渡して、そのバッファの内容のファイルを生成。
		/**
			内部的にはmakeFileを用いてテンポラリファイルを生成している。
		*/
		this(void[] mem) { 
			setMemory(mem); 
		}

		///	デストラクタ
		/**
			デストラクタでは、releaseを呼び出す。
		*/
		~this() { release(); }
	private:
		bool bMade;
		// 自分で作ったテンポラリファイルならば、
		//	終了時に削除する必要あり

		char[] filename;
	}

	/// 例外を投げない書き込みメソッド
	/**
		書き込みするときに、FileArchiverを指定できる
		arc == null なら、そのまま書き出す

		成功すれば0。失敗すれば非0が返る。
	*/
	static y4d_result write(char[] filename,void[] data,FileArchiverBase arc)
	{
		if (!data) return y4d_result.invalid_parameter; // no data
		if (!arc) {
			if (FileSys.writeSimple(filename,data))
				return y4d_result.file_write_error; // だめぽ
			return y4d_result.no_error;
		} else {
			return arc.write(filename,data);
		}
	}

	///	例外を投げない書き込みメソッド
	/*
		成功すれば0。失敗すれば非0が返る。
	*/
	static y4d_result write(char[] filename,void[] data)
	{
		return write(filename,data,null);
	}

	///	pathを取得。
	static char[][] getPathList() { return pathlist; }

	///	pathを設定。ここで設定したものは、makeFullPathのときに使われる
	/**
		設定するpathの終端は、\ および / でなくとも構わない。
		(\ および / で あっても良い)
	*/
	static void setPath(char[][] p) { pathlist = p; }

	///	pathを追加。ここで設定したpathを追加する。
	/**
		設定するpathの終端は、\ および / でなくとも構わない。
		(\ および / で あっても良い)
		ただし、"."や".."などを指定するときは、"\" か "/"を
		付与してないといけない。(こんなのを指定しないで欲しいが)

		ディフォルトでは""のみがaddPathで追加されている。
		(カレントフォルダを検索するため)
	*/
	static void addPath(char[] p) { pathlist ~= p; }

	///	FileArchiverBaseの派生クラス(zip解凍クラスetc..)を設定する
	/**
		ここで設定したものは、isExist,readで有効。
	*/
	static void setArchiver(FileArchiverBase[] a) { archiver = a; }

	///	FileArchiverBaseの派生クラス(zip解凍クラスetc..)を追加する
	/**
		ここで設定したものは、makeFullName,read/readRWで有効。
	*/
	static void addArchiver(FileArchiverBase s) { archiver ~= s; }

	///	FileArchiverBaseの派生クラス(zip解凍クラスetc..)を取得する
	/**
		ここで設定したものは、makeFullName,read/readRWで有効。
	*/
	static FileArchiverBase[] getArchiver() { return archiver; }

	///	親フォルダ名を返す
	/**
	<PRE>
		ディレクトリ名の取得。
		例えば、 "d:\path\foo.bat" なら "d:\path\" を返します。
		"d:\path"や"d:\path\"ならば、"d:\"を返します。
		※　std.path.getDirNameでは、"d:\path\"に対しては
			"d:\path"を返すことに注意。
		※　windows環境においては、'/' は使えないことに注意。
			(std.path.sep=='\\'であるため)
		※　終端は必ず'\'のついている状態になる
		※　終端が ".." , "..\" , "../"ならば、これは駆け上がり不可。
			(".."の場合も終端は必ず'\'のついている状態になる)
	</PRE>
	*/
	static char[] getDirName(char[] fname) {
		//	コピーしておく
		char[] fullname = fname.dup;

		//	終端が'\'か'/'ならば1文字削る
		int l = fullname.length;
		if (l) {
			char c = fullname[l-1];
			if (c=='\\' || c=='/') { --l; fullname.length = l; }
		}
		//	もし終端が".."ならば、これは駆け上がり不可。
		if (l >= 2 && fullname[l-2]=='.' && fullname[l-1]=='.'){
			//	かけ上がれねーよヽ(`Д´)ノ
		} else {
			fullname = cast(char[]) std.path.getDirName(fullname);
		}
		l = fullname.length;
		if (l && fullname[l-1]!='\\' && fullname[l-1]!='/')
			{ fullname ~= std.path.sep; }
		return fullname;
	}

	///	絶対パス,相対パス名のファイル名部分を返す。
	/**
		例) c:\abc\def\h.txt　ならば h.txt <BR>
		c:\ のようなものを渡した場合nullが返る。
	*/
	static char[] getPureFileName(char[] filename){
		char[] dir = getDirName(filename);
		if (dir.length >= filename.length) return null; // ないヤン
		return filename[dir.length..filename.length];
	}

	///	コンストラクタ
	/**
		コンストラクタでは、addPath("./");で、カレントフォルダを
		検索対象としている。
	*/
	static this() {
		// ↓これはDMD0.178で不可になった
		addPath(cast(char[]) "");
		foreach(path;pathlist) {
			printf("path:%*s\n",path);
		}

version(Win32){
} else version(linux){
		syncobj=new SyncObj;
} else {
		syncobj=new SyncObj;
}
	}

private:

version(Win32){
} else version(linux){
	class SyncObj {}
	static SyncObj syncobj;
} else {
	class SyncObj {}
	static SyncObj syncobj;
}

	//	nameにaを足す
	static void add(inout char[] name,char[] a)
	{
		if (!a) return ; // aが空なのでこのまま帰る
		for(int i=0;i<a.length;++i){
			char c = a[i];
			bool bSep = false;
			if		(c=='/' || c == '\\') {
				bSep = true;
			}
			else if (c=='.') {
				//	次の文字をスキャンする
				char n = (i+1 <a.length) ? a[i+1] : 0;
				//	./ か .\
				if (n=='/' || n=='\\') { i++; continue; }
				//	../ か ..\
				if (n=='.') {
					char n2 = (i+2 < a.length) ? a[i+2] : 0;
					if (n2=='/' || n2=='\\')	{
						char[] nn = getDirName(name);
						if (nn==name) {
						//	".."分の除去失敗。
						//	rootフォルダかのチェックを
						//	したほうがいいのだろうか..
							name ~= ".." ~ std.path.sep;
						} else {
							name = nn;
						}
							i+=2; continue;
					}
				}
			}
			if (bSep) {
				name ~= std.path.sep;
			} else {
				name ~= c;
			}
		}
	}

	//	path list
	static char[][] pathlist;

	//	file search method
	static FileArchiverBase[] archiver;
}

//---------------------------------------------------------------------------
///	Streamをランダムアクセスするためのwrapper
class StreamRandAccessor {

	///	openしたストリームを渡すナリよ！
	void	setStream(std.stream.Stream f_){
		stream_length = f_.size();
		f = f_;
		// 読み込んでいないので。
		pos = 0;
		readsize = 0;
		bWrite = false;
	}

	///	ストリームのサイズを返す
	/**
		std.stream.Stream.size() は、終端にseekするので
		そのへんのオーバーヘッドを考えると、こいつを直接取得したほうが
		良いと思われ。
	*/
	ulong	stream_size() { return stream_length; }

	///	ストリームの先頭からiのoffsetの位置のデータubyteを読み込む
	ubyte getUbyte(uint i){
		check(i,1);
		// long長の配列っていうのはつれへんのかいな...
		return data[i];
	}

	///	ストリームの先頭からiのoffsetの位置に、データubyteを書き込み
	void pushUbyte(uint i,ubyte b){
		check(i,1);
		data[i] = b;
		bWrite =true;
	}


	///	ストリームの先頭からiのoffsetの位置のデータushortを読み込む
	/**
		little endian,big endianの区別も行なう。
	*/
	ushort getUshort(uint i)
	{
		check(i,2);

	version (LittleEndian)
	{
		return cast(ushort)(*cast(ushort *)&data[i]);
	}
	else
	{
		ubyte b0 = data[i];
		ubyte b1 = data[i + 1];
		return (b1 << 8) | b0;
	}
	}

	///	ストリームの先頭からiのoffsetの位置のデータuintを読み込む
	/**
		little endian,big endianの区別も行なう。
	*/
	uint getUint(uint i)
	{
		check(i,4);

	version (LittleEndian)
	{
		return *cast(uint *)&data[i];
	}
	else
	{
		return bswap(*cast(uint *)&data[i]);
	}
	}

	///	ストリームの先頭からiのoffsetの位置のデータushortを書き込む
	/**
		little endian,big endianの区別も行なう。
	*/
	void putUshort(uint i, ushort us)
	{
		check(i,2);

	version (LittleEndian)
	{
		*cast(ushort *)&data[i] = us;
	}
	else
	{
		data[0] = cast(ubyte)us;
		data[1] = cast(ubyte)(us >> 8);
	}
		bWrite =true;
	}

	///	ストリームの先頭からiのoffsetの位置のデータuintを書き込む
	/**
		little endian,big endianの区別も行なう。
	*/
	void putUint(uint i, uint ui)
	{
		check(i,4);

	version (BigEndian)
	{
		ui = bswap(ui);
	}
		*cast(uint *)&data[i] = ui;
		bWrite =true;
	}

	/// 書き込みを行なう
	/**
		pushUint等でデータに対して書き込みを行なったものを
		ストリームに戻す。このメソッドを明示的に呼び出さなくとも
		256バイトのストリーム読み込み用バッファが内部的に用意されており、
		現在読み込んでいるストリームバッファの外にアクセスしたときには
		自動的に書き込まれる。
	*/
	void	flush() {
		if (bWrite) {
			//	writebackしなくては
			f.writeBlock(cast(void*)&data[0],readsize);
			bWrite = false;
		}
	}

	///	posからsize分読み込む(バッファリング等はしない)
	/**
		ulongには読み込まれたサイズを返す
	*/
	ulong read(void[] data,uint pos,int size){
		flush();
		//	ファイルのシークを行なう前にはflushさせておかないと、
		//	あとで戻ってきて書き込むとシーク時間がもったいない

		f.seek(pos,std.stream.SeekPos.Set);
		int s = f.readBlock(cast(void*) data,size);
		return s;
	}

	this() {}

	///	setStreamも兼ねるコンストラクタ
	this(std.stream.Stream f) { setStream(f); }

	~this() { flush(); }

private:
	std.stream.Stream f;
	ulong stream_length;
	ubyte data[256]; // 読み込みバッファ
	int  readsize;	 // バッファに読み込めたサイズ。
	uint pos;		 // 現在読み込んでいるバッファのストリーム上の位置
	bool  bWrite;	 // このバッファに対して書き込みを行なったか？
					 //	書き込みを行なったら、他のバッファを読み込んだときに
					 //	この分をwritebackする必要がある

	//	このストリームのiの位置にsizeバイトのアクセスをしたいのだが、
	//	バッファに読まれているかチェックして読み込まれていなければ読み込む。
	//	渡されたiは、data[i]で目的のところにアクセスできるように調整される。
	void check(inout uint i,ulong size){
//		if (i+size<pos || pos+readsize <= i){
		if ( i < pos || pos + readsize < i + size ) {
		//	バッファ外でんがな
			flush();
			// size<128と仮定して良い
			//	アクセスする場所がbufferの中央に来るように調整。
			uint offset = cast(uint) ((data.length - size)/2);
			if (i < offset) {
				pos = 0;
			} else {
				pos = i - offset;
			}
			f.seek(pos,std.stream.SeekPos.Set);
			readsize = f.readBlock(cast(void*)&data[0],data.length);
		}
		i -= pos;
	}

}

//---------------------------------------------------------------------------

///	Fileで用いる、圧縮ファイルを扱うためのクラス
/**
	Fileにこのクラスの派生クラスをセットしてやれば、
	FileSys.readやisExistで、圧縮ファイルも検索するようになる。<BR>

	実装例として FileArchiverZipFile も参考にすること。
*/
abstract class FileArchiverBase
{
	///	存在するかを問い合わせる関数
	/**
		path等は無視。file名には '/'は用いておらず、'\'を用いてあり
		(linux環境では'/')、また、".."や"."は付帯していないと仮定して良い。
	*/
	bool isExist(char[] filename);

	/// 存在するか調べて読み込む
	/**
		存在しないときは、nullが戻る

		path等は無視。file名には '/'は用いておらず、'\'を用いてあり
		(linux環境では'/')、また、".."や"."は付帯していないと仮定して良い。
	*/
	void[] read(char[] filename);

	///	ファイルを書き込む
	/**
		このクラスのreadで読み出せるように書き込みを実装する
		(実装しなくとも良い)

		書き出しに成功すれば0,失敗すれば非0が返るようにする。
	*/
	y4d_result write(char[]filename,void[] data);

	///	ファイルのenumeratorを返す
	/**
		自分のサポートする書庫ファイルの場合は、そのenumeratorを返す。
	*/
	DirEnumerator getEnumerator(char[] filename);
}

/// 独自形式の圧縮ファイルをシームレスに扱うためのクラス(サンプル)
/**
	FileSys.addArchiverでセットすれば、
	FileSys.read / readRW / isExistで読み込るようになる。
*/
class FileArchiverSample : FileArchiverBase {

	///	存在するかどうかのチェック関数
	/**
		ここでは、与えられたファイル名に".bin"を付与したファイルが存在すれば
		trueを返します。
	*/
	bool isExist(char[] filename){
		return FileSys.isExist(filename ~ ".bin");
	}

	/// 読み込む関数
	/**
		ここでは与えられたファイル名に".bin"を付与したファイルがあれば
		読み込みます。(独自の圧縮形式等ならば、そのあと、decode処理を
		行なえば良い)
	*/
	void[] read(char[] filename){
		return FileSys.readSimple(filename ~ ".bin");
	}

	///	書き込む関数
	/**
		ここでは与えられたファイル名に".bin"を付与したファイルに書き出します。
		(独自の圧縮形式等ならば、そのあと、encode処理を行なってから
		書き出すと良い)

		この関数は実装していなくてもok
	*/
	y4d_result write(char[] filename,void[] rdata){
		return FileSys.writeSimple(filename ~ ".bin",rdata);
	}

	///	enumeratorも実装しなくてok
	DirEnumerator getEnumerator(char[] filename) { return null; }
}

/// .zip ファイル(単体)をシームレスに扱うためのクラス
/**
<PRE>
	src/some.txt.zipのアーカイブがあれば、
		src/some.txt
	とアクセスすることが出来る。

	使用例)
		FileSys.addArchiver(new FileArchiverZipFile);
		void[] data = FileSys.read("src/some.txt");

	※	phobosのzlibのランタイムを使用しているため、通常のzipファイルではなく
		zlibで扱っているzipファイルしか解凍できない。
</PRE>
*/
class FileArchiverZipFile : FileArchiverBase {

	bool isExist(char[] filename){
		//	.zipをつけたファイルがあればok
		return FileSys.isExist(filename ~ ".zip");
	}

	void[] read(char[] filename){
		void[] mem;
		mem = FileSys.readSimple(filename ~ ".zip");
		if (!mem) return null;


		void[] data;
		try {
			data = cast(void[]) std.zlib.uncompress(mem); // zlib呼び出して解決!
		} catch (std.zlib. ZlibException){ // 展開エラー
			return null;
		}
		return data;
	}

	y4d_result write(char[]filename,void[] data){
		void[] c_data;
		try {
			c_data = cast(void[]) std.zlib.compress(data);
		} catch (std.zlib. ZlibException){ // 圧縮エラー
			return y4d_result.zlib_error;	//	やってらんねー
		}
		if (FileSys.write(filename,c_data))
			return y4d_result.file_write_error;	//	書き出し失敗
		return y4d_result.no_error;
	}

	///	enumeratorも実装しなくてok
	DirEnumerator getEnumerator(char[] filename) { return null; }
}


/+
///	zip ファイル(アーカイブ)をシームレスに扱うためのクラス
/**
<PRE>
	src/some.zip のアーカイブのなかに、a.txt b.txtが存在するとき、
		src/some/a.txt
		src/some/b.txt
	とアクセスすることが出来る。
</PRE>

このアーカイバーを単独で使うこともできる。
<PRE>
	FileArchiverZip a = new FileArchiverZip;
	void[] v = a.read(r"test\test\ToDo.txt");
	printf("%.*s",v);
	//	test.zipのなかにあるtest/ToDo.txtを読み込まれる

</PRE>

password付きzipファイルにも対応。
<PRE>
	//	FileArchiverZip.s_setPass("testpass");
	//	↑事前に全zip共通passを設定する場合。

	FileArchiverZip a = new FileArchiverZip;
	a.setPass("testpass");
	//	↑事後に読み込むファイルごとにpasswordを設定する場合。

	void[] v = a.read(r"test\ToDo.txt");
	printf("%.*s",v);

	１．passwordは、passwordつき書庫に対してのみ適用される。
	２．static void s_setPass(char[])メソッドによって、すべてのzipファイル
	共通のパスワードを設定することも出来る。
	３．s_setPassでパスワードが設定されていれば、このクラスのコンストラクタで
	そのパスワードをsetPass(char[])メソッドで取り込む。
	４．s_setPassは、このクラスのコンストラクタが起動したあとで
		設定しても無駄。その場合は、このクラスのsetPassを用いる必要がある。
</PRE>

*/
class FileArchiverZip : FileArchiverBase {

	/// ファイルから読み込む
	/**
	<PRE>
		1.FileSys.setPathで指定されているpathにあるファイルを優先
		2.無い場合、親フォルダにあるzipファイルを探していく
		というアルゴリズムになっています。
	</PRE>
	*/
	void[] read(char[] filename)
	{
		// 見つかればzipファイルを解凍して返す
		//	(保留中)
		void[] data;
		if (innerRead(filename,true,data)) {
			return data;
		}
		return null;
	}

	///	ファイルの存在チェック
	bool isExist(char[] filename) {
		void[] data;
		return innerRead(filename,false,data);
	}

	///	zipファイル内のファイル名を指定しての読み込み。
	/**
		filename : zipファイル名
		innerFileName : zip内のファイル名
		bRead : 読み込むのか？読み込むときはbuffにその内容が返る
		戻り値 :
			bRead=falseのとき、ヘッダ内に該当ファイルが存在すればtrue
			bRead=trueのとき、読み込みが成功すればtrue
	*/
	bool read(char[]filename,char[]innerFileName,bool bRead,
		out void[] buff) {
		// 見つかればzipファイルを解凍して返す

//		printf("%.*s , %.*s \n",filename,innerFileName);

		if (!FileSys.isRealExist(filename)) return false;

		auto std.stream.File file = new std.stream.File;
		try {
			file.open(filename);
			auto StreamRandAccessor acc = new StreamRandAccessor(file);
			acc.setStream(file);

			// Find 'end record index' by searching backwards for signature
			ulong endrecOffset;
			ulong stoppos;
			//	ulongって引き算とか比較が面倒くさいですなぁ．．（´Д｀）
			if (acc.stream_size()<66000) {
				stoppos = 0;
			} else {
				stoppos = acc.stream_size() - 66000;
			}
			for(uint i = cast(uint) (acc.stream_size() - 22); i >= stoppos; --i){
				if (acc.getUint(i) == 0x06054b50) { // "PK\0x05\0x06"
					ushort endcommentlength = acc.getUshort(i+20);
					if (i + 22 + endcommentlength != acc.stream_size)
						continue;
					endrecOffset = i;
				}
				goto endrecFound;
			}
			// ダメジャン
			return false;
endrecFound:;
			//	---- endrecOffsetが求まったナリよ！

			ushort filenum = acc.getUshort( cast(uint) (endrecOffset+10) );
			//	zipに格納されているファイルの数(分割zipは非対応)

			uint  c_pos = acc.getUint(cast(uint) endrecOffset+16);
			//	central directoryの位置

printf("filenum %d\n",filenum);

			//	---- central directoryが求まったなりよ！
			while (filenum-->0){
printf("SIGPOS : %d\n", c_pos);
				if (acc.getUint(c_pos)!=0x02014b50) { // シグネチャー確認!
					return false; // おかしいで、このファイル
				}
				uint compressed_size = acc.getUint(c_pos+20);
				uint uncompressed_size = acc.getUint(c_pos+24);
				ushort filename_length = acc.getUshort(c_pos+28);
				ushort extra_field_length = acc.getUshort(c_pos+30);
				ushort file_comment_length = acc.getUshort(c_pos+32);

//printf("filenamelength : %d\n",filename_length);
				// local_header_pos
				uint lh_pos = acc.getUint(c_pos+42);
				//	ファイル名の取得
				char[] fname = new char[filename_length];
				for(int i=0;i<filename_length;++i){
					fname[i] = acc.getUbyte(i+c_pos+46);
				}

//printf("fname : %.*s\n",fname);
				//	ファイル名が得られた。

				if (fname == innerFileName){
				//	一致したでー！これ読み込もうぜー!
//printf("lh_pos : %d\n",lh_pos);

					//	読み込み指定されてなければ
					//	ファイルが存在することを意味するtrueを返す
					if (!bRead) return true;

					if (acc.getUint(lh_pos)!=0x04034b50) {
						//	なんで？ヘッダのシグネチャー違うやん．．
						return false;
					}

					ushort lh_flag = acc.getUshort(lh_pos+6);
					ushort lh_compression_method = acc.getUshort(lh_pos+8);
					ushort lh_filename_length = acc.getUshort(lh_pos+26);
					ushort lh_extra_field = acc.getUshort(lh_pos+28);
					uint startpos = lh_pos + 30
						+ lh_filename_length + lh_extra_field;
					//	データの読み込み

					bool encry = cast(bool) ((lh_flag & 1) != 0);
					auto ubyte[] crypt_header;
					if (encry){
						crypt_header = new ubyte[12];
						if (acc.read(crypt_header,startpos,12)!=12)
							return false;
						compressed_size-=12;
						startpos+=12;
					}
					ubyte[] read_buffer = new ubyte[compressed_size];
					ulong readsize =
						acc.read(read_buffer,startpos,compressed_size);
					//	これ0ってことはあるのか..? まあ空のファイルかも知れないんで考慮する必要はないか。
					if (readsize!=compressed_size) return false;
					// 読み込みエラー

//printf("lh_compression_method : %d",lh_compression_method);

					if (encry) {
						//	暗号化ファイル
						/*
							暗号の解読
						*/
						uint key[3];
						void initKey(){
							key[0] = 305419896;
							key[1] = 591751049;
							key[2] = 878082192;
						}
						void updateKeys(ubyte b){
							key[0] = crc32.update_crc32(b,key[0]);
							key[1] = key[1] + (key[0] & 0x000000ff);
							key[1] = key[1] * 134775813 + 1;
							key[2] = crc32.update_crc32(cast(ubyte)(key[1] >> 24),key[2]);
						}
						ubyte decrypt_byte(){
							ushort temp = cast(ushort) (key[2] | 2);
							return cast(ubyte) ((temp * (temp ^ 1)) >> 8);
						}
						initKey();
						foreach(char c;zippass) updateKeys(cast(ubyte)c);

						//	Read the 12-byte encryption header into Buffer
						for(int i=0;i<12;++i) {
							ubyte c = cast(ubyte) ((cast(ubyte[]) crypt_header)[i] ^ decrypt_byte());
							updateKeys(c);
							(cast(ubyte[])crypt_header)[i] = c;
						}

						for(int i=0;i<compressed_size;++i) {
							ubyte c = cast(ubyte) ((cast(ubyte[])read_buffer)[i] ^ decrypt_byte());
							updateKeys(c);
							(cast(ubyte[])read_buffer)[i] = c;
						}

					}

					switch (lh_compression_method) {
					case 0:
						//	無圧縮のようなので、これをそのまま返す
						buff = read_buffer; return true;
					case 8:
						void[] data;

						try {
						// zlibを用いて解凍

		// -15 is a magic value used to decompress zip files.
		// It has the effect of not requiring the 2 byte header
		// and 4 byte trailer.
							data = std.zlib.uncompress(cast(void[])read_buffer,uncompressed_size,-15);

						// zlib呼び出して解決!
						} catch (std.zlib.ZlibException){ // 展開エラー
							return false;
						}
						//	解凍いけたいけた
						buff = data; return true;

					default:
						//	対応してないzip圧縮
						return false;
					}
				}

				//	さーて、来週のサザエさんは..
				c_pos += 46 + filename_length
					+ extra_field_length + file_comment_length;
			}

		} catch (std.stream.StreamException) {
			return false;
		}

		return true;
	}

	///	zip書き込みは未実装。std.zipを使うといい。
	y4d_result write(char[]filename,void[] data){
		return y4d_result.not_implemented;
	}

	///	enumerator
	DirEnumerator getEnumerator(char[] filename) {
		return new ZipDirEnumerator(null,filename);
	}

	///	zipファイル用のenumerator
	/**
		フォルダ階層つきだと、得られるファイル名のフォルダセパレータは'/'で
		あることに注意。
	*/
	static class ZipDirEnumerator : DirEnumerator {
		//	fileを渡す
		this(char[]dirname,char[] filename_){
			dirname = dirname;
			filename=filename_;
		}

	  int opApply(int delegate(inout char[] filename) dg) {
	  // readをこぴぺだ

		if (!FileSys.isRealExist(filename)) return 1;

		auto std.stream.File file = new std.stream.File;
		try {
			file.open(filename);
			auto StreamRandAccessor acc = new StreamRandAccessor(file);
			acc.setStream(file);

			// Find 'end record index' by searching backwards for signature
			ulong endrecOffset;
			ulong stoppos;
			//	ulongって引き算とか比較が面倒くさいですなぁ．．（´Д｀）
			if (acc.stream_size()<66000) {
				stoppos = 0;
			} else {
				stoppos = acc.stream_size() - 66000;
			}
			for(ulong i=acc.stream_size() - 22;i>=stoppos;--i){
				if (acc.getUint(cast(uint) i) == 0x06054b50) { // "PK\0x05\0x06"
					ushort endcommentlength = acc.getUshort(cast(uint) i+20);
					if (i + 22 + endcommentlength != acc.stream_size)
						continue;
					endrecOffset = i;
				}
				goto endrecFound;
			}
			// ダメジャン
			return 1;
endrecFound:;
			//	---- endrecOffsetが求まったナリよ！

			ushort filenum = acc.getUshort(cast(uint) endrecOffset+10);
			//	zipに格納されているファイルの数(分割zipは非対応)

			uint  c_pos = acc.getUint(cast(uint) endrecOffset+16);
			//	central directoryの位置

//	printf("filenum %d",filenum);

			//	---- central directoryが求まったなりよ！
			while (filenum-->0){
printf("SIGPOS : %d\n", c_pos);
				if (acc.getUint(c_pos)!=0x02014b50) { // シグネチャー確認!
					return false; // おかしいで、このファイル
				}
				uint compressed_size = acc.getUint(c_pos+20);
				uint uncompressed_size = acc.getUint(c_pos+24);
				ushort filename_length = acc.getUshort(c_pos+28);
				ushort extra_field_length = acc.getUshort(c_pos+30);
				ushort file_comment_length = acc.getUshort(c_pos+32);

//printf("filenamelength : %d",filename_length);
				// local_header_pos
				uint lh_pos = acc.getUint(c_pos+42);
				//	ファイル名の取得
				char[] fname = new char[filename_length];
				for(int i=0;i<filename_length;++i){
					fname[i] = acc.getUbyte(i+c_pos+46);
				}
//			printf("%.*s\n",fname);
				//	ファイル名が得られた。

				char[] fullfilename = dirname ~ fname;
				int result = dg(fullfilename);
				if (result) return result;

				//	さーて、来週のサザエさんは..
				c_pos += 46 + filename_length
					+ extra_field_length + file_comment_length;
			}

		} catch (std.stream.StreamException) {
			return 1;
		}
		return 1;

		}
	private:
		char[] filename;
	}

	///	zip書庫のパスワードを設定する(newする前に設定しておくこと)
	static void s_setPass(char[] pass) { s_zippass = pass;}

	///	zip書庫のパスワードを取得する
	static char[] s_getPass() { return s_zippass; }

	/// zip書庫のパスワードを設定する
	void setPass(char[] pass) { zippass = pass;}

	///	zip書庫のパスワードを取得する
	char[] getPass() { return zippass; }

	this() { zippass = s_zippass; }

private:
	static char[] s_zippass;
	char[] zippass;

	//	ファイルが存在しないときルートまで駆け上がって調べていく必要がある
	bool innerRead(char[]filename,bool bRead,out void[] data){
		char[] dirname;
		char[] filename2;
		char[] filename3;
		while (true){
			dirname = FileSys.getDirName(filename);
			if (dirname.length==0) return false;

			//	"c:\"のような名前なら、もうこれ以上のぼれマチェン
			if (dirname.length==3 && dirname[1]==':')
				return false;

			dirname.length = dirname.length-1;

			filename2 = dirname;	//	.zipを結合前の
			dirname ~= ".zip";
			char[] purename = FileSys.getPureFileName(filename);
			if (!purename) return false; // これ以上かけのぼれない
			char[] inFile  = purename ~ filename3;
			bool b = read(dirname,inFile,bRead,data);
			if (b) return true;
			filename3 = "/" ~ inFile;
			//	zipのファイル内で使用されているフォルダのセパレータは'/'
			filename = filename2;
		}
		
		return false;
	}

}
+/

public class ArchiveFileList
{
	public this(){
	}
	
	/// <summary>
	/// archiveファイルから、ファイルリストへのmapper
	/// </summary>
	public Object[char[]][char[]] getArchiveList()
	{ 
		return arcList; 
	}
	
	public void addArchiveList(char[] filename, Object[char[]] set) {
		arcList[filename] = set;
	}

	private Object[char[]][char[]] arcList;

	/// <summary>
	/// 1つのarchive(例:Zipファイル)内に存在するファイルのリストと、
	/// そのファイルに紐付けされた情報
	/// </summary>
	public Object[char[]] getFileList()
	{ 
		return fileList; 
	}
	
	private Object[char[]] fileList;


	/// <summary>
	/// FileListのなかに所定のファイルの情報があるのか
	/// </summary>
	public bool isExistArchive(char[] file)
	{
		return (file in arcList) !is null;
	}
}

public class FileArchiverZip : FileArchiverBase
{
	public this(long bufferSize=65535)
	{
		zippass = s_zippass;
		arcFileList = new ArchiveFileList();
		dstBuffer = new ubyte[bufferSize];
	}

	private ArchiveFileList arcFileList;
	// 展開先共有バッファ
	private	ubyte[] dstBuffer;

	/// 一回読み込んですぐ解放するリソース
	static bool isReadOneTimeResource(char[] filename) {
		const(char)[] ext = std.string.tolower(std.path.getExt(filename));
		return ( "png" == ext || "jpg" == ext || "tga" == ext || "bmp" == ext || 
				 "ogg" == ext || "wav" == ext || "mp3" == ext);
//		return ( /* "png" == ext || "jpg" == ext || "tga" == ext || "bmp" == ext || */ 
//				 "ogg" == ext || "wav" == ext || "mp3" == ext);
	}
	

	/// <summary>
	/// zip archiveのなかにあるファイルリスト等をcacheするのか
	/// (default = true)
	/// </summary>
	public bool ReadCache()
	{
		return readCache;
	}
	public bool ReadCache(bool b)
	{
		return readCache = b;
	}
	private bool readCache = true;

	/// <summary>
	/// ファイルから読み込む
	/// </summary>
	/// <remarks>
	///	1.FileSys.setPathで指定されているpathにあるファイルを優先
	///	2.無い場合、親フォルダにあるzipファイルを探していく
	///	というアルゴリズムになっています。
	/// </remarks>
	/// <param name="filename"></param>
	/// <returns></returns>
	public void[] read(char[] filename)
	{
		// 見つかればzipファイルを解凍して返す
		//	(保留中)
		ubyte[] data;
		if ( innerRead(filename, true , data) )
		{
			return data;
		}
		return null;
	}

	/// <summary>
	/// ファイルの存在チェック
	/// </summary>
	/// <param name="filename"></param>
	/// <returns></returns>
	public bool isExist(char[] filename)
	{
		ubyte[] data;
		return innerRead(filename, false , data);
	}

	private static class ZipPassUpdate
	{
		private uint[3] key;
		public void initKey()
		{
			key[0] = 305419896;
			key[1] = 591751049;
			key[2] = 878082192;
		}
		public void updateKeys(ubyte b)
		{
			key[0] = crc32.update_crc32(b , key[0]);
			key[1] = key[1] + ( key[0] & 0x000000ff );
			key[1] = key[1] * 134775813 + 1;
			key[2] = crc32.update_crc32( cast(ubyte) ( key[1] >> 24 ) , key[2]);
		}
		public ubyte decrypt_ubyte()
		{
			ushort temp = cast(ushort) ( key[2] | 2 );
			return cast(ubyte) ( ( temp * ( temp ^ 1 ) ) >> 8 );
		}
	}

	/// <summary>
	/// Zipファイル内のファイルリストをcacheするときに必要となる構造体
	/// </summary>
	private static class InnerZipFileInfo
	{
		public this(
			uint localHeaderPos ,
			uint compressed_size ,
			uint uncompressed_size
			)
		{
			this.localHeaderPos = localHeaderPos;
			this.compressed_size = compressed_size;
			this.uncompressed_size = uncompressed_size;
		}

		public uint localHeaderPos;
		public uint compressed_size;
		public uint uncompressed_size;
	}

	/// <summary>
	///	zipファイル内のファイル名を指定しての読み込み。
	/// </summary>
	/// <param name="filename">zipファイル名</param>
	/// <param name="innerFileName">zip内のファイル名</param>
	/// <param name="bRead">読み込むのか？読み込むときはbuffにその内容が返る</param>
	/// <param name="buff"></param>
	/// <returns>
	///		bRead=falseのとき、ヘッダ内に該当ファイルが存在すればtrue
	///		bRead=trueのとき、読み込みが成功すればtrue
	/// </returns>
	/// <summary>
	///	zipファイル内のファイル名を指定しての読み込み。
	/// 
	/// 大文字小文字の違いは無視する。
	/// </summary>
	/// <param name="filename">zipファイル名</param>
	/// <param name="innerFileName">zip内のファイル名</param>
	/// <param name="bRead">読み込むのか？読み込むときはbuffにその内容が返る</param>
	/// <param name="buff"></param>
	/// <returns>
	///		bRead=falseのとき、ヘッダ内に該当ファイルが存在すればtrue
	///		bRead=trueのとき、読み込みが成功すればtrue
	/// </returns>
	public bool read(char[] filename , char[] innerFileName , bool bRead , out ubyte[] buff)
	{
		// .NET Framework2.0には System.Io.CompressionにGZipStreamというクラスがある。
		// これはZipStreamを扱うクラスなのでファイルを扱う部分は自前で用意してやる必要がある
		// cf.
		// http://msdn2.microsoft.com/en-us/library/zs4f0x23.aspx

		buff = null;
		
		// ディレクトリ区切り文字を統制
		filename = filename;
		innerFileName = innerFileName;

		scope std.stream.File file = new std.stream.File;

		if (!FileSys.isRealExist(filename))
			return false;
			
//printf("zip file name:%*s\n", filename);

		// 二重openできないので、使いおわったら必ずCloseしなくてはならない。
		// そのためにtry～finallyで括ることにする。
		try
		{
			file.open(filename, FileMode.In);
			StreamRandAccessor acc = new StreamRandAccessor(file);
			acc.setStream(file);

			Object[char[]] cacheList;
			
			// 読み込みcacheが有効らしい
			if ( readCache )
			{
				filename = cast(char[]) std.string.tolower(filename);
				if ( arcFileList.isExistArchive(filename) )
				{
					
					// このarcFileListのなかから探し出す

					// 格納されているファイル名をToLower等で正規化しておく必要あり
					Object obj;
					try
					{
						innerFileName = cast(char[]) std.string.tolower(innerFileName);
						obj = arcFileList.getArchiveList()[filename][innerFileName];
//printf("HIT CACHE!!! %*s\n", innerFileName);
					}
					catch
					{
						return false; // 見つからない
					}
					// ここで取得したobjを元にファイルから読み込む

					InnerZipFileInfo info = cast(InnerZipFileInfo) obj;

					uint localHeaderPos = info.localHeaderPos;
					uint compressed_size = info.compressed_size;
					uint uncompressed_size = info.uncompressed_size;

					return innerExtract(acc , localHeaderPos , bRead , compressed_size , uncompressed_size ,
						 buff, isReadOneTimeResource(innerFileName));
				}
				// このファイルにファイルリスト読み込むのが先決では…
				Object[char[]][char[]] archiveList = arcFileList.getArchiveList();
				archiveList[filename] = cacheList;
			}

			// Find 'end record index' by searching backwards for signature
			ulong stoppos;
			//	ulongって引き算とか比較が面倒くさいですなぁ．．（´Д｀）
			if ( acc.stream_size() < 66000 )
			{
				stoppos = 0;
			}
			else
			{
				stoppos = acc.stream_size() - 66000;
			}
			ulong endrecOffset = 0;
			for ( uint i = cast(uint) (acc.stream_size() - 22); i >= stoppos ; --i )
			{
				if ( acc.getUint(i) == 0x06054b50 )
				{ // "PK\0x05\0x06"
					ushort endcommentlength = acc.getUshort(i + 20);
					if ( i + 22 + endcommentlength != acc.stream_size() )
						continue;
					endrecOffset = i;
				}
				goto endrecFound;
			}
//printf("Can't read!!\n");
			// ダメジャン
			return false;

		endrecFound:
			;
			//	---- endrecOffsetが求まったナリよ！

			ushort filenum = acc.getUshort( cast(uint) (endrecOffset + 10));
			//	zipに格納されているファイルの数(分割zipは非対応)

			uint c_pos = acc.getUint(cast(uint) endrecOffset+16);
			//	central directoryの位置

//printf("filenum %d\n",filenum);

			//	---- central directoryが求まったなりよ！
			while ( filenum-- > 0 )
			{
				if ( acc.getUint(c_pos) != 0x02014b50 )
				{ // シグネチャー確認!
//printf("INVALIDE sig!!!\n");
					return false;
					//  return false; // おかしいで、このファイル
				}

				uint compressed_size = acc.getUint(c_pos + 20);
				uint uncompressed_size = acc.getUint(c_pos + 24);
				ushort filename_length = acc.getUshort(c_pos + 28);
				ushort extra_field_length = acc.getUshort(c_pos + 30);
				ushort file_comment_length = acc.getUshort(c_pos + 32);

				//printf("filenamelength : %d",filename_length);
				// local_header_pos
				uint lh_pos = acc.getUint(c_pos + 42);
				//	ファイル名の取得
				char[] fname = new char[filename_length];
				for(int i=0;i<filename_length;++i){
					fname[i] = acc.getUbyte(i+c_pos+46);
				}
//printf("name : %*s\n",fname);
				//	ファイル名が得られた。

				// char[] fullfilename = dirname + fname;
				// yield return fullfilename;

				if ( cacheList !is null || readCache)
				{
//printf("caching file:%*s   ", std.string.tolower(fname));
					// readCacheが有効なら、まずは格納していく。
					cacheList[std.string.tolower(fname)] = 
						new InnerZipFileInfo(lh_pos , compressed_size , uncompressed_size);
				}
				else
				{
					if (fname == innerFileName)
					{
//printf("read file : %*s", fname);
						//	一致したでー！これ読み込もうぜー!
						return innerExtract(acc , lh_pos , bRead , compressed_size , uncompressed_size , 
						buff, isReadOneTimeResource(innerFileName));
					}
				}

				//	さーて、来週のサザエさんは..
				c_pos += 46 + filename_length
					+ extra_field_length + file_comment_length;

//printf("\nread continue... : %d\n", filenum);
			}
/+
			{
				printf("cache num:%d\n", cacheList.length);
				// デバッグ
				foreach (char[] keyName; cacheList.keys){
					printf("keyname : %*s\n", keyName);
				}
			}
+/			
			arcFileList.addArchiveList(filename, cacheList);

			// readCacheが有効なのに、ここまで到達するというのは、
			// archive内のfile listをcacheしていなかったので調べていたに違いない。
			// よって、再帰的に呼び出すことによって解決できる。
			if ( readCache ) {
				return read(filename , innerFileName , bRead , buff);
			}

		} catch (std.stream.StreamException) {
			return false;
		} catch(Object e) {
		}

		buff = null;
		return false;
	}

	/// <summary>
	/// Zipファイル解凍のための内部メソッド
	/// </summary>
	/// <param name="acc"></param>
	/// <param name="lh_pos">lh_pos == local header position</param>
	/// <param name="bRead"></param>
	/// <param name="buff"></param>
	/// <returns></returns>
	private bool innerExtract(StreamRandAccessor acc , uint lh_pos,bool bRead,
		uint compressed_size,
		uint uncompressed_size,
		out ubyte[] buff,
		bool isOneTimeResource)
	{
		buff = null;

		//	読み込み指定されてなければ
		//	ファイルが存在することを意味するtrueを返す
		if ( !bRead )
			return true;

		if ( acc.getUint(lh_pos) != 0x04034b50 )
		{
			//	なんで？ヘッダのシグネチャー違うやん．．
			return false;
		}

		ushort lh_flag = acc.getUshort(lh_pos + 6);
		ushort lh_compression_method = acc.getUshort(lh_pos + 8);
		ushort lh_filename_length = acc.getUshort(lh_pos + 26);
		ushort lh_extra_field = acc.getUshort(lh_pos + 28);
		long startpos = lh_pos + 30
			+ lh_filename_length + lh_extra_field;
		//	データの読み込み
		//	データの読み込み

		bool encry = ( lh_flag & 1 ) != 0;
		ubyte[] crypt_header = null;
		if ( encry )
		{
			crypt_header = new ubyte[12];
			if ( acc.read(crypt_header , startpos , 12) != 12 )
				return false;
			compressed_size -= 12;
			startpos += 12;
		}
		ubyte[] read_buffer;
		if (isOneTimeResource) {
			// 共有メモリ版
			if (dstBuffer.length <= compressed_size) {
				dstBuffer ~= new ubyte[compressed_size-dstBuffer.length];
			}
			read_buffer = dstBuffer[0..compressed_size];

		} else {
			// 安定版
			read_buffer = new ubyte[compressed_size];
		}
		long readsize =
			acc.read(read_buffer , startpos , compressed_size);
		//	これ0ってことはあるのか..? まあ空のファイルかも知れないんで考慮する必要はないか。
		if ( readsize != compressed_size )
			return false;
		// 読み込みエラー

		//printf("lh_compression_method : %d",lh_compression_method);

		if ( encry )
		{
			//	暗号化ファイル
			/*
				暗号の解読
			*/
			ZipPassUpdate zipupdate = new ZipPassUpdate();
			zipupdate.initKey();

			foreach ( char c ; zippass )
				zipupdate.updateKeys( cast(ubyte) c);

			//	Read the 12-ubyte encryption header into Buffer
			for ( int i = 0 ; i < 12 ; ++i )
			{
				ubyte c = cast(ubyte) ( crypt_header[i] ^ zipupdate.decrypt_ubyte() );
				zipupdate.updateKeys(c);
				crypt_header[i] = c;
			}

			for ( int i = 0 ; i < compressed_size ; ++i )
			{
				ubyte c = cast(ubyte) ( read_buffer[i] ^ zipupdate.decrypt_ubyte() );
				zipupdate.updateKeys(c);
				read_buffer[i] = c;
			}
		}

		switch ( lh_compression_method )
		{
			case 0:
				//	無圧縮のようなので、これをそのまま返す
				buff = read_buffer;
				return true;
			case 8:
				ubyte[] decompressedBuffer;

				try {
				// zlibを用いて解凍

// -15 is a magic value used to decompress zip files.
// It has the effect of not requiring the 2 byte header
// and 4 byte trailer.
					decompressedBuffer = cast(ubyte[]) std.zlib.uncompress(cast(void[])read_buffer,uncompressed_size,-15);

				// zlib呼び出して解決!
				} catch (std.zlib.ZlibException){ // 展開エラー
					return false;
				}

				//	解凍いけたいけた
				buff = decompressedBuffer;
				return true;

			default:
				//	対応してないzip圧縮
				buff = null;
				return false;
		}
		return false;
	}

	/// <summary>
	/// zip書き込みは未実装。
	/// System.IO.Compress.GZipStreamを使うといい(かな？)
	/// </summary>
	/// <param name="filename"></param>
	/// <param name="data"></param>
	/// <returns></returns>
	public y4d_result write(char[] filename , void[] data)
	{
		return y4d_result.not_implemented;
	}

	/// <summary>
	/// enumerator
	/// </summary>
	/// <remarks>
	/// このメソッドが呼び出されたときに設定されていた
	/// CodePageが、そのままDirEnumeratorに反映されるので注意すること。
	/// (これによって、zipファイルの中の文字列のcodesetが決定する)
	/// defaultではshift-jis。
	/// </remarks>
	/// <param name="filename"></param>
	/// <returns></returns>
	public DirEnumerator getEnumerator(char[] filename)
	{
		
		ZipDirEnumerator ze = new ZipDirEnumerator(null , filename);
		return ze;
	}

	/// <summary>
	/// Zipファイル内に格納されているファイルを列挙するためのもの。
	/// </summary>
	public static class ZipDirEnumerator : DirEnumerator
	{
		
		public this(char[] dirname_ , char[] filename_)
		{
			setDir(dirname_);
			filename = filename_;
		}

		public int opApply(int delegate(inout char[] filename) dg) {
		{
			//	ZipStream zip = new GZipStream(filename);
			//	foreach (ZipEntry e in zip) {
			//		yield return e.Name;
			//	}

			
			if (!FileSys.isRealExist(filename)) {
				return false;
			}
	
			scope std.stream.File file = new std.stream.File;
			StreamRandAccessor acc = new StreamRandAccessor(file);
			acc.setStream(file);

			// Find 'end record index' by searching backwards for signature
			long stoppos;
			//	ulongって引き算とか比較が面倒くさいですなぁ．．（´Д｀）
			if ( acc.stream_size() < 66000 )
			{
				stoppos = 0;
			}
			else
			{
				stoppos = acc.stream_size() - 66000;
			}
			long endrecOffset = 0;
			for ( long i = acc.stream_size() - 22 ; i >= stoppos ; --i )
			{
				if ( acc.getUint(i) == 0x06054b50 )
				{ // "PK\0x05\0x06"
					ushort endcommentlength = acc.getUshort(i + 20);
					if ( i + 22 + endcommentlength != acc.stream_size() )
						continue;
					endrecOffset = i;
				}
				goto endrecFound;
			}
			// ダメジャン
			return false;

		endrecFound:
			;
			//	---- endrecOffsetが求まったナリよ！

			ushort filenum = acc.getUshort(endrecOffset + 10);
			//	zipに格納されているファイルの数(分割zipは非対応)

			long c_pos = acc.getUint(endrecOffset + 16);
			//	central directoryの位置

			//	printf("filenum %d",filenum);

			//	---- central directoryが求まったなりよ！
			while ( filenum-- > 0 )
			{
				if ( acc.getUint(c_pos) != 0x02014b50 )
				{ // シグネチャー確認!
					return false;
					//  return false; // おかしいで、このファイル
				}
				uint compressed_size = acc.getUint(c_pos + 20);
				uint uncompressed_size = acc.getUint(c_pos + 24);
				ushort filename_length = acc.getUshort(c_pos + 28);
				ushort extra_field_length = acc.getUshort(c_pos + 30);
				ushort file_comment_length = acc.getUshort(c_pos + 32);

				//printf("filenamelength : %d",filename_length);
				// local_header_pos
				uint lh_pos = acc.getUint(c_pos + 42);
				//	ファイル名の取得
				char[] fname = new char[filename_length];
				for(int i=0;i<filename_length;++i){
					fname[i] = acc.getUbyte(i+c_pos+46);
				}

				//			printf("%.*name\n",fname);
				//	ファイル名が得られた。

				char[] fullfilename = dirname ~ fname;
				int result = dg(fullfilename);
				if (result) return result;

				//	さーて、来週のサザエさんは..
				c_pos += 46 + filename_length
					+ extra_field_length + file_comment_length;
			}

			if ( file != null )
				file.close();
			return 1;
		}
		}

		private char[] filename;
	}

	/// <summary>
	/// zip書庫の共通パスワードを設定/取得する(FileSysをnewする前に設定しておくこと)
	/// staticなプロパティ。
	/// </summary>
	/// <param name="pass"></param>
	public static char[] setPassStatic(char[] value)
	{
		return s_zippass = value;
	}
	public static char[] getPassStatic()
	{
		return s_zippass;
	}

	/// <summary>
	/// zip書庫のパスワードを設定/取得する
	/// </summary>
	/// <param name="pass"></param>
	public char[] setPass(char[] value)
	{
		return zippass = value;
	}
	public char[] getPass()
	{
		return zippass;
	}

	/// <summary>
	/// 全Zip共通のdefault pass
	/// </summary>
	private static char[] s_zippass;

	/// <summary>
	/// 今回のarchiverだけのpass
	/// </summary>
	private char[] zippass;

	/// <summary>
	/// ファイルが存在しないときルートまで駆け上がって調べていく必要があるので
	/// 駆け上がるための処理。
	/// </summary>
	/// <param name="filename"></param>
	/// <param name="bRead"></param>
	/// <param name="data"></param>
	/// <returns></returns>
	private bool innerRead(char[] filename , bool bRead , out ubyte[] data)
	{
		char[] dirname;
		char[] oldDirname;
		char[] innerFilename;

		data = null;
		while ( true )
		{
			dirname = FileSys.getDirName(filename);
			if ( dirname.length == 0 || dirname.length >= filename.length ) {
printf("#innerRead Not Found:%*s\n", filename);
				return false;
				// ↑これ以上、駆け上がれないのか
			}

			dirname = dirname[0..length - 1];
			oldDirname = dirname;

			char[] purename = FileSys.getPureFileName(filename);
			if ( purename == null )
				return false; // これ以上かけのぼれない
			char[] inFile = purename ~ innerFilename;
			bool b = read(dirname ~ zipExtName , inFile , bRead , data);
			if ( b ) {
				return true;
			}
			innerFilename = "/" ~ inFile;
			//	zipのファイル内で使用されているフォルダのセパレータは'/'
			filename = oldDirname;
		}
		// ありえないが
		return false;
	}
	
	/// <summary>
	/// zipファイルの拡張子を指定する。
	/// .datという拡張子のファイル(中身はzip)をzipファイルとして
	/// 扱いたいときは、ここで変更すると良い。
	/// 
	/// defaultでは ".zip"
	/// 
	/// ZipExtName = ""として、拡張子なしのzipファイルをフォルダに
	/// 見せかけることも可能。
	/// </summary>
	public char[] setZipExtName()
	{
		return zipExtName;
	}
	public char[] getZipExtName(char[] value)
	{
		return zipExtName = value;
	}
	private char[] zipExtName = cast(char[]) ".zip";
}





