Added Zip64 support to the .zip archiver.
authorRyan C. Gordon <icculus@icculus.org>
Fri, 01 Jun 2012 05:44:50 -0400
changeset 1283 8bcc48d243df
parent 1282 62256a6a8e4e
child 1284 455ac411a595
Added Zip64 support to the .zip archiver. Now we can handle .zip files > 4 gigabytes, etc.
src/archiver_lzma.c
src/archiver_unpacked.c
src/archiver_zip.c
src/physfs.c
src/physfs_internal.h
--- a/src/archiver_lzma.c	Sat Apr 07 21:33:18 2012 -0400
+++ b/src/archiver_lzma.c	Fri Jun 01 05:44:50 2012 -0400
@@ -177,7 +177,7 @@
  * Compare two files with each other based on the name
  * Used for sorting
  */
-static int lzma_file_cmp(void *_a, PHYSFS_uint32 one, PHYSFS_uint32 two)
+static int lzma_file_cmp(void *_a, size_t one, size_t two)
 {
     LZMAfile *files = (LZMAfile *) _a;
     return strcmp(files[one].item->Name, files[two].item->Name);
@@ -187,7 +187,7 @@
 /*
  * Swap two entries in the file array
  */
-static void lzma_file_swap(void *_a, PHYSFS_uint32 one, PHYSFS_uint32 two)
+static void lzma_file_swap(void *_a, size_t one, size_t two)
 {
     LZMAfile tmp;
     LZMAfile *first = &(((LZMAfile *) _a)[one]);
@@ -245,7 +245,7 @@
         }
     } /* for */
 
-   __PHYSFS_sort(archive->files, numFiles, lzma_file_cmp, lzma_file_swap);
+   __PHYSFS_sort(archive->files, (size_t) numFiles, lzma_file_cmp, lzma_file_swap);
 
     return 1;
 } /* lzma_load_files */
--- a/src/archiver_unpacked.c	Sat Apr 07 21:33:18 2012 -0400
+++ b/src/archiver_unpacked.c	Fri Jun 01 05:44:50 2012 -0400
@@ -145,7 +145,7 @@
 };
 
 
