/* HAR format loader */

#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <conio.h>

#define LINE_SIZE 320
#define TOT_LINES 200
#define VGA_MEM 0xa0000000
#define BIOSVID 0x10
#define VGA256 0x13
#define TEXT 0x3
#define SETMODE 0

/* Declare a variable type that holds the image information */
typedef struct {
	char identsize;
	char colormaptype;
	char imagetype;
	unsigned int colormapstart;
	unsigned int colormaplength;
	char colormapbits;
	unsigned int xstart;
	unsigned int ystart;
	unsigned int width, depth;
	char bits;
	char descriptor;
} TGAHEAD;

/* Function declarations */
void graphics (void);
void text (void);
void decode (FILE *fp);
void paletteset (char *buffer);
void putline(char *p, unsigned int n, int width);
void getline(char *p, unsigned int n, int width);
void error (char *message);

void main(int argc, char *argv[]) {
	FILE *fp;

	/* Make sure a filename was entered */
	if(argc > 1) {
		/* Open the file */
		if((fp = fopen (argv[argc - 1], "rb")) != NULL) {
			/* Go into graphics mode */
			graphics();
			/* Read and display the file */
			decode(fp);
			/* Close the file */
			fclose(fp);
			/* Wait for a keypress */
			while(!kbhit());
			/* Return to text mode */
			text();
		} else printf("Error opening %s\n", argv[argc - 1]);
	} else puts("Must specify a filename\n");
}

/* The graphics function puts the screen in graphics mode */
void graphics (void) {
	union REGS regs;
	regs.h.ah = 0;
	regs.h.al = 0x13;
	int86(0x10, &regs, &regs);
}

/* The text function returns the screen to text mode */
void text (void) {
	union REGS regs;
	regs.h.ah = 0;
	regs.h.al = 3;
	int86(0x10, &regs, &regs);
}

/* The decode function reads and displays the TGA file */
void decode (FILE *fp) {
	TGAHEAD tga;
	char *ptr;
	char buffer[768], buffer2[320], buffer3[320];
	char temp;
	int bytes, i, n, startline, endline, incline;

	/* Read the header for the file */
	if (fread((char *)&tga, 1, sizeof(TGAHEAD), fp)==sizeof(TGAHEAD)) {

			/* Read until the beginning of the image is reached */
			fseek(fp, (long)tga.identsize, SEEK_CUR);

			/* See if the file has an invalid number of colors */
			if(tga.colormaptype) {
				switch(tga.colormapbits) {
					case 24:
						for(i = tga.colormapstart; i < tga.colormaplength; i++) {
							if(i >= 256) break;
							buffer[i*3+2] = fgetc(fp);
							buffer[i*3+1] = fgetc(fp);
							buffer[i*3]   = fgetc(fp);
						}
						break;
					case 16:
						for(i = tga.colormapstart; i < tga.colormaplength; i++) {
							if(i >= 256) break;
							n = ((fgetc(fp) & 0xff) + ((fgetc(fp) & 0xff) << 8));
							buffer[i*3+2] = ((n >> 10) & 0x1f) << 3;
							buffer[i*3+1] = ((n >> 5 ) & 0x1f) << 3;
							buffer[i*3]   = (n & 0x1f) << 3;
						}
						break;
				}
				paletteset(buffer);
                        } else {
                                if(tga.imagetype==3) {
                                        for(i=0; i<=255; i++) {
                                                buffer[i * 3] = i;
                                                buffer[i * 3 + 1] = i;
                                                buffer[i * 3 + 2] = i;
                                        }
                                        paletteset(buffer);
                                } else {
                                        error("Image must be greyscale or have a palette.");
                                }
                        }


			/* Set up which parts of the file are to be displayed */
			startline = 0;
			endline = tga.depth;
			incline = 4;

			/* Read the level 1 image and display it */
			for(i = startline; i < endline; i += incline) {
				fread(&buffer, 1, tga.width, fp);
				putline(buffer, i, tga.width);
				putline(buffer, i + 1, tga.width);
				putline(buffer, i + 2, tga.width);
				putline(buffer, i + 3, tga.width);
			}

			/* Read the level 2 image and display it */
			for(i = startline; i < endline; i += incline) {
				fread(&buffer, 1, tga.width, fp);
				getline(buffer2, i, tga.width);
				getline(buffer3, i + 2, tga.width);
				for(n = 0; n < tga.width; n++) {
					buffer2[n] = buffer2[n] + buffer[n] / 2;
					buffer3[n] = buffer3[n] - buffer[n] / 2;
					if(buffer[n] & 1)
						if(buffer[n] < 0) buffer3[n]++;
						else buffer2[n]++;
				}
				putline(buffer2, i, tga.width);
				putline(buffer2, i + 1, tga.width);
				putline(buffer3, i + 2, tga.width);
				putline(buffer3, i + 3, tga.width);
			}

			for(i = startline; i < endline; i += incline) {
				fread(&buffer, 1, tga.width, fp);
				getline(buffer2, i, tga.width);
				getline(buffer3, i + 1, tga.width);
				for(n = 0; n < tga.width; n++) {
					buffer2[n] = buffer2[n] + buffer[n] / 2;
					buffer3[n] = buffer3[n] - buffer[n] / 2;
					if(buffer[n] & 1)
						if(buffer[n] < 0) buffer3[n]++;
						else buffer2[n]++;
				}
				putline(buffer2, i, tga.width);
				putline(buffer3, i + 1, tga.width);
			}

			for(i = startline; i < endline; i += incline) {
				fread(&buffer, 1, tga.width, fp);
				getline(buffer2, i + 2, tga.width);
				getline(buffer3, i + 3, tga.width);
				for(n = 0; n < tga.width; n++) {
					buffer2[n] = buffer2[n] + buffer[n] / 2;
					buffer3[n] = buffer3[n] - buffer[n] / 2;
					if(buffer[n] & 1)
						if(buffer[n] < 0) buffer3[n]++;
						else buffer2[n]++;
				}
				putline(buffer2, i + 2, tga.width);
				putline(buffer3, i + 3, tga.width);
			}
	}
}

/* The paletteset function sets the color palette */
void paletteset (char *buffer) {
	union REGS regs;
	int i;

	outp(0x3c6, 0xff);
	for(i = 0; i < 255; i++) {
		outp(0x3c8, i);
		outp(0x3c9, (*buffer++) >> 2);
		outp(0x3c9, (*buffer++) >> 2);
		outp(0x3c9, (*buffer++) >> 2);
	}
}

/* The putline function displays one line of the image */
void putline(char *buffer, unsigned int n, int width) {
	char far *ScreenMemory = (char far *)(VGA_MEM + n * LINE_SIZE);
	int j;

	for(j = 0; j < width; j++) *(ScreenMemory+j) = buffer[j];
}

/* The getline function reads a line of the image from screen memory */
void getline(char *buffer, unsigned int n, int width) {
	char far *ScreenMemory = (char far *)(VGA_MEM + n * LINE_SIZE);
	int j;

	for(j = 0; j < width; j++) *(buffer + j) = *(ScreenMemory + j);
}

/* The error function displays an error message and halts the program */
void error (char *message) {
	text();
	puts(message);
	exit(1);
}
