generalize zip reading

This commit is contained in:
Glenn Maynard
2005-03-24 09:40:56 +00:00
parent 8dfde50b5f
commit 382db74f8f
2 changed files with 82 additions and 55 deletions
+72 -52
View File
@@ -55,8 +55,26 @@ RageFileDriverZip::RageFileDriverZip( CString path ):
RageFileDriver( new NullFilenameDB ), RageFileDriver( new NullFilenameDB ),
m_Mutex( ssprintf("RageFileDriverZip(%s)", path.c_str()) ) m_Mutex( ssprintf("RageFileDriverZip(%s)", path.c_str()) )
{ {
if( !zip.Open(path) ) m_bFileOwned = true;
RageException::Throw( "Couldn't open %s: %s", path.c_str(), zip.GetError().c_str() ); m_sPath = path;
RageFile *pFile = new RageFile;
m_pZip = pFile;
if( !pFile->Open(path) )
RageException::Throw( "Couldn't open %s: %s", path.c_str(), pFile->GetError().c_str() );
ParseZipfile();
}
RageFileDriverZip::RageFileDriverZip( RageFileBasic *pFile ):
RageFileDriver( new NullFilenameDB ),
m_Mutex( ssprintf("RageFileDriverZip(%p)", pFile) )
{
m_sPath = ssprintf("%p", pFile);
m_bFileOwned = false;
m_pZip = pFile;
ParseZipfile(); ParseZipfile();
} }
@@ -83,9 +101,9 @@ static unsigned long makelong(const unsigned char *sig)
+ ((unsigned long)sig[0]); + ((unsigned long)sig[0]);
} }
void RageFileDriverZip::ReadEndCentralRecord( RageFile &zip, end_central_dir_record &ec ) void RageFileDriverZip::ReadEndCentralRecord( end_central_dir_record &ec )
{ {
const int OrigPos = zip.Tell(); const int OrigPos = m_pZip->Tell();
typedef unsigned char ec_byte_rec[ ECREC_SIZE+4 ]; typedef unsigned char ec_byte_rec[ ECREC_SIZE+4 ];
#define NUMBER_THIS_DISK 4 #define NUMBER_THIS_DISK 4
#define NUM_DISK_WITH_START_CENTRAL_DIR 6 #define NUM_DISK_WITH_START_CENTRAL_DIR 6
@@ -96,11 +114,11 @@ void RageFileDriverZip::ReadEndCentralRecord( RageFile &zip, end_central_dir_rec
#define ZIPFILE_COMMENT_LENGTH 20 #define ZIPFILE_COMMENT_LENGTH 20
ec_byte_rec byterec; ec_byte_rec byterec;
const int got = zip.Read( byterec, ECREC_SIZE+4 ); const int got = m_pZip->Read( byterec, ECREC_SIZE+4 );
if( got == -1 ) if( got == -1 )
RageException::Throw( "Couldn't open %s: %s", zip.GetPath().c_str(), zip.GetError().c_str() ); RageException::Throw( "Couldn't open %s: %s", m_sPath.c_str(), m_pZip->GetError().c_str() );
if ( got != ECREC_SIZE+4 ) if ( got != ECREC_SIZE+4 )
RageException::Throw( "%s: unexpected EOF", zip.GetPath().c_str() ); RageException::Throw( "%s: unexpected EOF", m_sPath.c_str() );
ec.number_this_disk = makeword(&byterec[NUMBER_THIS_DISK]); ec.number_this_disk = makeword(&byterec[NUMBER_THIS_DISK]);
ec.num_disk_start_cdir = makeword(&byterec[NUM_DISK_WITH_START_CENTRAL_DIR]); ec.num_disk_start_cdir = makeword(&byterec[NUM_DISK_WITH_START_CENTRAL_DIR]);
@@ -112,19 +130,19 @@ void RageFileDriverZip::ReadEndCentralRecord( RageFile &zip, end_central_dir_rec
const int expect_ecrec_offset = ec.offset_start_central_directory + ec.size_central_directory; const int expect_ecrec_offset = ec.offset_start_central_directory + ec.size_central_directory;
if( expect_ecrec_offset > OrigPos ) if( expect_ecrec_offset > OrigPos )
RageException::Throw( "Couldn't open %s: missing %ld bytes in zipfile", zip.GetPath().c_str(), RageException::Throw( "Couldn't open %s: missing %ld bytes in zipfile", m_sPath.c_str(),
expect_ecrec_offset - OrigPos ); expect_ecrec_offset - OrigPos );
} }
void RageFileDriverZip::ParseZipfile() void RageFileDriverZip::ParseZipfile()
{ {
/* Look for the end-central record. */ /* Look for the end-central record. */
const int searchlen = min( zip.GetFileSize(), 66000 ); const int searchlen = min( m_pZip->GetFileSize(), 66000 );
const int Size = zip.GetFileSize(); const int Size = m_pZip->GetFileSize();
int realpos = Size; int realpos = Size;
/* Loop through blocks of data, starting at the end. In general, need not /* Loop through blocks of data, starting at the end. In general, need not
* check whole zipfile for signature, but may want to do so if testing. */ * check whole zim_pZip for signature, but may want to do so if testing. */
int real_ecrec_offset = -1; int real_ecrec_offset = -1;
@@ -134,15 +152,15 @@ void RageFileDriverZip::ParseZipfile()
{ {
realpos -= INBUFSIZE; realpos -= INBUFSIZE;
realpos = max( 0, realpos ); realpos = max( 0, realpos );
zip.Seek( realpos ); m_pZip->Seek( realpos );
int got = zip.Read( tmp+tmp_used, sizeof(tmp)-tmp_used ); int got = m_pZip->Read( tmp+tmp_used, sizeof(tmp)-tmp_used );
if( got == -1 ) if( got == -1 )
RageException::Throw( "Couldn't open %s: %s", zip.GetPath().c_str(), zip.GetError().c_str() ); RageException::Throw( "Couldn't open %s: %s", m_sPath.c_str(), m_pZip->GetError().c_str() );
if( got == 0 ) if( got == 0 )
break; /* fall through and fail */ break; /* fall through and fail */
got += tmp_used; got += tmp_used;
/* 'P' must be at least (ECREC_SIZE+4) bytes from end of zipfile */ /* 'P' must be at least (ECREC_SIZE+4) bytes from end of zim_pZip */
for( int pos = got-(ECREC_SIZE+4); real_ecrec_offset == -1 && pos >= 0; --pos ) for( int pos = got-(ECREC_SIZE+4); real_ecrec_offset == -1 && pos >= 0; --pos )
{ {
const char *p = tmp+pos; const char *p = tmp+pos;
@@ -160,34 +178,34 @@ void RageFileDriverZip::ParseZipfile()
} }
if( real_ecrec_offset == -1 ) if( real_ecrec_offset == -1 )
RageException::Throw( "Couldn't open %s: End-of-central-directory signature not found", zip.GetPath().c_str() ); RageException::Throw( "Couldn't open %s: End-of-central-directory signature not found", m_sPath.c_str() );
zip.Seek( real_ecrec_offset ); m_pZip->Seek( real_ecrec_offset );
/* Get the end-central data. */ /* Get the end-central data. */
end_central_dir_record ec; end_central_dir_record ec;
ReadEndCentralRecord( zip, ec ); ReadEndCentralRecord( ec );
bool something = ec.number_this_disk == 1 && ec.num_disk_start_cdir == 1; bool something = ec.number_this_disk == 1 && ec.num_disk_start_cdir == 1;
if (!something && ec.number_this_disk != 0) if (!something && ec.number_this_disk != 0)
RageException::Throw( "Couldn't open %s: zipfile is part of multi-disk archive", zip.GetPath().c_str() ); RageException::Throw( "Couldn't open %s: zipfile is part of multi-disk archive", m_sPath.c_str() );
/* Seek to where the start of central directory should be, and make /* Seek to where the start of central directory should be, and make
* sure it's there. */ * sure it's there. */
zip.Seek( ec.offset_start_central_directory ); m_pZip->Seek( ec.offset_start_central_directory );
CString sig; CString sig;
int got = zip.Read( sig, 4 ); int got = m_pZip->Read( sig, 4 );
if( got != 4 || sig != central_hdr_sig ) if( got != 4 || sig != central_hdr_sig )
RageException::Throw( "Couldn't open %s: start of central directory not found; zipfile corrupt", zip.GetPath().c_str() ); RageException::Throw( "Couldn't open %s: start of central directory not found; zipfile corrupt", m_sPath.c_str() );
zip.Seek( ec.offset_start_central_directory ); m_pZip->Seek( ec.offset_start_central_directory );
/* Loop through files in central directory. */ /* Loop through files in central directory. */
while(1) while(1)
{ {
CString sig; CString sig;
if ( zip.Read( sig, 4 ) != 4 ) if ( m_pZip->Read( sig, 4 ) != 4 )
break; break;
/* Is it a new entry? */ /* Is it a new entry? */
@@ -199,22 +217,22 @@ void RageFileDriverZip::ParseZipfile()
{ {
/* Are we at the end_central record? */ /* Are we at the end_central record? */
if( sig != end_central_sig ) if( sig != end_central_sig )
LOG->Warn( "%s: expected end central file header signature not found", zip.GetPath().c_str() ); LOG->Warn( "%s: expected end central file header signature not found", m_sPath.c_str() );
} else { } else {
LOG->Warn( "%s: expected central file header signature not found", zip.GetPath().c_str() ); LOG->Warn( "%s: expected central file header signature not found", m_sPath.c_str() );
} }
break; break;
} }
FileInfo info; FileInfo info;
info.data_offset = -1; info.data_offset = -1;
int got = ProcessCdirFileHdr(zip, info); int got = ProcessCdirFileHdr( info );
if( got == -1 ) /* error */ if( got == -1 ) /* error */
break; break;
if( got == 0 ) /* skip */ if( got == 0 ) /* skip */
continue; continue;
zip.Seek( zip.Tell() + info.extra_field_length ); m_pZip->Seek( m_pZip->Tell() + info.extra_field_length );
FileInfo *pInfo = new FileInfo(info); FileInfo *pInfo = new FileInfo(info);
Files.push_back( pInfo ); Files.push_back( pInfo );
@@ -241,19 +259,19 @@ typedef unsigned char cdir_byte_hdr[ CREC_SIZE ];
#define C_INTERNAL_FILE_ATTRIBUTES 32 #define C_INTERNAL_FILE_ATTRIBUTES 32
#define C_EXTERNAL_FILE_ATTRIBUTES 34 #define C_EXTERNAL_FILE_ATTRIBUTES 34
#define C_RELATIVE_OFFSET_LOCAL_HEADER 38 #define C_RELATIVE_OFFSET_LOCAL_HEADER 38
int RageFileDriverZip::ProcessCdirFileHdr( RageFile &zip, FileInfo &info ) int RageFileDriverZip::ProcessCdirFileHdr( FileInfo &info )
{ {
/* Read the next central directory entry and do any necessary machine-type /* Read the next central directory entry and do any necessary machine-type
* conversions (byte ordering, structure padding compensation--do so by * conversions (byte ordering, structure padding compensation--do so by
* copying the data from the array into which it was read (byterec) to the * copying the data from the array into which it was read (byterec) to the
* usable struct. */ * usable struct. */
cdir_byte_hdr byterec; cdir_byte_hdr byterec;
int got = zip.Read( (char *)byterec, CREC_SIZE ); int got = m_pZip->Read( (char *)byterec, CREC_SIZE );
if ( got == -1 ) if ( got == -1 )
RageException::Throw( "Couldn't open %s: %s", zip.GetPath().c_str(), zip.GetError().c_str() ); RageException::Throw( "Couldn't open %s: %s", m_sPath.c_str(), m_pZip->GetError().c_str() );
if ( got != CREC_SIZE ) if ( got != CREC_SIZE )
{ {
LOG->Warn( "%s: unexpected EOF", zip.GetPath().c_str() ); LOG->Warn( "%s: unexpected EOF", m_sPath.c_str() );
return -1; return -1;
} }
@@ -263,7 +281,7 @@ int RageFileDriverZip::ProcessCdirFileHdr( RageFile &zip, FileInfo &info )
if( version_needed_to_extract > 20 ) /* compatible with PKUNZIP 2.0 */ if( version_needed_to_extract > 20 ) /* compatible with PKUNZIP 2.0 */
{ {
LOG->Warn( "File \"%s\" in \"%s\" uses unsupported ZIP version %i.%i", LOG->Warn( "File \"%s\" in \"%s\" uses unsupported ZIP version %i.%i",
info.fn.c_str(), zip.GetRealPath().c_str(), info.fn.c_str(), m_sPath.c_str(),
version_needed_to_extract / 10, version_needed_to_extract % 10 ); version_needed_to_extract / 10, version_needed_to_extract % 10 );
return 0; return 0;
} }
@@ -271,8 +289,7 @@ int RageFileDriverZip::ProcessCdirFileHdr( RageFile &zip, FileInfo &info )
const int general_purpose_bit_flag = makeword(&byterec[C_GENERAL_PURPOSE_BIT_FLAG]); const int general_purpose_bit_flag = makeword(&byterec[C_GENERAL_PURPOSE_BIT_FLAG]);
if( general_purpose_bit_flag & 1 ) if( general_purpose_bit_flag & 1 )
{ {
LOG->Warn( "Skipped encrypted \"%s\" in \"%s\"", LOG->Warn( "Skipped encrypted \"%s\" in \"%s\"", info.fn.c_str(), m_sPath.c_str() );
info.fn.c_str(), zip.GetRealPath().c_str() );
return 0; return 0;
} }
@@ -293,19 +310,19 @@ int RageFileDriverZip::ProcessCdirFileHdr( RageFile &zip, FileInfo &info )
if( external_file_attributes & 0x08 ) if( external_file_attributes & 0x08 )
return 0; return 0;
got = zip.Read( info.fn, info.filename_length ); got = m_pZip->Read( info.fn, info.filename_length );
if( got == -1 ) if( got == -1 )
RageException::Throw( "Couldn't open %s: %s", zip.GetPath().c_str(), zip.GetError().c_str() ); RageException::Throw( "Couldn't open %s: %s", m_sPath.c_str(), m_pZip->GetError().c_str() );
if( got != info.filename_length ) if( got != info.filename_length )
{ {
LOG->Warn( "%s: bad filename length %li", zip.GetPath().c_str(), info.filename_length ); LOG->Warn( "%s: bad filename length %li", m_sPath.c_str(), info.filename_length );
return 0; return 0;
} }
if( info.compression_method != STORED && info.compression_method != DEFLATED ) if( info.compression_method != STORED && info.compression_method != DEFLATED )
{ {
LOG->Warn( "File \"%s\" in \"%s\" uses unsupported compression method %i", LOG->Warn( "File \"%s\" in \"%s\" uses unsupported compression method %i",
info.fn.c_str(), zip.GetRealPath().c_str(), info.compression_method ); info.fn.c_str(), m_sPath.c_str(), info.compression_method );
return 0; return 0;
} }
@@ -317,6 +334,9 @@ RageFileDriverZip::~RageFileDriverZip()
{ {
for( unsigned i = 0; i < Files.size(); ++i ) for( unsigned i = 0; i < Files.size(); ++i )
delete Files[i]; delete Files[i];
if( m_bFileOwned )
delete m_pZip;
} }
RageFileBasic *RageFileDriverZip::Open( const CString &path, int mode, int &err ) RageFileBasic *RageFileDriverZip::Open( const CString &path, int mode, int &err )
@@ -339,48 +359,48 @@ RageFileBasic *RageFileDriverZip::Open( const CString &path, int mode, int &err
/* If we havn't figured out the offset to the real data yet, do so now. */ /* If we havn't figured out the offset to the real data yet, do so now. */
if( info->data_offset == -1 ) if( info->data_offset == -1 )
{ {
zip.Seek( info->offset ); m_pZip->Seek( info->offset );
/* Should be in proper position now, so check for sig. */ /* Should be in proper position now, so check for sig. */
CString sig; CString sig;
int got = zip.Read( sig, 4 ); int got = m_pZip->Read( sig, 4 );
if( got == -1 ) if( got == -1 )
RageException::Throw( "Read error in %s: %s", zip.GetPath().c_str(), zip.GetError().c_str() ); RageException::Throw( "Read error in %s: %s", m_sPath.c_str(), m_pZip->GetError().c_str() );
if( got != 4 ) if( got != 4 )
RageException::Throw( "%s: unexpected EOF", zip.GetPath().c_str() ); RageException::Throw( "%s: unexpected EOF", m_sPath.c_str() );
if( sig != local_hdr_sig ) if( sig != local_hdr_sig )
RageException::Throw( "%s: bad zipfile offset", zip.GetPath().c_str() ); RageException::Throw( "%s: bad zipfile offset", m_sPath.c_str() );
#define LREC_SIZE 26 /* lengths of local file headers, central */ #define LREC_SIZE 26 /* lengths of local file headers, central */
#define L_FILENAME_LENGTH 22 #define L_FILENAME_LENGTH 22
#define L_EXTRA_FIELD_LENGTH 24 #define L_EXTRA_FIELD_LENGTH 24
unsigned char byterec[LREC_SIZE]; unsigned char byterec[LREC_SIZE];
got = zip.Read( (char *)byterec, LREC_SIZE ); got = m_pZip->Read( (char *)byterec, LREC_SIZE );
if( got == -1 ) if( got == -1 )
RageException::Throw( "Read error in %s: %s", zip.GetPath().c_str(), zip.GetError().c_str() ); RageException::Throw( "Read error in %s: %s", m_sPath.c_str(), m_pZip->GetError().c_str() );
if( got != LREC_SIZE ) if( got != LREC_SIZE )
RageException::Throw( "%s: unexpected EOF", zip.GetPath().c_str() ); RageException::Throw( "%s: unexpected EOF", m_sPath.c_str() );
const int filename_length = makeword(&byterec[L_FILENAME_LENGTH]); const int filename_length = makeword(&byterec[L_FILENAME_LENGTH]);
const int extra_field_length = makeword(&byterec[L_EXTRA_FIELD_LENGTH]); const int extra_field_length = makeword(&byterec[L_EXTRA_FIELD_LENGTH]);
info->data_offset = zip.Tell() + filename_length + extra_field_length; info->data_offset = m_pZip->Tell() + filename_length + extra_field_length;
} }
/* We won't do any further access to zip, except to copy it (which is /* We won't do any further access to zip, except to copy it (which is
* threadsafe), so we can unlock now. */ * threadsafe), so we can unlock now. */
m_Mutex.Unlock(); m_Mutex.Unlock();
RageFileDriverSlice *pFile = new RageFileDriverSlice( zip.Copy(), info->data_offset, info->compr_size ); RageFileDriverSlice *pSlice = new RageFileDriverSlice( m_pZip->Copy(), info->data_offset, info->compr_size );
pFile->DeleteFileWhenFinished(); pSlice->DeleteFileWhenFinished();
switch( info->compression_method ) switch( info->compression_method )
{ {
case STORED: case STORED:
return pFile; return pSlice;
case DEFLATED: case DEFLATED:
{ {
RageFileObjInflate *pInflate = new RageFileObjInflate( pFile, info->uncompr_size ); RageFileObjInflate *pInflate = new RageFileObjInflate( pSlice, info->uncompr_size );
pInflate->DeleteFileWhenFinished(); pInflate->DeleteFileWhenFinished();
return pInflate; return pInflate;
} }
+10 -3
View File
@@ -12,22 +12,29 @@ class RageFileDriverZip: public RageFileDriver
{ {
public: public:
RageFileDriverZip( CString path ); RageFileDriverZip( CString path );
RageFileDriverZip( RageFileBasic *pFile );
virtual ~RageFileDriverZip(); virtual ~RageFileDriverZip();
RageFileBasic *Open( const CString &path, int mode, int &err ); RageFileBasic *Open( const CString &path, int mode, int &err );
void FlushDirCache( const CString &sPath ); void FlushDirCache( const CString &sPath );
void DeleteFileWhenFinished() { m_bFileOwned = true; }
private: private:
RageFile zip; bool m_bFileOwned;
RageFileBasic *m_pZip;
vector<FileInfo *> Files; vector<FileInfo *> Files;
CString m_sPath;
/* Open() must be threadsafe. Mutex access to "zip", since we seek /* Open() must be threadsafe. Mutex access to "zip", since we seek
* around in it when reading files. */ * around in it when reading files. */
RageMutex m_Mutex; RageMutex m_Mutex;
void ParseZipfile(); void ParseZipfile();
static void ReadEndCentralRecord( RageFile &zip, end_central_dir_record &ec ); void ReadEndCentralRecord( end_central_dir_record &ec );
static int ProcessCdirFileHdr( RageFile &zip, FileInfo &info ); int ProcessCdirFileHdr( FileInfo &info );
}; };
#endif #endif