summaryrefslogtreecommitdiff
path: root/samus/sa_zip.c
diff options
context:
space:
mode:
Diffstat (limited to 'samus/sa_zip.c')
-rw-r--r--samus/sa_zip.c463
1 files changed, 463 insertions, 0 deletions
diff --git a/samus/sa_zip.c b/samus/sa_zip.c
new file mode 100644
index 00000000..a94f82e0
--- /dev/null
+++ b/samus/sa_zip.c
@@ -0,0 +1,463 @@
+/*
+ * Support for a subset of PKZIP format v4.5:
+ * - no encryption
+ * - no multi-disk
+ * - only Store and Deflate
+ * - ZIP64 format (long long sizes and offsets) [TODO]
+ */
+
+#include "fitz.h"
+#include "samus.h"
+
+typedef struct sa_zip_s sa_zip;
+typedef struct sa_zipent_s sa_zipent;
+
+struct sa_zipent_s
+{
+ unsigned offset;
+ char *name;
+ unsigned csize;
+ unsigned usize;
+};
+
+struct sa_zip_s
+{
+ fz_file *file;
+ int len, cap;
+ sa_zipent *table;
+};
+
+typedef unsigned char byte;
+typedef unsigned short ushort;
+typedef unsigned long ulong;
+
+static inline ushort read2(fz_file *f)
+{
+ byte a = fz_readbyte(f);
+ byte b = fz_readbyte(f);
+ return (b << 8) | a;
+}
+
+static inline ulong read4(fz_file *f)
+{
+ byte a = fz_readbyte(f);
+ byte b = fz_readbyte(f);
+ byte c = fz_readbyte(f);
+ byte d = fz_readbyte(f);
+ return (d << 24) | (c << 16) | (b << 8) | a;
+}
+
+static fz_error *growzip(sa_zip *zip)
+{
+ sa_zipent *newtab;
+ int newcap;
+
+ if (zip->cap)
+ newcap = zip->cap * 2;
+ else
+ newcap = 100;
+
+ newtab = fz_realloc(zip->table, newcap * sizeof(sa_zipent));
+ if (!newtab)
+ return fz_outofmem;
+
+ memset(newtab + zip->cap, 0, (newcap - zip->cap) * sizeof(sa_zipent));
+ zip->cap = newcap;
+ zip->table = newtab;
+
+ return nil;
+}
+
+static fz_error *scanzipent(sa_zip *zip, sa_zipent *ent)
+{
+ ulong csize, usize;
+ ulong namesize, metasize;
+
+ (void) read2(zip->file); /* version */
+ (void) read2(zip->file); /* general */
+ (void) read2(zip->file); /* method */
+ (void) read2(zip->file); /* time */
+ (void) read2(zip->file); /* date */
+ (void) read4(zip->file); /* crc-32 */
+ csize = read4(zip->file);
+ usize = read4(zip->file);
+ namesize = read2(zip->file);
+ metasize = read2(zip->file);
+
+ ent->name = fz_malloc(namesize + 1);
+ if (!ent->name)
+ return fz_outofmem;
+
+ fz_read(zip->file, ent->name, namesize);
+ ent->name[namesize] = 0;
+ ent->csize = csize;
+ ent->usize = usize;
+
+ fz_seek(zip->file, metasize, 1);
+ fz_seek(zip->file, csize, 1);
+
+ return nil;
+}
+
+static fz_error *scanzip(sa_zip *zip)
+{
+ fz_error *error;
+ ulong offset;
+ ulong sign;
+
+ fz_seek(zip->file, 0, 0);
+
+ while (1)
+ {
+ offset = fz_tell(zip->file);
+
+ sign = read4(zip->file);
+
+ switch (sign)
+ {
+
+ /* local file header */
+ case 0x04034b50:
+ if (zip->len + 1 > zip->cap)
+ {
+ error = growzip(zip);
+ if (error)
+ return error;
+ }
+
+ zip->table[zip->len].offset = offset;
+
+ error = scanzipent(zip, zip->table + zip->len);
+ if (error)
+ return error;
+
+ zip->len ++;
+
+ break;
+
+ /* data descriptor */
+ case 0x08074b50:
+ (void) read4(zip->file); /* crc-32 */
+ (void) read4(zip->file); /* compressed size */
+ (void) read4(zip->file); /* uncompressed size */
+ break;
+
+ /* central directory */
+ case 0x02014b50:
+ return fz_ferror(zip->file);
+
+ default:
+ return fz_throw("ioerror: unknown zip signature");
+ }
+ }
+}
+
+static fz_error *readzipdir(sa_zip *zip, int startoffset)
+{
+ ulong sign;
+ ulong csize, usize;
+ ulong namesize, metasize, comsize;
+ ulong offset;
+ int i;
+
+ fz_seek(zip->file, startoffset, 0);
+
+ for (i = 0; i < zip->len; i++)
+ {
+ sign = read4(zip->file);
+ if (sign != 0x02014b50)
+ return fz_throw("ioerror: unknown zip signature");
+
+ (void) read2(zip->file); /* version made by */
+ (void) read2(zip->file); /* version to extract */
+ (void) read2(zip->file); /* general */
+ (void) read2(zip->file); /* method */
+ (void) read2(zip->file); /* last mod file time */
+ (void) read2(zip->file); /* last mod file date */
+ (void) read4(zip->file); /* crc-32 */
+ csize = read4(zip->file);
+ usize = read4(zip->file);
+ namesize = read2(zip->file);
+ metasize = read2(zip->file);
+ comsize = read2(zip->file);
+ (void) read2(zip->file); /* disk number start */
+ (void) read2(zip->file); /* int file atts */
+ (void) read4(zip->file); /* ext file atts */
+ offset = read4(zip->file);
+
+ zip->table[i].offset = offset;
+ zip->table[i].name = fz_malloc(namesize + 1);
+ zip->table[i].csize = csize;
+ zip->table[i].usize = usize;
+ if (!zip->table[i].name)
+ return fz_outofmem;
+
+ fz_read(zip->file, zip->table[i].name, namesize);
+ zip->table[i].name[namesize] = 0;
+
+ fz_seek(zip->file, metasize, 1);
+ fz_seek(zip->file, comsize, 1);
+ }
+
+ return fz_ferror(zip->file);
+}
+
+static fz_error *readzipendofdir(sa_zip *zip, int startoffset)
+{
+ ulong sign;
+ ulong count;
+ ulong offset;
+
+ fz_seek(zip->file, startoffset, 0);
+
+ sign = read4(zip->file);
+ if (sign != 0x06054b50)
+ return fz_throw("ioerror: unknown zip signature");
+
+ (void) read2(zip->file); /* this disk */
+ (void) read2(zip->file); /* start disk */
+ (void) read2(zip->file); /* ents in this disk */
+ count = read2(zip->file); /* ents in central directory */
+ (void) read4(zip->file); /* size of central directory */
+ offset = read4(zip->file); /* offset to central directory */
+
+ zip->len = zip->cap = count;
+ zip->table = fz_malloc(zip->cap * sizeof(sa_zipent));
+ if (!zip->table)
+ return fz_outofmem;
+
+ memset(zip->table, 0, zip->cap * sizeof(sa_zipent));
+
+ return readzipdir(zip, offset);
+}
+
+static fz_error *findzipendofdir(sa_zip *zip)
+{
+ byte buf[512];
+ int filesize;
+ int maxback;
+ int backread;
+ int offset;
+ int len;
+ int i;
+
+ filesize = fz_seek(zip->file, 0, 2);
+ if (filesize == -1)
+ return fz_ferror(zip->file);
+
+ maxback = MIN(filesize, 0xFFFF + sizeof buf);
+
+ backread = MIN(maxback, sizeof buf);
+ while (backread < maxback)
+ {
+ fz_seek(zip->file, filesize - backread, 0);
+ len = fz_read(zip->file, buf, sizeof buf);
+ if (len < 0)
+ return fz_ferror(zip->file);
+
+ for (i = len - 4; i > 0; i--)
+ {
+ if (buf[i+0] == 0x50 && buf[i+1] == 0x4b &&
+ buf[i+2] == 0x05 && buf[i+3] == 0x06)
+ {
+ offset = filesize - backread + i;
+ return readzipendofdir(zip, offset);
+ }
+ }
+
+ backread += sizeof buf - 4;
+ }
+
+ return fz_throw("ioerror: no 'end of central directory' in zip");
+}
+
+/*
+ * Open a ZIP archive for reading.
+ * Load the table of contents.
+ */
+fz_error *
+sa_openzip(sa_zip **zipp, char *filename)
+{
+ fz_error *error;
+ sa_zip *zip;
+
+ zip = *zipp = fz_malloc(sizeof(sa_zip));
+ if (!zip)
+ return fz_outofmem;
+
+ zip->file = nil;
+ zip->len = 0;
+ zip->cap = 0;
+ zip->table = nil;
+
+ error = fz_openfile(&zip->file, filename, FZ_READ);
+ if (error)
+ return error;
+
+ error = findzipendofdir(zip);
+ if (error)
+ {
+ fz_warn("%s", error->msg);
+ fz_droperror(error);
+ return scanzip(zip);
+ }
+
+ return nil;
+}
+
+/*
+ * Free the table of contents and close the underlying file.
+ */
+void
+sa_closezip(sa_zip *zip)
+{
+ int i;
+
+ if (zip->file)
+ fz_closefile(zip->file);
+
+ for (i = 0; i < zip->len; i++)
+ if (zip->table[i].name)
+ fz_free(zip->table[i].name);
+
+ fz_free(zip->table);
+}
+
+/*
+ * Print a table of contents of the zip archive
+ */
+void
+sa_debugzip(sa_zip *zip)
+{
+ int i;
+
+ for (i = 0; i < zip->len; i++)
+ {
+ printf("%6u ", zip->table[i].csize);
+ printf("%6u ", zip->table[i].usize);
+ printf("%s\n", zip->table[i].name);
+ }
+}
+
+/*
+ * Seek and push decoding filter to read an individual file in the zip archive.
+ */
+fz_error *
+sa_openzipstream(sa_zip *zip, char *name)
+{
+ fz_error *error;
+ fz_filter *filter;
+ fz_obj *obj;
+ ulong sign, version, general, method;
+ ulong csize, usize;
+ ulong namesize, metasize;
+ int t;
+ int i;
+
+ for (i = 0; i < zip->len; i++)
+ {
+ if (!strcmp(name, zip->table[i].name))
+ {
+ t = fz_seek(zip->file, zip->table[i].offset, 0);
+ if (t < 0)
+ return fz_ferror(zip->file);
+
+ sign = read4(zip->file);
+ if (sign != 0x04034b50)
+ return fz_throw("ioerror: unknown zip signature");
+
+ version = read2(zip->file);
+ general = read2(zip->file);
+ method = read2(zip->file);
+ (void) read2(zip->file); /* time */
+ (void) read2(zip->file); /* date */
+ (void) read4(zip->file); /* crc-32 */
+ csize = read4(zip->file);
+ usize = read4(zip->file);
+ namesize = read2(zip->file);
+ metasize = read2(zip->file);
+
+ fz_seek(zip->file, namesize, 1);
+ fz_seek(zip->file, metasize, 1);
+
+ if ((version & 0xff) > 45)
+ return fz_throw("ioerror: unsupported zip version");
+ if (general & 0x0001)
+ return fz_throw("ioerror: encrypted zip entry");
+
+ switch (method)
+ {
+ case 0:
+printf("null filter\n");
+ error = fz_newnullfilter(&filter, csize);
+ break;
+ case 8:
+printf("flated filter\n");
+ error = fz_packobj(&obj, "<</ZIP true>>");
+ if (error)
+ return error;
+ error = fz_newflated(&filter, obj);
+ fz_dropobj(obj);
+ break;
+ default:
+ error = fz_throw("ioerror: unsupported compression method");
+ break;
+ }
+ if (error)
+ return error;
+
+ error = fz_pushfilter(zip->file, filter);
+ fz_dropfilter(filter);
+ if (error)
+ return error;
+
+ return nil;
+ }
+ }
+
+ return fz_throw("ioerror: file not found in zip: '%s'", name);
+}
+
+/*
+ * Pop decompression filter and clean up after reading a file in the archive.
+ */
+void
+sa_closezipstream(sa_zip *zip)
+{
+ fz_popfilter(zip->file);
+}
+
+
+int main(int argc, char **argv)
+{
+ fz_error *error;
+ fz_buffer *buf;
+ sa_zip *zip;
+ int i;
+
+ error = sa_openzip(&zip, argv[1]);
+ if (error)
+ fz_abort(error);
+
+ sa_debugzip(zip);
+
+ for (i = 2; i < argc; i++)
+ {
+ error = sa_openzipstream(zip, argv[i]);
+ if (error)
+ fz_abort(error);
+ error = fz_readfile(&buf, zip->file);
+ if (error)
+ fz_abort(error);
+ sa_closezipstream(zip);
+
+ fwrite(buf->rp, 1, buf->wp - buf->rp, stdout);
+
+ fz_dropbuffer(buf);
+ }
+
+ sa_closezip(zip);
+
+ return 0;
+}
+