-static int entryCmp(void *_a, PHYSFS_uint32 one, PHYSFS_uint32 two)
+static int entryCmp(void *_a, size_t one, size_t two)
 {
     if (one != two)
     {
@@ -157,7 +157,7 @@
 } /* entryCmp */
 
 
-static void entrySwap(void *_a, PHYSFS_uint32 one, PHYSFS_uint32 two)
+static void entrySwap(void *_a, size_t one, size_t two)
 {
     if (one != two)
     {
@@ -458,7 +458,7 @@
         BAIL_MACRO(PHYSFS_ERR_OUT_OF_MEMORY, NULL);
     } /* if */
 
-    __PHYSFS_sort(e, num, entryCmp, entrySwap);
+    __PHYSFS_sort(e, (size_t) num, entryCmp, entrySwap);
     info->io = io;
     info->entryCount = num;
     info->entries = e;
--- a/src/archiver_zip.c	Sat Apr 07 21:33:18 2012 -0400
+++ b/src/archiver_zip.c	Fri Jun 01 05:44:50 2012 -0400
@@ -64,13 +64,13 @@
     char *name;                         /* Name of file in archive        */
     struct _ZIPentry *symlink;          /* NULL or file we symlink to     */
     ZipResolveType resolved;            /* Have we resolved file/symlink? */
-    PHYSFS_uint32 offset;               /* offset of data in archive      */
+    PHYSFS_uint64 offset;               /* offset of data in archive      */
     PHYSFS_uint16 version;              /* version made by                */
     PHYSFS_uint16 version_needed;       /* version needed to extract      */
     PHYSFS_uint16 compression_method;   /* compression method             */
     PHYSFS_uint32 crc;                  /* crc-32                         */
-    PHYSFS_uint32 compressed_size;      /* compressed size                */
-    PHYSFS_uint32 uncompressed_size;    /* uncompressed size              */
+    PHYSFS_uint64 compressed_size;      /* compressed size                */
+    PHYSFS_uint64 uncompressed_size;    /* uncompressed size              */
     PHYSFS_sint64 last_mod_time;        /* last file mod time             */
 } ZIPentry;
 
@@ -80,8 +80,9 @@
 typedef struct
 {
     PHYSFS_Io *io;
-    PHYSFS_uint16 entryCount; /* Number of files in ZIP.                     */
-    ZIPentry *entries;        /* info on all files in ZIP.                   */
+    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.            */
 } ZIPinfo;
 
 /*
@@ -99,9 +100,12 @@
 
 
 /* Magic numbers... */
-#define ZIP_LOCAL_FILE_SIG          0x04034b50
-#define ZIP_CENTRAL_DIR_SIG         0x02014b50
-#define ZIP_END_OF_CENTRAL_DIR_SIG  0x06054b50
+#define ZIP_LOCAL_FILE_SIG                          0x04034b50
+#define ZIP_CENTRAL_DIR_SIG                         0x02014b50
+#define ZIP_END_OF_CENTRAL_DIR_SIG                  0x06054b50
+#define ZIP64_END_OF_CENTRAL_DIR_SIG                0x06064b50
+#define ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG  0x07064b50
+#define ZIP64_EXTENDED_INFO_EXTRA_FIELD_SIG         0x0001
 
 /* compression methods... */
 #define COMPMETH_NONE 0
@@ -165,6 +169,17 @@
 
 
 /*
+ * Read an unsigned 64-bit int and swap to native byte order.
+ */
+static int readui64(PHYSFS_Io *io, PHYSFS_uint64 *val)
+{
+    PHYSFS_uint64 v;
+    BAIL_IF_MACRO(!__PHYSFS_readAll(io, &v, sizeof (v)), ERRPASS, 0);
+    *val = PHYSFS_swapULE64(v);
+    return 1;
+} /* readui64 */
+
+/*
  * Read an unsigned 32-bit int and swap to native byte order.
  */
 static int readui32(PHYSFS_Io *io, PHYSFS_uint32 *val)
@@ -322,7 +337,7 @@
 static PHYSFS_sint64 ZIP_length(PHYSFS_Io *io)
 {
     const ZIPfileinfo *finfo = (ZIPfileinfo *) io->opaque;
-    return finfo->entry->uncompressed_size;
+    return (PHYSFS_sint64) finfo->entry->uncompressed_size;
 } /* ZIP_length */
 
 
@@ -521,9 +536,9 @@
 } /* isZip */
 
 
