The Allegro Wiki is migrating to github at https://github.com/liballeg/allegro_wiki/wiki

Difference between revisions of "Zipfile Reader"

From Allegro Wiki
Jump to: navigation, search
(twiki import)
 
m
Line 1: Line 1:
 
  
 
=Zipfile Reader Manual=
 
=Zipfile Reader Manual=
Line 1,109: Line 1,108:
 
  </highlightSyntax>
 
  </highlightSyntax>
  
 +
[[Category:CodeSnippet]]
 +
[[Category:LanguageC]]
 +
[[Category:LanguageCPP]]
 +
[[Category:Allegro Extension]]

Revision as of 06:19, January 26, 2007

Zipfile Reader Manual

This is a unicode-aware zipfile reader through Allegro's PACKFILE functions. It is written in C and exports two main functions for buffered reading from a .zip archive using Allegro's packfile functions. It exports three additional functions for querying the filename and filesize of the files inside the archive.

It requires Allegro 4.1.18 or better and zlib.

To use it, simply grab the zip from the download section and add zipfile.c and zipfile.h to your own project. Don't forget to link with zlib (-lz).

Main API functions

<highlightSyntax language="c">PACKFILE *pack_fopen_zip_dir(const char *filename); PACKFILE *pack_fopen_zip(const PACKFILE *zip, const char *filename);

</highlightSyntax>

The pack_fopen_zip_dir() function opens a .zip archive and reads the directory header and directory file headers (ie. the table of contents) and stores those into the returned PACKFILE structure for faster access to the file inside the archive. You may only use the returned PACKFILE for the first argument of the next function.

The pack_fopen_zip() function takes a PACKFILE pointer, as returned by pack_fopen_zip_dir(), and a filename and it opens this file inside the archive. The PACKFILE pointer returned by this function can be used with all read-only packfile functions. To load a bitmap from "allegro.pcx" in "example.zip", for example, you would use the following code:

<highlightSyntax language="c">PACKFILE *zip = pack_fopen_zip_dir("example.zip"); PACKFILE *pf = pack_fopen_zip(zip, "allegro.pcx"); BITMAP *bmp = load_bitmap_pf(pf, NULL);

</highlightSyntax>

Note that this example code isn't unicode aware (in contrary to the Zipfile readers, which aim to be, and I think they are). In real programs, you would also want to test for errors after each of the above three function calls.

Additional API functions

<highlightSyntax language="c">int zip_get_num_dir_entries(const PACKFILE *zip); char *zip_get_name(const PACKFILE *zip, int i, char *dest); long zip_get_size(const PACKFILE *zip, int i);

</highlightSyntax>

These three functions can be used to iterate through the files in the archive, like this (assuming zip is a PACKFILE pointer previously acquired by calling pack_fopen_zip_dir()):

<highlightSyntax language="c">int n = zip_get_num_dir_entries(zip); for (int i = 0; i < n; ++i) {

  char buf[512]; // The longest filename in the zip (in U_CURRENT format) must fit into this buffer.
  allegro_message("filename: %s\nfilesize: %ld\n", zip_get_name(zip, i, buf), zip_get_size(zip, i));

}

</highlightSyntax>

C++ Wrapper Class

The package contains a simple wrapper class for use in C++ programs.

<highlightSyntax language="c">class ZipFile {

  PACKFILE *zip;
public:
  ZipFile(): zip(NULL) {}
  ZipFile(const char *n): zip(openDir(n)) {}
  ~ZipFile() { close(); }
  PACKFILE *openDir(const char *n) { close(); return (zip = pack_fopen_zip_dir(n)); }
  PACKFILE *open(const char *n) const { return pack_fopen_zip(zip, n); }
  PACKFILE *getPackfile() { return zip; }
  const PACKFILE *getPackfile() const { return zip; }
  bool  isOk() const { return (bool)zip; }
  void  close() { pack_fclose(zip); }
  int   getNumDirEntries() const { return zip_get_num_dir_entries(zip); }
  char *getName(int i, char *p) const { return zip_get_name(zip, i, p); }
  long  getSize(int i) const { return zip_get_size(zip, i); }

};

</highlightSyntax>

Known Bugs/Quirks

  • The reader can only read zipfiles which do not contain a comment. (though the zipfile may contain comments on a per file basis)

Downloads & Links

zipfile.h

<highlightSyntax language="c">/* Author: Tobi Vollebregt */

/*

  1. include <std_disclaimer.h>
  "I do not accept responsibility for any effects, adverse or otherwise, 
  that this code may have on you, your computer, your sanity, your dog, 
  and anything else that you can think of. Use it at your own risk."
  • /

/* Zip archive reader functions through Allegro's PACKFILE system.

*
*  Copyright (C) 2005  Tobi Vollebregt
*  License: Allegro
*
*  Requires Allegro WIP 4.1.18 or higher.
*/
  1. ifndef __tjv_zipfile_h__
  2. define __tjv_zipfile_h__
  1. ifndef ALLEGRO_VERSION
  2. error Must #include "allegro.h" before this file.
  3. endif
  1. if 10000*ALLEGRO_VERSION+100*ALLEGRO_SUB_VERSION+ALLEGRO_WIP_VERSION<40118
  2. error Requires Allegro WIP 4.1.18 or higher.
  3. endif
  1. ifdef __cplusplus

