Artifact 4c58bb3061c0a99d990a27dce2a5906146af6fe1
- File
src/bga_melter.d
-
2015-05-05 06:49:05
- part of checkin
[9b639cf2d6]
on branch trunk
- Working version for update to 2.067.
The problem was __gshared. Replacing it with TLS fixed the issue. Remaining problem is that "hack.d"'s CloseHandle hack is not working anymore.
(user: kinaba) [annotate]
-
2015-05-05 06:49:05
- part of checkin
[9b639cf2d6]
on branch trunk
- Working version for update to 2.067.
import win32.windows; import std.c.stdio; import std.file; import std.string; import etc.c.zlib; import libbz2.bzlib; import util; //---------------------------------------------------------------- // Bga書庫のファイルヘッダ形式 //---------------------------------------------------------------- align(1) struct BgaHeader { int checksum; // type~fname のsigned charでの和 char method[4]; // "GZIP" または "BZ2\0" uint compressed_size; // 圧縮後のデータサイズ( ヘッダは含まない ) uint original_size; // 元のファイルサイズ ushort date; // ファイルの更新日付( DOS形式 ) ushort time; // ファイルの更新時刻( DOS形式 ) ubyte attrib; // ファイルの属性 // ( 1:RO 2:Hid 4:Sys 8:Vol 16:Dir 32:Arc ) ubyte header_type; // ヘッダの種類( 現在は常に 0 ) ushort arc_type; // アーカイブタイプ // 0:(ext==↓?非圧縮:圧縮) 1:圧縮 2:非圧縮 // .ARC, .ARJ, .BZ2, .BZA, .CAB, .GZ, .GZA, .LZH, // .LZS, .PAK, .RAR, .TAZ, .TBZ, .TGZ, .Z, .ZIP, .ZOO ushort dir_name_len; // ディレクトリ名の長さ ushort file_name_len; // ファイル名の長さ string fname; // dir_name_len + file_name_len ( no '\0' ) } //---------------------------------------------------------------- // エラー発生時に投げる例外 //---------------------------------------------------------------- class BgaMelterError : Error { int errcode; this( int e ) { super("BgaMelterError"); errcode=e; } } enum { ERROR_FILE_OPEN=0x800D,// ファイルを開けませんでした。 ERROR_MAKEDIRECTORY=0x8012,// ディレクトリが作成できません。 ERROR_CANNOT_WRITE=0x8013,// 書き込みエラーが生じました。 ERROR_HEADER_CRC=0x8016,// 書庫のヘッダのチェックサムが合っていません。 ERROR_ARC_FILE_OPEN=0x8018,// 書庫を開く事が出来ません。 ERROR_NOT_ARC_FILE=0x8019,// 書庫のファイル名が指定されていません。 ERROR_CANNOT_READ=0x801A,// ファイルの読み込み時にエラーが生じました。 ERROR_FILE_STYLE=0x801B,// 指定されたファイルは有効な書庫ではありません。 ERROR_COMMAND_NAME=0x801C,// コマンド指定が間違っています。 ERROR_MORE_HEAP_MEMORY=0x801D,// 作業用のためのヒープメモリが不足しています。 ERROR_ALREADY_RUNNING=0x801F,// 既に BGA32.DLL が動作中です。 ERROR_USER_CANCEL=0x8020,// ユーザーによって処理を中断されました。 ERROR_TMP_OPEN=0x8025,// 作業ファイルが作成できません。 ERROR_ARC_READ_ONLY=0x8027,// 書き込み専用属性の書庫に対する操作はできません。 ERROR_NOT_FIND_ARC_FILE=0x8029,// 指定されたディレクトリには書庫がありませんでした。 ERROR_RESPONSE_READ=0x802A,// レスポンスファイルの読み込み時にエラーが生じました。 ERROR_TMP_COPY=0x802C,// 作業ファイルの書庫への書き戻しができませんでした。 ERROR_NOT_FIND_FILE=0x8031,// ファイルが見つかりません。 ERROR_GET_ATTRIBUTES=0x8034,// ファイル属性が取得できません。 ERROR_GET_INFORMATION=0x8036,// ファイル情報が取得できません。 } //---------------------------------------------------------------- // コールバック関数の返答用型 //---------------------------------------------------------------- enum BgaAnswer { MeltIt, SkipIt, Abort } //---------------------------------------------------------------- // Filep // D言語らしからぬのですがzlibやlibbz2と簡単に連携する // 都合上std.c.stdio.FILE*でファイルを読み書きします。 //---------------------------------------------------------------- extern(C) { // stdio.h int fileno( FILE *fp ) { return fp._file; } // io.h int lseek( int fd, int offset, int mode ); int dup( int fd ); int close( int fd ); } class Filep { private FILE* fp; private this( FILE* p ) { fp = p; } static Filep open( string filename, bool read ) { FILE* fp = fopen(filename.toStringz(), read?"rb":"wb"); return (fp ? new Filep(fp) : null); } int dup_han() { int fd = dup( fileno(fp) ); lseek( fd, cur(), 0 ); return fd; } void[] read( int siz ) { char[] buf; buf.length = siz; int rsiz = fread( buf.ptr, 1, siz, fp ); if( rsiz < 0 ) throw new BgaMelterError(ERROR_FILE_OPEN); buf.length = rsiz; return buf; } void write( void[] buf ) { while( buf.length > 0 ) { int rsiz = fwrite( buf.ptr, 1, buf.length, fp ); if( rsiz < 0 ) return; buf = buf[rsiz .. $]; } } int cur() { return ftell(fp); } void seek_to( int i ) { fseek( fp, i, std.c.stdio.SEEK_SET ); } void close() { fclose(fp); } FILE* get_fp() { return fp; } } //---------------------------------------------------------------- // メインクラス //---------------------------------------------------------------- class BgaMelter { alias BgaAnswer delegate(ref BgaHeader) FileHandler; alias BgaAnswer delegate(int, int) ProgressHandler; private Filep fp = null; this( string arc_name ) { fp = Filep.open( arc_name, true ); if( fp is null ) throw new BgaMelterError(ERROR_FILE_OPEN); } void close() { if( fp ) { fp.close(); fp = null; } } void start( FileHandler fh, ProgressHandler ph ) { try { // ヘッダを探す int hpos = find_header(); if( hpos == -1 ) throw new BgaMelterError(ERROR_NOT_ARC_FILE); fp.seek_to(hpos); // ループ: // ヘッダ読みとり BgaHeader hdr; while( read_header(hdr) ) { // 次のヘッダ位置を計算しておく uint nextpos = fp.cur() + hdr.compressed_size; try { // callback BgaAnswer a = fh(hdr); if( a == BgaAnswer.Abort ) return; if( a == BgaAnswer.SkipIt ) continue; // 出力先ファイルを開く if( lastChar(hdr.fname)=='\\' ) { pathMake(hdr.fname); continue; } Filep outf = Filep.open( pathMake(hdr.fname), false ); if( outf is null ) throw new BgaMelterError(ERROR_FILE_OPEN); // 解凍処理 bool bContinue = true; if( !is_compressed(hdr) ) bContinue = NcDec( hdr.original_size, outf, ph ); else if( hdr.method == "GZIP" ) bContinue = GzDec( hdr.compressed_size, hdr.original_size, outf, ph ); else if( hdr.method == "BZ2\0" ) bContinue = BzDec( hdr.original_size, outf, ph ); // 閉じて属性設定 outf.close(); dosSetFTime( hdr.fname, hdr.date, hdr.time ); SetFileAttributesA( hdr.fname.toStringz(), hdr.attrib ); if( !bContinue ) return; } finally { fp.seek_to(nextpos); } } } finally { close(); } } static int signed_char( char c ) { int cn = c; return (cn>=0x80 ? cn|0xffffff00 : cn); } private int find_header() { string dat = cast(string) fp.read(0x10000); for( int i=0; i<dat.length-28; ++i ) { if( dat[i+4]!='G' && dat[i+4]!='B' ) continue; if( dat[i+5]!='Z' ) continue; if( dat[i+6]!='I' && dat[i+6]!='2' ) continue; if( dat[i+7]!='P' && dat[i+7]!='\0') continue; int checksum = dat[i+0]+(dat[i+1]<<8)+(dat[i+2]<<16)+(dat[i+3]<<24); uint fnlen = dat[i+24]+(dat[i+25]<<8)+dat[i+26]+(dat[i+27]<<8); if( i+28+fnlen > dat.length ) continue; int sum = 0; for( int j=i+4; j!=i+28+fnlen; ++j ) sum += signed_char(dat[j]); if( checksum == sum ) return i; } return -1; } private bool read_header( out BgaHeader hdr ) { // リトルエンディアンを仮定。ヘッダ読み込み void[] buf = fp.read(28); if( buf.length < 28 ) return false; buf.length = BgaHeader.sizeof; hdr = (cast(BgaHeader[]) buf)[0]; hdr.fname = ""; // ファイル名 hdr.fname = cast(string) fp.read(hdr.dir_name_len + hdr.file_name_len); if( hdr.fname.length < hdr.dir_name_len + hdr.file_name_len ) return false; // チェックサム int sum = 0; for( int i=4; i!=28; ++i ) sum += signed_char((cast(char[])buf)[i]); foreach( char c ; hdr.fname ) sum += signed_char(c); return (sum == hdr.checksum); } private bool is_compressed( ref BgaHeader hdr ) // ref=just for optimization { // ヘッダから、ファイルが圧縮格納されているかどうかを判定 if( hdr.arc_type==2 ) return false; if( hdr.arc_type==0 && hdr.compressed_size==hdr.original_size ) { int x = hdr.fname.lastIndexOf('.'); if( x == -1 ) return true; string ext = hdr.fname[x+1 .. $].toLower(); if( ext=="arc" || ext=="arj" || ext=="bz2" || ext=="bza" || ext=="cab" || ext=="gz" || ext=="gza" || ext=="lzh" || ext=="lzs" || ext=="pak" || ext=="rar" || ext=="taz" || ext=="tbz" || ext=="tgz" || ext=="z" || ext=="zip" || ext=="zoo" ) return false; } return true; } static string pathMake( string path ) { char* ps = cast(char*)path.toStringz(); for(char* p=ps;; ++p) { for(; *p!=0&&*p!='\\'&&*p!='/'; p=CharNextA(p)) {} if( *p==0 ) break; CreateDirectoryA( ps[0..(p-ps)].toStringz(), null ); } return path; } enum BUFSIZ = 65536; private bool NcDec( uint usiz, Filep outf, ProgressHandler ph ) { uint init_usiz = usiz; // 非圧縮。コピーするだけ while( usiz ) { string r = cast(string) fp.read( BUFSIZ<usiz?BUFSIZ:usiz ); usiz -= r.length; outf.write(cast(char[])r); // dlg if( BgaAnswer.Abort==ph(init_usiz-usiz,usiz) ) return false; } // dlg if( BgaAnswer.Abort==ph(init_usiz-usiz,usiz) ) return false; return true; } private bool GzDec( uint csiz, uint usiz, Filep outf, ProgressHandler ph ) { uint init_usiz = usiz; // zlibで展開 fp.read(10); csiz -= 10; // ヘッダ,フッタスキップ ubyte[] inbuf; inbuf.length = 65536; ubyte[] outbuf; outbuf.length = 65536; // zlib準備 z_stream zs; zs.zalloc = null; zs.zfree = null; // 出力バッファ zs.next_out = outbuf.ptr; zs.avail_out = outbuf.length; // 入力バッファ inbuf = cast(ubyte[]) fp.read( csiz<65536 ? csiz : 65536 ); csiz -= inbuf.length; zs.next_in = inbuf.ptr; zs.avail_in = inbuf.length; // スタート inflateInit2( &zs, -15 ); try { // 書庫から入力し終わるまでループ int err = Z_OK; while( csiz&&usiz ) { while( zs.avail_out > 0 ) { err = etc.c.zlib.inflate( &zs, Z_PARTIAL_FLUSH ); if( err!=Z_STREAM_END && err!=Z_OK ) csiz=0; if( !csiz ) break; if( zs.avail_in<=0 ) { inbuf = cast(ubyte[]) fp.read( csiz<65536 ? csiz : 65536 ); csiz -= inbuf.length; zs.next_in = inbuf.ptr; zs.avail_in = inbuf.length; if( inbuf.length==0 ) { err = Z_STREAM_END; csiz = 0; break; } } } int written = outbuf.length - zs.avail_out; if( usiz < written ) written = usiz; usiz -= written; outf.write( outbuf[0..written] ); zs.next_out = outbuf.ptr; zs.avail_out = outbuf.length; // dlg if( BgaAnswer.Abort==ph(init_usiz-usiz,usiz) ) return false; } // 出力残しを無くす。 while( err!=Z_STREAM_END&&usiz ) { err = etc.c.zlib.inflate(&zs,Z_PARTIAL_FLUSH); if( err!=Z_STREAM_END && err!=Z_OK ) break; int written = outbuf.length - zs.avail_out; if( usiz < written ) written = usiz; usiz -= written; outf.write( outbuf[0..written] ); zs.next_out = outbuf.ptr; zs.avail_out = outbuf.length; // dlg if( BgaAnswer.Abort==ph(init_usiz-usiz,usiz) ) return false; } // 終了 } finally { inflateEnd(&zs); } // dlg if( BgaAnswer.Abort==ph(init_usiz-usiz,usiz) ) return false; return true; } private bool BzDec( uint usiz, Filep outf, ProgressHandler ph ) { uint init_usiz = usiz; // libbz2で展開 int err; BZFILE* b = BZ2_bzReadOpen( &err, fp.get_fp(), 0, 0, null, 0 ); if( err!=BZ_OK || b is null ) return true; try { char[] buf; buf.length = BUFSIZ; int len; while( 0<(len=BZ2_bzRead( &err, b, buf.ptr, BUFSIZ<usiz?BUFSIZ:usiz )) ) { outf.write( buf[0..len] ); usiz -= len; if( err != BZ_OK ) break; // dlg if( BgaAnswer.Abort==ph(init_usiz-usiz,usiz) ) return false; } } finally { BZ2_bzReadClose( &err, b ); } // dlg if( BgaAnswer.Abort==ph(init_usiz-usiz,usiz) ) return false; return true; } }