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 }