/* 
 * Fire Emblem GBA Decompressor
 * 
 * By Tristan Wilkinson AKA Dark Twilkitri
 * 
 * Decompresses the data compressed with the indexed dictionary scheme
 * that seems to be common to the GBA FEs.
 * 
 * Last updated: 03/02/2005
 */

/* Preprocessor */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#define COMP_SCRUBBY 0x40
#define COMP_LZSS_EXT 0x11
#define COMP_LZSS 0x10

#define BLOCK_WIDTH 64
#define BLOCK_HEIGHT 64
#define BLOCKS_WIDE 2
#define BLOCKS_HIGH 3
#define BRICK_WIDTH 8
#define BRICK_HEIGHT 8
#define BRICKS_WIDE 8
#define BRICKS_HIGH 8


/* Function Prototypes */
void diction(unsigned short dictcall, unsigned char *data, unsigned int curoff);
void dictionEx2(unsigned short dictcall, unsigned char *data, unsigned int curoff);
void dictionEx3(unsigned int dictcall, unsigned char *data, unsigned int curoff);
void dictionEx4(unsigned int dictcall, unsigned char *data, unsigned int curoff);


/* Globals */
unsigned		int		bitmapred[BLOCK_WIDTH * BLOCKS_WIDE][BLOCK_HEIGHT * BLOCKS_HIGH];
unsigned		int		bitmapgreen[BLOCK_WIDTH * BLOCKS_WIDE][BLOCK_HEIGHT * BLOCKS_HIGH];
unsigned		int		bitmapblue[BLOCK_WIDTH * BLOCKS_WIDE][BLOCK_HEIGHT * BLOCKS_HIGH];
unsigned		int		blocksize = BLOCK_HEIGHT * BLOCK_WIDTH;
unsigned		int		bricksize = BRICK_HEIGHT * BRICK_WIDTH;


