/* LZ48 simple C source file / crappy version by roudoudou - Flower Corp. 2016 */

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<malloc.h>

int LZ48_encode_extended_length(unsigned char *odata, int length)
{
	int ioutput=0;

	while (length>255) {
		odata[ioutput++]=0xFF;
		length-=255;
	}
	odata[ioutput++]=length;
	return ioutput;
}

int LZ48_encode_block(unsigned char *odata,unsigned char *data, int literaloffset,int literalcpt,int offset,int maxlength)
{
	int ioutput=1;
	int token=0;
	int i;

	if (literalcpt<15) {
		token=literalcpt<<4; 
	} else {
		token=0xF0;
		ioutput+=LZ48_encode_extended_length(odata+ioutput,literalcpt-15);
	}

	for (i=0;i<literalcpt;i++) odata[ioutput++]=data[literaloffset++];

	if (maxlength<18) {
		if (maxlength>2) {
			token|=(maxlength-3);
		}
	} else {
		token|=0xF;
		ioutput+=LZ48_encode_extended_length(odata+ioutput,maxlength-18);
	}

	odata[ioutput++]=offset-1;
	
	odata[0]=token;
	return ioutput;
}

unsigned char *LZ48_encode(unsigned char *data, int length, int *retlength)
{
	int i,startscan,current=1,token,ioutput=1,curscan;
	int maxoffset,maxlength,matchlength,literal=0,literaloffset=1;
	unsigned char *odata=NULL;
	
	if (!length) return NULL;

	odata=malloc((size_t)(length*1.1));
	if (!odata) {
		fprintf(stderr,"memory full");
		exit(-1);
	}

	/* first byte always literal */
	odata[0]=data[0];

	/* force short data encoding */
	if (length<5) {
		token=(length-1)<<4;
		odata[ioutput++]=token;
		for (i=1;i<length;i++) odata[ioutput++]=data[current++];
		odata[ioutput++]=0xFF;
		*retlength=ioutput;
		return odata;
	}

	while (current<length) {
		maxlength=0;
		startscan=current-255;
		if (startscan<0) startscan=0;
		while (startscan<current) {
			matchlength=0;
			curscan=current;
			for (i=startscan;curscan<length;i++) {
				if (data[i]==data[curscan++]) matchlength++; else break;
			}
			if (matchlength>=3 && matchlength>maxlength) {
				maxoffset=startscan;
				maxlength=matchlength;
			}
			startscan++;
		}
		if (maxlength) {
			ioutput+=LZ48_encode_block(odata+ioutput,data,literaloffset,literal,current-maxoffset,maxlength);
			current+=maxlength;
			literaloffset=current;
			literal=0;
		} else {
			literal++;
			current++;
		}
	}
	ioutput+=LZ48_encode_block(odata+ioutput,data,literaloffset,literal,0,0);
	*retlength=ioutput;
	return odata;
}

void LZ48_decode(unsigned char *data, unsigned char *odata)
{
	int HL=0,DE=0;
	int literallength,matchlength,HLmatch;

	odata[DE++]=data[HL++];

	while (1) {
		literallength=(data[HL] & 0xF0)>>4;
		matchlength=(data[HL++] & 0xF);

		if (literallength==15) {
			while (data[HL]==255) {
				literallength+=255;
				HL++;
			}
			literallength+=data[HL++];
		}

		while (literallength>0) {
			odata[DE++]=data[HL++];
			literallength--;
		}

		/* matchkey */
		if (matchlength==15) {
			while (data[HL]==255) {
				matchlength+=255;
				HL++;
			}
			matchlength+=data[HL++];
		}
		matchlength+=3;
		if (data[HL]==0xFF) return; else HLmatch=DE-(data[HL++]+1);
		
		while (matchlength) {
			odata[DE++]=odata[HLmatch++];
			matchlength--;
		}
	}
}

