/*
    Copyright 2004,2005,2006,2007,2008 Luigi Auriemma

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA

    http://www.gnu.org/licenses/gpl-2.0.txt
*/

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <ctype.h>
#include <zlib.h>

typedef uint8_t     u8;
typedef uint16_t    u16;
typedef uint32_t    u32;



#define VER     "0.1.1"
#define BUFFSZ  0xffff



u32 zipit(FILE *fdi, FILE *fdo, int wbits);
int get_num(u8 *data);
void myfw(FILE *fd, void *data, unsigned size);
void std_zerr(z_stream *z);
void std_err(void);



int main(int argc, char *argv[]) {
    FILE    *fdi,
            *fdo;
    u32     offset   = 0,
            len;
    int     i,
            wbits    = 15,
            recreate = 0;
    u8      *input,
            *output;

    setbuf(stdout, NULL);
    setbuf(stderr, NULL);

    fputs("\n"
        "PackZip "VER"\n"
        "by Luigi Auriemma\n"
        "e-mail: aluigi@autistici.org\n"
        "web:    aluigi.org\n"
        "\n", stderr);

    if(argc < 3) {
        fprintf(stderr, "\n"
            "Usage: %s [options] <input> <output>\n"
            "\n"
            "Options:\n"
            "-o OFF   offset of the output file where storing the raw zipped data\n"
            "-c       recreate from scratch the output file if already exists\n"
            "-w BITS  windowBits value (usually -15 to -8 and 8 to 15), default is %d\n"
            "\n"
            "by default this tool \"injects\" the new compressed data in the output file if\n"
            "already exists, useful for modifying archives of unknown format replacing only\n"
            "the data which has been modified without touching the rest\n"
            "\n", argv[0], wbits);
        exit(1);
    }

    argc -= 2;
    for(i = 1; i < argc; i++) {
        if(((argv[i][0] != '-') && (argv[i][0] != '/')) || (strlen(argv[i]) != 2)) {
            fprintf(stderr, "\nError: wrong argument (%s)\n", argv[i]);
            exit(1);
        }
        switch(argv[i][1]) {
            case 'o': offset    = get_num(argv[++i]);   break;
            case 'c': recreate  = 1;                    break;
            case 'w': wbits     = get_num(argv[++i]);   break;
            default: {
                fprintf(stderr, "\nError: wrong argument (%s)\n", argv[i]);
                exit(1);
            }
        }
    }
    input  = argv[argc];
    output = argv[argc + 1];

    if(!strcmp(input, "-")) {
        fdi = stdin;
    } else {
        fprintf(stderr, "- open input file %s:\n", input);
        fdi = fopen(input, "rb");
        if(!fdi) std_err();
    }

    if(!strcmp(output, "-")) {
        fdo = stdout;
    } else {
        fprintf(stderr, "- open output file %s:\n", output);
        if(recreate) {
            fdo = fopen(output, "wb");
            if(!fdo) std_err();
            fprintf(stderr, "- recreate output file\n");
        } else {
            fdo = fopen(output, "r+b");
            if(!fdo) {
                fdo = fopen(output, "wb");
                if(!fdo) std_err();
            }
        }
    }

    fprintf(stderr, 
        "- offset        0x%08x\n"
        "- windowbits    %d\n",
        offset, wbits);

    if(offset) {
        fprintf(stderr, "- seek offset\n");
        if(fseek(fdo, offset, SEEK_SET)) std_err();
    }

    fprintf(stderr, "- compression: ");
    len = zipit(fdi, fdo, wbits);

    if(fdi != stdin)  fclose(fdi);
    if(fdo != stdout) fclose(fdo);

    fprintf(stderr, "\n- %u zipped bytes written\n", len);
    return(0);
}



u32 zipit(FILE *fdi, FILE *fdo, int wbits) {
    z_stream    z;
    u32     ret;
    int     insz,
            zerr;
    u8      *in,
            *out;

    in  = malloc(BUFFSZ);
    out = malloc(BUFFSZ);
    if(!in || !out) std_err();

    z.zalloc = (alloc_func)0;
    z.zfree  = (free_func)0;
    z.opaque = (voidpf)0;
    if(deflateInit2(&z, Z_BEST_COMPRESSION, Z_DEFLATED, wbits, 9, Z_DEFAULT_STRATEGY)) {
        fprintf(stderr, "\nError: zlib initialization error\n");
        exit(1);
    }

    while((insz = fread(in, 1, BUFFSZ, fdi)) > 0) {
        fputc('.', stderr);

        z.next_in   = in;
        z.avail_in  = insz;
        do {
            z.next_out  = out;
            z.avail_out = BUFFSZ;

            zerr = deflate(&z, Z_NO_FLUSH);
            if(zerr != Z_OK) std_zerr(&z);
            myfw(fdo, out, BUFFSZ - z.avail_out);
        } while(z.avail_in);
    }
    for(;;) {
        z.next_out  = out;
        z.avail_out = BUFFSZ;

        zerr = deflate(&z, Z_FINISH);
        myfw(fdo, out, BUFFSZ - z.avail_out);

        if(zerr == Z_STREAM_END) break;
        if(zerr != Z_OK) std_zerr(&z);
    }

    ret = z.total_out;
    deflateEnd(&z);
    free(in);
    free(out);
    return(ret);
}



int get_num(u8 *data) {
    int     num = 0;

    if((data[0] == '$') || (data[0] == 'h') || ((strlen(data) > 2) && (tolower(data[1]) == 'x'))) {
        sscanf(data, "%x", &num);
    } else {
        sscanf(data, "%u", &num);
    }
    return(num);
}



void myfw(FILE *fd, void *data, unsigned size) {
    if(fwrite(data, 1, size, fd) == size) return;
    fprintf(stderr, "\nError: problems during the writing of the output file, check your free space\n");
    exit(1);
}



void std_zerr(z_stream *z) {
    fprintf(stderr, "\nError: zlib error, %s\n", z->msg ? z->msg : "");
    deflateEnd(z);
    exit(1);
}



void std_err(void) {
    perror("\nError");
    exit(1);
}


