/*
 * unzip.c
 * 
 * This is a collection of several routines from gzip-1.0.3 
 * adapted for Linux.
 *
 * malloc by Hannu Savolainen 1993 and Matthias Urlichs 1994
 * puts by Nick Holloway 1993, better puts by Martin Mares 1995
 * High loaded stuff by Hans Lermen & Werner Almesberger, Feb. 1996
 *
 * Adapted for MEMDISK by H. Peter Anvin, April 2003
 */

#include <stdint.h>
#include "memdisk.h"
#include "conio.h"

/*
 * gzip declarations
 */

#define OF(args)  args
#define STATIC static

#define memzero(s, n)     memset ((s), 0, (n))

typedef uint8_t  uch;
typedef uint16_t ush;
typedef uint32_t ulg;

#define WSIZE 0x8000	        /* Window size must be at least 32k, */
				/* and a power of two */

static uch *inbuf;		/* input pointer */
static uch window[WSIZE];	/* sliding output window buffer */

static unsigned insize;		/* total input bytes read */
static unsigned inbytes;	/* valid bytes in inbuf */
static unsigned outcnt;		/* bytes in output buffer */

/* gzip flag byte */
#define ASCII_FLAG   0x01 /* bit 0 set: file probably ASCII text */
#define CONTINUATION 0x02 /* bit 1 set: continuation of multi-part gzip file */
#define EXTRA_FIELD  0x04 /* bit 2 set: extra field present */
#define ORIG_NAME    0x08 /* bit 3 set: original file name present */
#define COMMENT      0x10 /* bit 4 set: file comment present */
#define ENCRYPTED    0x20 /* bit 5 set: file is encrypted */
#define RESERVED     0xC0 /* bit 6,7:   reserved */

/* Diagnostic functions */
#ifdef DEBUG
#  define Assert(cond,msg) {if(!(cond)) error(msg);}
#  define Trace(x) fprintf x
#  define Tracev(x) {if (verbose) fprintf x ;}
#  define Tracevv(x) {if (verbose>1) fprintf x ;}
#  define Tracec(c,x) {if (verbose && (c)) fprintf x ;}
#  define Tracecv(c,x) {if (verbose>1 && (c)) fprintf x ;}
#else
#  define Assert(cond,msg)
#  define Trace(x)
#  define Tracev(x)
#  define Tracevv(x)
#  define Tracec(c,x)
#  define Tracecv(c,x)
#endif

static int  fill_inbuf(void);
static void flush_window(void);
static void error(char *m);
static void gzip_mark(void **);
static void gzip_release(void **);

static ulg crc_32_tab[256];

/* Get byte from input buffer */
static inline uch get_byte(void)
{
  if ( inbytes ) {
    uch b = *inbuf++;
    inbytes--;
    return b;
  } else {
    return fill_inbuf();	/* Input buffer underrun */
  }
}

/* Unget byte from input buffer */
static inline void unget_byte(void)
{
  inbytes++;
  inbuf--;
}

static ulg bytes_out = 0;	/* Number of bytes output */
static uch *output_data;	/* Output data pointer */
static ulg output_size;		/* Number of output bytes expected */

static void *malloc(int size);
static void free(void *where);

static ulg free_mem_ptr, free_mem_end_ptr;

#include "inflate.c"

static void *malloc(int size)
{
  void *p;
  
  if (size < 0) error("malloc error");
  
  free_mem_ptr = (free_mem_ptr + 3) & ~3;	/* Align */
  
  p = (void *)free_mem_ptr;
  free_mem_ptr += size;
  
  if (free_mem_ptr >= free_mem_end_ptr)
    error("out of memory");
  
  return p;
}

static void free(void *where)
{
  /* Don't care */
  (void)where;
}

static void gzip_mark(void **ptr)
{
  *ptr = (void *) free_mem_ptr;
}