extern "C" {

  1. endif /* defined(__cplusplus) */

/* Usage example:

*  Suppose you have a file archive.zip containing the files
*  foo and bar, you would read from them as follows:
*
*  PACKFILE *zip, *foo, *bar;
*
*  zip = pack_fopen_zip_dir("archive.zip");
*
*  if (!zip) { show error message }
*
*  foo = pack_fopen_zip(zip, "foo");
*  bar = pack_fopen_zip(zip, "bar");
*  pack_fclose(zip);
*
*  if (!foo || !bar) { show error message }
*
*  { read from packfiles foo and bar like
*    you read from any other packfile }
*
*  pack_fclose(foo);
*  pack_fclose(bar);
*/

PACKFILE *pack_fopen_zip_dir(AL_CONST char* filename); PACKFILE *pack_fopen_zip(AL_CONST PACKFILE *zip, AL_CONST char *filename);

int zip_get_num_dir_entries(AL_CONST PACKFILE *zip); char *zip_get_name(AL_CONST PACKFILE *zip, int i, char *dest); long zip_get_size(AL_CONST PACKFILE *zip, int i);

  1. ifdef __cplusplus

}; /* end of extern "C" */

/* Simple (example) wrapper class. */ class ZipFile {

  PACKFILE *zip;
public:
  ZipFile(): zip(NULL) {}
  ZipFile(const char *n): zip(openDir(n)) {}
  ~ZipFile() { close(); }
  PACKFILE *openDir(const char *n) { close(); return (zip = pack_fopen_zip_dir(n)); }
  /* [2005/03/19] Typo fixed (credits go to CGamesPlay) */
  PACKFILE *open(const char *n) const { return pack_fopen_zip(zip, n); }
  PACKFILE *getPackfile() { return zip; }
  const PACKFILE *getPackfile() const { return zip; }
  bool  isOk() const { return (bool)zip; }
  void  close() { pack_fclose(zip); }
  int   getNumDirEntries() const { return zip_get_num_dir_entries(zip); }
  char *getName(int i, char *p) const { return zip_get_name(zip, i, p); }
  long  getSize(int i) const { return zip_get_size(zip, i); }

};

  1. endif /* defined(__cplusplus) */
  1. endif /* !defined(__tjv_zipfile_h__) */
</highlightSyntax>

zipfile.c

<highlightSyntax language="c">/* Author: Tobi Vollebregt */

/*

  1. include <std_disclaimer.h>
  "I do not accept responsibility for any effects, adverse or otherwise,
  that this code may have on you, your computer, your sanity, your dog,
  and anything else that you can think of. Use it at your own risk."
  • /

/* Zip archive reader functions through Allegro's PACKFILE system.

*
*  Copyright (C) 2005  Tobi Vollebregt
*  License: Allegro
*
*  Requires Allegro WIP 4.1.18 or higher.
*/

/* Coded with the help of a quick'n dirty

*  ZIP file reader class by Javier Arevalo.
*/

/* ChangeLog:

*  2005.05.24 Fixed a minor bug causing some assertions to fail
*             when zipfile.c was compiled with assertions enabled.
*  2005.05.27 Fixed two warnings about implicit char* to Bytef* conversion.
*             (credits go to Indeterminatus for pointing this out)
*  2005.05.27 Added some "(void) parameter;" lines to suppress unused parameter
*             warnings when compiling with -Wall -W (gcc).
*  2005.05.28 Fixed a bug which caused infinite loops to occur occassionally
*             when reading data from a stored (not deflated) file. This was
*             caused by hacking the Allegro PACKFILE code to fake an arbitrary
*             EOF (the line "f->normal.todo = fh->ucSize;"). This is fixed
*             by adding wrapper functions which behave just like the functions
*             for deflated files (more consistency) and behave as much as
*             possible like Allegro's normal packfile functions, while keeping
*             a separate "todo" counter to make sure EOF is reached on end of
*             the stored file (and _not_ the zipfile).
*             (credits go to Indeterminatus for pointing this out)
*/
  1. include <allegro.h>
  2. include <zlib.h>
  3. include <string.h>
  4. include "zipfile.h"

/* Compile time assertion. */

  1. define CT_ASSERT(x, msg) \
  typedef int __tjv_cta__ ## msg ## __ [(x) ? 1 : -1];
  1. define ZIP_DIR_HEADER_SIGNATURE 0x06054b50
  2. define ZIP_DIR_FILE_HEADER_SIGNATURE 0x02014b50
  3. define ZIP_LOCAL_HEADER_SIGNATURE 0x04034b50
  4. define COMP_STORE 0
  5. define COMP_DEFLAT 8

typedef unsigned long dword; typedef unsigned short word;

CT_ASSERT(sizeof(dword) == 4, invalid_dword_size); CT_ASSERT(sizeof(word) == 2, invalid_word_size);

/* Structures must be packed. If your platform doesn't know about the pack

* #pragma's, you may try to remove the #pragma's altogether, since my code
* just won't compile if the size of (one of) the structures is invalid. */
  1. ifdef ALLEGRO_GCC
#pragma pack(push, 2)
  1. else