-static void zip_free_entries(ZIPentry *entries, PHYSFS_uint32 max)
+static void zip_free_entries(ZIPentry *entries, PHYSFS_uint64 max)
 {
-    PHYSFS_uint32 i;
+    PHYSFS_uint64 i;
     for (i = 0; i < max; i++)
     {
         ZIPentry *entry = &entries[i];
@@ -545,9 +560,9 @@
 {
     ZIPentry *a = info->entries;
     PHYSFS_sint32 pathlen = (PHYSFS_sint32) strlen(path);
-    PHYSFS_sint32 lo = 0;
-    PHYSFS_sint32 hi = (PHYSFS_sint32) (info->entryCount - 1);
-    PHYSFS_sint32 middle;
+    PHYSFS_sint64 lo = 0;
+    PHYSFS_sint64 hi = (PHYSFS_sint64) (info->entryCount - 1);
+    PHYSFS_sint64 middle;
     const char *thispath = NULL;
     int rc;
 
@@ -695,7 +710,7 @@
 
 static int zip_resolve_symlink(PHYSFS_Io *io, ZIPinfo *info, ZIPentry *entry)
 {
-    const PHYSFS_uint32 size = entry->uncompressed_size;
+    const PHYSFS_uint64 size = entry->uncompressed_size;
     char *path = NULL;
     int rc = 0;
 
@@ -716,7 +731,7 @@
     else  /* symlink target path is compressed... */
     {
         z_stream stream;
-        const PHYSFS_uint32 complen = entry->compressed_size;
+        const PHYSFS_uint64 complen = entry->compressed_size;
         PHYSFS_uint8 *compressed = (PHYSFS_uint8*) __PHYSFS_smallAlloc(complen);
         if (compressed != NULL)
         {
@@ -768,6 +783,8 @@
      *  archive created with Sun's Java tools, apparently. We only
      *  consider this archive corrupted if those entries don't match and
      *  aren't zero. That seems to work well.
+     * We also ignore a mismatch if the value is 0xFFFFFFFF here, since it's
+     *  possible that's a Zip64 thing.
      */
 
     BAIL_IF_MACRO(!io->seek(io, entry->offset), ERRPASS, 0);
@@ -781,10 +798,15 @@
     BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, 0);  /* date/time */
     BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, 0);
     BAIL_IF_MACRO(ui32 && (ui32 != entry->crc), PHYSFS_ERR_CORRUPT, 0);
+
     BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, 0);
-    BAIL_IF_MACRO(ui32 && (ui32!=entry->compressed_size),PHYSFS_ERR_CORRUPT,0);
+    BAIL_IF_MACRO(ui32 && (ui32 != 0xFFFFFFFF) &&
+                  (ui32 != entry->compressed_size), PHYSFS_ERR_CORRUPT, 0);
+
     BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, 0);
-    BAIL_IF_MACRO(ui32&&(ui32!=entry->uncompressed_size),PHYSFS_ERR_CORRUPT,0);
+    BAIL_IF_MACRO(ui32 && (ui32 != 0xFFFFFFFF) &&
+                 (ui32 != entry->uncompressed_size), PHYSFS_ERR_CORRUPT, 0);
+
     BAIL_IF_MACRO(!readui16(io, &fnamelen), ERRPASS, 0);
     BAIL_IF_MACRO(!readui16(io, &extralen), ERRPASS, 0);
 
@@ -913,10 +935,13 @@
 } /* zip_dos_time_to_physfs_time */
 
 
-static int zip_load_entry(PHYSFS_Io *io, ZIPentry *entry, PHYSFS_uint32 ofs_fixup)
+static int zip_load_entry(PHYSFS_Io *io, const int zip64, ZIPentry *entry,
+                          PHYSFS_uint64 ofs_fixup)
 {
     PHYSFS_uint16 fnamelen, extralen, commentlen;
     PHYSFS_uint32 external_attr;
+    PHYSFS_uint32 starting_disk;
+    PHYSFS_uint64 offset;
     PHYSFS_uint16 ui16;
     PHYSFS_uint32 ui32;
     PHYSFS_sint64 si64;
@@ -933,16 +958,19 @@
     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, &entry->compressed_size), ERRPASS, 0);
-    BAIL_IF_MACRO(!readui32(io, &entry->uncompressed_size), 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);  /* disk number start */
+    BAIL_IF_MACRO(!readui16(io, &ui16), ERRPASS, 0);
+    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, &entry->offset), ERRPASS, 0);
-    entry->offset += ofs_fixup;
+    BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, 0);
+    offset = (PHYSFS_uint64) ui32;
 
     entry->symlink = NULL;  /* will be resolved later, if necessary. */
     entry->resolved = (zip_has_symlink_attr(entry, external_attr)) ?
@@ -960,7 +988,80 @@
     if (si64 == -1)
         goto zip_load_entry_puked;
 
