Rewrite, loosely based on Info-ZIP.
This commit is contained in:
+578
-127
@@ -1,84 +1,155 @@
|
||||
/*
|
||||
|
||||
Derived from Info-ZIP 5.50. Heavily stripped and rewritten. Only retains
|
||||
STORE and DEFLATE decompression types; that's all that's in use these days.
|
||||
Could probably readd SHRINK easily.
|
||||
- Glenn
|
||||
|
||||
Copyright (c) 1990-2002 Info-ZIP. All rights reserved.
|
||||
|
||||
For the purposes of this copyright and license, "Info-ZIP" is defined as
|
||||
the following set of individuals:
|
||||
|
||||
Mark Adler, John Bush, Karl Davis, Harald Denker, Jean-Michel Dubois,
|
||||
Jean-loup Gailly, Hunter Goatley, Ian Gorman, Chris Herborth, Dirk Haase,
|
||||
Greg Hartwig, Robert Heath, Jonathan Hudson, Paul Kienitz, David Kirschbaum,
|
||||
Johnny Lee, Onno van der Linden, Igor Mandrichenko, Steve P. Miller,
|
||||
Sergio Monesi, Keith Owens, George Petrov, Greg Roelofs, Kai Uwe Rommel,
|
||||
Steve Salisbury, Dave Smith, Christian Spieler, Antoine Verheijen,
|
||||
Paul von Behren, Rich Wales, Mike White
|
||||
|
||||
This software is provided "as is," without warranty of any kind, express
|
||||
or implied. In no event shall Info-ZIP or its contributors be held liable
|
||||
for any direct, indirect, incidental, special or consequential damages
|
||||
arising out of the use of or inability to use this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
definition, disclaimer, and this list of conditions.
|
||||
|
||||
2. Redistributions in binary form (compiled executables) must reproduce
|
||||
the above copyright notice, definition, disclaimer, and this list of
|
||||
conditions in documentation and/or other materials provided with the
|
||||
distribution. The sole exception to this condition is redistribution
|
||||
of a standard UnZipSFX binary as part of a self-extracting archive;
|
||||
that is permitted without inclusion of this license, as long as the
|
||||
normal UnZipSFX banner has not been removed from the binary or disabled.
|
||||
|
||||
3. Altered versions--including, but not limited to, ports to new operating
|
||||
systems, existing ports with new graphical interfaces, and dynamic,
|
||||
shared, or static library versions--must be plainly marked as such
|
||||
and must not be misrepresented as being the original source. Such
|
||||
altered versions also must not be misrepresented as being Info-ZIP
|
||||
releases--including, but not limited to, labeling of the altered
|
||||
versions with the names "Info-ZIP" (or any variation thereof, including,
|
||||
but not limited to, different capitalizations), "Pocket UnZip," "WiZ"
|
||||
or "MacZip" without the explicit permission of Info-ZIP. Such altered
|
||||
versions are further prohibited from misrepresentative use of the
|
||||
Zip-Bugs or Info-ZIP e-mail addresses or of the Info-ZIP URL(s).
|
||||
|
||||
4. Info-ZIP retains the right to use the names "Info-ZIP," "Zip," "UnZip,"
|
||||
"UnZipSFX," "WiZ," "Pocket UnZip," "Pocket Zip," and "MacZip" for its
|
||||
own source and binary releases.
|
||||
*/
|
||||
|
||||
#include "global.h"
|
||||
#include "RageFileDriverZip.h"
|
||||
#include "RageLog.h"
|
||||
#include "RageUtil.h"
|
||||
#include "RageUtil_FileDB.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <zzip/zzip.h>
|
||||
#include <zzip/plugin.h>
|
||||
#include <zlib/zlib.h>
|
||||
|
||||
#if defined(_WINDOWS)
|
||||
#ifdef DEBUG
|
||||
#pragma comment(lib, "zzip/zziplibd.lib")
|
||||
#else
|
||||
#pragma comment(lib, "zzip/zziplib.lib")
|
||||
#endif
|
||||
#pragma comment(lib, "zlib/zdll.lib")
|
||||
#endif
|
||||
#pragma comment(lib, "zzip/zlib.lib")
|
||||
|
||||
int OggRageFile_open(zzip_char_t* name, int flags, ...)
|
||||
#define INBUFSIZE 1024*4
|
||||
|
||||
struct FileInfo
|
||||
{
|
||||
RageFile *f = new RageFile;
|
||||
if( !f->Open(name) )
|
||||
{
|
||||
LOG->Trace("Opening \"%s\" failed: %s", name, f->GetError().c_str());
|
||||
delete f;
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (int) f;
|
||||
}
|
||||
CString fn;
|
||||
long offset;
|
||||
long data_offset;
|
||||
|
||||
int OggRageFile_close(int fd)
|
||||
long extra_field_length;
|
||||
long filename_length;
|
||||
unsigned short compression_method;
|
||||
unsigned long crc; /* crc (needed if extended header) */
|
||||
unsigned long compr_size; /* compressed size (needed if extended header) */
|
||||
unsigned long uncompr_size; /* uncompressed size (needed if extended header) */
|
||||
unsigned short diskstart; /* no of volume where this entry starts */
|
||||
};
|
||||
|
||||
struct end_central_dir_record
|
||||
{
|
||||
RageFile *f = (RageFile *) fd;
|
||||
delete f;
|
||||
return 0;
|
||||
}
|
||||
unsigned short number_this_disk;
|
||||
unsigned short num_disk_start_cdir;
|
||||
unsigned short num_entries_centrl_dir_ths_disk;
|
||||
unsigned short total_entries_central_dir;
|
||||
unsigned long size_central_directory;
|
||||
unsigned long offset_start_central_directory;
|
||||
unsigned short zipfile_comment_length;
|
||||
};
|
||||
|
||||
zzip_ssize_t OggRageFile_read(int fd, void* buf, zzip_size_t len)
|
||||
{
|
||||
RageFile *f = (RageFile *) fd;
|
||||
return f->Read( buf, len );
|
||||
}
|
||||
#define STORED 0
|
||||
#define DEFLATED 8
|
||||
|
||||
zzip_off_t OggRageFile_seeks(int fd, zzip_off_t offset, int whence)
|
||||
{
|
||||
RageFile *f = (RageFile *) fd;
|
||||
return f->Seek( offset, whence );
|
||||
}
|
||||
|
||||
zzip_off_t OggRageFile_filesize(int fd)
|
||||
{
|
||||
RageFile *f = (RageFile *) fd;
|
||||
return f->GetFileSize();
|
||||
}
|
||||
|
||||
zzip_ssize_t OggRageFile_write(int fd, _zzip_const void* buf, zzip_size_t len)
|
||||
{
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
static zzip_plugin_io_handlers ZzipCallbacks;
|
||||
|
||||
class RageFileObjZip: public RageFileObj
|
||||
class RageFileObjZipDeflated: public RageFileObj
|
||||
{
|
||||
private:
|
||||
ZZIP_FILE *fp;
|
||||
RageFileDriverZip *dir; /* hack: for errors */
|
||||
const FileInfo &info;
|
||||
RageFile zip;
|
||||
int CFilePos, UFilePos;
|
||||
|
||||
z_stream dstrm;
|
||||
char decomp_buf[INBUFSIZE], *decomp_buf_ptr;
|
||||
int decomp_buf_avail;
|
||||
|
||||
public:
|
||||
RageFileObjZip( ZZIP_FILE *fp, RageFileDriverZip *dir, RageFile &p );
|
||||
virtual ~RageFileObjZip();
|
||||
virtual int Read(void *buffer, size_t bytes);
|
||||
virtual int Write(const void *buffer, size_t bytes);
|
||||
virtual void Rewind();
|
||||
virtual int Seek( int offset );
|
||||
virtual int SeekCur( int offset );
|
||||
virtual int GetFileSize();
|
||||
RageFileObjZipDeflated( const RageFile &f, const FileInfo &info, RageFile &p );
|
||||
RageFileObjZipDeflated( const RageFileObjZipDeflated &cpy, RageFile &p );
|
||||
~RageFileObjZipDeflated();
|
||||
int Read(void *buffer, size_t bytes);
|
||||
int Write(const void *buffer, size_t bytes) { SetError( "Not implemented" ); return -1; }
|
||||
void Rewind();
|
||||
int Seek( int offset );
|
||||
int GetFileSize() { return info.uncompr_size; }
|
||||
RageFileObj *Copy( RageFile &p ) const
|
||||
{
|
||||
return new RageFileObjZipDeflated( *this, p );
|
||||
}
|
||||
};
|
||||
|
||||
class RageFileObjZipStored: public RageFileObj
|
||||
{
|
||||
private:
|
||||
const FileInfo &info;
|
||||
RageFile zip;
|
||||
int FilePos;
|
||||
|
||||
public:
|
||||
RageFileObjZipStored( const RageFile &f, const FileInfo &info, RageFile &p );
|
||||
int Read(void *buffer, size_t bytes);
|
||||
int Write(const void *buffer, size_t bytes) { SetError( "Not implemented" ); return -1; }
|
||||
|
||||
void Rewind()
|
||||
{
|
||||
zip.Seek( info.data_offset );
|
||||
FilePos = 0;
|
||||
}
|
||||
|
||||
int Seek( int offset );
|
||||
int GetFileSize() { return info.uncompr_size; }
|
||||
|
||||
RageFileObj *Copy( RageFile &p ) const
|
||||
{
|
||||
return new RageFileObjZipStored( zip, info, p );
|
||||
}
|
||||
};
|
||||
|
||||
class ZipFilenameDB: public FilenameDB
|
||||
@@ -93,34 +164,268 @@ public:
|
||||
RageFileDriverZip::RageFileDriverZip( CString path ):
|
||||
RageFileDriver( new ZipFilenameDB )
|
||||
{
|
||||
static bool initted = false;
|
||||
if( !initted )
|
||||
{
|
||||
initted = true;
|
||||
zzip_init_io( &ZzipCallbacks, 0 );
|
||||
if( !zip.Open(path) )
|
||||
RageException::Throw( "Couldn't open %s: %s", path.c_str(), zip.GetError().c_str() );
|
||||
|
||||
ZzipCallbacks.fd.open = OggRageFile_open;
|
||||
ZzipCallbacks.fd.close = OggRageFile_close;
|
||||
ZzipCallbacks.fd.read = OggRageFile_read;
|
||||
ZzipCallbacks.fd.seeks = OggRageFile_seeks;
|
||||
ZzipCallbacks.fd.filesize = OggRageFile_filesize;
|
||||
ZzipCallbacks.fd.write = OggRageFile_write;
|
||||
ParseZipfile();
|
||||
}
|
||||
|
||||
#define ECREC_SIZE 18
|
||||
|
||||
static CString central_hdr_sig = "\x50\x4B\x01\x02";
|
||||
static CString local_hdr_sig = "\x50\x4B\x03\x04";
|
||||
static CString end_central_sig = "\x50\x4B\x05\x06";
|
||||
|
||||
|
||||
/* XXX */
|
||||
static unsigned short makeword(const unsigned char *b)
|
||||
{
|
||||
return (unsigned short)((b[1] << 8) | b[0]);
|
||||
}
|
||||
|
||||
|
||||
static unsigned long makelong(const unsigned char *sig)
|
||||
{
|
||||
return (((unsigned long)sig[3]) << 24)
|
||||
+ (((unsigned long)sig[2]) << 16)
|
||||
+ (((unsigned long)sig[1]) << 8)
|
||||
+ ((unsigned long)sig[0]);
|
||||
}
|
||||
|
||||
void RageFileDriverZip::ReadEndCentralRecord( RageFile &zip, end_central_dir_record &ec )
|
||||
{
|
||||
const int OrigPos = zip.Tell();
|
||||
typedef unsigned char ec_byte_rec[ ECREC_SIZE+4 ];
|
||||
#define NUMBER_THIS_DISK 4
|
||||
#define NUM_DISK_WITH_START_CENTRAL_DIR 6
|
||||
#define NUM_ENTRIES_CENTRL_DIR_THS_DISK 8
|
||||
#define TOTAL_ENTRIES_CENTRAL_DIR 10
|
||||
#define SIZE_CENTRAL_DIRECTORY 12
|
||||
#define OFFSET_START_CENTRAL_DIRECTORY 16
|
||||
#define ZIPFILE_COMMENT_LENGTH 20
|
||||
|
||||
ec_byte_rec byterec;
|
||||
const int got = zip.Read( byterec, ECREC_SIZE+4 );
|
||||
if( got == -1 )
|
||||
RageException::Throw( "Couldn't open %s: %s", zip.GetPath().c_str(), zip.GetError().c_str() );
|
||||
if ( got != ECREC_SIZE+4 )
|
||||
RageException::Throw( "%s: unexpected EOF", zip.GetPath().c_str() );
|
||||
|
||||
ec.number_this_disk = makeword(&byterec[NUMBER_THIS_DISK]);
|
||||
ec.num_disk_start_cdir = makeword(&byterec[NUM_DISK_WITH_START_CENTRAL_DIR]);
|
||||
ec.num_entries_centrl_dir_ths_disk = makeword(&byterec[NUM_ENTRIES_CENTRL_DIR_THS_DISK]);
|
||||
ec.total_entries_central_dir = makeword(&byterec[TOTAL_ENTRIES_CENTRAL_DIR]);
|
||||
ec.size_central_directory = makelong(&byterec[SIZE_CENTRAL_DIRECTORY]);
|
||||
ec.offset_start_central_directory = makelong(&byterec[OFFSET_START_CENTRAL_DIRECTORY]);
|
||||
ec.zipfile_comment_length = makeword(&byterec[ZIPFILE_COMMENT_LENGTH]);
|
||||
|
||||
const int expect_ecrec_offset = ec.offset_start_central_directory + ec.size_central_directory;
|
||||
if( expect_ecrec_offset > OrigPos )
|
||||
RageException::Throw( "Couldn't open %s: missing %ld bytes in zipfile", zip.GetPath().c_str(),
|
||||
expect_ecrec_offset - OrigPos );
|
||||
}
|
||||
|
||||
void RageFileDriverZip::ParseZipfile()
|
||||
{
|
||||
/* Look for the end-central record. */
|
||||
const int searchlen = min( zip.GetFileSize(), 66000 );
|
||||
const int Size = zip.GetFileSize();
|
||||
int realpos = Size;
|
||||
|
||||
/* 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. */
|
||||
|
||||
int real_ecrec_offset = -1;
|
||||
|
||||
char tmp[INBUFSIZE];
|
||||
int tmp_used = 0;
|
||||
while( real_ecrec_offset == -1 && realpos >= Size-searchlen )
|
||||
{
|
||||
realpos -= INBUFSIZE;
|
||||
zip.Seek( max( 0, realpos ) );
|
||||
int got = zip.Read( tmp+tmp_used, sizeof(tmp)-tmp_used );
|
||||
if( got == -1 )
|
||||
RageException::Throw( "Couldn't open %s: %s", zip.GetPath().c_str(), zip.GetError().c_str() );
|
||||
if( got == 0 )
|
||||
break; /* fall through and fail */
|
||||
got += tmp_used;
|
||||
|
||||
/* 'P' must be at least (ECREC_SIZE+4) bytes from end of zipfile */
|
||||
for( int pos = got-(ECREC_SIZE+4); real_ecrec_offset == -1 && pos >= 0; --pos )
|
||||
{
|
||||
const char *p = tmp+pos;
|
||||
if ( *p != (unsigned char)0x50 ) /* ASCII 'P' */
|
||||
continue;
|
||||
|
||||
if( strncmp((char *)p, end_central_sig, 4))
|
||||
continue;
|
||||
real_ecrec_offset = realpos + pos;
|
||||
}
|
||||
|
||||
/* sig may span block boundary: */
|
||||
memcpy((char *)tmp+INBUFSIZE-3, (char *)tmp, 3);
|
||||
tmp_used = 3;
|
||||
}
|
||||
|
||||
zzip_error_t err;
|
||||
dir = zzip_dir_open_ext_io( path, &err, NULL, NULL ); //&ZzipCallbacks.fd );
|
||||
if( dir == NULL )
|
||||
RageException::Throw( "Couldn't open %s: %s", path.c_str(), zzip_strerror(err) );
|
||||
if( real_ecrec_offset == -1 )
|
||||
RageException::Throw( "Couldn't open %s: End-of-central-directory signature not found", zip.GetPath().c_str() );
|
||||
|
||||
while( ZZIP_DIRENT *ent = zzip_readdir(dir) )
|
||||
FDB->AddFile( ent->d_name, ent->st_size, 0 );
|
||||
zip.Seek( real_ecrec_offset );
|
||||
|
||||
zzip_rewinddir( dir );
|
||||
/* Get the end-central data. */
|
||||
end_central_dir_record ec;
|
||||
ReadEndCentralRecord( zip, ec );
|
||||
|
||||
bool something = ec.number_this_disk == 1 && ec.num_disk_start_cdir == 1;
|
||||
if (!something && ec.number_this_disk != 0)
|
||||
RageException::Throw( "Couldn't open %s: zipfile is part of multi-disk archive", zip.GetPath().c_str() );
|
||||
|
||||
/* Seek to where the start of central directory should be, and make
|
||||
* sure it's there. */
|
||||
zip.Seek( ec.offset_start_central_directory );
|
||||
|
||||
CString sig;
|
||||
int got = zip.Read( sig, 4 );
|
||||
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() );
|
||||
|
||||
zip.Seek( ec.offset_start_central_directory );
|
||||
|
||||
bool endsig_found = false;
|
||||
|
||||
/* Loop through files in central directory. */
|
||||
while(1)
|
||||
{
|
||||
CString sig;
|
||||
if ( zip.Read( sig, 4 ) != 4 )
|
||||
break;
|
||||
|
||||
/* Is it a new entry? */
|
||||
if( sig != central_hdr_sig )
|
||||
{
|
||||
/* Not a new central directory entry. Is the number of processed entries
|
||||
* compatible with the number of entries as stored in the end_central record? */
|
||||
if( Files.size() == (unsigned)ec.total_entries_central_dir )
|
||||
{
|
||||
/* yes, so look if we ARE back at the end_central record */
|
||||
endsig_found = ( sig == end_central_sig );
|
||||
} else {
|
||||
LOG->Warn( "%s: expected central file header signature not found", zip.GetPath().c_str() );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
FileInfo info;
|
||||
info.data_offset = -1;
|
||||
int got = ProcessCdirFileHdr(zip, info);
|
||||
if( got == -1 ) /* error */
|
||||
break;
|
||||
if( got == 0 ) /* skip */
|
||||
continue;
|
||||
|
||||
zip.SeekCur( info.extra_field_length );
|
||||
|
||||
FileInfo *pInfo = new FileInfo(info);
|
||||
Files.push_back( pInfo );
|
||||
FDB->AddFile( pInfo->fn, pInfo->uncompr_size, pInfo->crc, pInfo );
|
||||
}
|
||||
}
|
||||
|
||||
#define CREC_SIZE 42
|
||||
typedef unsigned char cdir_byte_hdr[ CREC_SIZE ];
|
||||
#define C_VERSION_MADE_BY_0 0
|
||||
#define C_VERSION_MADE_BY_1 1
|
||||
#define C_VERSION_NEEDED_TO_EXTRACT_0 2
|
||||
#define C_VERSION_NEEDED_TO_EXTRACT_1 3
|
||||
#define C_GENERAL_PURPOSE_BIT_FLAG 4
|
||||
#define C_COMPRESSION_METHOD 6
|
||||
#define C_LAST_MOD_DOS_DATETIME 8
|
||||
#define C_CRC32 12
|
||||
#define C_COMPRESSED_SIZE 16
|
||||
#define C_UNCOMPRESSED_SIZE 20
|
||||
#define C_FILENAME_LENGTH 24
|
||||
#define C_EXTRA_FIELD_LENGTH 26
|
||||
#define C_FILE_COMMENT_LENGTH 28
|
||||
#define C_DISK_NUMBER_START 30
|
||||
#define C_INTERNAL_FILE_ATTRIBUTES 32
|
||||
#define C_EXTERNAL_FILE_ATTRIBUTES 34
|
||||
#define C_RELATIVE_OFFSET_LOCAL_HEADER 38
|
||||
int RageFileDriverZip::ProcessCdirFileHdr( RageFile &zip, FileInfo &info )
|
||||
{
|
||||
/* Read the next central directory entry and do any necessary machine-type
|
||||
* conversions (byte ordering, structure padding compensation--do so by
|
||||
* copying the data from the array into which it was read (byterec) to the
|
||||
* usable struct. */
|
||||
cdir_byte_hdr byterec;
|
||||
int got = zip.Read( (char *)byterec, CREC_SIZE );
|
||||
if ( got == -1 )
|
||||
RageException::Throw( "Couldn't open %s: %s", zip.GetPath().c_str(), zip.GetError().c_str() );
|
||||
if ( got != CREC_SIZE )
|
||||
{
|
||||
LOG->Warn( "%s: unexpected EOF", zip.GetPath().c_str() );
|
||||
return -1;
|
||||
}
|
||||
|
||||
// crec.version_made_by[0] = byterec[C_VERSION_MADE_BY_0];
|
||||
// crec.version_made_by[1] = byterec[C_VERSION_MADE_BY_1];
|
||||
const int version_needed_to_extract = byterec[C_VERSION_NEEDED_TO_EXTRACT_0];
|
||||
if( version_needed_to_extract > 20 ) /* compatible with PKUNZIP 2.0 */
|
||||
{
|
||||
LOG->Warn( "File \"%s\" in \"%s\" uses unsupported ZIP version %i.%i",
|
||||
info.fn.c_str(), zip.GetRealPath().c_str(),
|
||||
version_needed_to_extract / 10, version_needed_to_extract % 10 );
|
||||
return 0;
|
||||
}
|
||||
|
||||
const int general_purpose_bit_flag = makeword(&byterec[C_GENERAL_PURPOSE_BIT_FLAG]);
|
||||
if( general_purpose_bit_flag & 1 )
|
||||
{
|
||||
LOG->Warn( "Skipped encrypted \"%s\" in \"%s\"",
|
||||
info.fn.c_str(), zip.GetRealPath().c_str() );
|
||||
return 0;
|
||||
}
|
||||
|
||||
info.compression_method = makeword(&byterec[C_COMPRESSION_METHOD]);
|
||||
// int last_mod_dos_datetime = makelong(&byterec[C_LAST_MOD_DOS_DATETIME]);
|
||||
info.crc = makelong(&byterec[C_CRC32]);
|
||||
info.compr_size = makelong(&byterec[C_COMPRESSED_SIZE]);
|
||||
info.uncompr_size = makelong(&byterec[C_UNCOMPRESSED_SIZE]);
|
||||
info.filename_length = makeword(&byterec[C_FILENAME_LENGTH]);
|
||||
info.extra_field_length = makeword(&byterec[C_EXTRA_FIELD_LENGTH]);
|
||||
// int file_comment_length = makeword(&byterec[C_FILE_COMMENT_LENGTH]);
|
||||
info.diskstart = makeword(&byterec[C_DISK_NUMBER_START]);
|
||||
// int internal_file_attributes = makeword(&byterec[C_INTERNAL_FILE_ATTRIBUTES]);
|
||||
int external_file_attributes = makelong(&byterec[C_EXTERNAL_FILE_ATTRIBUTES]); /* LONG, not word! */
|
||||
info.offset = makelong(&byterec[C_RELATIVE_OFFSET_LOCAL_HEADER]);
|
||||
|
||||
/* Skip directories. */
|
||||
if( external_file_attributes & 0x08 )
|
||||
return 0;
|
||||
|
||||
got = zip.Read( info.fn, info.filename_length );
|
||||
if( got == -1 )
|
||||
RageException::Throw( "Couldn't open %s: %s", zip.GetPath().c_str(), zip.GetError().c_str() );
|
||||
if( got != info.filename_length )
|
||||
{
|
||||
LOG->Warn( "%s: bad filename length %i", zip.GetPath().c_str(), info.filename_length );
|
||||
return 0;
|
||||
}
|
||||
|
||||
if( info.compression_method != STORED && info.compression_method != DEFLATED )
|
||||
{
|
||||
LOG->Warn( "File \"%s\" in \"%s\" uses unsupported compression method %i",
|
||||
info.fn.c_str(), zip.GetRealPath().c_str(), info.compression_method );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
RageFileDriverZip::~RageFileDriverZip()
|
||||
{
|
||||
zzip_dir_close( dir );
|
||||
for( unsigned i = 0; i < Files.size(); ++i )
|
||||
delete Files[i];
|
||||
}
|
||||
|
||||
RageFileObj *RageFileDriverZip::Open( const CString &path, RageFile::OpenMode mode, RageFile &p, int &err )
|
||||
@@ -131,24 +436,59 @@ RageFileObj *RageFileDriverZip::Open( const CString &path, RageFile::OpenMode mo
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LockMut( lock );
|
||||
|
||||
ZZIP_FILE *fp = zzip_file_open( dir, path, ZZIP_CASELESS|ZZIP_ONLYZIP );
|
||||
|
||||
if( !fp )
|
||||
File *f = FDB->GetFile( path );
|
||||
if( f == NULL )
|
||||
{
|
||||
err = zzip_errno( zzip_error(dir) );
|
||||
err = ENOENT;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return new RageFileObjZip( fp, this, p );
|
||||
}
|
||||
/* If we havn't figured out the offset to the real data yet, do so now. */
|
||||
FileInfo *info = (FileInfo *) f->priv;
|
||||
if( info->data_offset == -1 )
|
||||
{
|
||||
zip.Seek( info->offset );
|
||||
|
||||
int RageFileDriverZip::GetFileModTime( const CString &sPath )
|
||||
{
|
||||
/* XXX: we can use CRC instead */
|
||||
/* Don't spend time looking up the real mod time; use the mtime of the ZIP itself. */
|
||||
return FDB->GetFileModTime( sPath );
|
||||
/* Should be in proper position now, so check for sig. */
|
||||
CString sig;
|
||||
int got = zip.Read( sig, 4 );
|
||||
if( got == -1 )
|
||||
RageException::Throw( "Read error in %s: %s", zip.GetPath().c_str(), zip.GetError().c_str() );
|
||||
if( got != 4 )
|
||||
RageException::Throw( "%s: unexpected EOF", zip.GetPath().c_str() );
|
||||
|
||||
if( sig != local_hdr_sig )
|
||||
RageException::Throw( "%s: bad zipfile offset", zip.GetPath().c_str() );
|
||||
|
||||
#define LREC_SIZE 26 /* lengths of local file headers, central */
|
||||
#define L_FILENAME_LENGTH 22
|
||||
#define L_EXTRA_FIELD_LENGTH 24
|
||||
unsigned char byterec[LREC_SIZE];
|
||||
got = zip.Read( (char *)byterec, LREC_SIZE );
|
||||
if( got == -1 )
|
||||
RageException::Throw( "Read error in %s: %s", zip.GetPath().c_str(), zip.GetError().c_str() );
|
||||
if( got != LREC_SIZE )
|
||||
RageException::Throw( "%s: unexpected EOF", zip.GetPath().c_str() );
|
||||
|
||||
const int filename_length = makeword(&byterec[L_FILENAME_LENGTH]);
|
||||
const int extra_field_length = makeword(&byterec[L_EXTRA_FIELD_LENGTH]);
|
||||
info->data_offset = zip.Tell() + filename_length + extra_field_length;
|
||||
}
|
||||
|
||||
zip.Seek( info->data_offset );
|
||||
|
||||
switch( info->compression_method )
|
||||
{
|
||||
case STORED:
|
||||
return new RageFileObjZipStored( zip, *info, p );
|
||||
case DEFLATED:
|
||||
return new RageFileObjZipDeflated( zip, *info, p );
|
||||
default:
|
||||
/* unknown compression method */
|
||||
ASSERT( 0 );
|
||||
err = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* NOP for now. This could check to see if the ZIP's mtime has changed, and reload. */
|
||||
@@ -157,64 +497,175 @@ void RageFileDriverZip::FlushDirCache( const CString &sPath )
|
||||
|
||||
}
|
||||
|
||||
RageFileObjZip::RageFileObjZip( ZZIP_FILE *fp_, RageFileDriverZip *dir_, RageFile &p ):
|
||||
RageFileObj( p )
|
||||
/* We make a copy of the RageFile: multiple files may read from the same ZIP at once;
|
||||
* this way, we don't have to keep seeking around. */
|
||||
RageFileObjZipDeflated::RageFileObjZipDeflated( const RageFile &f, const FileInfo &info_, RageFile &p ):
|
||||
RageFileObj( p ),
|
||||
zip( f ),
|
||||
info(info_)
|
||||
{
|
||||
fp=fp_;
|
||||
dir=dir_;
|
||||
decomp_buf_avail = 0;
|
||||
|
||||
dstrm.zalloc = Z_NULL;
|
||||
dstrm.zfree = Z_NULL;
|
||||
|
||||
int err = inflateInit2( &dstrm, -MAX_WBITS );
|
||||
if( err == Z_MEM_ERROR )
|
||||
RageException::Throw( "inflateInit2( %i ): out of memory", -MAX_WBITS );
|
||||
if( err != Z_OK )
|
||||
LOG->Trace( "Huh? inflateInit2() err = %i", err );
|
||||
|
||||
decomp_buf_ptr = decomp_buf;
|
||||
CFilePos = UFilePos = 0;
|
||||
}
|
||||
|
||||
RageFileObjZip::~RageFileObjZip()
|
||||
RageFileObjZipDeflated::RageFileObjZipDeflated( const RageFileObjZipDeflated &cpy, RageFile &p ):
|
||||
RageFileObj( p ),
|
||||
zip( cpy.zip ),
|
||||
info( cpy.info )
|
||||
{
|
||||
LockMut( dir->lock );
|
||||
zzip_file_close( fp );
|
||||
/* XXX completely untested */
|
||||
/* Copy the entire decode state. */
|
||||
inflateCopy( &dstrm, const_cast<z_streamp>(&cpy.dstrm) );
|
||||
|
||||
decomp_buf_ptr = decomp_buf + (cpy.decomp_buf_ptr - cpy.decomp_buf);
|
||||
decomp_buf_avail = cpy.decomp_buf_avail;
|
||||
CFilePos = cpy.CFilePos;
|
||||
UFilePos = cpy.UFilePos;
|
||||
}
|
||||
|
||||
int RageFileObjZip::Read( void *buf, size_t bytes )
|
||||
{
|
||||
LockMut( dir->lock );
|
||||
|
||||
const int ret = zzip_file_read( fp, (char *) buf, bytes );
|
||||
if( ret == -1 )
|
||||
RageFileObjZipDeflated::~RageFileObjZipDeflated()
|
||||
{
|
||||
int err = inflateReset( &dstrm );
|
||||
if( err != Z_OK )
|
||||
LOG->Trace( "Huh? inflateReset() err = %i", err );
|
||||
}
|
||||
|
||||
int RageFileObjZipDeflated::Read( void *buf, size_t bytes )
|
||||
{
|
||||
bool done=false;
|
||||
int ret = 0;
|
||||
while( bytes && CFilePos < (int) info.compr_size && !done )
|
||||
{
|
||||
SetError( zzip_strerror_of(dir->dir) );
|
||||
return -1;
|
||||
if ( !decomp_buf_avail )
|
||||
{
|
||||
decomp_buf_ptr = decomp_buf;
|
||||
decomp_buf_avail = 0;
|
||||
int got = zip.Read( decomp_buf, sizeof(decomp_buf) );
|
||||
if( got == -1 )
|
||||
{
|
||||
SetError( zip.GetError() );
|
||||
return -1;
|
||||
}
|
||||
if( got == 0 )
|
||||
break;
|
||||
|
||||
decomp_buf_avail = got;
|
||||
}
|
||||
|
||||
dstrm.next_in = (Bytef *) decomp_buf_ptr;
|
||||
dstrm.avail_in = decomp_buf_avail;
|
||||
dstrm.next_out = (Bytef *) buf;
|
||||
dstrm.avail_out = bytes;
|
||||
|
||||
|
||||
int err = inflate(&dstrm, Z_PARTIAL_FLUSH);
|
||||
switch( err )
|
||||
{
|
||||
case Z_DATA_ERROR:
|
||||
SetError( "Data error" );
|
||||
return -1;
|
||||
case Z_MEM_ERROR:
|
||||
SetError( "out of memory" );
|
||||
return -1;
|
||||
case Z_STREAM_END:
|
||||
done = true;
|
||||
break;
|
||||
case Z_OK:
|
||||
break;
|
||||
default:
|
||||
LOG->Trace( "Huh? inflate err %i", err );
|
||||
}
|
||||
|
||||
const int used = (char *)dstrm.next_in - decomp_buf_ptr;
|
||||
CFilePos += used;
|
||||
decomp_buf_ptr += used;
|
||||
decomp_buf_avail -= used;
|
||||
|
||||
const int got = (char *)dstrm.next_out - (char *)buf;
|
||||
UFilePos += got;
|
||||
ret += got;
|
||||
buf = (char *)buf + got;
|
||||
bytes -= got;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int RageFileObjZip::Write(const void *buffer, size_t bytes)
|
||||
void RageFileObjZipDeflated::Rewind()
|
||||
{
|
||||
SetError( "Not implemented" );
|
||||
return -1;
|
||||
inflateReset( &dstrm );
|
||||
decomp_buf_ptr = decomp_buf;
|
||||
decomp_buf_avail = 0;
|
||||
|
||||
zip.Seek( info.data_offset );
|
||||
CFilePos = 0;
|
||||
UFilePos = 0;
|
||||
}
|
||||
|
||||
void RageFileObjZip::Rewind()
|
||||
int RageFileObjZipDeflated::Seek( int offset )
|
||||
{
|
||||
LockMut( dir->lock );
|
||||
zzip_rewind( fp );
|
||||
return RageFileObj::Seek( offset ); /* XXX */
|
||||
/* if( offset < this->UFilePos )
|
||||
{
|
||||
offset += UFilePos;
|
||||
Rewind();
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
int RageFileObjZip::Seek( int offset )
|
||||
/* */
|
||||
|
||||
RageFileObjZipStored::RageFileObjZipStored( const RageFile &f, const FileInfo &info_, RageFile &p ):
|
||||
RageFileObj( p ),
|
||||
zip( f ),
|
||||
info(info_)
|
||||
{
|
||||
LockMut( dir->lock );
|
||||
return zzip_seek( fp, offset, SEEK_SET );
|
||||
FilePos = 0;
|
||||
}
|
||||
|
||||
int RageFileObjZip::SeekCur( int offset )
|
||||
int RageFileObjZipStored::Read( void *buf, size_t bytes )
|
||||
{
|
||||
LockMut( dir->lock );
|
||||
return zzip_seek( fp, offset, SEEK_CUR );
|
||||
const int bytes_left = info.compr_size-this->FilePos;
|
||||
const int got = zip.Read( buf, min( (int) bytes, bytes_left ) );
|
||||
if( got == -1 )
|
||||
{
|
||||
SetError( zip.GetError() );
|
||||
return -1;
|
||||
}
|
||||
|
||||
FilePos += got;
|
||||
|
||||
return got;
|
||||
}
|
||||
|
||||
int RageFileObjZip::GetFileSize()
|
||||
{
|
||||
LockMut( dir->lock );
|
||||
|
||||
/* XXX very slow */
|
||||
const int OldPos = zzip_tell( fp );
|
||||
int ret = zzip_seek( fp, 0, SEEK_END );
|
||||
zzip_seek( fp, OldPos, SEEK_SET );
|
||||
int RageFileObjZipStored::Seek( int offset )
|
||||
{
|
||||
ASSERT( offset >= 0 );
|
||||
offset = min( (unsigned) offset, info.compr_size );
|
||||
|
||||
int ret = zip.Seek( info.data_offset + offset );
|
||||
if( ret == -1 )
|
||||
{
|
||||
SetError( zip.GetError() );
|
||||
return -1;
|
||||
}
|
||||
ret -= info.data_offset;
|
||||
ASSERT( ret >= 0 );
|
||||
FilePos = ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user