/**************************************************************************
 *
 * FILE  dtvpack.c
 * Written by Daniel Kahlin <daniel@kahlin.net>
 * dtvpack-0.3.c,v 1.1 2006-05-27 18:29:57 tlr Exp
 *
 * DESCRIPTION
 *   This program encodes (or decodes) in the packed format used in the
 *   C64 DTV V2 flash file system.
 *
 * HISTORY
 *   0.3 (20060527) tlr 
 *     - packing implemented. Byte identical result for DTVMENU (!).
 *     - better error reporting.
 *
 *   0.2 (20060527) tlr
 *     - implemented decompression.
 *
 *   0.1 (20060526) tlr 
 *     - first public version.
 *
 * LICENSE
 *   dtvpack - C64 DTV flash file encoder/decoder
 *
 *   Copyright (c) 2006 Daniel Kahlin <daniel@kahlin.net>
 *   All rights reserved.
 * 
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions
 *   are met:
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *   3. Neither the names of the copyright holders nor the names of their
 *      contributors may be used to endorse or promote products derived 
 *      from this software without specific prior written permission.
 * 
 *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *   ``AS IS´´ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 *   A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR
 *   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 *   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 *   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 *   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 *   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 ******/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define PROGRAM "dtvpack"
#define VERSION "0.3"

int store(FILE *infp, FILE *outfp);
int pack(FILE *infp, FILE *outfp);
int depack(FILE *infp, FILE *outfp);

int insize,outsize;
int verbose_g;

int main(int argc, char *argv[])
{
    int c;
    FILE *infp;
    FILE *outfp;
    char *srcname=argv[1];
    char *destname=argv[2];
    int store_mode;
    int depack_mode;
    int depack_addr;

    /* defaults */
    verbose_g=0;
    store_mode=0;
    depack_mode=0;
    depack_addr=0x0801;


    /*
     * scan for valid options
     */
    while (EOF!=(c=getopt (argc, argv, "0da:vVh"))) {
        switch (c) {
	
	/* a missing parameter */
	case ':':
	/* an illegal option */
	case '?':
	    exit (1);

	/* print version */
	case 'v':
	    verbose_g=1;
	    break;

	/* print version */
	case 'V':
	    fprintf (stdout, PROGRAM " " VERSION "\n");
	    exit(0);

	/* print help */
	case 'h':
	    fprintf(stderr,
PROGRAM " " VERSION ": C64 DTV flash file encoder/decoder\n"
"Copyright (c) 2006 Daniel Kahlin <daniel@kahlin.net>\n"
"Written by Daniel Kahlin <daniel@kahlin.net>\n"
"http://www.kahlin.net/daniel/dtv/\n"
"\n"
"usage: " PROGRAM " [-0][-d][-a<addr>][-v][-h][-V] <source> <target>\n"
#if 0
"       " PROGRAM " [-0][-v] <source> <target>\n"
"       " PROGRAM " -d[-a<addr>][-v] <source> <target>\n"
"       " PROGRAM " -h\n"
"       " PROGRAM " -V\n"
#endif
"\n"
"Valid options:\n"
"    -0              do not compress, just store\n"
"    -d              decompress\n"
"    -a<addr>        set load address for decompressed file (default: 0x0801)\n"
"    -v              be verbose\n"
"    -h              displays this help text\n"
"    -V              output program version\n"
"\n"
"This program encodes (or decodes) in the packed format used in the\n"
"C64 DTV V2 flash file system.\n"
	    );
	    exit(0);
	  
	/* run without compression */
	case '0':
	    store_mode=1;
	    break;

	/* run in depack mode */
	case 'd':
	    depack_mode=1;
	    break;

	/* set depack address */
	case 'a':
	    {
		char* endptr;
		depack_addr=strtoul(optarg,&endptr,0);
		if (endptr!=optarg+strlen(optarg)) {
		    fprintf(stderr,PROGRAM ": invalid address\n");
		    exit(-1);
		}
	    }
	    break;

	/* default behavior */
	default:
	    break;
	}
    }

    /*
     * optind now points at the first non option argument
     * we expect two more arguments (inname, outname)
     */
    if (argc-optind < 2) {
	fprintf(stderr,PROGRAM ": too few arguments\n");
	exit(-1);
    }
    srcname=argv[optind];
    destname=argv[optind+1];

    /* 
     * open files
     */
    infp=fopen(srcname,"rb");
    if (!infp) {
	fprintf(stderr,PROGRAM ": couldn't open file for reading\n");
	exit(-1);
    }
    outfp=fopen(destname,"wb");
    if (!outfp) {
	fprintf(stderr,PROGRAM ": couldn't open file for writing\n");
	exit(-1);
    }
    insize=0;
    outsize=0;

    if (!depack_mode) {
	/* load addr */
	uint16_t addr=fgetc(infp) + (fgetc(infp) << 8);
	insize+=2;

	if (verbose_g)
	    printf("load addr: 0x%04X\n",addr);

	if (store_mode)
	    store(infp,outfp);
	else
	    pack(infp,outfp);

	if (verbose_g)
	    printf("encoded %d bytes into %d bytes\n", insize, outsize);
    } else {
	/* load addr */
	fputc( (depack_addr&0xff), outfp);
	fputc( (depack_addr>>8), outfp);
	outsize+=2;

	depack(infp,outfp);
	if (verbose_g)
	    printf("decoded %d bytes into %d bytes\n", insize, outsize);
    }

    fclose(infp);
    fclose(outfp);
    exit(0);
}