#pragma pack(2)
  1. endif

/* ZIP_LOCAL_HEADER: file header before the compressed data of the file */ typedef struct ZIP_LOCAL_HEADER {

  dword   sig;            /* ZIP_LOCAL_HEADER_SIGNATURE */
  word    version;
  word    flag;
  word    compression;    /* COMP_xxxx */
  word    modTime;
  word    modDate;
  dword   crc32;
  dword   cSize;
  dword   ucSize;
  word    fnameLen;       /* Filename string follows header. */
  word    xtraLen;        /* Extra field follows filename. */

} ZIP_LOCAL_HEADER;

CT_ASSERT(sizeof(ZIP_LOCAL_HEADER) == 30, invalid_zip_local_header_size);

/* ZIP_DIR_HEADER: end record of a zip archive */ typedef struct ZIP_DIR_HEADER {

  dword   sig;            /* ZIP_DIR_HEADER_SIGNATURE */
  word    nDisk;
  word    nStartDisk;
  word    nDirEntries;
  word    totalDirEntries;
  dword   dirSize;
  dword   dirOffset;
  word    cmntLen;

} ZIP_DIR_HEADER;

CT_ASSERT(sizeof(ZIP_DIR_HEADER) == 22, invalid_zip_dir_header_size);

/* ZIP_DIR_FILE_HEADER: file header in directory data (before end record) */ typedef struct ZIP_DIR_FILE_HEADER {

  dword   sig;            /* ZIP_DIR_FILE_HEADER_SIGNATURE */
  word    verMade;
  word    verNeeded;
  word    flag;
  word    compression;    /* COMP_xxxx */
  word    modTime;
  word    modDate;
  dword   crc32;
  dword   cSize;          /* Compressed size */
  dword   ucSize;         /* Uncompressed size */
  word    fnameLen;       /* Filename string follows header. */
  word    xtraLen;        /* Extra field follows filename. */
  word    cmntLen;        /* Comment field follows extra field. */
  word    diskStart;
  word    intAttr;
  dword   extAttr;
  dword   hdrOffset;

} ZIP_DIR_FILE_HEADER;

CT_ASSERT(sizeof(ZIP_DIR_FILE_HEADER) == 46, invalid_zip_dir_file_header_size);

  1. ifdef ALLEGRO_GCC
#pragma pack(pop)
  1. else
#pragma pack()
  1. endif
  1. ifdef ALLEGRO_BIG_ENDIAN

static void swap_dword(dword *_dw) {

  char *dw = (char*)_dw;
  dw[0] ^= dw[3] ^= dw[0];
  dw[1] ^= dw[2] ^= dw[1];

}

static void swap_word(word *_w) {

  char *w = (char*)_w;
  w[0] ^= w[1] ^= w[0];

}

static void fix_zip_local_header(ZIP_LOCAL_HEADER *z) {

  swap_dword(&z->sig);
  swap_word(&z->version);
  swap_word(&z->flag);
  swap_word(&z->compression);
  swap_word(&z->modTime);
  swap_word(&z->modDate);
  swap_dword(&z->crc32);
  swap_dword(&z->cSize);
  swap_dword(&z->ucSize);
  swap_word(&z->fnameLen);
  swap_word(&z->xtraLen);

}

static void fix_zip_dir_header(ZIP_DIR_HEADER *z) {

  swap_dword(&z->sig);
  swap_word(&z->nDisk);
  swap_word(&z->nStartDisk);
  swap_word(&z->nDirEntries);
  swap_word(&z->totalDirEntries);
  swap_dword(&z->dirSize);
  swap_dword(&z->dirOffset);
  swap_word(&z->cmntLen);

}

static void fix_zip_dir_file_header(ZIP_DIR_FILE_HEADER *z) {

  swap_dword(&z->sig);
  swap_word(&z->verMade);
  swap_word(&z->verNeeded);
  swap_word(&z->flag);
  swap_word(&z->compression);
  swap_word(&z->modTime);
  swap_word(&z->modDate);
  swap_dword(&z->crc32);
  swap_dword(&z->cSize);
  swap_dword(&z->ucSize);
  swap_word(&z->fnameLen);
  swap_word(&z->xtraLen);
  swap_word(&z->cmntLen);
  swap_word(&z->diskStart);
  swap_word(&z->intAttr);
  swap_dword(&z->extAttr);
  swap_dword(&z->hdrOffset);

}

  1. else /* defined(ALLEGRO_BIG_ENDIAN) */
  1. define fix_zip_local_header(z)
  2. define fix_zip_dir_header(z)
  3. define fix_zip_dir_file_header(z)
  1. endif /* !defined(ALLEGRO_BIG_ENDIAN) */

/* ZIP_DEFLATE_USERDATA: the userdata field of the PACKFILE structure returned

*  by pack_fopen_zip() points to a ZIP_DEFLATE_USERDATA structure or
*  a ZIP_STORE_USERDATA depending on whether the file was stored or deflated. */

typedef struct ZIP_DEFLATE_USERDATA {

  dword sig;              /* ZIP_LOCAL_HEADER_SIGNATURE */
  dword cSize;            /* Compressed size */
  int ungetc;             /* ungetc() buffer */
  PACKFILE *f;            /* the zip file */
  z_stream stream;        /* the zlib stream */
  int err;                /* zlib error code */
  char inbuf[4096];       /* input buffer (compressed data) */

} ZIP_DEFLATE_USERDATA;

