// Zipofig. A quick-n-dirty tool to rip files from ZIP archives.
// Please note that files are extracted but not uncompressed.
// For more details visit http://www.literatecode.com 
//
// Written by Ilya O. Levin, Nov 2003
//
// How to compile:
//                     cl zipofig.c /RELEASE
//
// Run without parameters to see help on usage.
//
// Compatibility notes:
// Zipofig was written to be compiled with MS Visual C compiler. 
// If you want to compile Zipofig with different compiler then
// please address the usage of __int64, _lseeki64, _read, _close
// and _telli64 yourself.
//

#include <io.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>

#define ARCH_NAME argv[2]
#define F_BUFFER_SIZE 32768

#pragma pack(1)
typedef struct
{
  // unsigned long sig;      // signature, must be 0x02014b50
  unsigned short versmb;  // version made by
  unsigned short vers;    // version needed
  unsigned short flags;   // some bit flags
  unsigned short method;  // compression method, 0=store
  unsigned short lmtime;  // time of last modification
  unsigned short lmdate;  // date of last modification
  unsigned long crc32;
  unsigned long compsize; // compressed size
  unsigned long origsize; // original size
  unsigned short fnamlen; // length of file name field
  unsigned short extrlen; // length of extra field
  unsigned short fremlen; // length of file commend field 
  unsigned short dskns;   // disk number start
  unsigned short intfa;   // internal file attributes
  unsigned long extfa;    // external file attribute
  unsigned long roffs;    // relative offset of local header
  // file_name char[fnamlen] 
  // extra_field char[extrlen]
  // file_rem char[fremlen]
} TZIPFileHeader;

typedef struct
{
  // unsigned long sig;       // signature, must be 0x06054b50
  unsigned short thisdsk;  // number of this disk
  unsigned short CDdsk;    // number of disk with the start of central dir (CD)
  unsigned short CDnThis;  // number of entries in the CD on this disk
  unsigned short CDn;      // total number of entries in the CD
  unsigned long CDsize;    // size of the central dir
  unsigned long CDoffs;    // offset of start of CD
  unsigned short zipremlen; // length of ZIP file comment
  // zip_comment char[zipremlen]
} TZIPEndOfCentralDirRecord;

typedef struct
{
  unsigned long sig;      // signature, must be 0x04034b50
  unsigned short vers;    // version needed
  unsigned short flags;   // some bit flags
  unsigned short method;  // compression method, 0=store
  unsigned short lmtime;  // time of last modification
  unsigned short lmdate;  // date of last modification
  unsigned long crc32;
  unsigned long compsize; // compressed size
  unsigned long origsize; // original size
  unsigned short fnamlen; // length of file name field
  unsigned short extrlen; // length of extra field
  // file_name char[fnamlen] 
  // extra_field char[extrlen]
} TZIPLocalHeader;


volatile __int64 g_diroff;     // offset of central directory in zip file

void find_dir_offs(long);      // find offset of central directory in zip file
void enum_archive(long, char); // enum contents of zip archive

int main(int argc, char *argv[])
{
  long fhn;

  printf("Zipofig. A quick-n-dirty ripper for zip archives\n"
         "Written by Ilya O. Levin, http://www.literatecode.com\n"
         "NO WARRANTIES OF ANY KIND, USE AT YOUR OWN RISK\n\n");

  if (argc<3) 
  {
    printf("Usage:     zipofig <command> archive\n"
           "Examples:  zipofig l yale.zip, zipofig e pkscrewed.zip\n"
           "Commands:\n"
           "   l: list contents of archive\t\te: rip files from archive\n"
          );
    return 1;
  }

  printf("Archive: %s\n\n", ARCH_NAME);

  fhn=_open(ARCH_NAME, O_RDONLY|O_BINARY,0);
  if (fhn!=-1)
  {
    g_diroff=0;
    switch(tolower(argv[1][0]))
    {
      case 'l':               // list contents of archive
        find_dir_offs(fhn);
        enum_archive(fhn, 0); 
        break;
      case 'e':               // rip files from archive
        find_dir_offs(fhn);
        enum_archive(fhn, 1);
        break;
      default:
        printf("Unknown command specified - %c\n", argv[1][0]);
        break;
    }
    _close(fhn);
  } else printf("Archive does not exist or unaccessible\n");
  return 0;
}  // main 


