--- a/misc/libphysfs/archiver_zip.c Mon Apr 10 09:05:16 2017 -0400
+++ b/misc/libphysfs/archiver_zip.c Mon Apr 10 12:06:43 2017 -0400
@@ -15,12 +15,7 @@
#include <errno.h>
#include <time.h>
-#define USE_MINIZ 1
-#if USE_MINIZ
#include "physfs_miniz.h"
-#else
-#include <zlib.h>
-#endif
/*
* A buffer of ZIP_READBUFSIZE is allocated for each compressed file opened,
@@ -51,6 +46,7 @@
ZIP_UNRESOLVED_SYMLINK,
ZIP_RESOLVING,
ZIP_RESOLVED,
+ ZIP_DIRECTORY,
ZIP_BROKEN_FILE,
ZIP_BROKEN_SYMLINK
} ZipResolveType;
@@ -67,11 +63,16 @@
PHYSFS_uint64 offset; /* offset of data in archive */
PHYSFS_uint16 version; /* version made by */
PHYSFS_uint16 version_needed; /* version needed to extract */
+ PHYSFS_uint16 general_bits; /* general purpose bits */
PHYSFS_uint16 compression_method; /* compression method */
PHYSFS_uint32 crc; /* crc-32 */
PHYSFS_uint64 compressed_size; /* compressed size */
PHYSFS_uint64 uncompressed_size; /* uncompressed size */
PHYSFS_sint64 last_mod_time; /* last file mod time */
+ PHYSFS_uint32 dos_mod_time; /* original MS-DOS style mod time */
+ struct _ZIPentry *hashnext; /* next item in this hash bucket */
+ struct _ZIPentry *children; /* linked list of kids, if dir */
+ struct _ZIPentry *sibling; /* next item in same dir */
} ZIPentry;
/*
@@ -79,10 +80,12 @@
*/
typedef struct
{
- PHYSFS_Io *io;
- int zip64; /* non-zero if this is a Zip64 archive. */
- PHYSFS_uint64 entryCount; /* Number of files in ZIP. */
- ZIPentry *entries; /* info on all files in ZIP. */
+ PHYSFS_Io *io; /* the i/o interface for this archive. */
+ ZIPentry root; /* root of directory tree. */
+ ZIPentry **hash; /* all entries hashed for fast lookup. */
+ size_t hashBuckets; /* number of buckets in hash. */
+ int zip64; /* non-zero if this is a Zip64 archive. */
+ int has_crypto; /* non-zero if any entry uses encryption. */
} ZIPinfo;
/*
@@ -95,6 +98,8 @@
PHYSFS_uint32 compressed_position; /* offset in compressed data. */
PHYSFS_uint32 uncompressed_position; /* tell() position. */
PHYSFS_uint8 *buffer; /* decompression buffer. */
+ PHYSFS_uint32 crypto_keys[3]; /* for "traditional" crypto. */
+ PHYSFS_uint32 initial_crypto_keys[3]; /* for "traditional" crypto. */
z_stream stream; /* zlib stream state. */
} ZIPfileinfo;
@@ -115,6 +120,103 @@
#define UNIX_FILETYPE_MASK 0170000
#define UNIX_FILETYPE_SYMLINK 0120000
+#define ZIP_GENERAL_BITS_TRADITIONAL_CRYPTO (1 << 0)
+#define ZIP_GENERAL_BITS_IGNORE_LOCAL_HEADER (1 << 3)
+
+/* support for "traditional" PKWARE encryption. */
+static int zip_entry_is_tradional_crypto(const ZIPentry *entry)
+{
+ return (entry->general_bits & ZIP_GENERAL_BITS_TRADITIONAL_CRYPTO) != 0;
+} /* zip_entry_is_traditional_crypto */
+
+static int zip_entry_ignore_local_header(const ZIPentry *entry)
+{
+ return (entry->general_bits & ZIP_GENERAL_BITS_IGNORE_LOCAL_HEADER) != 0;
+} /* zip_entry_is_traditional_crypto */
+
+static PHYSFS_uint32 zip_crypto_crc32(const PHYSFS_uint32 crc, const PHYSFS_uint8 val)
+{
+ int i;
+ PHYSFS_uint32 xorval = (crc ^ ((PHYSFS_uint32) val)) & 0xFF;
+ for (i = 0; i < 8; i++)
+ xorval = ((xorval & 1) ? (0xEDB88320 ^ (xorval >> 1)) : (xorval >> 1));
+ return xorval ^ (crc >> 8);
+} /* zip_crc32 */
+
+static void zip_update_crypto_keys(PHYSFS_uint32 *keys, const PHYSFS_uint8 val)
+{
+ keys[0] = zip_crypto_crc32(keys[0], val);
+ keys[1] = keys[1] + (keys[0] & 0x000000FF);
+ keys[1] = (keys[1] * 134775813) + 1;
+ keys[2] = zip_crypto_crc32(keys[2], (PHYSFS_uint8) ((keys[1] >> 24) & 0xFF));
+} /* zip_update_crypto_keys */
+
+static PHYSFS_uint8 zip_decrypt_byte(const PHYSFS_uint32 *keys)
+{
+ const PHYSFS_uint16 tmp = keys[2] | 2;
+ return (PHYSFS_uint8) ((tmp * (tmp ^ 1)) >> 8);
+} /* zip_decrypt_byte */
+
+static PHYSFS_sint64 zip_read_decrypt(ZIPfileinfo *finfo, void *buf, PHYSFS_uint64 len)
+{
+ PHYSFS_Io *io = finfo->io;
+ const PHYSFS_sint64 br = io->read(io, buf, len);
+
+ /* Decompression the new data if necessary. */
+ if (zip_entry_is_tradional_crypto(finfo->entry) && (br > 0))
+ {
+ PHYSFS_uint32 *keys = finfo->crypto_keys;
+ PHYSFS_uint8 *ptr = (PHYSFS_uint8 *) buf;
+ PHYSFS_sint64 i;
+ for (i = 0; i < br; i++, ptr++)
+ {
+ const PHYSFS_uint8 ch = *ptr ^ zip_decrypt_byte(keys);
+ zip_update_crypto_keys(keys, ch);
+ *ptr = ch;
+ } /* for */
+ } /* if */
+
+ return br;
+} /* zip_read_decrypt */
+
+static int zip_prep_crypto_keys(ZIPfileinfo *finfo, const PHYSFS_uint8 *crypto_header, const PHYSFS_uint8 *password)
+{
+ /* It doesn't appear to be documented in PKWare's APPNOTE.TXT, but you
+ need to use a different byte in the header to verify the password
+ if general purpose bit 3 is set. Discovered this from Info-Zip.
+ That's what the (verifier) value is doing, below. */
+
+ PHYSFS_uint32 *keys = finfo->crypto_keys;
+ const ZIPentry *entry = finfo->entry;
+ const int usedate = zip_entry_ignore_local_header(entry);
+ const PHYSFS_uint8 verifier = (PHYSFS_uint8) ((usedate ? (entry->dos_mod_time >> 8) : (entry->crc >> 24)) & 0xFF);
+ PHYSFS_uint8 finalbyte = 0;
+ int i = 0;
+
+ /* initialize vector with defaults, then password, then header. */
+ keys[0] = 305419896;
+ keys[1] = 591751049;
+ keys[2] = 878082192;
+
+ while (*password)
+ zip_update_crypto_keys(keys, *(password++));
+
+ for (i = 0; i < 12; i++)
+ {
+ const PHYSFS_uint8 c = crypto_header[i] ^ zip_decrypt_byte(keys);
+ zip_update_crypto_keys(keys, c);
+ finalbyte = c;
+ } /* for */
+
+ /* you have a 1/256 chance of passing this test incorrectly. :/ */
+ if (finalbyte != verifier)
+ BAIL_MACRO(PHYSFS_ERR_BAD_PASSWORD, 0);
+
+ /* save the initial vector for seeking purposes. Not secure!! */
+ memcpy(finfo->initial_crypto_keys, finfo->crypto_keys, 12);
+ return 1;
+} /* zip_prep_crypto_keys */
+
/*
* Bridge physfs allocation functions to zlib's format...
@@ -163,10 +265,17 @@
*/
static int zlib_err(const int rc)
{
- __PHYSFS_setError(zlib_error_code(rc));
+ PHYSFS_setErrorCode(zlib_error_code(rc));
return rc;
} /* zlib_err */
+/*
+ * Hash a string for lookup an a ZIPinfo hashtable.
+ */
+static inline PHYSFS_uint32 zip_hash_string(const ZIPinfo *info, const char *s)
+{
+ return __PHYSFS_hashString(s, strlen(s)) % info->hashBuckets;
+} /* zip_hash_string */
/*
* Read an unsigned 64-bit int and swap to native byte order.
@@ -206,7 +315,6 @@
static PHYSFS_sint64 ZIP_read(PHYSFS_Io *_io, void *buf, PHYSFS_uint64 len)
{
ZIPfileinfo *finfo = (ZIPfileinfo *) _io->opaque;
- PHYSFS_Io *io = finfo->io;
ZIPentry *entry = finfo->entry;
PHYSFS_sint64 retval = 0;
PHYSFS_sint64 maxread = (PHYSFS_sint64) len;
@@ -219,7 +327,7 @@
BAIL_IF_MACRO(maxread == 0, ERRPASS, 0); /* quick rejection. */
if (entry->compression_method == COMPMETH_NONE)
- retval = io->read(io, buf, maxread);
+ retval = zip_read_decrypt(finfo, buf, maxread);
else
{
finfo->stream.next_out = buf;
@@ -240,7 +348,7 @@
if (br > ZIP_READBUFSIZE)
br = ZIP_READBUFSIZE;
- br = io->read(io, finfo->buffer, (PHYSFS_uint64) br);
+ br = zip_read_decrypt(finfo, finfo->buffer, (PHYSFS_uint64) br);
if (br <= 0)
break;
@@ -282,12 +390,13 @@
ZIPfileinfo *finfo = (ZIPfileinfo *) _io->opaque;
ZIPentry *entry = finfo->entry;
PHYSFS_Io *io = finfo->io;
+ const int encrypted = zip_entry_is_tradional_crypto(entry);
BAIL_IF_MACRO(offset > entry->uncompressed_size, PHYSFS_ERR_PAST_EOF, 0);
- if (entry->compression_method == COMPMETH_NONE)
+ if (!encrypted && (entry->compression_method == COMPMETH_NONE))
{
- const PHYSFS_sint64 newpos = offset + entry->offset;
+ PHYSFS_sint64 newpos = offset + entry->offset;
BAIL_IF_MACRO(!io->seek(io, newpos), ERRPASS, 0);
finfo->uncompressed_position = (PHYSFS_uint32) offset;
} /* if */
@@ -308,13 +417,16 @@
if (zlib_err(inflateInit2(&str, -MAX_WBITS)) != Z_OK)
return 0;
- if (!io->seek(io, entry->offset))
+ if (!io->seek(io, entry->offset + (encrypted ? 12 : 0)))
return 0;
inflateEnd(&finfo->stream);
inflateCopy(&finfo->stream, &str);
inflateEnd(&str);
finfo->uncompressed_position = finfo->compressed_position = 0;
+
+ if (encrypted)
+ memcpy(finfo->crypto_keys, finfo->initial_crypto_keys, 12);
} /* if */
while (finfo->uncompressed_position != offset)
@@ -435,7 +547,7 @@
int found = 0;
filelen = io->length(io);
- BAIL_IF_MACRO(filelen == -1, ERRPASS, 0);
+ BAIL_IF_MACRO(filelen == -1, ERRPASS, -1);
/*
* Jump to the end of the file and start reading backwards.
@@ -489,7 +601,7 @@
(buf[i + 3] == 0x06) )
{
found = 1; /* that's the signature! */
- break;
+ break;
} /* if */
} /* for */
@@ -537,71 +649,35 @@
} /* isZip */
-static void zip_free_entries(ZIPentry *entries, PHYSFS_uint64 max)
+/* Find the ZIPentry for a path in platform-independent notation. */
+static ZIPentry *zip_find_entry(ZIPinfo *info, const char *path)
{
- PHYSFS_uint64 i;
- for (i = 0; i < max; i++)
+ PHYSFS_uint32 hashval;
+ ZIPentry *prev = NULL;
+ ZIPentry *retval;
+
+ if (*path == '\0')
+ return &info->root;
+
+ hashval = zip_hash_string(info, path);
+ for (retval = info->hash[hashval]; retval; retval = retval->hashnext)
{
- ZIPentry *entry = &entries[i];
- if (entry->name != NULL)
- allocator.Free(entry->name);
+ if (strcmp(retval->name, path) == 0)
+ {
+ if (prev != NULL) /* move this to the front of the list */
+ {
+ prev->hashnext = retval->hashnext;
+ retval->hashnext = info->hash[hashval];
+ info->hash[hashval] = retval;
+ } /* if */
+
+ return retval;
+ } /* if */
+
+ prev = retval;
} /* for */
- allocator.Free(entries);
-} /* zip_free_entries */
-
-
-/*
- * This will find the ZIPentry associated with a path in platform-independent
- * notation. Directories don't have ZIPentries associated with them, but
- * (*isDir) will be set to non-zero if a dir was hit.
- */
-static ZIPentry *zip_find_entry(const ZIPinfo *info, const char *path,
- int *isDir)
-{
- ZIPentry *a = info->entries;
- PHYSFS_sint32 pathlen = (PHYSFS_sint32) strlen(path);
- PHYSFS_sint64 lo = 0;
- PHYSFS_sint64 hi = (PHYSFS_sint64) (info->entryCount - 1);
- PHYSFS_sint64 middle;
- const char *thispath = NULL;
- int rc;
-
- while (lo <= hi)
- {
- middle = lo + ((hi - lo) / 2);
- thispath = a[middle].name;
- rc = strncmp(path, thispath, pathlen);
-
- if (rc > 0)
- lo = middle + 1;
-
- else if (rc < 0)
- hi = middle - 1;
-
- else /* substring match...might be dir or entry or nothing. */
- {
- if (isDir != NULL)
- {
- *isDir = (thispath[pathlen] == '/');
- if (*isDir)
- return NULL;
- } /* if */
-
- if (thispath[pathlen] == '\0') /* found entry? */
- return &a[middle];
- /* adjust search params, try again. */
- else if (thispath[pathlen] > '/')
- hi = middle - 1;
- else
- lo = middle + 1;
- } /* if */
- } /* while */
-
- if (isDir != NULL)
- *isDir = 0;
-
- BAIL_MACRO(PHYSFS_ERR_NO_SUCH_PATH, NULL);
+ BAIL_MACRO(PHYSFS_ERR_NOT_FOUND, NULL);
} /* zip_find_entry */
@@ -693,7 +769,7 @@
ZIPentry *entry;
zip_expand_symlink_path(path);
- entry = zip_find_entry(info, path, NULL);
+ entry = zip_find_entry(info, path);
if (entry != NULL)
{
if (!zip_resolve(io, info, entry)) /* recursive! */
@@ -725,7 +801,7 @@
path = (char *) __PHYSFS_smallAlloc(size + 1);
BAIL_IF_MACRO(!path, PHYSFS_ERR_OUT_OF_MEMORY, 0);
-
+
if (entry->compression_method == COMPMETH_NONE)
rc = __PHYSFS_readAll(io, path, size);
@@ -788,6 +864,10 @@
* possible that's a Zip64 thing.
*/
+ /* !!! FIXME: apparently these are zero if general purpose bit 3 is set,
+ !!! FIXME: which is probably true for Jar files, fwiw, but we don't
+ !!! FIXME: care about these values anyhow. */
+
BAIL_IF_MACRO(!io->seek(io, entry->offset), ERRPASS, 0);
BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, 0);
BAIL_IF_MACRO(ui32 != ZIP_LOCAL_FILE_SIG, PHYSFS_ERR_CORRUPT, 0);
@@ -819,7 +899,10 @@
static int zip_resolve(PHYSFS_Io *io, ZIPinfo *info, ZIPentry *entry)
{
int retval = 1;
- ZipResolveType resolve_type = entry->resolved;
+ const ZipResolveType resolve_type = entry->resolved;
+
+ if (resolve_type == ZIP_DIRECTORY)
+ return 1; /* we're good. */
/* Don't bother if we've failed to resolve this entry before. */
BAIL_IF_MACRO(resolve_type == ZIP_BROKEN_FILE, PHYSFS_ERR_CORRUPT, 0);
@@ -861,6 +944,77 @@
} /* zip_resolve */
+static int zip_hash_entry(ZIPinfo *info, ZIPentry *entry);
+
+/* Fill in missing parent directories. */
+static ZIPentry *zip_hash_ancestors(ZIPinfo *info, char *name)
+{
+ ZIPentry *retval = &info->root;
+ char *sep = strrchr(name, '/');
+
+ if (sep)
+ {
+ const size_t namelen = (sep - name) + 1;
+
+ *sep = '\0'; /* chop off last piece. */
+ retval = zip_find_entry(info, name);
+ *sep = '/';
+
+ if (retval != NULL)
+ {
+ if (retval->resolved != ZIP_DIRECTORY)
+ BAIL_MACRO(PHYSFS_ERR_CORRUPT, NULL);
+ return retval; /* already hashed. */
+ } /* if */
+
+ /* okay, this is a new dir. Build and hash us. */
+ retval = (ZIPentry *) allocator.Malloc(sizeof (ZIPentry) + namelen);
+ BAIL_IF_MACRO(!retval, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ memset(retval, '\0', sizeof (*retval));
+ retval->name = ((char *) retval) + sizeof (ZIPentry);
+ memcpy(retval->name, name, namelen);
+ retval->name[namelen] = '\0';
+ retval->resolved = ZIP_DIRECTORY;
+ if (!zip_hash_entry(info, retval))
+ {
+ allocator.Free(retval);
+ return NULL;
+ } /* if */
+ } /* else */
+
+ return retval;
+} /* zip_hash_ancestors */
+
+
+static int zip_hash_entry(ZIPinfo *info, ZIPentry *entry)
+{
+ PHYSFS_uint32 hashval;
+ ZIPentry *parent;
+
+ assert(!zip_find_entry(info, entry->name)); /* checked elsewhere */
+
+ parent = zip_hash_ancestors(info, entry->name);
+ if (!parent)
+ return 0;
+
+ hashval = zip_hash_string(info, entry->name);
+ entry->hashnext = info->hash[hashval];
+ info->hash[hashval] = entry;
+
+ entry->sibling = parent->children;
+ parent->children = entry;
+ return 1;
+} /* zip_hash_entry */
+
+
+static int zip_entry_is_symlink(const ZIPentry *entry)
+{
+ return ((entry->resolved == ZIP_UNRESOLVED_SYMLINK) ||
+ (entry->resolved == ZIP_BROKEN_SYMLINK) ||
+ (entry->symlink));
+} /* zip_entry_is_symlink */
+
+
static int zip_version_does_symlinks(PHYSFS_uint32 version)
{
int retval = 0;
@@ -893,14 +1047,6 @@
} /* zip_version_does_symlinks */
-static int zip_entry_is_symlink(const ZIPentry *entry)
-{
- return ((entry->resolved == ZIP_UNRESOLVED_SYMLINK) ||
- (entry->resolved == ZIP_BROKEN_SYMLINK) ||
- (entry->symlink));
-} /* zip_entry_is_symlink */
-
-
static int zip_has_symlink_attr(ZIPentry *entry, PHYSFS_uint32 extern_attr)
{
PHYSFS_uint16 xattr = ((extern_attr >> 16) & 0xFFFF);
@@ -936,9 +1082,11 @@
} /* zip_dos_time_to_physfs_time */
-static int zip_load_entry(PHYSFS_Io *io, const int zip64, ZIPentry *entry,
- PHYSFS_uint64 ofs_fixup)
+static ZIPentry *zip_load_entry(PHYSFS_Io *io, const int zip64,
+ const PHYSFS_uint64 ofs_fixup)
{
+ ZIPentry entry;
+ ZIPentry *retval = NULL;
PHYSFS_uint16 fnamelen, extralen, commentlen;
PHYSFS_uint32 external_attr;
PHYSFS_uint32 starting_disk;
@@ -947,43 +1095,57 @@
PHYSFS_uint32 ui32;
PHYSFS_sint64 si64;
+ memset(&entry, '\0', sizeof (entry));
+
/* sanity check with central directory signature... */
- BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, 0);
- BAIL_IF_MACRO(ui32 != ZIP_CENTRAL_DIR_SIG, PHYSFS_ERR_CORRUPT, 0);
+ if (!readui32(io, &ui32)) return NULL;
+ BAIL_IF_MACRO(ui32 != ZIP_CENTRAL_DIR_SIG, PHYSFS_ERR_CORRUPT, NULL);
/* Get the pertinent parts of the record... */
- BAIL_IF_MACRO(!readui16(io, &entry->version), ERRPASS, 0);
- BAIL_IF_MACRO(!readui16(io, &entry->version_needed), ERRPASS, 0);
- BAIL_IF_MACRO(!readui16(io, &ui16), ERRPASS, 0); /* general bits */
- BAIL_IF_MACRO(!readui16(io, &entry->compression_method), ERRPASS, 0);
- BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, 0);
- entry->last_mod_time = zip_dos_time_to_physfs_time(ui32);
- BAIL_IF_MACRO(!readui32(io, &entry->crc), ERRPASS, 0);
- BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, 0);
- entry->compressed_size = (PHYSFS_uint64) ui32;
- BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, 0);
- entry->uncompressed_size = (PHYSFS_uint64) ui32;
- BAIL_IF_MACRO(!readui16(io, &fnamelen), ERRPASS, 0);
- BAIL_IF_MACRO(!readui16(io, &extralen), ERRPASS, 0);
- BAIL_IF_MACRO(!readui16(io, &commentlen), ERRPASS, 0);
- BAIL_IF_MACRO(!readui16(io, &ui16), ERRPASS, 0);
+ if (!readui16(io, &entry.version)) return NULL;
+ if (!readui16(io, &entry.version_needed)) return NULL;
+ if (!readui16(io, &entry.general_bits)) return NULL; /* general bits */
+ if (!readui16(io, &entry.compression_method)) return NULL;
+ if (!readui32(io, &entry.dos_mod_time)) return NULL;
+ entry.last_mod_time = zip_dos_time_to_physfs_time(entry.dos_mod_time);
+ if (!readui32(io, &entry.crc)) return NULL;
+ if (!readui32(io, &ui32)) return NULL;
+ entry.compressed_size = (PHYSFS_uint64) ui32;
+ if (!readui32(io, &ui32)) return NULL;
+ entry.uncompressed_size = (PHYSFS_uint64) ui32;
+ if (!readui16(io, &fnamelen)) return NULL;
+ if (!readui16(io, &extralen)) return NULL;
+ if (!readui16(io, &commentlen)) return NULL;
+ if (!readui16(io, &ui16)) return NULL;
starting_disk = (PHYSFS_uint32) ui16;
- BAIL_IF_MACRO(!readui16(io, &ui16), ERRPASS, 0); /* internal file attribs */
- BAIL_IF_MACRO(!readui32(io, &external_attr), ERRPASS, 0);
- BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, 0);
+ if (!readui16(io, &ui16)) return NULL; /* internal file attribs */
+ if (!readui32(io, &external_attr)) return NULL;
+ if (!readui32(io, &ui32)) return NULL;
offset = (PHYSFS_uint64) ui32;
- entry->symlink = NULL; /* will be resolved later, if necessary. */
- entry->resolved = (zip_has_symlink_attr(entry, external_attr)) ?
- ZIP_UNRESOLVED_SYMLINK : ZIP_UNRESOLVED_FILE;
+ retval = (ZIPentry *) allocator.Malloc(sizeof (ZIPentry) + fnamelen + 1);
+ BAIL_IF_MACRO(retval == NULL, PHYSFS_ERR_OUT_OF_MEMORY, 0);
+ memcpy(retval, &entry, sizeof (*retval));
+ retval->name = ((char *) retval) + sizeof (ZIPentry);
- entry->name = (char *) allocator.Malloc(fnamelen + 1);
- BAIL_IF_MACRO(entry->name == NULL, PHYSFS_ERR_OUT_OF_MEMORY, 0);
- if (!__PHYSFS_readAll(io, entry->name, fnamelen))
+ if (!__PHYSFS_readAll(io, retval->name, fnamelen))
goto zip_load_entry_puked;
- entry->name[fnamelen] = '\0'; /* null-terminate the filename. */
- zip_convert_dos_path(entry, entry->name);
+ retval->name[fnamelen] = '\0'; /* null-terminate the filename. */
+ zip_convert_dos_path(retval, retval->name);
+
+ retval->symlink = NULL; /* will be resolved later, if necessary. */
+
+ if (retval->name[fnamelen - 1] == '/')
+ {
+ retval->name[fnamelen - 1] = '\0';
+ retval->resolved = ZIP_DIRECTORY;
+ } /* if */
+ else
+ {
+ retval->resolved = (zip_has_symlink_attr(&entry, external_attr)) ?
+ ZIP_UNRESOLVED_SYMLINK : ZIP_UNRESOLVED_FILE;
+ } /* else */
si64 = io->tell(io);
if (si64 == -1)
@@ -996,8 +1158,8 @@
if ( (zip64) &&
((offset == 0xFFFFFFFF) ||
(starting_disk == 0xFFFFFFFF) ||
- (entry->compressed_size == 0xFFFFFFFF) ||
- (entry->uncompressed_size == 0xFFFFFFFF)) )
+ (retval->compressed_size == 0xFFFFFFFF) ||
+ (retval->uncompressed_size == 0xFFFFFFFF)) )
{
int found = 0;
PHYSFS_uint16 sig, len;
@@ -1023,18 +1185,18 @@
GOTO_IF_MACRO(!found, PHYSFS_ERR_CORRUPT, zip_load_entry_puked);
- if (entry->uncompressed_size == 0xFFFFFFFF)
+ if (retval->uncompressed_size == 0xFFFFFFFF)
{
GOTO_IF_MACRO(len < 8, PHYSFS_ERR_CORRUPT, zip_load_entry_puked);
- if (!readui64(io, &entry->uncompressed_size))
+ if (!readui64(io, &retval->uncompressed_size))
goto zip_load_entry_puked;
len -= 8;
} /* if */
- if (entry->compressed_size == 0xFFFFFFFF)
+ if (retval->compressed_size == 0xFFFFFFFF)
{
GOTO_IF_MACRO(len < 8, PHYSFS_ERR_CORRUPT, zip_load_entry_puked);
- if (!readui64(io, &entry->compressed_size))
+ if (!readui64(io, &retval->compressed_size))
goto zip_load_entry_puked;
len -= 8;
} /* if */
@@ -1060,69 +1222,74 @@
GOTO_IF_MACRO(starting_disk != 0, PHYSFS_ERR_CORRUPT, zip_load_entry_puked);
- entry->offset = offset + ofs_fixup;
+ retval->offset = offset + ofs_fixup;
/* seek to the start of the next entry in the central directory... */
if (!io->seek(io, si64 + extralen + commentlen))
goto zip_load_entry_puked;
- return 1; /* success. */
+ return retval; /* success. */
zip_load_entry_puked:
- allocator.Free(entry->name);
- return 0; /* failure. */
+ allocator.Free(retval);
+ return NULL; /* failure. */
} /* zip_load_entry */
-static int zip_entry_cmp(void *_a, size_t one, size_t two)
-{
- if (one != two)
- {
- const ZIPentry *a = (const ZIPentry *) _a;
- return strcmp(a[one].name, a[two].name);
- } /* if */
-
- return 0;
-} /* zip_entry_cmp */
-
-
-static void zip_entry_swap(void *_a, size_t one, size_t two)
+/* This leaves things allocated on error; the caller will clean up the mess. */
+static int zip_load_entries(ZIPinfo *info,
+ const PHYSFS_uint64 data_ofs,
+ const PHYSFS_uint64 central_ofs,
+ const PHYSFS_uint64 entry_count)
{
- if (one != two)
- {
- ZIPentry tmp;
- ZIPentry *first = &(((ZIPentry *) _a)[one]);
- ZIPentry *second = &(((ZIPentry *) _a)[two]);
- memcpy(&tmp, first, sizeof (ZIPentry));
- memcpy(first, second, sizeof (ZIPentry));
- memcpy(second, &tmp, sizeof (ZIPentry));
- } /* if */
-} /* zip_entry_swap */
-
-
-static int zip_load_entries(PHYSFS_Io *io, ZIPinfo *info,
- const PHYSFS_uint64 data_ofs,
- const PHYSFS_uint64 central_ofs)
-{
- const PHYSFS_uint64 max = info->entryCount;
+ PHYSFS_Io *io = info->io;
const int zip64 = info->zip64;
PHYSFS_uint64 i;
- BAIL_IF_MACRO(!io->seek(io, central_ofs), ERRPASS, 0);
+ if (!io->seek(io, central_ofs))
+ return 0;
- info->entries = (ZIPentry *) allocator.Malloc(sizeof (ZIPentry) * max);
- BAIL_IF_MACRO(!info->entries, PHYSFS_ERR_OUT_OF_MEMORY, 0);
+ for (i = 0; i < entry_count; i++)
+ {
+ ZIPentry *entry = zip_load_entry(io, zip64, data_ofs);
+ ZIPentry *find;
+
+ if (!entry)
+ return 0;
- for (i = 0; i < max; i++)
- {
- if (!zip_load_entry(io, zip64, &info->entries[i], data_ofs))
+ find = zip_find_entry(info, entry->name);
+ if (find != NULL) /* duplicate? */
{
- zip_free_entries(info->entries, i);
+ if (find->last_mod_time != 0) /* duplicate? */
+ {
+ allocator.Free(entry);
+ BAIL_MACRO(PHYSFS_ERR_CORRUPT, 0);
+ } /* if */
+ else /* we filled this in as a placeholder. Update it. */
+ {
+ find->offset = entry->offset;
+ find->version = entry->version;
+ find->version_needed = entry->version_needed;
+ find->compression_method = entry->compression_method;
+ find->crc = entry->crc;
+ find->compressed_size = entry->compressed_size;
+ find->uncompressed_size = entry->uncompressed_size;
+ find->last_mod_time = entry->last_mod_time;
+ allocator.Free(entry);
+ continue;
+ } /* else */
+ } /* if */
+
+ if (!zip_hash_entry(info, entry))
+ {
+ allocator.Free(entry);
return 0;
} /* if */
+
+ if (zip_entry_is_tradional_crypto(entry))
+ info->has_crypto = 1;
} /* for */
- __PHYSFS_sort(info->entries, (size_t) max, zip_entry_cmp, zip_entry_swap);
return 1;
} /* zip_load_entries */
@@ -1182,36 +1349,47 @@
/* Just try moving back at most 256k. Oh well. */
if ((offset < pos) && (pos > 4))
{
- /* we assume you can eat this stack if you handle Zip64 files. */
- PHYSFS_uint8 buf[256 * 1024];
+ const PHYSFS_uint64 maxbuflen = 256 * 1024;
PHYSFS_uint64 len = pos - offset;
+ PHYSFS_uint8 *buf = NULL;
PHYSFS_sint32 i;
- if (len > sizeof (buf))
- len = sizeof (buf);
+ if (len > maxbuflen)
+ len = maxbuflen;
+
+ buf = (PHYSFS_uint8 *) __PHYSFS_smallAlloc(len);
+ BAIL_IF_MACRO(!buf, PHYSFS_ERR_OUT_OF_MEMORY, -1);
- BAIL_IF_MACRO(!io->seek(io, pos - len), ERRPASS, -1);
- BAIL_IF_MACRO(!__PHYSFS_readAll(io, buf, len), ERRPASS, -1);
+ if (!io->seek(io, pos - len) || !__PHYSFS_readAll(io, buf, len))
+ {
+ __PHYSFS_smallFree(buf);
+ return -1; /* error was set elsewhere. */
+ } /* if */
+
for (i = (PHYSFS_sint32) (len - 4); i >= 0; i--)
{
- if (buf[i] != 0x50)
- continue;
- if ( (buf[i+1] == 0x4b) &&
- (buf[i+2] == 0x06) &&
- (buf[i+3] == 0x06) )
+ if ( (buf[i] == 0x50) && (buf[i+1] == 0x4b) &&
+ (buf[i+2] == 0x06) && (buf[i+3] == 0x06) )
+ {
+ __PHYSFS_smallFree(buf);
return pos - (len - i);
+ } /* if */
} /* for */
+
+ __PHYSFS_smallFree(buf);
} /* if */
BAIL_MACRO(PHYSFS_ERR_CORRUPT, -1); /* didn't find it. */
} /* zip64_find_end_of_central_dir */
-static int zip64_parse_end_of_central_dir(PHYSFS_Io *io, ZIPinfo *info,
+static int zip64_parse_end_of_central_dir(ZIPinfo *info,
PHYSFS_uint64 *data_start,
PHYSFS_uint64 *dir_ofs,
+ PHYSFS_uint64 *entry_count,
PHYSFS_sint64 pos)
{
+ PHYSFS_Io *io = info->io;
PHYSFS_uint64 ui64;
PHYSFS_uint32 ui32;
PHYSFS_uint16 ui16;
@@ -1279,8 +1457,8 @@
BAIL_IF_MACRO(!readui64(io, &ui64), ERRPASS, 0);
/* total number of entries in the central dir */
- BAIL_IF_MACRO(!readui64(io, &info->entryCount), ERRPASS, 0);
- BAIL_IF_MACRO(ui64 != info->entryCount, PHYSFS_ERR_CORRUPT, 0);
+ BAIL_IF_MACRO(!readui64(io, entry_count), ERRPASS, 0);
+ BAIL_IF_MACRO(ui64 != *entry_count, PHYSFS_ERR_CORRUPT, 0);
/* size of the central directory */
BAIL_IF_MACRO(!readui64(io, &ui64), ERRPASS, 0);
@@ -1300,10 +1478,12 @@
} /* zip64_parse_end_of_central_dir */
-static int zip_parse_end_of_central_dir(PHYSFS_Io *io, ZIPinfo *info,
+static int zip_parse_end_of_central_dir(ZIPinfo *info,
PHYSFS_uint64 *data_start,
- PHYSFS_uint64 *dir_ofs)
+ PHYSFS_uint64 *dir_ofs,
+ PHYSFS_uint64 *entry_count)
{
+ PHYSFS_Io *io = info->io;
PHYSFS_uint16 entryCount16;
PHYSFS_uint32 offset32;
PHYSFS_uint32 ui32;
@@ -1323,10 +1503,12 @@
/* Seek back to see if "Zip64 end of central directory locator" exists. */
/* this record is 20 bytes before end-of-central-dir */
- rc = zip64_parse_end_of_central_dir(io, info, data_start, dir_ofs, pos-20);
- BAIL_IF_MACRO(rc == 0, ERRPASS, 0);
- if (rc == 1)
- return 1; /* we're done here. */
+ rc = zip64_parse_end_of_central_dir(info, data_start, dir_ofs,
+ entry_count, pos - 20);
+
+ /* Error or success? Bounce out of here. Keep going if not zip64. */
+ if ((rc == 0) || (rc == 1))
+ return rc;
assert(rc == -1); /* no error, just not a Zip64 archive. */
@@ -1348,7 +1530,7 @@
BAIL_IF_MACRO(!readui16(io, &entryCount16), ERRPASS, 0);
BAIL_IF_MACRO(ui16 != entryCount16, PHYSFS_ERR_CORRUPT, 0);
- info->entryCount = entryCount16;
+ *entry_count = entryCount16;
/* size of the central directory */
BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, 0);
@@ -1385,11 +1567,30 @@
} /* zip_parse_end_of_central_dir */
+static int zip_alloc_hashtable(ZIPinfo *info, const PHYSFS_uint64 entry_count)
+{
+ size_t alloclen;
+
+ info->hashBuckets = (size_t) (entry_count / 5);
+ if (!info->hashBuckets)
+ info->hashBuckets = 1;
+
+ alloclen = info->hashBuckets * sizeof (ZIPentry *);
+ info->hash = (ZIPentry **) allocator.Malloc(alloclen);
+ BAIL_IF_MACRO(!info->hash, PHYSFS_ERR_OUT_OF_MEMORY, 0);
+ memset(info->hash, '\0', alloclen);
+
+ return 1;
+} /* zip_alloc_hashtable */
+
+static void ZIP_closeArchive(void *opaque);
+
static void *ZIP_openArchive(PHYSFS_Io *io, const char *name, int forWriting)
{
ZIPinfo *info = NULL;
- PHYSFS_uint64 data_start;
- PHYSFS_uint64 cent_dir_ofs;
+ PHYSFS_uint64 dstart; /* data start */
+ PHYSFS_uint64 cdir_ofs; /* central dir offset */
+ PHYSFS_uint64 entry_count;
assert(io != NULL); /* shouldn't ever happen. */
@@ -1399,138 +1600,40 @@
info = (ZIPinfo *) allocator.Malloc(sizeof (ZIPinfo));
BAIL_IF_MACRO(!info, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
memset(info, '\0', sizeof (ZIPinfo));
+ info->root.resolved = ZIP_DIRECTORY;
info->io = io;
- if (!zip_parse_end_of_central_dir(io, info, &data_start, ¢_dir_ofs))
+ if (!zip_parse_end_of_central_dir(info, &dstart, &cdir_ofs, &entry_count))
+ goto ZIP_openarchive_failed;
+ else if (!zip_alloc_hashtable(info, entry_count))
+ goto ZIP_openarchive_failed;
+ else if (!zip_load_entries(info, dstart, cdir_ofs, entry_count))
goto ZIP_openarchive_failed;
- if (!zip_load_entries(io, info, data_start, cent_dir_ofs))
- goto ZIP_openarchive_failed;
-
+ assert(info->root.sibling == NULL);
return info;
ZIP_openarchive_failed:
- if (info != NULL)
- allocator.Free(info);
-
+ info->io = NULL; /* don't let ZIP_closeArchive destroy (io). */
+ ZIP_closeArchive(info);
return NULL;
} /* ZIP_openArchive */
-static PHYSFS_sint64 zip_find_start_of_dir(ZIPinfo *info, const char *path,
- int stop_on_first_find)
-{
- PHYSFS_sint64 lo = 0;
- PHYSFS_sint64 hi = (PHYSFS_sint64) (info->entryCount - 1);
- PHYSFS_sint64 middle;
- PHYSFS_uint32 dlen = (PHYSFS_uint32) strlen(path);
- PHYSFS_sint64 retval = -1;
- const char *name;
- int rc;
-
- if (*path == '\0') /* root dir? */
- return 0;
-
- if ((dlen > 0) && (path[dlen - 1] == '/')) /* ignore trailing slash. */
- dlen--;
-
- while (lo <= hi)
- {
- middle = lo + ((hi - lo) / 2);
- name = info->entries[middle].name;
- rc = strncmp(path, name, dlen);
- if (rc == 0)
- {
- char ch = name[dlen];
- if ('/' < ch) /* make sure this isn't just a substr match. */
- rc = -1;
- else if ('/' > ch)
- rc = 1;
- else
- {
- if (stop_on_first_find) /* Just checking dir's existance? */
- return middle;
-
- if (name[dlen + 1] == '\0') /* Skip initial dir entry. */
- return (middle + 1);
-
- /* there might be more entries earlier in the list. */
- retval = middle;
- hi = middle - 1;
- } /* else */
- } /* if */
-
- if (rc > 0)
- lo = middle + 1;
- else
- hi = middle - 1;
- } /* while */
-
- return retval;
-} /* zip_find_start_of_dir */
-
-
-/*
- * Moved to seperate function so we can use alloca then immediately throw
- * away the allocated stack space...
- */
-static void doEnumCallback(PHYSFS_EnumFilesCallback cb, void *callbackdata,
- const char *odir, const char *str, PHYSFS_sint32 ln)
-{
- char *newstr = __PHYSFS_smallAlloc(ln + 1);
- if (newstr == NULL)
- return;
-
- memcpy(newstr, str, ln);
- newstr[ln] = '\0';
- cb(callbackdata, odir, newstr);
- __PHYSFS_smallFree(newstr);
-} /* doEnumCallback */
-
-
-static void ZIP_enumerateFiles(PHYSFS_Dir *opaque, const char *dname,
- int omitSymLinks, PHYSFS_EnumFilesCallback cb,
+static void ZIP_enumerateFiles(void *opaque, const char *dname,
+ PHYSFS_EnumFilesCallback cb,
const char *origdir, void *callbackdata)
{
ZIPinfo *info = ((ZIPinfo *) opaque);
- PHYSFS_sint32 dlen, dlen_inc;
- PHYSFS_sint64 i, max;
-
- i = zip_find_start_of_dir(info, dname, 0);
- if (i == -1) /* no such directory. */
- return;
-
- dlen = (PHYSFS_sint32) strlen(dname);
- if ((dlen > 0) && (dname[dlen - 1] == '/')) /* ignore trailing slash. */
- dlen--;
-
- dlen_inc = ((dlen > 0) ? 1 : 0) + dlen;
- max = (PHYSFS_sint64) info->entryCount;
- while (i < max)
+ const ZIPentry *entry = zip_find_entry(info, dname);
+ if (entry && (entry->resolved == ZIP_DIRECTORY))
{
- char *e = info->entries[i].name;
- if ((dlen) && ((strncmp(e, dname, dlen) != 0) || (e[dlen] != '/')))
- break; /* past end of this dir; we're done. */
-
- if ((omitSymLinks) && (zip_entry_is_symlink(&info->entries[i])))
- i++;
- else
+ for (entry = entry->children; entry; entry = entry->sibling)
{
- char *add = e + dlen_inc;
- char *ptr = strchr(add, '/');
- PHYSFS_sint32 ln = (PHYSFS_sint32) ((ptr) ? ptr-add : strlen(add));
- doEnumCallback(cb, callbackdata, origdir, add, ln);
- ln += dlen_inc; /* point past entry to children... */
-
- /* increment counter and skip children of subdirs... */
- while ((++i < max) && (ptr != NULL))
- {
- char *e_new = info->entries[i].name;
- if ((strncmp(e, e_new, ln) != 0) || (e_new[ln] != '/'))
- break;
- } /* while */
- } /* else */
- } /* while */
+ const char *ptr = strrchr(entry->name, '/');
+ cb(callbackdata, origdir, ptr ? ptr + 1 : entry->name);
+ } /* for */
+ } /* if */
} /* ZIP_enumerateFiles */
@@ -1561,15 +1664,33 @@
} /* zip_get_io */
-static PHYSFS_Io *ZIP_openRead(PHYSFS_Dir *opaque, const char *fnm,
- int *fileExists)
+static PHYSFS_Io *ZIP_openRead(void *opaque, const char *filename)
{
PHYSFS_Io *retval = NULL;
ZIPinfo *info = (ZIPinfo *) opaque;
- ZIPentry *entry = zip_find_entry(info, fnm, NULL);
+ ZIPentry *entry = zip_find_entry(info, filename);
ZIPfileinfo *finfo = NULL;
+ PHYSFS_Io *io = NULL;
+ PHYSFS_uint8 *password = NULL;
+ int i;
- *fileExists = (entry != NULL);
+ /* if not found, see if maybe "$PASSWORD" is appended. */
+ if ((!entry) && (info->has_crypto))
+ {
+ const char *ptr = strrchr(filename, '$');
+ if (ptr != NULL)
+ {
+ const PHYSFS_uint64 len = (PHYSFS_uint64) (ptr - filename);
+ char *str = (char *) __PHYSFS_smallAlloc(len + 1);
+ BAIL_IF_MACRO(!str, PHYSFS_ERR_OUT_OF_MEMORY, NULL);
+ memcpy(str, filename, len);
+ str[len] = '\0';
+ entry = zip_find_entry(info, str);
+ __PHYSFS_smallFree(str);
+ password = (PHYSFS_uint8 *) (ptr + 1);
+ } /* if */
+ } /* if */
+
BAIL_IF_MACRO(!entry, ERRPASS, NULL);
retval = (PHYSFS_Io *) allocator.Malloc(sizeof (PHYSFS_Io));
@@ -1579,8 +1700,9 @@
GOTO_IF_MACRO(!finfo, PHYSFS_ERR_OUT_OF_MEMORY, ZIP_openRead_failed);
memset(finfo, '\0', sizeof (ZIPfileinfo));
- finfo->io = zip_get_io(info->io, info, entry);
- GOTO_IF_MACRO(!finfo->io, ERRPASS, ZIP_openRead_failed);
+ io = zip_get_io(info->io, info, entry);
+ GOTO_IF_MACRO(!io, ERRPASS, ZIP_openRead_failed);
+ finfo->io = io;
finfo->entry = ((entry->symlink != NULL) ? entry->symlink : entry);
initializeZStream(&finfo->stream);
@@ -1593,6 +1715,18 @@
goto ZIP_openRead_failed;
} /* if */
+ if (!zip_entry_is_tradional_crypto(entry))
+ GOTO_IF_MACRO(password != NULL, PHYSFS_ERR_BAD_PASSWORD, ZIP_openRead_failed);
+ else
+ {
+ PHYSFS_uint8 crypto_header[12];
+ GOTO_IF_MACRO(password == NULL, PHYSFS_ERR_BAD_PASSWORD, ZIP_openRead_failed);
+ if (io->read(io, crypto_header, 12) != 12)
+ goto ZIP_openRead_failed;
+ else if (!zip_prep_crypto_keys(finfo, crypto_header, password))
+ goto ZIP_openRead_failed;
+ } /* if */
+
memcpy(retval, &ZIP_Io, sizeof (PHYSFS_Io));
retval->opaque = finfo;
@@ -1620,53 +1754,74 @@
} /* ZIP_openRead */
-static PHYSFS_Io *ZIP_openWrite(PHYSFS_Dir *opaque, const char *filename)
+static PHYSFS_Io *ZIP_openWrite(void *opaque, const char *filename)
{
BAIL_MACRO(PHYSFS_ERR_READ_ONLY, NULL);
} /* ZIP_openWrite */
-static PHYSFS_Io *ZIP_openAppend(PHYSFS_Dir *opaque, const char *filename)
+static PHYSFS_Io *ZIP_openAppend(void *opaque, const char *filename)
{
BAIL_MACRO(PHYSFS_ERR_READ_ONLY, NULL);
} /* ZIP_openAppend */
-static void ZIP_closeArchive(PHYSFS_Dir *opaque)
+static void ZIP_closeArchive(void *opaque)
{
- ZIPinfo *zi = (ZIPinfo *) (opaque);
- zi->io->destroy(zi->io);
- zip_free_entries(zi->entries, zi->entryCount);
- allocator.Free(zi);
+ ZIPinfo *info = (ZIPinfo *) (opaque);
+
+ if (!info)
+ return;
+
+ if (info->io)
+ info->io->destroy(info->io);
+
+ assert(info->root.sibling == NULL);
+ assert(info->hash || (info->root.children == NULL));
+
+ if (info->hash)
+ {
+ size_t i;
+ for (i = 0; i < info->hashBuckets; i++)
+ {
+ ZIPentry *entry;
+ ZIPentry *next;
+ for (entry = info->hash[i]; entry; entry = next)
+ {
+ next = entry->hashnext;
+ allocator.Free(entry);
+ } /* for */
+ } /* for */
+ allocator.Free(info->hash);
+ } /* if */
+
+ allocator.Free(info);
} /* ZIP_closeArchive */
-static int ZIP_remove(PHYSFS_Dir *opaque, const char *name)
+static int ZIP_remove(void *opaque, const char *name)
{
BAIL_MACRO(PHYSFS_ERR_READ_ONLY, 0);
} /* ZIP_remove */
-static int ZIP_mkdir(PHYSFS_Dir *opaque, const char *name)
+static int ZIP_mkdir(void *opaque, const char *name)
{
BAIL_MACRO(PHYSFS_ERR_READ_ONLY, 0);
} /* ZIP_mkdir */
-static int ZIP_stat(PHYSFS_Dir *opaque, const char *filename, int *exists,
- PHYSFS_Stat *stat)
+static int ZIP_stat(void *opaque, const char *filename, PHYSFS_Stat *stat)
{
- int isDir = 0;
- const ZIPinfo *info = (const ZIPinfo *) opaque;
- const ZIPentry *entry = zip_find_entry(info, filename, &isDir);
+ ZIPinfo *info = (ZIPinfo *) opaque;
+ const ZIPentry *entry = zip_find_entry(info, filename);
/* !!! FIXME: does this need to resolve entries here? */
- *exists = isDir || (entry != 0);
- if (!*exists)
+ if (entry == NULL)
return 0;
- if (isDir)
+ else if (entry->resolved == ZIP_DIRECTORY)
{
stat->filesize = 0;
stat->filetype = PHYSFS_FILETYPE_DIRECTORY;
@@ -1695,24 +1850,26 @@
const PHYSFS_Archiver __PHYSFS_Archiver_ZIP =
{
+ CURRENT_PHYSFS_ARCHIVER_API_VERSION,
{
"ZIP",
"PkZip/WinZip/Info-Zip compatible",
"Ryan C. Gordon <icculus@icculus.org>",
- "http://icculus.org/physfs/",
+ "https://icculus.org/physfs/",
+ 1, /* supportsSymlinks */
},
- ZIP_openArchive, /* openArchive() method */
- ZIP_enumerateFiles, /* enumerateFiles() method */
- ZIP_openRead, /* openRead() method */
- ZIP_openWrite, /* openWrite() method */
- ZIP_openAppend, /* openAppend() method */
- ZIP_remove, /* remove() method */
- ZIP_mkdir, /* mkdir() method */
- ZIP_closeArchive, /* closeArchive() method */
- ZIP_stat /* stat() method */
+ ZIP_openArchive,
+ ZIP_enumerateFiles,
+ ZIP_openRead,
+ ZIP_openWrite,
+ ZIP_openAppend,
+ ZIP_remove,
+ ZIP_mkdir,
+ ZIP_stat,
+ ZIP_closeArchive
};
#endif /* defined PHYSFS_SUPPORTS_ZIP */
-/* end of zip.c ... */
+/* end of archiver_zip.c ... */