typedef struct ZIP_STORE_USERDATA {

  dword sig;              /* ZIP_LOCAL_HEADER_SIGNATURE */
  PACKFILE *f;            /* the zip file */
  long todo;              /* how many bytes till the end of the stored file? */
  int ungetc;             /* ungetc() buffer */

} ZIP_STORE_USERDATA;

static int zip_putc(int c, void *_f); static long zip_fwrite(AL_CONST void *p, long n, void *_f);

static int zip_deflate_fclose(void *_f); static int zip_deflate_getc(void *_f); static int zip_deflate_ungetc(int ch, void *_f); static long zip_deflate_fread(void *p, long n, void *_f); static int zip_deflate_fseek(void *_f, int offset); static int zip_deflate_feof(void *_f); static int zip_deflate_ferror(void *_f);

static int zip_store_fclose(void *_f); static int zip_store_getc(void *_f); static int zip_store_ungetc(int ch, void *_f); static long zip_store_fread(void *p, long n, void *_f); static int zip_store_fseek(void *_f, int offset); static int zip_store_feof(void *_f); static int zip_store_ferror(void *_f);

static int zip_dir_fclose(void *_f); static int zip_dir_getc(void *_f); static int zip_dir_ungetc(int ch, void *_f); static int zip_dir_putc(int c, void *_f); static long zip_dir_fread(void *p, long n, void *_f); static long zip_dir_fwrite(AL_CONST void *p, long n, void *_f); static int zip_dir_fseek(void *_f, int offset); static int zip_dir_feof(void *_f); static int zip_dir_ferror(void *_f);

/* userdata points to a ZIP_DEFLATE_USERDATA structure in this case */

static PACKFILE_VTABLE zip_deflate_vtable = {

  zip_deflate_fclose,
  zip_deflate_getc,
  zip_deflate_ungetc,
  zip_deflate_fread,
  zip_putc,
  zip_fwrite,
  zip_deflate_fseek,
  zip_deflate_feof,
  zip_deflate_ferror

};

/* userdata points to a PACKFILE structure in this case */ /* this is just a wrapper to the normal packfile functions implementing an EOF before the end of the zipfile. */

static PACKFILE_VTABLE zip_store_vtable = {

  zip_store_fclose,
  zip_store_getc,
  zip_store_ungetc,
  zip_store_fread,
  zip_putc,
  zip_fwrite,
  zip_store_fseek,
  zip_store_feof,
  zip_store_ferror

};

/* userdata points to a chunk of memory containing, in the following order:

*  dh:       ZIP_DIR_HEADER dh;
*  dir_data: (ZIP_DIR_FILE_HEADER+fname+xtra+cmnt)[dh.nDirEntries]
*              (the size of the entire dir_data field is dh.dirSize)
*  dir:      ZIP_DIR_FILE_HEADER* dir[dh.nDirEntries];
*              (currently unused, I must still add functions to walk
*              through the directory entries).
*  zipfname: NULL-terminated U_CURRENT string giving the name of the zip file.
*              (as passed to pack_fopen_zip_dir().)
*/

static PACKFILE_VTABLE zip_dir_vtable = {

  zip_dir_fclose,
  zip_dir_getc,
  zip_dir_ungetc,
  zip_dir_fread,
  zip_dir_putc,
  zip_dir_fwrite,
  zip_dir_fseek,
  zip_dir_feof,
  zip_dir_ferror

};

static int zip_deflate_fclose(void *_f) {

  ZIP_DEFLATE_USERDATA *z = (ZIP_DEFLATE_USERDATA*)_f;
  ASSERT(z);
  ASSERT(z->sig == ZIP_LOCAL_HEADER_SIGNATURE);
  inflateEnd(&z->stream);
  pack_fclose(z->f);
  free(_f);
  return 0;

}

static int zip_deflate_getc(void *_f) {

  unsigned char ch;
  ASSERT(_f);
  ASSERT(((ZIP_DEFLATE_USERDATA*)_f)->sig == ZIP_LOCAL_HEADER_SIGNATURE);
  return zip_deflate_fread(&ch, 1, _f) == 1 ? ch : EOF;

}

static int zip_deflate_ungetc(int ch, void *_f) {

  ZIP_DEFLATE_USERDATA *z = (ZIP_DEFLATE_USERDATA*)_f;
  ASSERT(z);
  ASSERT(z->sig == ZIP_LOCAL_HEADER_SIGNATURE);
  return (z->ungetc = ch); /* can't fail */

}

static int zip_putc(int c, void *_f) {

  /* this function is used for deflated and stored files. */
  ASSERT(_f);
  ASSERT(((ZIP_DEFLATE_USERDATA*)_f)->sig == ZIP_LOCAL_HEADER_SIGNATURE);
  (void) c; /* suppress unused parameter warning */
  return EOF; /* read-only */

}

