0792db752a
merging with the main branch.
375 lines
11 KiB
C++
375 lines
11 KiB
C++
/*
|
|
* Ref: http://www.info-zip.org/pub/infozip/doc/appnote-981119-iz.zip
|
|
*/
|
|
|
|
#include "global.h"
|
|
#include "RageFileDriverZip.h"
|
|
#include "RageFileDriverSlice.h"
|
|
#include "RageFileDriverDeflate.h"
|
|
#include "RageFile.h"
|
|
#include "RageLog.h"
|
|
#include "RageUtil.h"
|
|
#include "RageUtil_FileDB.h"
|
|
#include <cerrno>
|
|
|
|
static struct FileDriverEntry_ZIP: public FileDriverEntry
|
|
{
|
|
FileDriverEntry_ZIP(): FileDriverEntry( "ZIP" ) { }
|
|
RageFileDriver *Create( const RString &sRoot ) const { return new RageFileDriverZip( sRoot ); }
|
|
} const g_RegisterDriver;
|
|
|
|
|
|
RageFileDriverZip::RageFileDriverZip():
|
|
RageFileDriver( new NullFilenameDB ),
|
|
m_Mutex( "RageFileDriverZip" )
|
|
{
|
|
m_bFileOwned = false;
|
|
m_pZip = NULL;
|
|
}
|
|
|
|
RageFileDriverZip::RageFileDriverZip( const RString &sPath ):
|
|
RageFileDriver( new NullFilenameDB ),
|
|
m_Mutex( "RageFileDriverZip" )
|
|
{
|
|
m_bFileOwned = false;
|
|
m_pZip = NULL;
|
|
Load( sPath );
|
|
}
|
|
|
|
bool RageFileDriverZip::Load( const RString &sPath )
|
|
{
|
|
ASSERT( m_pZip == NULL ); /* don't load twice */
|
|
|
|
m_bFileOwned = true;
|
|
m_sPath = sPath;
|
|
m_Mutex.SetName( ssprintf("RageFileDriverZip(%s)", sPath.c_str()) );
|
|
|
|
RageFile *pFile = new RageFile;
|
|
|
|
if( !pFile->Open(sPath) )
|
|
{
|
|
WARN( ssprintf("Couldn't open %s: %s", sPath.c_str(), pFile->GetError().c_str()) );
|
|
delete pFile;
|
|
return false;
|
|
}
|
|
|
|
m_pZip = pFile;
|
|
|
|
return ParseZipfile();
|
|
}
|
|
|
|
bool RageFileDriverZip::Load( RageFileBasic *pFile )
|
|
{
|
|
ASSERT( m_pZip == NULL ); /* don't load twice */
|
|
m_sPath = ssprintf("%p", pFile);
|
|
m_Mutex.SetName( ssprintf("RageFileDriverZip(%p)", pFile) );
|
|
|
|
m_pZip = pFile;
|
|
|
|
return ParseZipfile();
|
|
}
|
|
|
|
|
|
bool RageFileDriverZip::ReadEndCentralRecord( int &iTotalEntries, int &iCentralDirectoryOffset )
|
|
{
|
|
RString sError;
|
|
RString sSig = FileReading::ReadString( *m_pZip, 4, sError );
|
|
FileReading::read_16_le( *m_pZip, sError ); /* skip number of this disk */
|
|
FileReading::read_16_le( *m_pZip, sError ); /* skip disk with central directory */
|
|
FileReading::read_16_le( *m_pZip, sError ); /* skip number of entries on this disk */
|
|
iTotalEntries = FileReading::read_16_le( *m_pZip, sError );
|
|
FileReading::read_32_le( *m_pZip, sError ); /* skip size of the central directory */
|
|
iCentralDirectoryOffset = FileReading::read_32_le( *m_pZip, sError );
|
|
int iCommentLength = FileReading::read_16_le( *m_pZip, sError );
|
|
m_sComment = FileReading::ReadString( *m_pZip, iCommentLength, sError );
|
|
|
|
if( sError != "" )
|
|
{
|
|
WARN( ssprintf("%s: %s", m_sPath.c_str(), sError.c_str()) );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Find the end of central directory record, and seek to it. */
|
|
bool RageFileDriverZip::SeekToEndCentralRecord()
|
|
{
|
|
const int iSearchTo = max( m_pZip->GetFileSize() - 1024*32, 0 );
|
|
int iRealPos = m_pZip->GetFileSize();
|
|
|
|
while( iRealPos > 0 && iRealPos >= iSearchTo )
|
|
{
|
|
/* Move back in the file; leave some overlap between checks, to handle
|
|
* the case where the signature crosses the block boundary. */
|
|
char buf[1024*4];
|
|
iRealPos -= sizeof(buf) - 4;
|
|
iRealPos = max( 0, iRealPos );
|
|
m_pZip->Seek( iRealPos );
|
|
|
|
int iGot = m_pZip->Read( buf, sizeof(buf) );
|
|
if( iGot == -1 )
|
|
{
|
|
WARN( ssprintf("%s: %s", m_sPath.c_str(), m_pZip->GetError().c_str()) );
|
|
return false;
|
|
}
|
|
|
|
for( int iPos = iGot - 4; iPos >= 0; --iPos )
|
|
{
|
|
if( memcmp(buf + iPos, "\x50\x4B\x05\x06", 4) )
|
|
continue;
|
|
|
|
m_pZip->Seek( iRealPos + iPos );
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool RageFileDriverZip::ParseZipfile()
|
|
{
|
|
if( !SeekToEndCentralRecord() )
|
|
{
|
|
WARN( ssprintf("Couldn't open %s: couldn't find end of central directory record", m_sPath.c_str()) );
|
|
return false;
|
|
}
|
|
|
|
/* Read the end of central directory record. */
|
|
int iTotalEntries, iCentralDirectoryOffset;
|
|
if( !ReadEndCentralRecord(iTotalEntries, iCentralDirectoryOffset) )
|
|
return false; /* warned already */
|
|
|
|
/* Seek to the start of the central file directory. */
|
|
m_pZip->Seek( iCentralDirectoryOffset );
|
|
|
|
/* Loop through files in central directory. */
|
|
for( int i = 0; i < iTotalEntries; ++i )
|
|
{
|
|
FileInfo info;
|
|
info.m_iDataOffset = -1;
|
|
int got = ProcessCdirFileHdr( info );
|
|
if( got == -1 ) /* error */
|
|
break;
|
|
if( got == 0 ) /* skip */
|
|
continue;
|
|
|
|
FileInfo *pInfo = new FileInfo( info );
|
|
m_pFiles.push_back( pInfo );
|
|
FDB->AddFile( "/" + pInfo->m_sName, pInfo->m_iUncompressedSize, pInfo->m_iCRC32, pInfo );
|
|
}
|
|
|
|
if( m_pFiles.size() == 0 )
|
|
WARN( ssprintf("%s: no files found in central file header", m_sPath.c_str()) );
|
|
|
|
return true;
|
|
}
|
|
|
|
int RageFileDriverZip::ProcessCdirFileHdr( FileInfo &info )
|
|
{
|
|
RString sError;
|
|
RString sSig = FileReading::ReadString( *m_pZip, 4, sError );
|
|
if( sSig != "\x50\x4B\x01\x02" )
|
|
{
|
|
WARN( ssprintf("%s: central directory record signature not found", m_sPath.c_str()) );
|
|
return -1;
|
|
}
|
|
|
|
FileReading::read_8( *m_pZip, sError ); /* skip version made by */
|
|
int iOSMadeBy = FileReading::read_8( *m_pZip, sError );
|
|
FileReading::read_16_le( *m_pZip, sError ); /* skip version needed to extract */
|
|
int iGeneralPurpose = FileReading::read_16_le( *m_pZip, sError );
|
|
info.m_iCompressionMethod = (ZipCompressionMethod) FileReading::read_16_le( *m_pZip, sError );
|
|
FileReading::read_16_le( *m_pZip, sError ); /* skip last mod file time */
|
|
FileReading::read_16_le( *m_pZip, sError ); /* skip last mod file date */
|
|
info.m_iCRC32 = FileReading::read_32_le( *m_pZip, sError );
|
|
info.m_iCompressedSize = FileReading::read_32_le( *m_pZip, sError );
|
|
info.m_iUncompressedSize = FileReading::read_32_le( *m_pZip, sError );
|
|
int iFilenameLength = FileReading::read_16_le( *m_pZip, sError );
|
|
int iExtraFieldLength = FileReading::read_16_le( *m_pZip, sError );
|
|
int iFileCommentLength = FileReading::read_16_le( *m_pZip, sError );
|
|
FileReading::read_16_le( *m_pZip, sError ); /* relative offset of local header */
|
|
FileReading::read_16_le( *m_pZip, sError ); /* skip internal file attributes */
|
|
unsigned iExternalFileAttributes = FileReading::read_32_le( *m_pZip, sError );
|
|
info.m_iOffset = FileReading::read_32_le( *m_pZip, sError );
|
|
|
|
/* Check for errors before reading variable-length fields. */
|
|
if( sError != "" )
|
|
{
|
|
WARN( ssprintf("%s: %s", m_sPath.c_str(), sError.c_str()) );
|
|
return -1;
|
|
}
|
|
|
|
info.m_sName = FileReading::ReadString( *m_pZip, iFilenameLength, sError );
|
|
FileReading::SkipBytes( *m_pZip, iExtraFieldLength, sError ); /* skip extra field */
|
|
FileReading::SkipBytes( *m_pZip, iFileCommentLength, sError ); /* skip file comment */
|
|
|
|
if( sError != "" )
|
|
{
|
|
WARN( ssprintf("%s: %s", m_sPath.c_str(), sError.c_str()) );
|
|
return -1;
|
|
}
|
|
|
|
/* Check usability last, so we always read past the whole entry and don't leave the
|
|
* file pointer in the middle of a record. */
|
|
if( iGeneralPurpose & 1 )
|
|
{
|
|
WARN( ssprintf("Skipped encrypted \"%s\" in \"%s\"", info.m_sName.c_str(), m_sPath.c_str()) );
|
|
return 0;
|
|
}
|
|
|
|
/* Skip directories. */
|
|
if( iExternalFileAttributes & (1<<4) )
|
|
return 0;
|
|
|
|
info.m_iFilePermissions = 0;
|
|
enum { MADE_BY_UNIX = 3 };
|
|
switch( iOSMadeBy )
|
|
{
|
|
case MADE_BY_UNIX:
|
|
info.m_iFilePermissions = (iExternalFileAttributes >> 16) & 0x1FF;
|
|
break;
|
|
}
|
|
|
|
if( info.m_iCompressionMethod != STORED && info.m_iCompressionMethod != DEFLATED )
|
|
{
|
|
WARN( ssprintf("File \"%s\" in \"%s\" uses unsupported compression method %i",
|
|
info.m_sName.c_str(), m_sPath.c_str(), info.m_iCompressionMethod) );
|
|
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
bool RageFileDriverZip::ReadLocalFileHeader( FileInfo &info )
|
|
{
|
|
/* Seek to and read the local file header. */
|
|
m_pZip->Seek( info.m_iOffset );
|
|
|
|
RString sError;
|
|
RString sSig = FileReading::ReadString( *m_pZip, 4, sError );
|
|
|
|
if( sError != "" )
|
|
{
|
|
WARN( ssprintf("%s: error opening \"%s\": %s", m_sPath.c_str(), info.m_sName.c_str(), sError.c_str()) );
|
|
return false;
|
|
}
|
|
|
|
if( sSig != "\x50\x4B\x03\x04" )
|
|
{
|
|
WARN( ssprintf("%s: local file header not found for \"%s\"", m_sPath.c_str(), info.m_sName.c_str()) );
|
|
return false;
|
|
}
|
|
|
|
FileReading::SkipBytes( *m_pZip, 22, sError ); /* skip most of the local file header */
|
|
|
|
const int iFilenameLength = FileReading::read_16_le( *m_pZip, sError );
|
|
const int iExtraFieldLength = FileReading::read_16_le( *m_pZip, sError );
|
|
info.m_iDataOffset = m_pZip->Tell() + iFilenameLength + iExtraFieldLength;
|
|
|
|
if( sError != "" )
|
|
{
|
|
WARN( ssprintf("%s: %s", m_sPath.c_str(), sError.c_str()) );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
RageFileDriverZip::~RageFileDriverZip()
|
|
{
|
|
for( unsigned i = 0; i < m_pFiles.size(); ++i )
|
|
delete m_pFiles[i];
|
|
|
|
if( m_bFileOwned )
|
|
delete m_pZip;
|
|
}
|
|
|
|
const RageFileDriverZip::FileInfo *RageFileDriverZip::GetFileInfo( const RString &sPath ) const
|
|
{
|
|
return (const FileInfo *) FDB->GetFilePriv( sPath );
|
|
}
|
|
|
|
RageFileBasic *RageFileDriverZip::Open( const RString &sPath, int iMode, int &iErr )
|
|
{
|
|
if( iMode & RageFile::WRITE )
|
|
{
|
|
iErr = ERROR_WRITING_NOT_SUPPORTED;
|
|
return NULL;
|
|
}
|
|
|
|
FileInfo *info = (FileInfo *) FDB->GetFilePriv( sPath );
|
|
if( info == NULL )
|
|
{
|
|
iErr = ENOENT;
|
|
return NULL;
|
|
}
|
|
|
|
m_Mutex.Lock();
|
|
|
|
/* If we haven't figured out the offset to the real data yet, do so now. */
|
|
if( info->m_iDataOffset == -1 )
|
|
{
|
|
if( !ReadLocalFileHeader(*info) )
|
|
{
|
|
m_Mutex.Unlock();
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* We won't do any further access to zip, except to copy it (which is
|
|
* threadsafe), so we can unlock now. */
|
|
m_Mutex.Unlock();
|
|
|
|
RageFileDriverSlice *pSlice = new RageFileDriverSlice( m_pZip->Copy(), info->m_iDataOffset, info->m_iCompressedSize );
|
|
pSlice->DeleteFileWhenFinished();
|
|
|
|
switch( info->m_iCompressionMethod )
|
|
{
|
|
case STORED:
|
|
return pSlice;
|
|
case DEFLATED:
|
|
{
|
|
RageFileObjInflate *pInflate = new RageFileObjInflate( pSlice, info->m_iUncompressedSize );
|
|
pInflate->DeleteFileWhenFinished();
|
|
return pInflate;
|
|
}
|
|
default:
|
|
/* unknown compression method */
|
|
iErr = ENOSYS;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* NOP for now. This could check to see if the ZIP's mtime has changed, and reload. */
|
|
void RageFileDriverZip::FlushDirCache( const RString &sPath )
|
|
{
|
|
|
|
}
|
|
|
|
/*
|
|
* Copyright (c) 2003-2005 Glenn Maynard
|
|
* All rights reserved.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the
|
|
* "Software"), to deal in the Software without restriction, including
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
* distribute, and/or sell copies of the Software, and to permit persons to
|
|
* whom the Software is furnished to do so, provided that the above
|
|
* copyright notice(s) and this permission notice appear in all copies of
|
|
* the Software and that both the above copyright notice(s) and this
|
|
* permission notice appear in supporting documentation.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
|
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
|
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
|
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
|
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
* PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|