-        /* seek to the start of the next entry in the central directory... */
+    /*
+     * The actual sizes didn't fit in 32-bits; look for the Zip64
+     *  extended information extra field...
+     */
+    if ( (zip64) &&
+         ((offset == 0xFFFFFFFF) ||
+          (starting_disk == 0xFFFFFFFF) ||
+          (entry->compressed_size == 0xFFFFFFFF) ||
+          (entry->uncompressed_size == 0xFFFFFFFF)) )
+    {
+        int found = 0;
+        PHYSFS_uint16 sig, len;
+        while (extralen > 4)
+        {
+            if (!readui16(io, &sig))
+                goto zip_load_entry_puked;
+            else if (!readui16(io, &len))
+                goto zip_load_entry_puked;
+
+            si64 += 4 + len;
+            extralen -= 4 + len;
+            if (sig != ZIP64_EXTENDED_INFO_EXTRA_FIELD_SIG)
+            {
+                if (!io->seek(io, si64))
+                    goto zip_load_entry_puked;
+                continue;
+            } /* if */
+
+            found = 1;
+            break;
+        } /* while */
+
+        GOTO_IF_MACRO(!found, PHYSFS_ERR_CORRUPT, zip_load_entry_puked);
+
+        if (entry->uncompressed_size == 0xFFFFFFFF)
+        {
+            GOTO_IF_MACRO(len < 8, PHYSFS_ERR_CORRUPT, zip_load_entry_puked);
+            if (!readui64(io, &entry->uncompressed_size))
+                goto zip_load_entry_puked;
+            len -= 8;
+        } /* if */
+
+        if (entry->compressed_size == 0xFFFFFFFF)
+        {
+            GOTO_IF_MACRO(len < 8, PHYSFS_ERR_CORRUPT, zip_load_entry_puked);
+            if (!readui64(io, &entry->compressed_size))
+                goto zip_load_entry_puked;
+            len -= 8;
+        } /* if */
+
+        if (offset == 0xFFFFFFFF)
+        {
+            GOTO_IF_MACRO(len < 8, PHYSFS_ERR_CORRUPT, zip_load_entry_puked);
+            if (!readui64(io, &offset))
+                goto zip_load_entry_puked;
+            len -= 8;
+        } /* if */
+
+        if (starting_disk == 0xFFFFFFFF)
+        {
+            GOTO_IF_MACRO(len < 8, PHYSFS_ERR_CORRUPT, zip_load_entry_puked);
+            if (!readui32(io, &starting_disk))
+                goto zip_load_entry_puked;
+            len -= 4;
+        } /* if */
+
+        GOTO_IF_MACRO(len != 0, PHYSFS_ERR_CORRUPT, zip_load_entry_puked);
+    } /* if */
+
+    GOTO_IF_MACRO(starting_disk != 0, PHYSFS_ERR_CORRUPT, zip_load_entry_puked);
+
+    entry->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;
 
@@ -972,7 +1073,7 @@
 } /* zip_load_entry */
 
 