/* helper function for zip_deflate_fread(). Returns FALSE on error, TRUE on success. */ static int zip_refill_buffer(ZIP_DEFLATE_USERDATA *z) {

  ASSERT(z);
  ASSERT(z->sig == ZIP_LOCAL_HEADER_SIGNATURE);
  if (z->stream.avail_in == 0)
  {
     long n = MIN(sizeof(z->inbuf), z->cSize - z->stream.total_in);
     z->stream.next_in = (Bytef *)z->inbuf;
     z->stream.avail_in = n;
     if (pack_fread(z->inbuf, n, z->f) != n)
     {
        z->err = Z_DATA_ERROR;
        return FALSE;
     }
  }
  return TRUE;

}

/* workhorse function, used by zip_deflate_getc() and zip_deflate_fseek() */ static long zip_deflate_fread(void *p, long n, void *_f) {

  ZIP_DEFLATE_USERDATA *z = (ZIP_DEFLATE_USERDATA*)_f;
  ASSERT(p);
  ASSERT(n >= 0);
  ASSERT(z);
  ASSERT(z->sig == ZIP_LOCAL_HEADER_SIGNATURE);
  if (n && z->ungetc >= 0)
  {
     char *q = (char*)p;
     *q = z->ungetc;
     p = q + 1;
     --n;
     z->ungetc = -1;
  }
  if (n)
  {
     z->stream.next_out = (Bytef *)p;
     z->stream.avail_out = n;
     z->err = Z_OK;
     while (z->stream.avail_out && z->err == Z_OK && zip_refill_buffer(z))
        z->err = inflate(&z->stream, Z_NO_FLUSH);
     return n - z->stream.avail_out;
  }
  return 0;

}

static long zip_fwrite(AL_CONST void *p, long n, void *_f) {

  /* this function is used for deflated and stored files. */
  ASSERT(_f);
  ASSERT(((ZIP_DEFLATE_USERDATA*)_f)->sig == ZIP_LOCAL_HEADER_SIGNATURE);
  (void) p; (void) n; /* suppress unused parameter warning */
  return EOF; /* read-only */

}

static int zip_deflate_fseek(void *_f, int offset) {

  ASSERT(offset >= 0);
  ASSERT(_f);
  ASSERT(((ZIP_DEFLATE_USERDATA*)_f)->sig == ZIP_LOCAL_HEADER_SIGNATURE);
  while (offset--) if (zip_deflate_getc(_f) == EOF) return EOF;
  return 0;

}

static int zip_deflate_feof(void *_f) {

  ZIP_DEFLATE_USERDATA *z = (ZIP_DEFLATE_USERDATA*)_f;
  ASSERT(z);
  ASSERT(z->sig == ZIP_LOCAL_HEADER_SIGNATURE);
  return z->ungetc < 0 && z->err == Z_STREAM_END;

}

static int zip_deflate_ferror(void *_f) {

  ZIP_DEFLATE_USERDATA *z = (ZIP_DEFLATE_USERDATA*)_f;
  ASSERT(z);
  ASSERT(z->sig == ZIP_LOCAL_HEADER_SIGNATURE);
  if (z->ungetc >= 0 || z->err == Z_STREAM_END) return 0;
  return z->err;

}

static int zip_store_fclose(void *_f) {

  ZIP_STORE_USERDATA *z = (ZIP_STORE_USERDATA*)_f;
  ASSERT(z);
  ASSERT(z->sig == ZIP_LOCAL_HEADER_SIGNATURE);
  pack_fclose(z->f);
  free(_f);
  return 0;

}

static int zip_store_getc(void *_f) {

  unsigned char ch;
  ASSERT(_f);
  ASSERT(((ZIP_DEFLATE_USERDATA*)_f)->sig == ZIP_LOCAL_HEADER_SIGNATURE);
  return zip_store_fread(&ch, 1, _f) == 1 ? ch : EOF;

}

static int zip_store_ungetc(int ch, void *_f) {

  ZIP_DEFLATE_USERDATA *z = (ZIP_DEFLATE_USERDATA*)_f;
  ASSERT(z);
  ASSERT(z->sig == ZIP_LOCAL_HEADER_SIGNATURE);
  return (z->ungetc = ch); /* can't fail */

}

/* workhorse function, used by zip_deflate_getc() and zip_deflate_fseek() */ static long zip_store_fread(void *p, long n, void *_f) {

  ZIP_STORE_USERDATA *z = (ZIP_STORE_USERDATA*)_f;
  long bytes_read;
  ASSERT(p);
  ASSERT(n >= 0);
  ASSERT(z);
  ASSERT(z->sig == ZIP_LOCAL_HEADER_SIGNATURE);
  if (n && z->ungetc >= 0)
  {
     char *q = (char*)p;
     *q = z->ungetc;
     p = q + 1;
     --n;
     z->ungetc = -1;
  }
  bytes_read = pack_fread(p, MIN(n, z->todo), z->f);
  z->todo -= bytes_read;
  return bytes_read;

}

static int zip_store_fseek(void *_f, int offset) {

  ASSERT(offset >= 0);
  ASSERT(_f);
  ASSERT(((ZIP_STORE_USERDATA*)_f)->sig == ZIP_LOCAL_HEADER_SIGNATURE);
  while (offset--) if (zip_store_getc(_f) == EOF) return EOF;
  return 0;

}