void find_dir_offs(long fhn)
{
  TZIPEndOfCentralDirRecord e;
  long rc;
  unsigned long sig;

  // scan for signature of 'EndOfCentralDir' record
  printf("* looking for zip struct... ");
  _lseek(fhn, -18, SEEK_END);
  _read(fhn, &sig, sizeof(sig));
  while(_telli64(fhn)>32) if(sig==0x06054b50)
  {
      rc=_read(fhn, &e, sizeof(e));
      if (rc==sizeof(e)) g_diroff=e.CDoffs;
      break;
  } else {_lseek(fhn, -5, SEEK_CUR); _read(fhn, &sig, sizeof(sig));}

  printf("%s\n", (g_diroff)?"ok":"failed");

} // find_dir_offs


void enum_archive (long fhn, char ripemall)
{
  TZIPFileHeader h;
  TZIPLocalHeader o;
  long rc, outfhn;
  __int64 boo,ofpos;
  unsigned long sig, lbuf, numripped=0, numtotal=0;
  char *fn, *buf;

  if (!g_diroff) return; // do nothing if cd offset is not initialized

  if (ripemall) 
  { 
     buf=(char *)malloc(F_BUFFER_SIZE);
     if (buf==NULL) 
     {
        printf("* Insufficient memory for I/O operations\n");
        return;
     }
     else printf("* Ripping:\n");
  }
  else printf("\nCompressed    Original     CRC32    Name\n"
              "------------ ------------ -------- -------------\n");
 
  _lseeki64(fhn, g_diroff, SEEK_SET);
  rc=_read(fhn, &sig, sizeof(sig));

  while (rc>3)
  {
    if (sig==0x02014b50)
    {
      boo=_telli64(fhn)-4;
      rc=_read(fhn, &h, sizeof(h));
      if (rc<sizeof(h)) break;
      fn=(char *) malloc(h.fnamlen+1);
      if (fn==NULL) break;
      fn[h.fnamlen]=0;
      rc=_read(fhn, fn, h.fnamlen);
      if (ripemall)
      {
        if (h.compsize&&h.origsize&&h.crc32) // file to rip
        {
           printf("- %s...", fn);
           numtotal++;
           ofpos=_telli64(fhn); 
           _lseeki64(fhn, h.roffs, SEEK_SET);
           outfhn=_open(fn, O_WRONLY|O_BINARY|O_CREAT, S_IREAD|S_IWRITE);
           if (outfhn!=-1)
           {
             // skip local file header
             _read(fhn, &o, sizeof(o)); 
             if (o.sig==0x04034b50) // local file header valid
             {
                _lseeki64(fhn, o.extrlen+o.fnamlen, SEEK_CUR);
                while(h.compsize)
                {
                  if (h.compsize<F_BUFFER_SIZE) lbuf=h.compsize; 
                  else lbuf=F_BUFFER_SIZE;
                  _read(fhn, buf, lbuf);
                  _write(outfhn, buf, lbuf);
                  h.compsize-=lbuf;
                }
                numripped++;
                printf("ok\n");
             } else printf("bad hdr\n");
             _close(outfhn);
           } else printf("failed\n");
           _lseeki64(fhn, ofpos, SEEK_SET);
        } else // directory to create
        {
           printf("- creating %s... ", fn);
           printf("%s\n", (_mkdir(fn))?"failed":"ok");
        }
      } 
      else // list content of archive mode
      {
        printf("%12d %12d %08x %s\n", h.compsize, h.origsize, h.crc32, fn);
        numtotal++;
      }
      free(fn);
      if (h.compsize) _lseeki64(fhn, h.extrlen+h.fremlen, SEEK_CUR); else _lseeki64(fhn, boo+1, SEEK_SET);
    }
    else _lseeki64(fhn, -3, SEEK_CUR); 
    rc=_read(fhn, &sig, sizeof(sig));
  }

  if (ripemall) free(buf);

  printf("\nTotal in archive %d file(s)\n"
         "Ripped %d file(s)\n", 
         numtotal, numripped);
} // enum_archive