-static int zip_entry_cmp(void *_a, PHYSFS_uint32 one, PHYSFS_uint32 two)
+static int zip_entry_cmp(void *_a, size_t one, size_t two)
 {
     if (one != two)
     {
@@ -984,7 +1085,7 @@
 } /* zip_entry_cmp */
 
 
-static void zip_entry_swap(void *_a, PHYSFS_uint32 one, PHYSFS_uint32 two)
+static void zip_entry_swap(void *_a, size_t one, size_t two)
 {
     if (one != two)
     {
@@ -999,10 +1100,12 @@
 
 
 static int zip_load_entries(PHYSFS_Io *io, ZIPinfo *info,
-                            PHYSFS_uint32 data_ofs, PHYSFS_uint32 central_ofs)
+                            const PHYSFS_uint64 data_ofs,
+                            const PHYSFS_uint64 central_ofs)
 {
-    PHYSFS_uint32 max = info->entryCount;
-    PHYSFS_uint32 i;
+    const PHYSFS_uint64 max = info->entryCount;
+    const int zip64 = info->zip64;
+    PHYSFS_uint64 i;
 
     BAIL_IF_MACRO(!io->seek(io, central_ofs), ERRPASS, 0);
 
@@ -1011,26 +1114,203 @@
 
     for (i = 0; i < max; i++)
     {
-        if (!zip_load_entry(io, &info->entries[i], data_ofs))
+        if (!zip_load_entry(io, zip64, &info->entries[i], data_ofs))
         {
             zip_free_entries(info->entries, i);
             return 0;
         } /* if */
     } /* for */
 
-    __PHYSFS_sort(info->entries, max, zip_entry_cmp, zip_entry_swap);
+    __PHYSFS_sort(info->entries, (size_t) max, zip_entry_cmp, zip_entry_swap);
     return 1;
 } /* zip_load_entries */
 
 
+static PHYSFS_sint64 zip64_find_end_of_central_dir(PHYSFS_Io *io,
+                                                   PHYSFS_sint64 _pos,
+                                                   PHYSFS_uint64 offset)
+{
+    /*
+     * Naturally, the offset is useless to us; it is the offset from the
+     *  start of file, which is meaningless if we've appended this .zip to
+     *  a self-extracting .exe. We need to find this on our own. It should
+     *  be directly before the locator record, but the record in question,
+     *  like the original end-of-central-directory record, ends with a
+     *  variable-length field. Unlike the original, which has to store the
+     *  size of that variable-length field in a 16-bit int and thus has to be
+     *  within 64k, the new one gets 64-bits.
+     *
+     * Fortunately, the only currently-specified record for that variable
+     *  length block is some weird proprietary thing that deals with EBCDIC
+     *  and tape backups or something. So we don't seek far.
+     */
+
+    PHYSFS_uint32 ui32;
+    const PHYSFS_uint64 pos = (PHYSFS_uint64) _pos;
+
+    assert(_pos > 0);
+
+    /* Try offset specified in the Zip64 end of central directory locator. */
+    /* This works if the entire PHYSFS_Io is the zip file. */
+    BAIL_IF_MACRO(!io->seek(io, offset), ERRPASS, -1);
+    BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, -1);
+    if (ui32 == ZIP64_END_OF_CENTRAL_DIR_SIG)
+        return offset;
+
+    /* Try 56 bytes before the Zip64 end of central directory locator. */
+    /* This works if the record isn't variable length and is version 1. */
+    if (pos > 56)
+    {
+        BAIL_IF_MACRO(!io->seek(io, pos-56), ERRPASS, -1);
+        BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, -1);
+        if (ui32 == ZIP64_END_OF_CENTRAL_DIR_SIG)
+            return pos-56;
+    } /* if */
+
+    /* Try 84 bytes before the Zip64 end of central directory locator. */
+    /* This works if the record isn't variable length and is version 2. */
+    if (pos > 84)
+    {
+        BAIL_IF_MACRO(!io->seek(io, pos-84), ERRPASS, -1);
+        BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, -1);
+        if (ui32 == ZIP64_END_OF_CENTRAL_DIR_SIG)
+            return pos-84;
+    } /* if */
+
+    /* Ok, brute force: we know it's between (offset) and (pos) somewhere. */
+    /*  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];
+        PHYSFS_uint64 len = pos - offset;
+        PHYSFS_uint32 i;
+
+        if (len > sizeof (buf))
+            len = sizeof (buf);
+
+        BAIL_IF_MACRO(!io->seek(io, pos - len), ERRPASS, -1);
+        BAIL_IF_MACRO(!__PHYSFS_readAll(io, buf, len), ERRPASS, -1);
+        for (i = len - 4; i >= 0; i--)
+        {
+            if (buf[i] != 0x50)
+                continue;
+            if ( (buf[i+1] == 0x4b) &&
+                 (buf[i+2] == 0x06) &&
+                 (buf[i+3] == 0x06) )
+                return pos - (len - i);
+        } /* for */
+    } /* 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,
+                                          PHYSFS_uint64 *data_start,
+                                          PHYSFS_uint64 *dir_ofs,
+                                          PHYSFS_sint64 pos)
+{
+    PHYSFS_uint64 ui64;
+    PHYSFS_uint32 ui32;
+    PHYSFS_uint16 ui16;
+    //PHYSFS_sint64 len;
+
+    /* We should be positioned right past the locator signature. */
+
+    if ((pos < 0) || (!io->seek(io, pos)))
+        return 0;
+
+    BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, 0);
+    if (ui32 != ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIG)
+        return -1;  /* it's not a Zip64 archive. Not an error, though! */
+
+    info->zip64 = 1;
+
+    /* number of the disk with the start of the central directory. */
+    BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, 0);
+    BAIL_IF_MACRO(ui32 != 0, PHYSFS_ERR_CORRUPT, 0);
+
+    /* offset of Zip64 end of central directory record. */
+    BAIL_IF_MACRO(!readui64(io, &ui64), ERRPASS, 0);
+
+    /* total number of disks */
+    BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, 0);
+    BAIL_IF_MACRO(ui32 != 1, PHYSFS_ERR_CORRUPT, 0);
+
+    pos = zip64_find_end_of_central_dir(io, pos, ui64);
+    if (pos < 0)
+        return 0;  /* oh well. */
+
+    /*
+     * For self-extracting archives, etc, there's crapola in the file
+     *  before the zipfile records; we calculate how much data there is
+     *  prepended by determining how far the zip64-end-of-central-directory
+     *  offset is from where it is supposed to be...the difference in bytes
+     *  is how much arbitrary data is at the start of the physical file.
+     */
+    assert(((PHYSFS_uint64) pos) >= ui64);
+    *data_start = ((PHYSFS_uint64) pos) - ui64;
+
+    BAIL_IF_MACRO(!io->seek(io, pos), ERRPASS, 0);
+
+    /* check signature again, just in case. */
+    BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, 0);
+    BAIL_IF_MACRO(ui32 != ZIP64_END_OF_CENTRAL_DIR_SIG, PHYSFS_ERR_CORRUPT, 0);
+
+    /* size of Zip64 end of central directory record. */
+    BAIL_IF_MACRO(!readui64(io, &ui64), ERRPASS, 0);
+
+    /* version made by. */
+    BAIL_IF_MACRO(!readui16(io, &ui16), ERRPASS, 0);
+
+    /* version needed to extract. */
+    BAIL_IF_MACRO(!readui16(io, &ui16), ERRPASS, 0);
+
+    /* number of this disk. */
+    BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, 0);
+    BAIL_IF_MACRO(ui32 != 0, PHYSFS_ERR_CORRUPT, 0);
+
+    /* number of disk with start of central directory record. */
+    BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, 0);
+    BAIL_IF_MACRO(ui32 != 0, PHYSFS_ERR_CORRUPT, 0);
+
+    /* total number of entries in the central dir on this disk */
+    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);
+
+    /* size of the central directory */
+    BAIL_IF_MACRO(!readui64(io, &ui64), ERRPASS, 0);
+
+    /* offset of central directory */
+    BAIL_IF_MACRO(!readui64(io, dir_ofs), ERRPASS, 0);
+
+    /* Since we know the difference, fix up the central dir offset... */
+    *dir_ofs += *data_start;
+
+    /*
+     * There are more fields here, for encryption and feature-specific things,
+     *  but we don't care about any of them at the moment.
+     */
+
+    return 1;  /* made it. */
+} /* zip64_parse_end_of_central_dir */
+
+
 static int zip_parse_end_of_central_dir(PHYSFS_Io *io, ZIPinfo *info,
-                                        PHYSFS_uint32 *data_start,
-                                        PHYSFS_uint32 *central_dir_ofs)
+                                        PHYSFS_uint64 *data_start,
+                                        PHYSFS_uint64 *dir_ofs)
 {
+    PHYSFS_uint16 entryCount16;
+    PHYSFS_uint32 offset32;
     PHYSFS_uint32 ui32;
     PHYSFS_uint16 ui16;
     PHYSFS_sint64 len;
     PHYSFS_sint64 pos;
+    int rc;
 
     /* find the end-of-central-dir record, and seek to it. */
     pos = zip_find_end_of_central_dir(io, &len);
@@ -1041,6 +1321,18 @@
     BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, 0);
     BAIL_IF_MACRO(ui32 != ZIP_END_OF_CENTRAL_DIR_SIG, PHYSFS_ERR_CORRUPT, 0);
 