static int zip_store_feof(void *_f) {

  ZIP_STORE_USERDATA *z = (ZIP_STORE_USERDATA*)_f;
  ASSERT(z);
  ASSERT(z->sig == ZIP_LOCAL_HEADER_SIGNATURE);
  /* if the zipfile ends it is an error, not an EOF;
  only if the zipped file ends it's an EOF. */
  return z->ungetc < 0 && z->todo <= 0;

}

static int zip_store_ferror(void *_f) {

  ZIP_STORE_USERDATA *z = (ZIP_STORE_USERDATA*)_f;
  ASSERT(z);
  ASSERT(z->sig == ZIP_LOCAL_HEADER_SIGNATURE);
  if (z->ungetc >= 0 || z->todo <= 0) return 0;
  /* it is an error if the zipfile ends before the end given in the headers */
  return pack_ferror(z->f) || pack_feof(z->f);

}

static int zip_dir_fclose(void *_f) {

  ASSERT(_f);
  ASSERT(((ZIP_DIR_HEADER*)_f)->sig == ZIP_DIR_HEADER_SIGNATURE);
  free(_f);
  return 0;

}

static int zip_dir_getc(void *_f) {

  ASSERT(_f);
  ASSERT(((ZIP_DIR_HEADER*)_f)->sig == ZIP_DIR_HEADER_SIGNATURE);
  ASSERT(FALSE);
  return EOF;

}

static int zip_dir_ungetc(int ch, void *_f) {

  ASSERT(_f);
  ASSERT(((ZIP_DIR_HEADER*)_f)->sig == ZIP_DIR_HEADER_SIGNATURE);
  ASSERT(FALSE);
  (void) ch; /* suppress unused parameter warning */
  return EOF;

}

static int zip_dir_putc(int c, void *_f) {

  ASSERT(_f);
  ASSERT(((ZIP_DIR_HEADER*)_f)->sig == ZIP_DIR_HEADER_SIGNATURE);
  ASSERT(FALSE);
  (void) c; /* suppress unused parameter warning */
  return EOF;

}

static long zip_dir_fread(void *p, long n, void *_f) {

  ASSERT(_f);
  ASSERT(((ZIP_DIR_HEADER*)_f)->sig == ZIP_DIR_HEADER_SIGNATURE);
  ASSERT(FALSE);
  (void) p; (void) n; /* suppress unused parameter warning */
  return EOF;

}

static long zip_dir_fwrite(AL_CONST void *p, long n, void *_f) {

  ASSERT(_f);
  ASSERT(((ZIP_DIR_HEADER*)_f)->sig == ZIP_DIR_HEADER_SIGNATURE);
  ASSERT(FALSE);
  (void) p; (void) n; /* suppress unused parameter warning */
  return EOF;

}

static int zip_dir_fseek(void *_f, int offset) {

  ASSERT(_f);
  ASSERT(((ZIP_DIR_HEADER*)_f)->sig == ZIP_DIR_HEADER_SIGNATURE);
  ASSERT(FALSE);
  (void) offset; /* suppress unused parameter warning */
  return EOF;

}

static int zip_dir_feof(void *_f) {

  ASSERT(_f);
  ASSERT(((ZIP_DIR_HEADER*)_f)->sig == ZIP_DIR_HEADER_SIGNATURE);
  ASSERT(FALSE);
  return TRUE;

}

static int zip_dir_ferror(void *_f) {

  ASSERT(_f);
  ASSERT(((ZIP_DIR_HEADER*)_f)->sig == ZIP_DIR_HEADER_SIGNATURE);
  ASSERT(FALSE);
  return TRUE;

}

/* pack_fopen_zip:

*  Opens a PACKFILE to read from a file which resides inside a zip archive.
*  You must give it the PACKFILE representing the zip file (as returned by
*  pack_fopen_zip_dir) and the path/filename inside the archive.
*/

