private import std.string;
private import std.file;
private import std.c.stdio;
private import etc.c.zlib;
private import win32.ansi.windows;
private import libbz2.bzlib;
private 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; // ファイル名の長さ
char[] 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( char[] filename, bool read )
{
FILE* fp = fopen( toStringz(filename), 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, 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, 1, buf.length, fp );
if( rsiz < 0 ) return;
buf = buf[rsiz .. length];
}
}
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(inout BgaHeader) FileHandler;
alias BgaAnswer delegate(int, int) ProgressHandler;
private Filep fp = null;
this( char[] 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 );
SetFileAttributes( hdr.fname, 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()
{
char[] dat = cast(char[]) 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 )
{
// リトルエンディアンを仮定。ヘッダ読み込み
char[] buf = cast(char[]) fp.read(28);
if( buf.length < 28 ) return false;
buf.length = BgaHeader.sizeof;
hdr = (cast(BgaHeader[]) buf)[0];
hdr.fname = "";
// ファイル名
hdr.fname = cast(char[]) 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(buf[i]);
foreach( char c ; hdr.fname ) sum += signed_char(c);
return (sum == hdr.checksum);
}
private bool is_compressed( inout BgaHeader hdr ) // inout=just for optimization
{
// ヘッダから、ファイルが圧縮格納されているかどうかを判定
if( hdr.arc_type==2 )
return false;
if( hdr.arc_type==0 && hdr.compressed_size==hdr.original_size )
{
int x = rfind( hdr.fname, '.' );
if( x == -1 )
return true;
char[] ext = tolower(hdr.fname[x+1 .. length]);
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 char[] pathMake( char[] path )
{
char* ps = toStringz(path);
for(char* p=ps;;)
{
for(; *p!=0&&*p!='\\'&&*p!='/'; p=CharNext(p)) {}
if( *p==0 )
break;
CreateDirectory( toStringz(ps[0..(p-ps)]), null );
++p;
}
return path;
}
enum { BUFSIZ = 65536 }
private bool NcDec( uint usiz, Filep outf, ProgressHandler ph )
{
uint init_usiz = usiz;
// 非圧縮。コピーするだけ
while( usiz )
{
char[] r = cast(char[]) fp.read( BUFSIZ<usiz?BUFSIZ:usiz );
usiz -= r.length;
outf.write(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;
zs.avail_out = outbuf.length;
// 入力バッファ
inbuf = cast(ubyte[]) fp.read( csiz<65536 ? csiz : 65536 );
csiz -= inbuf.length;
zs.next_in = inbuf;
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;
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;
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;
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, 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;
}
}