+    /* 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. */
+
+    assert(rc == -1);  /* no error, just not a Zip64 archive. */
+
+    /* Not Zip64? Seek back to where we were and keep processing. */
+    BAIL_IF_MACRO(!io->seek(io, pos + 4), ERRPASS, 0);
+
     /* number of this disk */
     BAIL_IF_MACRO(!readui16(io, &ui16), ERRPASS, 0);
     BAIL_IF_MACRO(ui16 != 0, PHYSFS_ERR_CORRUPT, 0);
@@ -1053,15 +1345,16 @@
     BAIL_IF_MACRO(!readui16(io, &ui16), ERRPASS, 0);
 
     /* total number of entries in the central dir */
-    BAIL_IF_MACRO(!readui16(io, &info->entryCount), ERRPASS, 0);
-    BAIL_IF_MACRO(ui16 != info->entryCount, PHYSFS_ERR_CORRUPT, 0);
+    BAIL_IF_MACRO(!readui16(io, &entryCount16), ERRPASS, 0);
+    BAIL_IF_MACRO(ui16 != entryCount16, PHYSFS_ERR_CORRUPT, 0);
 
     /* size of the central directory */
     BAIL_IF_MACRO(!readui32(io, &ui32), ERRPASS, 0);
 
     /* offset of central directory */