PACKFILE *pack_fopen_zip(AL_CONST PACKFILE *zip, AL_CONST char *filename) {

  char fname[512]; /* U_ASCII format, UNIX slashes */
  char tmp[512];
  AL_CONST ZIP_DIR_HEADER *dh;
  AL_CONST char *dir_data, *pfh;
  AL_CONST ZIP_DIR_FILE_HEADER **dir;
  AL_CONST char *zipfilename; /* U_CURRENT format */
  int fnameLen, i;
  ASSERT(zip);
  ASSERT(zip->userdata);
  ASSERT(filename);
  /* Setup pointers. */
  dh = (AL_CONST ZIP_DIR_HEADER*)zip->userdata;
  ASSERT(dh->sig == ZIP_DIR_HEADER_SIGNATURE);
  dir_data = (AL_CONST char*)(dh + 1);
  dir = (AL_CONST ZIP_DIR_FILE_HEADER**)(dir_data + dh->dirSize);
  zipfilename = (AL_CONST char*)(dir + dh->nDirEntries);
  /* We need the fname converted to ASCII *and* stored in fname. */
  do_uconvert(filename, U_CURRENT, fname, U_ASCII, sizeof(fname));
  fnameLen = strlen(fname);
  if (fnameLen > (int)sizeof(fname)) fnameLen = sizeof(fname);
  /* Convert DOS backslashes to UNIX slashes */
  for (i = 0; i < fnameLen; ++i)
     if (fname[i] == '\\') fname[i] = '/';
  /* Look for our entry. */
  for (i = 0, pfh = dir_data; i < dh->nDirEntries; i++)
  {
     AL_CONST ZIP_DIR_FILE_HEADER *fh = (ZIP_DIR_FILE_HEADER*)pfh;
     /* Check the directory entry integrity. */
     ASSERT(fh->sig == ZIP_DIR_FILE_HEADER_SIGNATURE);
     /* Is this our entry? */
     pfh += sizeof(*fh);
     if (fh->fnameLen == fnameLen && !memcmp(pfh, fname, fnameLen))
     {
        PACKFILE *f;
        ZIP_LOCAL_HEADER h;
        ZIP_DEFLATE_USERDATA *z;
        /* Go to the actual file and read the local header. */
        if (!(f = pack_fopen(zipfilename, "r")) ||
           pack_fseek(f, fh->hdrOffset) ||
           pack_fread(&h, sizeof(h), f) != sizeof(h))
        {
           TRACE("%s#%s: %s\n", uconvert_toascii(zipfilename, tmp), fname, strerror(*allegro_errno));
           pack_fclose(f);
           return NULL;
        }
        /* Fix endianness */
        fix_zip_local_header(&h);
        /* Check */
        if (h.sig != ZIP_LOCAL_HEADER_SIGNATURE)
        {
           TRACE("%s#%s: invalid ZIP_LOCAL_HEADER signature\n", uconvert_toascii(zipfilename, tmp), fname);
           pack_fclose(f);
           return NULL;
        }
        /* Skip extra fields */
        if (pack_fseek(f, h.fnameLen + h.xtraLen))
        {
           TRACE("%s#%s: %s\n", uconvert_toascii(zipfilename, tmp), fname, strerror(*allegro_errno));
           pack_fclose(f);
           return NULL;
        }
        if (h.compression == COMP_STORE)
        {
           ZIP_STORE_USERDATA *z2;
           /* Fake an early EOF and return the zip PACKFILE. */
           /* This appeared to be erroneous! */
           /* f->normal.todo = fh->ucSize; */
           if (!(z2 = (ZIP_STORE_USERDATA*)malloc(sizeof(ZIP_STORE_USERDATA))))
           {
              TRACE("%s#%s: %s\n", uconvert_toascii(zipfilename, tmp), fname, strerror(*allegro_errno));
              pack_fclose(f);
              return NULL;
           }
           z2->sig = ZIP_LOCAL_HEADER_SIGNATURE;
           z2->f = f;
           z2->todo = h.ucSize;
           z2->ungetc = -1;
           return pack_fopen_vtable(&zip_store_vtable, z2);
        }
        else if (h.compression != COMP_DEFLAT)
        {
           TRACE("%s#%s: unknown compression\n", uconvert_toascii(zipfilename, tmp), fname);
           pack_fclose(f);
           return NULL;
        }
        /* Alloc compressed data buffer and read the whole stream */
        if (!(z = (ZIP_DEFLATE_USERDATA*)malloc(sizeof(ZIP_DEFLATE_USERDATA))))
        {
           TRACE("%s#%s: %s\n", uconvert_toascii(zipfilename, tmp), fname, strerror(*allegro_errno));
           pack_fclose(f);
           return NULL;
        }
        memset(z, 0, sizeof(ZIP_DEFLATE_USERDATA));
        z->sig = ZIP_LOCAL_HEADER_SIGNATURE;
        z->cSize = h.cSize;
        z->ungetc = -1;
        z->f = f;
        /* Perform inflation. wbits < 0 indicates no zlib header inside the data. */
        zip_refill_buffer(z);
        z->err = inflateInit2(&z->stream, -MAX_WBITS);
        if (z->err != Z_OK)
        {
           TRACE("%s#%s: %s\n", uconvert_toascii(zipfilename, tmp), fname, z->stream.msg);
           zip_deflate_fclose(z);
           return NULL;
        }
        return pack_fopen_vtable(&zip_deflate_vtable, z);
     }
     /* Skip extra and comment fields. */
     pfh += fh->fnameLen + fh->xtraLen + fh->cmntLen;
  }
  /* No such entry, return error. */
  *allegro_errno = ENOENT;
  TRACE("%s#%s: %s\n", uconvert_toascii(zipfilename, tmp), fname, strerror(*allegro_errno));
  return NULL;

}

/* pack_fopen_zip_dir:

*  Opens the zip archive specified by filename and reads the directory and
*  directory file headers. These headers are stored in a PACKFILE structure
*  and must be passed to pack_fopen_zip() to actually read a file from the
*  zip archive. Returns NULL on error, the zip packfile on success.
*/