/* Main */
int main(int argc, char* argv[])
{
	/* Locals */
	unsigned char   comptype;
	unsigned char   curbyte;
	unsigned char   curpbyte;
	unsigned char   curpix;
	unsigned char  *data;
	unsigned char   dictlocs;
	         char  *palletename;
	         FILE  *bitmap;
			 FILE  *palletefile;
	         FILE  *rom;
	unsigned int    strtoff;
	unsigned int    curstorlen;
	unsigned int    maxstorlen;
	unsigned int    errornum;
	unsigned short  runlength;
	unsigned int    runcounter;
	unsigned int    palcounter;
	unsigned int    blockxcount;
	unsigned int    blockycount;
	unsigned int    brickxcount;
	unsigned int    brickycount;
	unsigned int    pixelxcount;
	unsigned int    pixelycount;
	unsigned int    paloff;
	unsigned short  dictcall;
	unsigned int	dictcallex;
	unsigned short  curpallent;

	unsigned		int			palred[256], palgreen[256], palblue[256];

	//Check arguments
	if(argc < 5 || argc > 7)
	{
		fprintf(stderr, "Invalid argument amount\nfedsdc file outputfile offset datalen [option1] [option2]\n");
		fprintf(stderr, "option1 is 'def' or nonexistant - pull pallete from file\n");
		fprintf(stderr, "option1 is 'pal' - pull pallete from file.p\n");
		fprintf(stderr, "option1 is anything else - write out decompressed data without turning it into a picture\n");
		fprintf(stderr, "option2 is nonexistant - decompress file by magic number in file (0x10 - LZSS, 0x11 - Extended LZSS, 0x40 - Scrubby RLE, anything else - LZSS)\n");
		fprintf(stderr, "option2 is anything else - don't decompress file\n");
		exit(1);
	}

	//open ROM for reading
	rom = fopen(argv[1], "rb");
	if(!rom)
	{
		fprintf(stderr, "Could not open ROM, %s.\n", argv[1]);
		exit(1);
	}

	strtoff = (int)strtol(argv[3], NULL, 16);
	_get_errno(&errornum);
	if(errornum == ERANGE)
	{
		fprintf(stderr, "Error with starting offset %s.\n", argv[3]);
		fclose(rom);
		exit(1);
	}

	maxstorlen = (int)strtol(argv[4], NULL, 16);
	if(!maxstorlen)
	{
		fprintf(stderr, "Error with storage length %s.\n", argv[3]);
		fclose(rom);
		exit(1);
	}

	//shift the file pointer to the offset
	fseek(rom, strtoff, SEEK_SET);

	//read in compression type
	comptype = fgetc(rom);
	fseek(rom, 3, SEEK_CUR);

	//set up data storage
	data = (unsigned char*)malloc(maxstorlen);
	curstorlen = 0;

	if(argc == 7)
	{
		while(curstorlen < maxstorlen)
		{
			data[curstorlen++] = fgetc(rom);
		}
	}
	else if(comptype == COMP_SCRUBBY)
	{
		//loop
		while(curstorlen < maxstorlen)
		{
			curbyte = fgetc(rom);

			//is current byte a zero run-length command?
			if(curbyte == 0x00) //yes
			{
				//collect length

				runlength = fgetc(rom);
				if(runlength & 0x80)
				{
					runlength -= 0x80;
					runlength <<= 8;
					runlength += fgetc(rom);
				}

				runlength++;

				for(runcounter = 0; runcounter < runlength; runcounter++)
				{
					data[curstorlen++] = 0;
				}
			}
			else //no
				data[curstorlen++] = curbyte;
		}
	}
	else if(comptype == COMP_LZSS_EXT)
	{
		dictlocs = fgetc(rom);
		curpbyte = 0x80;

		while(curstorlen < maxstorlen)
		{
			//is current pseudobyte a dictionary command?
			if(curpbyte & dictlocs) //yes
			{
				//collect dictionary call
				dictcall = (fgetc(rom) << 8);
				dictcall += fgetc(rom);
				
				if(dictcall < 0x2000)
				{
					dictcallex = dictcall << 8;
					dictcallex += fgetc(rom);

					if(dictcallex >= 0x100000)
					{
						dictcallex <<= 8;
						dictcallex += fgetc(rom);

						dictionEx4(dictcallex, data, curstorlen);
						curstorlen += ((dictcallex & 0x0FFFF000) >> 12) + 273;
					}
					else
					{
						dictionEx3(dictcallex, data, curstorlen);
						curstorlen += ((dictcallex & 0x0FF000) >> 12) + 17;
					}
				}
				else
				{
					dictionEx2(dictcall, data, curstorlen);
					curstorlen += ((dictcall & 0xF000) >> 12) + 1;
				}
			}
			else //no
				data[curstorlen++] = fgetc(rom);

			//shift pseudobyte marker
			curpbyte >>= 1;
			if(!curpbyte) // block finished
			{
				curpbyte = 0x80;
				dictlocs = fgetc(rom);
			}
		}
	}
	else
	{
		dictlocs = fgetc(rom);
		curpbyte = 0x80;

		while(curstorlen < maxstorlen)
		{
			//is current pseudobyte a dictionary command?
			if(curpbyte & dictlocs) //yes
			{
				//collect dictionary call
				dictcall = (fgetc(rom) << 8);
				dictcall += fgetc(rom);
				
				diction(dictcall, data, curstorlen);
				curstorlen += ((dictcall & 0xF000) >> 12) + 3;
			}
			else //no
			{
				data[curstorlen++] = fgetc(rom);
			}

			//shift pseudobyte marker
			curpbyte >>= 1;
			if(!curpbyte) // block finished
			{
				curpbyte = 0x80;
				dictlocs = fgetc(rom);
			}
		}		
	}

	//decomp should now be finished
	//print location and close the ROM
	printf("%06X\n", (int)ftell(rom));
	fclose(rom);

	//If we have an extra argument write out the data file without picturising
	if(argc == 6)
	{
		if(!strcmp(argv[5], "pal")) /* arg 6 is 'pal' */
		{
			palletename = malloc(strlen(argv[1]) + 3); //adding .p\0
			palletename[0] = '\0';
			strcat(palletename, argv[1]);
			strcat(palletename, ".p");

			palletefile = fopen(palletename, "rb");
			if(!palletefile)
			{
				fprintf(stderr, "Could not open pallete file %s.\n", palletename);
				free(data);
				exit(1);
			}

			//Collect the pallete
			for(palcounter = 0; palcounter < 256; palcounter++)
			{
				curpallent = fgetc(palletefile);
				curpallent += fgetc(palletefile) << 8;

				palblue[palcounter] = (curpallent & 0x001F) << 3;
				palgreen[palcounter] = (curpallent & 0x03E0) >> 2;
				palred[palcounter] = (curpallent & 0x7C00) >> 7;
			}

			fclose(palletefile);
			printf("Pallete collected.\n");
			paloff = 0;
		}
		else if(strcmp(argv[5], "def")) /* arg 6 isn't 'def' */
		{
			bitmap = fopen(argv[2], "wb");
			if(!bitmap)
			{
				fprintf(stderr, "Could not open output file %s.\n", argv[2]);
				free(data);
				exit(1);
			}

			fwrite(data, 1, curstorlen, bitmap);


			fclose(bitmap);
			free(data);

			return 0;
		}
	}
	else
	{
		//Collect the pallete
		for(palcounter = 0; palcounter < 256; palcounter++)
		{
			palblue[palcounter] = (data[(palcounter * 2)] & 0x1F) << 3;
			palgreen[palcounter] = ((data[(palcounter * 2)] & 0xE0) >> 2) + ((data[(palcounter * 2) + 1] & 0x03) << 6);
			palred[palcounter] = ((data[(palcounter * 2) + 1] & 0x7C) << 1);
		}
		paloff = 512;
	}

	/* Collect Image */
	for(blockycount = 0; blockycount < BLOCKS_HIGH; blockycount++)
	{
		for(blockxcount = 0; blockxcount < BLOCKS_WIDE; blockxcount++)
		{
			for(brickycount = 0; brickycount < BRICKS_HIGH; brickycount++)
			{
				for(brickxcount = 0; brickxcount < BRICKS_WIDE; brickxcount++)
				{
					for(pixelycount = 0; pixelycount < BRICK_HEIGHT; pixelycount++)
					{
						for(pixelxcount = 0; pixelxcount < BRICK_WIDTH; pixelxcount++)
						{
							curpix = data[paloff + (blockxcount * blocksize) + (blockycount * BLOCKS_WIDE * blocksize)
								+ (brickxcount * bricksize) + (brickycount * BRICKS_WIDE * bricksize)
								+ pixelxcount + (pixelycount * BRICK_WIDTH)];

							bitmapred[pixelxcount + (blockxcount * BLOCK_WIDTH) + (brickxcount * BRICK_WIDTH)][pixelycount + (blockycount * BLOCK_HEIGHT) + (brickycount * BRICK_HEIGHT)] = palred[curpix];
							bitmapgreen[pixelxcount + (blockxcount * BLOCK_WIDTH) + (brickxcount * BRICK_WIDTH)][pixelycount + (blockycount * BLOCK_HEIGHT) + (brickycount * BRICK_HEIGHT)] = palgreen[curpix];
							bitmapblue[pixelxcount + (blockxcount * BLOCK_WIDTH) + (brickxcount * BRICK_WIDTH)][pixelycount + (blockycount * BLOCK_HEIGHT) + (brickycount * BRICK_HEIGHT)] = palblue[curpix];
						}
					}
				}
			}
		}
	}


	//open the output file
	bitmap = fopen(argv[2], "wb");
	if(!bitmap)
	{
		fprintf(stderr, "Could not open output file %s.\n", argv[2]);
		free(data);
		exit(1);
	}

		/* BMP Header Rigmarole :[ */
		/* File Header */
		//Filetype (BM)
		fputc('B', bitmap);
		fputc('M', bitmap);
		//Filesize (Width by Height by 3. Then add 54.)
		fputc(((BLOCK_WIDTH * BLOCKS_WIDE * BLOCK_HEIGHT * BLOCKS_HIGH * 3) + 54) & 0x000000FF, bitmap);
		fputc((((BLOCK_WIDTH * BLOCKS_WIDE * BLOCK_HEIGHT * BLOCKS_HIGH * 3) + 54) & 0x0000FF00) >> 8, bitmap);
		fputc((((BLOCK_WIDTH * BLOCKS_WIDE * BLOCK_HEIGHT * BLOCKS_HIGH * 3) + 54) & 0x00FF0000) >> 16, bitmap);
		fputc((((BLOCK_WIDTH * BLOCKS_WIDE * BLOCK_HEIGHT * BLOCKS_HIGH * 3) + 54) & 0xFF000000) >> 24, bitmap);
		//Reserved
		fputc(0, bitmap);
		fputc(0, bitmap);
		fputc(0, bitmap);
		fputc(0, bitmap);
		//Bitmap Data Start (54)
		fputc(54, bitmap);
		fputc(0, bitmap);
		fputc(0, bitmap);
		fputc(0, bitmap);

		/* Bitmap Header */
		//Bitmap Header Size
		fputc(40, bitmap);
		fputc(0, bitmap);
		fputc(0, bitmap);
		fputc(0, bitmap);
		//Width
		fputc((BLOCK_WIDTH * BLOCKS_WIDE) & 0x000000FF, bitmap);
		fputc(((BLOCK_WIDTH * BLOCKS_WIDE) & 0x0000FF00) >> 8, bitmap);
		fputc(((BLOCK_WIDTH * BLOCKS_WIDE) & 0x00FF0000) >> 16, bitmap);
		fputc(((BLOCK_WIDTH * BLOCKS_WIDE) & 0xFF000000) >> 24, bitmap);
		//Height
		fputc((BLOCK_HEIGHT * BLOCKS_HIGH) & 0x000000FF, bitmap);
		fputc(((BLOCK_HEIGHT * BLOCKS_HIGH) & 0x0000FF00) >> 8, bitmap);
		fputc(((BLOCK_HEIGHT * BLOCKS_HIGH) & 0x00FF0000) >> 16, bitmap);
		fputc(((BLOCK_HEIGHT * BLOCKS_HIGH) & 0xFF000000) >> 24, bitmap);
		//Planes (always 1)
		fputc(1, bitmap);
		fputc(0, bitmap);
		//Bits Per Pixel
		fputc(24, bitmap);
		fputc(0, bitmap);
		//Compression (no)
		fputc(0, bitmap);
		fputc(0, bitmap);
		fputc(0, bitmap);
		fputc(0, bitmap);
		//Byte size of Bitmap (not required)
		fputc(0, bitmap);
		fputc(0, bitmap);
		fputc(0, bitmap);
		fputc(0, bitmap);
		//horizontal, vertical resolution
		fputc(0, bitmap);
		fputc(0, bitmap);
		fputc(0, bitmap);
		fputc(0, bitmap);
		fputc(0, bitmap);
		fputc(0, bitmap);
		fputc(0, bitmap);
		fputc(0, bitmap);
		//Colours used, colours important (not required)
		fputc(0, bitmap);
		fputc(0, bitmap);
		fputc(0, bitmap);
		fputc(0, bitmap);
		fputc(0, bitmap);
		fputc(0, bitmap);
		fputc(0, bitmap);
		fputc(0, bitmap);

		/* Header Done */
		
		/* Now Picture */
		for(pixelycount = 0; pixelycount < (BLOCK_HEIGHT * BLOCKS_HIGH); pixelycount++) //vertical
		{
			for(pixelxcount = 0; pixelxcount < (BLOCK_WIDTH * BLOCKS_WIDE); pixelxcount++) //horizontal
			{
				fputc(bitmapred[pixelxcount][((BLOCK_HEIGHT * BLOCKS_HIGH) - 1) - pixelycount], bitmap);
				fputc(bitmapgreen[pixelxcount][((BLOCK_HEIGHT * BLOCKS_HIGH) - 1) - pixelycount], bitmap);
				fputc(bitmapblue[pixelxcount][((BLOCK_HEIGHT * BLOCKS_HIGH) - 1) - pixelycount], bitmap);
			}
		}


	fclose(bitmap);
	free(data);

	return 0;
}