-    BAIL_IF_MACRO(!readui32(io, central_dir_ofs), ERRPASS, 0);
-    BAIL_IF_MACRO(pos < *central_dir_ofs + ui32, PHYSFS_ERR_CORRUPT, 0);
+    BAIL_IF_MACRO(!readui32(io, &offset32), ERRPASS, 0);
+    *dir_ofs = (PHYSFS_uint64) offset32;
+    BAIL_IF_MACRO(pos < (*dir_ofs + ui32), PHYSFS_ERR_CORRUPT, 0);
 
     /*
      * For self-extracting archives, etc, there's crapola in the file
@@ -1071,10 +1364,10 @@
      *  sizeof central dir)...the difference in bytes is how much arbitrary
      *  data is at the start of the physical file.
      */
-    *data_start = (PHYSFS_uint32) (pos - (*central_dir_ofs + ui32));
+    *data_start = (PHYSFS_uint64) (pos - (*dir_ofs + ui32));
 
     /* Now that we know the difference, fix up the central dir offset... */
-    *central_dir_ofs += *data_start;
+    *dir_ofs += *data_start;
 
     /* zipfile comment length */
     BAIL_IF_MACRO(!readui16(io, &ui16), ERRPASS, 0);
@@ -1093,8 +1386,8 @@
 static void *ZIP_openArchive(PHYSFS_Io *io, const char *name, int forWriting)
 {
     ZIPinfo *info = NULL;
-    PHYSFS_uint32 data_start;
-    PHYSFS_uint32 cent_dir_ofs;
+    PHYSFS_uint64 data_start;
+    PHYSFS_uint64 cent_dir_ofs;
 
     assert(io != NULL);  /* shouldn't ever happen. */
 
@@ -1122,14 +1415,14 @@
 } /* ZIP_openArchive */
 
 
