Artifact Content

Not logged in

Artifact 9075347c70b2342c7bab6d0b4b99195e0862c08c


     1  private import std.string;
     2  private import std.file;
     3  private import std.c.stdio;
     4  private import etc.c.zlib;
     5  private import win32.ansi.windows;
     6  private import libbz2.bzlib;
     7  private import util;
     8  
     9  //----------------------------------------------------------------
    10  // Bga書庫のファイルヘッダ形式
    11  //----------------------------------------------------------------
    12  
    13  align(1) struct BgaHeader
    14  {
    15  	 int   checksum;        // type~fname のsigned charでの和
    16  	char   method[4];       // "GZIP" または "BZ2\0"
    17  	uint   compressed_size; // 圧縮後のデータサイズ( ヘッダは含まない )
    18  	uint   original_size;   // 元のファイルサイズ
    19  	ushort date;            // ファイルの更新日付( DOS形式 )
    20  	ushort time;            // ファイルの更新時刻( DOS形式 )
    21  	ubyte  attrib;          // ファイルの属性
    22  	                        // ( 1:RO 2:Hid 4:Sys 8:Vol 16:Dir 32:Arc )
    23  	ubyte  header_type;     // ヘッダの種類( 現在は常に 0 )
    24  	ushort arc_type;        // アーカイブタイプ
    25  	                        // 0:(ext==↓?非圧縮:圧縮) 1:圧縮 2:非圧縮
    26  	                        //	.ARC, .ARJ, .BZ2, .BZA, .CAB, .GZ, .GZA, .LZH,
    27  	                        //	.LZS, .PAK, .RAR, .TAZ, .TBZ, .TGZ, .Z, .ZIP, .ZOO
    28  	ushort dir_name_len;    // ディレクトリ名の長さ
    29  	ushort file_name_len;   // ファイル名の長さ
    30  	char[] fname;           // dir_name_len + file_name_len ( no '\0' )
    31  }
    32  
    33  //----------------------------------------------------------------
    34  // エラー発生時に投げる例外
    35  //----------------------------------------------------------------
    36  
    37  class BgaMelterError : Error
    38  {
    39  	int errcode;
    40  	this( int e ) { super("BgaMelterError"); errcode=e; }
    41  }
    42  
    43  enum
    44  {
    45  	ERROR_FILE_OPEN=0x800D,//         ファイルを開けませんでした。
    46  	ERROR_MAKEDIRECTORY=0x8012,//     ディレクトリが作成できません。
    47  	ERROR_CANNOT_WRITE=0x8013,//      書き込みエラーが生じました。
    48  	ERROR_HEADER_CRC=0x8016,//        書庫のヘッダのチェックサムが合っていません。
    49  	ERROR_ARC_FILE_OPEN=0x8018,//     書庫を開く事が出来ません。
    50  	ERROR_NOT_ARC_FILE=0x8019,//      書庫のファイル名が指定されていません。
    51  	ERROR_CANNOT_READ=0x801A,//       ファイルの読み込み時にエラーが生じました。
    52  	ERROR_FILE_STYLE=0x801B,//        指定されたファイルは有効な書庫ではありません。
    53  	ERROR_COMMAND_NAME=0x801C,//      コマンド指定が間違っています。
    54  	ERROR_MORE_HEAP_MEMORY=0x801D,//  作業用のためのヒープメモリが不足しています。
    55  	ERROR_ALREADY_RUNNING=0x801F,//   既に BGA32.DLL が動作中です。
    56  	ERROR_USER_CANCEL=0x8020,//       ユーザーによって処理を中断されました。
    57  	ERROR_TMP_OPEN=0x8025,//          作業ファイルが作成できません。
    58  	ERROR_ARC_READ_ONLY=0x8027,//     書き込み専用属性の書庫に対する操作はできません。
    59  	ERROR_NOT_FIND_ARC_FILE=0x8029,// 指定されたディレクトリには書庫がありませんでした。
    60  	ERROR_RESPONSE_READ=0x802A,//     レスポンスファイルの読み込み時にエラーが生じました。
    61  	ERROR_TMP_COPY=0x802C,//          作業ファイルの書庫への書き戻しができませんでした。
    62  	ERROR_NOT_FIND_FILE=0x8031,//     ファイルが見つかりません。
    63  	ERROR_GET_ATTRIBUTES=0x8034,//    ファイル属性が取得できません。
    64  	ERROR_GET_INFORMATION=0x8036,//   ファイル情報が取得できません。
    65  }
    66  
    67  //----------------------------------------------------------------
    68  // コールバック関数の返答用型
    69  //----------------------------------------------------------------
    70  
    71  enum BgaAnswer
    72  {
    73  	MeltIt, SkipIt, Abort
    74  }
    75  
    76  //----------------------------------------------------------------
    77  // Filep
    78  //   D言語らしからぬのですがzlibやlibbz2と簡単に連携する
    79  //   都合上std.c.stdio.FILE*でファイルを読み書きします。
    80  //----------------------------------------------------------------
    81  
    82  extern(C)
    83  {
    84  	// stdio.h
    85      int fileno( FILE *fp ) { return fp._file; }
    86  	// io.h
    87  	int lseek( int fd, int offset, int mode );
    88  	int dup( int fd ); 
    89  	int close( int fd );
    90  }
    91  
    92  class Filep
    93  {
    94  	private FILE* fp;
    95  	private this( FILE* p ) { fp = p; }
    96  
    97  	static Filep open( char[] filename, bool read )
    98  	{
    99  		FILE* fp = fopen( toStringz(filename), read?"rb":"wb" );
   100  		return (fp ? new Filep(fp) : null);
   101  	}
   102  
   103  	int dup_han()
   104  	{
   105  		int fd = dup( fileno(fp) );
   106  		lseek( fd, cur(), 0 );
   107  		return fd;
   108  	}
   109  
   110  	void[] read( int siz )
   111  	{
   112  		char[] buf; buf.length = siz;
   113  		int rsiz = fread( buf, 1, siz, fp );
   114  		if( rsiz < 0 )
   115  			throw new BgaMelterError(ERROR_FILE_OPEN);
   116  		buf.length = rsiz;
   117  		return buf;
   118  	}
   119  
   120  	void write( void[] buf )
   121  	{
   122  		while( buf.length > 0 )
   123  		{
   124  			int rsiz = fwrite( buf, 1, buf.length, fp );
   125  			if( rsiz < 0 ) return;
   126  			buf = buf[rsiz .. length];
   127  		}
   128  	}
   129  
   130  	int cur()
   131  	{
   132  		return ftell(fp);
   133  	}
   134  
   135  	void seek_to( int i )
   136  	{
   137  		fseek( fp, i, std.c.stdio.SEEK_SET );
   138  	}
   139  
   140  	void close()
   141  	{
   142  		fclose(fp);
   143  	}
   144  
   145  	FILE* get_fp()
   146  	{
   147  		return fp;
   148  	}
   149  }
   150  
   151  
   152  //----------------------------------------------------------------
   153  // メインクラス
   154  //----------------------------------------------------------------
   155  
   156  class BgaMelter
   157  {
   158  	alias BgaAnswer delegate(inout BgaHeader) FileHandler;
   159  	alias BgaAnswer delegate(int, int)        ProgressHandler;
   160  
   161  	private Filep fp = null;
   162  
   163  	this( char[] arc_name )
   164  	{
   165  		fp = Filep.open( arc_name, true );
   166  		if( fp is null )
   167  			throw new BgaMelterError(ERROR_FILE_OPEN);
   168  	}
   169  
   170  	void close()
   171  	{
   172  		if( fp ) { fp.close(); fp = null; }
   173  	}
   174  
   175  	void start( FileHandler fh, ProgressHandler ph )
   176  	{
   177  		try
   178  		{
   179  			// ヘッダを探す
   180  			int hpos = find_header();
   181  			if( hpos == -1 )
   182  				throw new BgaMelterError(ERROR_NOT_ARC_FILE);
   183  			fp.seek_to(hpos);
   184  
   185  			// ループ:
   186  			// ヘッダ読みとり
   187  			BgaHeader hdr;
   188  			while( read_header(hdr) )
   189  			{
   190  				// 次のヘッダ位置を計算しておく
   191  				uint nextpos = fp.cur() + hdr.compressed_size;
   192  				try
   193  				{
   194  					// callback
   195  					BgaAnswer a = fh(hdr);
   196  					if( a == BgaAnswer.Abort )  return;
   197  					if( a == BgaAnswer.SkipIt ) continue;
   198  
   199  					// 出力先ファイルを開く
   200  					if( lastChar(hdr.fname)=='\\' )
   201  					  { pathMake(hdr.fname); continue; }
   202  
   203  					Filep outf = Filep.open( pathMake(hdr.fname), false );
   204  					if( outf is null )
   205  						throw new BgaMelterError(ERROR_FILE_OPEN);
   206  
   207  					// 解凍処理
   208  					bool bContinue = true;
   209  					if( !is_compressed(hdr) )
   210  						bContinue = NcDec( hdr.original_size, outf, ph );
   211  					else if( hdr.method == "GZIP" )
   212  						bContinue = GzDec( hdr.compressed_size, hdr.original_size, outf, ph );
   213  					else if( hdr.method == "BZ2\0" )
   214  						bContinue = BzDec( hdr.original_size, outf, ph );
   215  
   216  					// 閉じて属性設定
   217  					outf.close();
   218  					dosSetFTime( hdr.fname, hdr.date, hdr.time );
   219  					SetFileAttributes( hdr.fname, hdr.attrib );
   220  					if( !bContinue )
   221  						return;
   222  				}
   223  				finally { fp.seek_to(nextpos); }
   224  			}
   225  		}
   226  		finally { close(); }
   227  	}
   228  
   229  	static int signed_char( char c )
   230  	{
   231  		int cn = c;
   232  		return (cn>=0x80 ? cn|0xffffff00 : cn);
   233  	}
   234  
   235  	private int find_header()
   236  	{
   237  		char[] dat = cast(char[]) fp.read(0x10000);
   238  
   239  		for( int i=0; i<dat.length-28; ++i )
   240  		{
   241  			if( dat[i+4]!='G' && dat[i+4]!='B' ) continue;
   242  			if( dat[i+5]!='Z'                  ) continue;
   243  			if( dat[i+6]!='I' && dat[i+6]!='2' ) continue;
   244  			if( dat[i+7]!='P' && dat[i+7]!='\0') continue;
   245  
   246  			 int checksum = dat[i+0]+(dat[i+1]<<8)+(dat[i+2]<<16)+(dat[i+3]<<24);
   247  			uint fnlen    = dat[i+24]+(dat[i+25]<<8)+dat[i+26]+(dat[i+27]<<8);
   248  			if( i+28+fnlen > dat.length )        continue;
   249  
   250  			int sum = 0;
   251  			for( int j=i+4; j!=i+28+fnlen; ++j )
   252  				sum += signed_char(dat[j]);
   253  			if( checksum == sum )
   254  				return i;
   255  		}
   256  
   257  		return -1;
   258  	}
   259  
   260  	private bool read_header( out BgaHeader hdr )
   261  	{
   262  		// リトルエンディアンを仮定。ヘッダ読み込み
   263  		char[] buf = cast(char[]) fp.read(28);
   264  		if( buf.length < 28 ) return false;
   265  		buf.length = BgaHeader.sizeof;
   266  		hdr = (cast(BgaHeader[]) buf)[0];
   267  		hdr.fname = "";
   268  
   269  		// ファイル名
   270  		hdr.fname = cast(char[]) fp.read(hdr.dir_name_len + hdr.file_name_len);
   271  		if( hdr.fname.length < hdr.dir_name_len + hdr.file_name_len ) return false;
   272  
   273  		// チェックサム
   274  		int sum = 0;
   275  		for( int i=4; i!=28; ++i )    sum += signed_char(buf[i]);
   276  		foreach( char c ; hdr.fname ) sum += signed_char(c);
   277  		return (sum == hdr.checksum);
   278  	}
   279  
   280  	private bool is_compressed( inout BgaHeader hdr ) // inout=just for optimization
   281  	{
   282  		// ヘッダから、ファイルが圧縮格納されているかどうかを判定
   283  		if( hdr.arc_type==2 )
   284  			return false;
   285  		if( hdr.arc_type==0 && hdr.compressed_size==hdr.original_size )
   286  		{
   287  			int x = rfind( hdr.fname, '.' );
   288  			if( x == -1 )
   289  				return true;
   290  			char[] ext = tolower(hdr.fname[x+1 .. length]);
   291  			if( ext=="arc" || ext=="arj" || ext=="bz2" || ext=="bza"
   292  			 || ext=="cab" || ext=="gz"  || ext=="gza" || ext=="lzh"
   293  			 || ext=="lzs" || ext=="pak" || ext=="rar" || ext=="taz"
   294  			 || ext=="tbz" || ext=="tgz" || ext=="z"   || ext=="zip"
   295  			 || ext=="zoo" )
   296  				return false;
   297  		}
   298  		return true;
   299  	}
   300  
   301  	static char[] pathMake( char[] path )
   302  	{
   303  		char* ps = toStringz(path);
   304  		for(char* p=ps;;)
   305  		{
   306  			for(; *p!=0&&*p!='\\'&&*p!='/'; p=CharNext(p)) {}
   307  			if( *p==0 )
   308  				break;
   309  			CreateDirectory( toStringz(ps[0..(p-ps)]), null );
   310  			++p;
   311  		}
   312  		return path;
   313  	}
   314  
   315  	enum { BUFSIZ = 65536 }
   316  
   317  	private bool NcDec( uint usiz, Filep outf, ProgressHandler ph )
   318  	{
   319  		uint init_usiz = usiz;
   320  
   321  		// 非圧縮。コピーするだけ
   322  		while( usiz )
   323  		{
   324  			char[] r = cast(char[]) fp.read( BUFSIZ<usiz?BUFSIZ:usiz );
   325  			usiz -= r.length;
   326  			outf.write(r);
   327  
   328  			// dlg
   329  			if( BgaAnswer.Abort==ph(init_usiz-usiz,usiz) ) return false;
   330  		}
   331  
   332  		// dlg
   333  		if( BgaAnswer.Abort==ph(init_usiz-usiz,usiz) ) return false;
   334  		return true;
   335  	}
   336  
   337  	private bool GzDec( uint csiz, uint usiz, Filep outf, ProgressHandler ph )
   338  	{
   339  		uint init_usiz = usiz;
   340  
   341  		// zlibで展開
   342  		fp.read(10); csiz -= 10; // ヘッダ,フッタスキップ
   343  
   344  		ubyte[]  inbuf;  inbuf.length = 65536;
   345  		ubyte[] outbuf; outbuf.length = 65536;
   346  
   347  		// zlib準備
   348  		z_stream zs;
   349  		zs.zalloc   = null;
   350  		zs.zfree    = null;
   351  
   352  		// 出力バッファ
   353  		zs.next_out  = outbuf;
   354  		zs.avail_out = outbuf.length;
   355  
   356  		// 入力バッファ
   357  		inbuf = cast(ubyte[]) fp.read( csiz<65536 ? csiz : 65536 );
   358  		csiz       -= inbuf.length;
   359  		zs.next_in  = inbuf;
   360  		zs.avail_in = inbuf.length;
   361  
   362  		// スタート
   363  		inflateInit2( &zs, -15 );
   364  		try {
   365  
   366  		// 書庫から入力し終わるまでループ
   367  		int err = Z_OK;
   368  		while( csiz&&usiz )
   369  		{
   370  			while( zs.avail_out > 0 )
   371  			{
   372  				err = etc.c.zlib.inflate( &zs, Z_PARTIAL_FLUSH );
   373  				if( err!=Z_STREAM_END && err!=Z_OK )
   374  					csiz=0;
   375  				if( !csiz )
   376  					break;
   377  
   378  				if( zs.avail_in<=0 )
   379  				{
   380  					inbuf = cast(ubyte[]) fp.read( csiz<65536 ? csiz : 65536 );
   381  					csiz       -= inbuf.length;
   382  					zs.next_in  = inbuf;
   383  					zs.avail_in = inbuf.length;
   384  
   385  					if( inbuf.length==0 )
   386  					{
   387  						err  = Z_STREAM_END;
   388  						csiz = 0;
   389  						break;
   390  					}
   391  				}
   392  			}
   393  
   394  			int written = outbuf.length - zs.avail_out;
   395  			if( usiz < written ) written = usiz;
   396  			usiz -= written;
   397  			outf.write( outbuf[0..written] );
   398  			zs.next_out  = outbuf;
   399  			zs.avail_out = outbuf.length;
   400  
   401  			// dlg
   402  			if( BgaAnswer.Abort==ph(init_usiz-usiz,usiz) ) return false;
   403  		}
   404  
   405  		// 出力残しを無くす。
   406  		while( err!=Z_STREAM_END&&usiz )
   407  		{
   408  			err = etc.c.zlib.inflate(&zs,Z_PARTIAL_FLUSH);
   409  			if( err!=Z_STREAM_END && err!=Z_OK )
   410  				break;
   411  
   412  			int written = outbuf.length - zs.avail_out;
   413  			if( usiz < written ) written = usiz;
   414  			usiz -= written;
   415  			outf.write( outbuf[0..written] );
   416  			zs.next_out  = outbuf;
   417  			zs.avail_out = outbuf.length;
   418  
   419  			// dlg
   420  			if( BgaAnswer.Abort==ph(init_usiz-usiz,usiz) ) return false;
   421  		}
   422  
   423  		// 終了
   424  		} finally { inflateEnd(&zs); }
   425  
   426  		// dlg
   427  		if( BgaAnswer.Abort==ph(init_usiz-usiz,usiz) ) return false;
   428  		return true;
   429  	}
   430  
   431  	private bool BzDec( uint usiz, Filep outf, ProgressHandler ph )
   432  	{
   433  		uint init_usiz = usiz;
   434  
   435  		// libbz2で展開
   436  		int err;
   437  		BZFILE* b = BZ2_bzReadOpen( &err, fp.get_fp(), 0, 0, NULL, 0 );
   438  		if( err!=BZ_OK || b is null )
   439  			return true;
   440  
   441  		try
   442  		{
   443  			char[] buf; buf.length = BUFSIZ;
   444  			int len;
   445  			while( 0<(len=BZ2_bzRead( &err, b, buf, BUFSIZ<usiz?BUFSIZ:usiz )) )
   446  			{
   447  				outf.write( buf[0..len] );
   448  				usiz -= len;
   449  				if( err != BZ_OK )
   450  					break;
   451  
   452  				// dlg
   453  				if( BgaAnswer.Abort==ph(init_usiz-usiz,usiz) ) return false;
   454  			}
   455  		} finally { BZ2_bzReadClose( &err, b ); }
   456  
   457  		// dlg
   458  		if( BgaAnswer.Abort==ph(init_usiz-usiz,usiz) ) return false;
   459  		return true;
   460  	}
   461  }