static void gzip_release(void **ptr)
{
  free_mem_ptr = (long) *ptr;
}
 
/* ===========================================================================
 * Fill the input buffer. This is called only when the buffer is empty
 * and at least one byte is really needed.
 */
static int fill_inbuf(void)
{
  /* This should never happen.  We have already pointed the algorithm
     to all the data we have. */
  printf("failed\nDecompression error: ran out of input data\n");
  die();
}

/* ===========================================================================
 * Write the output window window[0..outcnt-1] and update crc and bytes_out.
 * (Used for the decompressed data only.)
 */
static void flush_window(void)
{
    ulg c = crc;         /* temporary variable */
    unsigned n;
    uch *in, *out, ch;

    if ( bytes_out+outcnt > output_size )
      error("output buffer overrun");
    
    in = window;
    out = output_data;
    for (n = 0; n < outcnt; n++) {
	    ch = *out++ = *in++;
	    c = crc_32_tab[(c ^ ch) & 0xff] ^ (c >> 8);
    }
    crc = c;
    output_data = out;
    bytes_out += (ulg)outcnt;
    outcnt = 0;
}

static void error(char *x)
{
  printf("failed\nDecompression error: %s\n", x);
  die();
}

/* GZIP header */
struct gzip_header {
  uint16_t magic;
  uint8_t method;
  uint8_t flags;
  uint32_t timestamp;
  uint8_t extra_flags;
  uint8_t os_type;
} __attribute__ ((packed));
/* (followed by optional and variable length "extra", "original name",
   and "comment" fields) */

struct gzip_trailer {
  uint32_t crc;
  uint32_t dbytes;
} __attribute__ ((packed));

/* PKZIP header.  See
 * <http://www.pkware.com/products/enterprise/white_papers/appnote.html>.
 */
struct pkzip_header {
  uint32_t magic;
  uint16_t version;
  uint16_t flags;
  uint16_t method;
  uint16_t modified_time;
  uint16_t modified_date;
  uint32_t crc;
  uint32_t zbytes;
  uint32_t dbytes;
  uint16_t filename_len;
  uint16_t extra_len;
} __attribute__ ((packed));
/* (followed by optional and variable length "filename" and "extra"
   fields) */

/* gzip flag byte */
#define ASCII_FLAG   0x01 /* bit 0 set: file probably ASCII text */
#define CONTINUATION 0x02 /* bit 1 set: continuation of multi-part gzip file */
#define EXTRA_FIELD  0x04 /* bit 2 set: extra field present */
#define ORIG_NAME    0x08 /* bit 3 set: original file name present */
#define COMMENT      0x10 /* bit 4 set: file comment present */
#define ENCRYPTED    0x20 /* bit 5 set: file is encrypted */
#define RESERVED     0xC0 /* bit 6,7:   reserved */

/* pkzip flag byte */
#define PK_ENCRYPTED     0x01  /* bit 0 set: file is encrypted */
#define PK_DATADESC       0x08  /* bit 3 set: file has trailing "data
                                   descriptor" */
#define PK_UNSUPPORTED    0xFFF0 /* All other bits must be zero */


/* Return 0 if (indata, size) points to a ZIP file, and fill in
   compressed data size, uncompressed data size, CRC, and offset of
   data.

   If indata is not a ZIP file, return -1. */