unsigned char * LZ48_decrunch(unsigned char *data, int *osize)
{
	int literallength,matchlength;
	int HL=1,DE=1;
	unsigned char *odata=NULL;
	
	while (1) {
		literallength=(data[HL] & 0xF0)>>4;
		matchlength=(data[HL++] & 0xF);
		if (literallength==15) {
			while (data[HL]==255) {
				literallength+=255;
				HL++;
			}
			literallength+=data[HL++];
		}

		DE+=literallength;
		HL+=literallength;

		/* matchkey */
		if (matchlength==15) {
			while (data[HL]==255) {
				matchlength+=255;
				HL++;
			}
			matchlength+=data[HL++];
		}
		matchlength+=3;
		if (data[HL]==0xFF) break; else HL++;
		
		DE+=matchlength;
	}
	*osize=DE;
	odata=malloc(*osize);
	if (!odata) {
		fprintf(stderr,"memory full\n");
		exit(-1);
	}
	LZ48_decode(data,odata);
	return odata;
}

void Usage()
{
	printf("usage: lz48 <options>\n");
	printf("options are:\n");
	printf("-i <inputfile>\n");
	printf("-o <outputfile>\n");
	printf("-b hexatext output (enable if you forget outputfile)\n");
	printf("-d decrunch\n");
	printf("\n");
	exit(0);
}

int ParseOptions(char **argv,int argc, char **inputfilename, char **outputfilename, int *hexoutput, int *crunch)
{
	int i=0;

	if (argv[i][0]=='-')
	{
		switch(argv[i][1])
		{
			case 'O':
			case 'o':
				if (i+1<argc) *outputfilename=argv[i+1];
				i++;
				break;
			case 'I':
			case 'i':
				if (i+1<argc) *inputfilename=argv[i+1];
				i++;
				break;
			case 'D':
			case 'd':
				*crunch=0;
				break;
			case 'B':
			case 'b':
				*hexoutput=1;
				break;
			default:
				Usage();				
		}
	}
	else
		Usage();
	return i;
}

/*
 * 	GetParametersFromCommandLine	
 * 		retrieve parameters from command line and fill pointers to file names
 * 		*/
void GetParametersFromCommandLine(int argc, char **argv, char **inputfilename, char **outputfilename, int *hexoutput, int *crunch)
{
	#undef FUNC
	#define FUNC "GetParametersFromCommandLine"
	int i;
	
	for (i=1;i<argc;i++)
		i+=ParseOptions(&argv[i],argc-i,inputfilename,outputfilename,hexoutput,crunch);

	if (!*inputfilename) Usage();
	if (!*outputfilename) *hexoutput=1;
}



void main(int argc, char **argv)
{
	unsigned char *data=NULL,*newdata=NULL;
	char *inputfilename=NULL,*outputfilename=NULL;
	int hexoutput=0,isize,crunch=1;
	int newsize,i,cr;
	FILE *fin=NULL,*fout=stdout;

	fprintf(stderr,"LZ48 cruncher / roudoudou - Flower Corp. 2016\n");

	GetParametersFromCommandLine(argc,argv,&inputfilename,&outputfilename,&hexoutput,&crunch);

	fopen_s(&fin, inputfilename,"rb");
	if (!fin) Usage();
	fseek(fin,0,SEEK_END);
	isize=ftell(fin);
	fseek(fin,0,SEEK_SET);
	data=malloc(isize);
	if (!data) {
		fprintf(stderr,"memory full\n");
		exit(-1);
	}
	if (fread(data,1,isize,fin)!=isize) {
		fprintf(stderr,"read error\n");
		exit(-1);
	}
	fclose(fin);
	switch (crunch) {
		case 0:newdata=LZ48_decrunch(data,&newsize);break;
		case 1:newdata=LZ48_encode(data,isize,&newsize);break;
	}
printf("input: %d output: %d\n",isize,newsize);
	if (hexoutput) {
		for (i=cr=0;i<newsize;i++) {
			if (!cr) {
				printf("DEFB %02X",newdata[i]);
				cr=1;
			} else {
				if (cr==15) {
					printf(",%02X\n",newdata[i]);
					cr=0;
				} else {
					printf(",%02X",newdata[i]);
					cr++;
				}
			}
		}
		if (cr) printf("\n");
	} else {
		fopen_s(&fout, outputfilename,"wb");
		if (!fout) Usage();
		if (fwrite(newdata,1,newsize,fout)!=newsize) {
			fprintf(stderr,"write error\n");
			exit(-1);
		}
		fclose(fout);
	}
}

