Files
itgmania212121/src/RageFileDriverZip.cpp
T

375 lines
11 KiB
C++
Raw Normal View History

2011-03-17 01:47:30 -04:00
/*
* 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.
*/