int check_zip(void *indata, uint32_t size, uint32_t *zbytes_p,
              uint32_t *dbytes_p, uint32_t *orig_crc, uint32_t *offset_p) {
  struct gzip_header *gzh = (struct gzip_header *)indata;
  struct pkzip_header *pkzh = (struct pkzip_header *)indata;
  uint32_t offset;

  if (gzh->magic == 0x8b1f) {
    struct gzip_trailer *gzt = indata + size - sizeof (struct gzip_trailer);
    /* We only support method #8, DEFLATED */
    if (gzh->method != 8)  {
      error("gzip file uses invalid method");
      return -1;
    }
    if (gzh->flags & ENCRYPTED) {
      error("gzip file is encrypted; not supported");
      return -1;
    }
    if (gzh->flags & CONTINUATION) {
      error("gzip file is a continuation file; not supported");
      return -1;
    }
    if (gzh->flags & RESERVED) {
      error("gzip file has unsupported flags");
      return -1;
    }
    offset = sizeof (*gzh);
    if (gzh->flags & EXTRA_FIELD) {
      /* Skip extra field */
      unsigned len = *(unsigned *)(indata + offset);
      offset += 2 + len;
    }
    if (gzh->flags & ORIG_NAME) {
      /* Discard the old name */
      uint8_t *p = indata;
      while (p[offset] != 0 && offset < size) {
        offset++;
      }
      offset++;
    }
    
    if (gzh->flags & COMMENT) {
      /* Discard the comment */
      uint8_t *p = indata;
      while (p[offset] != 0 && offset < size) {
        offset++;
      }
      offset++;
    }

    if (offset > size) {
      error ("gzip file corrupt");
      return -1;
    }
    *zbytes_p = size - offset - sizeof (struct gzip_trailer);
    *dbytes_p = gzt->dbytes;
    *orig_crc = gzt->crc;
    *offset_p = offset;
    return 0;
  }
  else if (pkzh->magic == 0x04034b50UL) {
    /* Magic number matches pkzip file. */
    
    offset = sizeof (*pkzh);
    if (pkzh->flags & PK_ENCRYPTED) {
      error("pkzip file is encrypted; not supported");
      return -1;
    }
    if (pkzh->flags & PK_DATADESC) {
      error("pkzip file uses data_descriptor field; not supported");
      return -1;
    }
    if (pkzh->flags & PK_UNSUPPORTED) {
      error("pkzip file has unsupported flags");
      return -1;
    }

    /* We only support method #8, DEFLATED */
    if (pkzh->method != 8) {
      error("pkzip file uses invalid method");
      return -1;
    }
    /* skip header */
    offset = sizeof (*pkzh);
    /* skip filename */
    offset += pkzh->filename_len;
    /* skip extra field */
    offset += pkzh->extra_len;

    if (offset + pkzh->zbytes > size) {
      error ("pkzip file corrupt");
      return -1;
    }

    *zbytes_p = pkzh->zbytes;
    *dbytes_p = pkzh->dbytes;
    *orig_crc = pkzh->crc;
    *offset_p = offset;
    return 0;
  }
  else {
    /* Magic number does not match. */
    return -1;
  }

  error ("Internal error in check_zip");
  return -1;
}

/*
 * Decompress the image, trying to flush the end of it as close
 * to end_mem as possible.  Return a pointer to the data block,
 * and change datalen.
 */
extern void _end;

void *unzip(void *indata, uint32_t zbytes, uint32_t dbytes,
            uint32_t orig_crc, void *target)
{
  /* Set up the heap; it's the 64K after the bounce buffer */
  free_mem_ptr = (ulg)sys_bounce + 0x10000;
  free_mem_end_ptr = free_mem_ptr + 0x10000;

  /* Set up input buffer */
  inbuf  = indata;
  /* Sometimes inflate() looks beyond the end of the compressed data,
     but it always backs up before it is done.  So we give it 4 bytes
     of slack. */
  insize = inbytes = zbytes + 4;

  /* Set up output buffer */
  outcnt = 0;
  output_data = target;
  output_size = dbytes;
  bytes_out = 0;

  makecrc();
  gunzip();

  /* Verify that gunzip() consumed the entire input. */
  if (inbytes != 4)
    error("compressed data length error");

  /* Check the uncompressed data length and CRC. */
  if ( bytes_out != dbytes )
    error("uncompressed data length error");

  if (orig_crc != CRC_VALUE)
    error("crc error");

  puts("ok\n");

  return target;
}