-static PHYSFS_sint32 zip_find_start_of_dir(ZIPinfo *info, const char *path,
+static PHYSFS_sint64 zip_find_start_of_dir(ZIPinfo *info, const char *path,
                                             int stop_on_first_find)
 {
-    PHYSFS_sint32 lo = 0;
-    PHYSFS_sint32 hi = (PHYSFS_sint32) (info->entryCount - 1);
+    PHYSFS_sint64 lo = 0;
+    PHYSFS_sint64 hi = (PHYSFS_sint64) (info->entryCount - 1);
     PHYSFS_sint32 middle;
     PHYSFS_uint32 dlen = (PHYSFS_uint32) strlen(path);
-    PHYSFS_sint32 retval = -1;
+    PHYSFS_sint64 retval = -1;
     const char *name;
     int rc;
 
@@ -1198,7 +1491,8 @@
                                const char *origdir, void *callbackdata)
 {
     ZIPinfo *info = ((ZIPinfo *) opaque);
-    PHYSFS_sint32 dlen, dlen_inc, max, i;
+    PHYSFS_sint32 dlen, dlen_inc;
+    PHYSFS_sint64 i, max;
 
     i = zip_find_start_of_dir(info, dname, 0);
     if (i == -1)  /* no such directory. */
@@ -1209,7 +1503,7 @@
         dlen--;
 
     dlen_inc = ((dlen > 0) ? 1 : 0) + dlen;
-    max = (PHYSFS_sint32) info->entryCount;
+    max = (PHYSFS_sint64) info->entryCount;
     while (i < max)
     {
         char *e = info->entries[i].name;
@@ -1384,7 +1678,7 @@
 
     else
     {
-        stat->filesize = entry->uncompressed_size;
+        stat->filesize = (PHYSFS_sint64) entry->uncompressed_size;
         stat->filetype = PHYSFS_FILETYPE_REGULAR;
     } /* else */
 
--- a/src/physfs.c	Sat Apr 07 21:33:18 2012 -0400
+++ b/src/physfs.c	Fri Jun 01 05:44:50 2012 -0400
@@ -589,11 +589,11 @@
 } /* doEnumStringList */
 
 
-static void __PHYSFS_bubble_sort(void *a, PHYSFS_uint32 lo, PHYSFS_uint32 hi,
-                         int (*cmpfn)(void *, PHYSFS_uint32, PHYSFS_uint32),
-                         void (*swapfn)(void *, PHYSFS_uint32, PHYSFS_uint32))
+static void __PHYSFS_bubble_sort(void *a, size_t lo, size_t hi,
+                                 int (*cmpfn)(void *, size_t, size_t),
+                                 void (*swapfn)(void *, size_t, size_t))
 {
-    PHYSFS_uint32 i;
+    size_t i;
     int sorted;
 
     do
@@ -611,13 +611,13 @@
 } /* __PHYSFS_bubble_sort */
 
 
-static void __PHYSFS_quick_sort(void *a, PHYSFS_uint32 lo, PHYSFS_uint32 hi,
-                         int (*cmpfn)(void *, PHYSFS_uint32, PHYSFS_uint32),
-                         void (*swapfn)(void *, PHYSFS_uint32, PHYSFS_uint32))
+static void __PHYSFS_quick_sort(void *a, size_t lo, size_t hi,
+                         int (*cmpfn)(void *, size_t, size_t),
+                         void (*swapfn)(void *, size_t, size_t))
 {
-    PHYSFS_uint32 i;
-    PHYSFS_uint32 j;
-    PHYSFS_uint32 v;
+    size_t i;
+    size_t j;
+    size_t v;
 
     if ((hi - lo) <= PHYSFS_QUICKSORT_THRESHOLD)
         __PHYSFS_bubble_sort(a, lo, hi, cmpfn, swapfn);
@@ -649,9 +649,9 @@
 } /* __PHYSFS_quick_sort */
 
 
-void __PHYSFS_sort(void *entries, PHYSFS_uint32 max,
-                   int (*cmpfn)(void *, PHYSFS_uint32, PHYSFS_uint32),
-                   void (*swapfn)(void *, PHYSFS_uint32, PHYSFS_uint32))
+void __PHYSFS_sort(void *entries, size_t max,
+                   int (*cmpfn)(void *, size_t, size_t),
+                   void (*swapfn)(void *, size_t, size_t))
 {
     /*
      * Quicksort w/ Bubblesort fallback algorithm inspired by code from here:
--- a/src/physfs_internal.h	Sat Apr 07 21:33:18 2012 -0400
+++ b/src/physfs_internal.h	Fri Jun 01 05:44:50 2012 -0400
@@ -299,9 +299,9 @@
  *
  *  See zip.c for an example.
  */
-void __PHYSFS_sort(void *entries, PHYSFS_uint32 max,
-                   int (*cmpfn)(void *, PHYSFS_uint32, PHYSFS_uint32),
-                   void (*swapfn)(void *, PHYSFS_uint32, PHYSFS_uint32));
+void __PHYSFS_sort(void *entries, size_t max,
+                   int (*cmpfn)(void *, size_t, size_t),
+                   void (*swapfn)(void *, size_t, size_t));
 
 /*
  * This isn't a formal error code, it's just for BAIL_MACRO.