Files
itgmania212121/stepmania/src/RageFileDriverZip.cpp
T

709 lines
22 KiB
C++

/*
* 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
*/
#include "global.h"
#include "RageFileDriverZip.h"
#include "RageLog.h"
#include "RageUtil.h"
#include "RageUtil_FileDB.h"
#include <cerrno>
#if defined(_WINDOWS) || defined(_XBOX)
#include "zlib/zlib.h"
#pragma comment(lib, "zlib/zdll.lib")
#elif defined(DARWIN)
/* Since crypto++ was added to the repository, <zlib.h> includes the zlib.h
* in there rather than the correct system one. I don't know why it would do
* this since crypto51 is not being listed as one of the include directories.
* I've never run into this problem before and looking at the command line
* used to compile RageFileDriverZip.o, I have no idea how it's happening.
* --Steve
*/
#include "/usr/include/zlib.h"
#else
#include <zlib.h>
#endif
#define INBUFSIZE 1024*4
static struct FileDriverEntry_ZIP: public FileDriverEntry
{
FileDriverEntry_ZIP(): FileDriverEntry( "ZIP" ) { }
RageFileDriver *Create( CString Root ) const { return new RageFileDriverZip( Root ); }
} const g_RegisterDriver;
struct FileInfo
{
CString fn;
long offset;
long data_offset;
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
{
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;
};
#define STORED 0
#define DEFLATED 8
class RageFileObjZipDeflated: public RageFileObj
{
private:
int m_iUncompressedSize;
RageFileBasic *m_pFile;
int m_iFilePos;
z_stream dstrm;
char decomp_buf[INBUFSIZE], *decomp_buf_ptr;
int decomp_buf_avail;
public:
/* pFile will be freed. */
RageFileObjZipDeflated( RageFileBasic *pFile, int iUncompressedSize );
RageFileObjZipDeflated( const RageFileObjZipDeflated &cpy );
~RageFileObjZipDeflated();
int ReadInternal( void *pBuffer, size_t iBytes );
int WriteInternal( const void *pBuffer, size_t iBytes ) { SetError( "Not implemented" ); return -1; }
int SeekInternal( int iOffset );
int GetFileSize() const { return m_iUncompressedSize; }
RageFileBasic *Copy() const
{
RageException::Throw( "Loading ZIPs from deflated ZIPs is currently disabled; see RageFileObjZipDeflated" );
// return new RageFileObjZipDeflated( *this, p );
}
};
class RageFileObjZipStored: public RageFileObj
{
private:
RageFileBasic *m_pFile;
int m_iFilePos;
int m_iOffset, m_iFileSize;
public:
/* pFile will be freed. */
RageFileObjZipStored( RageFileBasic *pFile, int iOffset, int iFileSize );
~RageFileObjZipStored();
int ReadInternal( void *pBuffer, size_t iBytes );
int WriteInternal( const void *pBuffer, size_t iBytes ) { SetError( "Not implemented" ); return -1; }
int SeekInternal( int iOffset );
int GetFileSize() const { return m_iFileSize; }
RageFileBasic *Copy() const
{
RageFileObjZipStored *pRet = new RageFileObjZipStored( m_pFile->Copy(), m_iOffset, m_iFileSize );
pRet->m_iFilePos = m_iFilePos;
return pRet;
}
};
RageFileDriverZip::RageFileDriverZip( CString path ):
RageFileDriver( new NullFilenameDB )
{
if( !zip.Open(path) )
RageException::Throw( "Couldn't open %s: %s", path.c_str(), zip.GetError().c_str() );
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 > 0 && realpos >= Size-searchlen )
{
realpos -= INBUFSIZE;
realpos = max( 0, realpos );
zip.Seek( 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;
}
if( real_ecrec_offset == -1 )
RageException::Throw( "Couldn't open %s: End-of-central-directory signature not found", zip.GetPath().c_str() );
zip.Seek( real_ecrec_offset );
/* 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 );
/* 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 )
{
/* Are we at the end_central record? */
if( sig != end_central_sig )
LOG->Warn( "%s: expected end central file header signature not found", zip.GetPath().c_str() );
} 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.Seek( zip.Tell() + 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 %li", 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()
{
for( unsigned i = 0; i < Files.size(); ++i )
delete Files[i];
}
RageFileBasic *RageFileDriverZip::Open( const CString &path, int mode, int &err )
{
if( mode == RageFile::WRITE )
{
err = ERROR_WRITING_NOT_SUPPORTED;
return NULL;
}
FileInfo *info = (FileInfo *) FDB->GetFilePriv( path );
if( info == NULL )
{
err = ENOENT;
return NULL;
}
/* If we havn't figured out the offset to the real data yet, do so now. */
if( info->data_offset == -1 )
{
zip.Seek( info->offset );
/* 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 );
RageFileBasic *pFile = new RageFileObjZipStored( zip.Copy(), info->data_offset, info->compr_size );
switch( info->compression_method )
{
case STORED:
return pFile;
case DEFLATED:
return new RageFileObjZipDeflated( pFile, info->uncompr_size );
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. */
void RageFileDriverZip::FlushDirCache( const CString &sPath )
{
}
RageFileObjZipDeflated::RageFileObjZipDeflated( RageFileBasic *pFile, int iUncompressedSize )
{
m_pFile = pFile;
decomp_buf_avail = 0;
m_iUncompressedSize = iUncompressedSize;
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;
m_iFilePos = 0;
}
RageFileObjZipDeflated::RageFileObjZipDeflated( const RageFileObjZipDeflated &cpy ):
RageFileObj( cpy )
{
/* XXX completely untested */
/* Copy the entire decode state. */
/* inflateInit2 isn't widespread yet */
ASSERT( 0 );
/*
m_pFile = cpy.m_pFile->Copy();
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;
m_iFilePos = cpy.m_iFilePos;
*/
}
RageFileObjZipDeflated::~RageFileObjZipDeflated()
{
delete m_pFile;
int err = inflateEnd( &dstrm );
if( err != Z_OK )
LOG->Trace( "Huh? inflateEnd() err = %i", err );
}
int RageFileObjZipDeflated::ReadInternal( void *buf, size_t bytes )
{
bool done=false;
int ret = 0;
while( bytes && !done )
{
if ( !decomp_buf_avail )
{
decomp_buf_ptr = decomp_buf;
decomp_buf_avail = 0;
int got = m_pFile->Read( decomp_buf, sizeof(decomp_buf) );
if( got == -1 )
{
SetError( m_pFile->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;
decomp_buf_ptr += used;
decomp_buf_avail -= used;
const int got = (char *)dstrm.next_out - (char *)buf;
m_iFilePos += got;
ret += got;
buf = (char *)buf + got;
bytes -= got;
}
return ret;
}
int RageFileObjZipDeflated::SeekInternal( int iPos )
{
/* Optimization: if offset is the end of the file, it's a lseek(0,SEEK_END). Don't
* decode anything. */
if( iPos >= m_iUncompressedSize )
{
m_iFilePos = m_iUncompressedSize;
m_pFile->Seek( m_pFile->GetFileSize() );
decomp_buf_ptr = decomp_buf;
decomp_buf_avail = 0;
inflateReset( &dstrm );
return m_iUncompressedSize;
}
if( iPos < m_iFilePos )
{
inflateReset( &dstrm );
decomp_buf_ptr = decomp_buf;
decomp_buf_avail = 0;
m_pFile->Seek( 0 );
m_iFilePos = 0;
}
int iOffset = iPos - m_iFilePos;
/* Can this be optimized? */
char buf[1024*4];
while( iOffset )
{
int got = ReadInternal( buf, min( (int) sizeof(buf), iOffset ) );
if( got == -1 )
return -1;
if( got == 0 )
break;
iOffset -= got;
}
return m_iFilePos;
}
RageFileObjZipStored::RageFileObjZipStored( RageFileBasic *pFile, int iOffset, int iFileSize )
{
m_pFile = pFile;
m_iOffset = iOffset;
m_iFileSize = iFileSize;
m_iFilePos = 0;
}
RageFileObjZipStored::~RageFileObjZipStored()
{
delete m_pFile;
}
int RageFileObjZipStored::ReadInternal( void *buf, size_t bytes )
{
/* Make sure we're reading from the right place. We might have been constructed
* with a file not pointing to iOffset. */
m_pFile->Seek( m_iFilePos+m_iOffset );
const int bytes_left = m_iFileSize-this->m_iFilePos;
const int got = m_pFile->Read( buf, min( (int) bytes, bytes_left ) );
if( got == -1 )
{
SetError( m_pFile->GetError() );
return -1;
}
m_iFilePos += got;
return got;
}
int RageFileObjZipStored::SeekInternal( int offset )
{
ASSERT( offset >= 0 );
offset = min( offset, m_iFileSize );
int ret = m_pFile->Seek( m_iOffset + offset );
if( ret == -1 )
{
SetError( m_pFile->GetError() );
return -1;
}
ret -= m_iOffset;
ASSERT( ret >= 0 );
m_iFilePos = ret;
return ret;
}
/*
* Copyright (c) 1990-2002 Info-ZIP. All rights reserved.
* Copyright (c) 2003-2004 Glenn Maynard. 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.
*/