PACKFILE *pack_fopen_zip_dir(AL_CONST char* filename) {

  char tmp[512];
  PACKFILE *f;
  long dhOffset;
  ZIP_DIR_HEADER dh;
  ZIP_DIR_FILE_HEADER *fh, **dir;
  void *data;
  char *dir_data, *pfh;
  int fnameLen, i;
  ASSERT(filename);
  /* Assuming no extra comment at the end, read the whole end record. */
  if (!(f = pack_fopen(filename, "r")) ||
     pack_fseek(f, (dhOffset = f->normal.todo - sizeof(ZIP_DIR_HEADER))) ||
     pack_fread(&dh, sizeof(dh), f) != sizeof(dh))
  {
     TRACE("%s: %s\n", uconvert_toascii(filename, tmp), strerror(*allegro_errno));
     pack_fclose(f);
     return NULL;
  }
  pack_fclose(f);
  /* Fix endianness */
  fix_zip_dir_header(&dh);
  /* Check */
  if (dh.sig != ZIP_DIR_HEADER_SIGNATURE)
  {
     TRACE("%s: invalid ZIP_DIR_HEADER signature\n", uconvert_toascii(filename, tmp));
     return NULL;
  }
  /* Go to the beginning of the directory. */
  if (!(f = pack_fopen(filename, "r")) ||
     pack_fseek(f, dhOffset - dh.dirSize))
  {
     TRACE("%s: %s\n", uconvert_toascii(filename, tmp), strerror(*allegro_errno));
     pack_fclose(f);
     return NULL;
  }
  /* Allocate the data buffer and read and setup the whole thing. */
  fnameLen = ustrsizez(filename);
  data = malloc(sizeof(dh) + dh.dirSize + dh.nDirEntries*sizeof(*dir) + fnameLen);
  if (!data) { pack_fclose(f); return NULL; }
  memcpy(data, &dh, sizeof(dh));
  dir_data = (char*)((ZIP_DIR_HEADER*)data + 1);
  pack_fread(dir_data, dh.dirSize, f);
  pack_fclose(f);
  dir = (ZIP_DIR_FILE_HEADER**)(dir_data + dh.dirSize);
  memset(dir, 0, dh.nDirEntries*sizeof(*dir));
  memcpy(dir + dh.nDirEntries, filename, fnameLen);
  /* Now process each entry. */
  for (i = 0, pfh = dir_data; i < dh.nDirEntries; ++i)
  {
     /* Store the address of nth file for quicker access. */
     dir[i] = fh = (ZIP_DIR_FILE_HEADER*)pfh;
     /* Fix endianness */
     fix_zip_dir_file_header(fh);
     /* Check the directory entry integrity. */
     if (fh->sig != ZIP_DIR_FILE_HEADER_SIGNATURE)
     {
        TRACE("%s: invalid ZIP_DIR_FILE_HEADER signature\n", uconvert_toascii(filename, tmp));
        free(data);
        return NULL;
     }
     /* Skip name, extra and comment fields. */
     pfh += sizeof(*fh) + fh->fnameLen + fh->xtraLen + fh->cmntLen;
  }
  return pack_fopen_vtable(&zip_dir_vtable, data);

}

int zip_get_num_dir_entries(AL_CONST PACKFILE *zip) {

  AL_CONST ZIP_DIR_HEADER *dh;
  ASSERT(zip);
  ASSERT(zip->userdata);
  /* Setup pointer. */
  dh = (AL_CONST ZIP_DIR_HEADER*)zip->userdata;
  ASSERT(dh->sig == ZIP_DIR_HEADER_SIGNATURE);
  return dh->nDirEntries;

}

char *zip_get_name(AL_CONST PACKFILE *zip, int i, char *dest) {

  char tmp[512];
  AL_CONST ZIP_DIR_HEADER *dh;
  AL_CONST char *dir_data;
  AL_CONST ZIP_DIR_FILE_HEADER **dir;
  int fnameLen;
  ASSERT(zip);
  ASSERT(zip->userdata);
  ASSERT(dest);
  /* Setup pointers. */
  dh = (AL_CONST ZIP_DIR_HEADER*)zip->userdata;
  ASSERT(dh->sig == ZIP_DIR_HEADER_SIGNATURE);
  dir_data = (AL_CONST char*)(dh + 1);
  dir = (AL_CONST ZIP_DIR_FILE_HEADER**)(dir_data + dh->dirSize);
  ASSERT(i >= 0);
  ASSERT(i < dh->nDirEntries);
  /* Copy to dest (unicode-awaraarrrg) */
  fnameLen = MIN(dir[i]->fnameLen, sizeof(tmp) - 1);
  memcpy(tmp, dir[i] + 1, fnameLen);
  tmp[fnameLen] = 0;
  do_uconvert(tmp, U_ASCII, dest, U_CURRENT, INT_MAX);
  return dest;

}

long zip_get_size(AL_CONST PACKFILE *zip, int i) {

  AL_CONST ZIP_DIR_HEADER *dh;
  AL_CONST char *dir_data;
  AL_CONST ZIP_DIR_FILE_HEADER **dir;
  ASSERT(zip);
  ASSERT(zip->userdata);
  /* Setup pointers. */
  dh = (AL_CONST ZIP_DIR_HEADER*)zip->userdata;
  ASSERT(dh->sig == ZIP_DIR_HEADER_SIGNATURE);
  dir_data = (AL_CONST char*)(dh + 1);
  dir = (AL_CONST ZIP_DIR_FILE_HEADER**)(dir_data + dh->dirSize);
  ASSERT(i >= 0);
  ASSERT(i < dh->nDirEntries);
  return dir[i]->ucSize;

}

</highlightSyntax>