/*
 * File Format
 * -----------
 * The files are compressed using a simple equal-sequence compression algorithm
 *
 * Every chunk of data starts with a code-byte.
 * If the code is $01-$7f, that number of bytes follow.
 * If the code is $80-$ff, then len=code & $7f, and a second byte follows
 * this copies len bytes of data from the current destination-$0100+secondbyte.
 * If the code is $00, then we are done.
 *
 * To just store the data without any compression, put an $7f byte before every
 * 127 bytes, and terminate with a $00 byte.
 *
 */
/**************************************************************************
 *
 * SECTION  packer
 *
 ******/
#define PK_WINDOW (0x100)
#define PK_RUNLEN (0x7f)
#define PK_MAXMATCH (0x7f)

inline void pk_put(uint8_t data, FILE *outfp)
{
    fputc(data,outfp);
    outsize++;
}

int pack(FILE *infp, FILE *outfp)
{
    uint8_t *srcbuf=0;
    int pos,srclen;
    int srcoffs;

    /* allocate a buffer, and load the file */
    pos=ftell(infp);
    fseek(infp, 0, SEEK_END);
    srclen=ftell(infp)-pos;
    fseek(infp, pos, SEEK_SET);
    srcbuf=malloc(srclen);
    insize+=fread(srcbuf, 1, srclen, infp);

    /* do the processing */
    uint8_t buf[PK_RUNLEN];
    int len;
    int i;

    srcoffs=0;
    len=0;
    while (1) {
	int st, ed;
	int match_len, match_offs;
	/* find matches */
	st=(srcoffs-PK_WINDOW > 0)?srcoffs-PK_WINDOW:0;
	ed=srcoffs;
	match_offs=0;
	match_len=0;
	for (i=st; i < ed; ++i) {
	    int offs=i;
	    int mlen=0;
	    while (srcbuf[i+mlen] == srcbuf[srcoffs+mlen]) {
		mlen++;
		if (srcoffs+mlen == srclen)
		    break;
		if (mlen == PK_MAXMATCH)
		    break;
	    }
	    if (mlen > match_len) {
		match_offs=offs;
		match_len=mlen;
	    }
	}

	/* determine if the match was good enough */
	if ( match_len > 2 ) {
	    /* flush */
	    if (len>0) {
		pk_put(len,outfp);
		for (i=0; i<len; ++i)
		    pk_put(buf[i],outfp);
		len=0;
	    }
	    /* write a match code */
	    pk_put(0x80 | match_len, outfp);
	    pk_put(0x100 + (match_offs-srcoffs), outfp);    
	    srcoffs+=match_len; /* one already incremented */
	} else {
	    /* store a byte */
	    int data;
	    data=srcbuf[srcoffs];
	    srcoffs++;

	    buf[len]=data;
	    len++;
	    /* flush if max run length */
	    if (len==PK_RUNLEN) {
		pk_put(len,outfp);
		for (i=0; i<len; ++i)
		    pk_put(buf[i],outfp);
		len=0;
	    }
	}

	/* are we all finished? */
	if (srcoffs>=srclen) {
	    break;
	}

    }
    /* flush */
    if (len>0) {
	pk_put(len,outfp);
	for (i=0; i<len; ++i)
	    pk_put(buf[i],outfp);
    }
    /* end marker */
    pk_put(0x00,outfp);

    free(srcbuf);
    return 0;
}



int store(FILE *infp, FILE *outfp)
{
    uint8_t inbuf[PK_RUNLEN];
    int len;

    while (!feof(infp)) {
	len=fread(inbuf, 1, PK_RUNLEN, infp);
	insize+=len;
	pk_put(len,outfp);
	fwrite(inbuf, 1, len, outfp);
	outsize+=len;
    }
    /* end marker */
    pk_put(0x00,outfp);
    return 0;
}


/**************************************************************************
 *
 * SECTION  depacker
 *
 ******/
#define DP_WINDOW (0x100)

uint8_t dp_buf[DP_WINDOW];
int dp_bfind;
int dp_chunkoffs;

inline uint8_t dp_get(FILE *infp)
{
    uint8_t data=fgetc(infp);
    if (feof(infp)) {
	fprintf(stderr,PROGRAM ": premature end of file at offset %d\n",insize);
	exit(-1);
    }
    insize++;
    return data;
}

inline uint8_t dp_getbr(int offs)
{
    uint8_t data;
    if ( (dp_bfind+offs) < 0 ) {
	fprintf(stderr,PROGRAM ": back-reference before start of file at offset %d\n",dp_chunkoffs);
	exit(-1);
    }
    data=dp_buf[(dp_bfind+offs) % DP_WINDOW];
    return data;
}

inline void dp_put(uint8_t data, FILE *outfp)
{
    fputc(data,outfp);
    outsize++;
    dp_buf[dp_bfind % DP_WINDOW]=data;
    dp_bfind++;
}

int depack(FILE *infp, FILE *outfp)
{
    int i;
    uint8_t code,len,offs,data;

    dp_bfind=0;
    while (1) {
	dp_chunkoffs=insize;
	code=dp_get(infp);

	/* end marker? */
	if (code == 0x00)
	    break;

	len = code & 0x7f;
	/* sequence marker? */
	if (code & 0x80) {
	    /* yes, look at back-reference */
	    offs=dp_get(infp);
	    for (i=0; i < len; ++i) {
		data=dp_getbr( -0x100+offs );
		dp_put(data,outfp);
	    }
	} else {
	    /* no, just copy data */
	    for (i=0; i < len; ++i) {
		data=dp_get(infp);
		dp_put(data,outfp);
	    }  
	}
    }
    return 0;
}

/* eof */