void diction(unsigned short dictcall, unsigned char* data, unsigned int curoff)
{
	/* Locals */
	         char length;
			 char x;
	unsigned int  dictstart;

	dictstart = curoff - ((dictcall & 0x0FFF) + 1);
	length = ((dictcall & 0xF000) >> 12) + 3;

	for(x = 0; x < length; x++) //transfer the given area
		data[curoff++] = data[dictstart++];
}

void dictionEx2(unsigned short dictcall, unsigned char* data, unsigned int curoff)
{
	/* Locals */
	         char length;
			 char x;
	unsigned int  dictstart;

	dictstart = curoff - ((dictcall & 0x0FFF) + 1);
	length = ((dictcall & 0xF000) >> 12) + 1;

	for(x = 0; x < length; x++) //transfer the given area
		data[curoff++] = data[dictstart++];
}

void dictionEx3(unsigned int dictcall, unsigned char* data, unsigned int curoff)
{
	/* Locals */
	unsigned int length;
	unsigned int x;
	unsigned int  dictstart;

	dictstart = curoff - ((dictcall & 0x00000FFF) + 1);
	length = ((dictcall & 0x0FF000) >> 12) + 17;

	printf("Diction3 call: %08X\nStarting at %08X running for %08X\n", dictcall, dictstart, length);

	for(x = 0; x < length; x++) //transfer the given area
		data[curoff++] = data[dictstart++];
}

void dictionEx4(unsigned int dictcall, unsigned char* data, unsigned int curoff)
{
	/* Locals */
	unsigned int length;
	unsigned int x;
	unsigned int  dictstart;

	printf("Diction4 call: %08X\n", dictcall);

	dictstart = curoff - ((dictcall & 0x00000FFF) + 1);
	length = ((dictcall & 0x0FFFF000) >> 12) + 273;

	for(x = 0; x < length; x++) //transfer the given area
		data[curoff++] = data[dictstart++];
}