/*
 * 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. */
    die("failed\nDecompression error: ran out of input data\n");
}

/* ===========================================================================
 * 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)
{
    die("failed\nDecompression error: %s\n", x);
}

/* 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;

static char heap[65536];

void *unzip(void *indata, uint32_t zbytes, uint32_t dbytes,
	    uint32_t orig_crc, void *target)
{
    /* Set up the heap; it is simply a chunk of bss memory */
    free_mem_ptr     = (size_t)heap;
    free_mem_end_ptr = (size_t)heap + sizeof heap;

    /* 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;
}
