shithub: rgbds

ref: 7e4104cabe8d995e40e552da2078fea87ff4ada6
dir: /src/fix/main.c/

View raw version
/*
 * RGBFix : Perform various tasks on a Gameboy image-file
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include "asmotor.h"

/*
 * Option defines
 *
 */

#define OPTF_DEBUG	0x01L
#define OPTF_PAD	0x02L
#define OPTF_VALIDATE	0x04L
#define OPTF_TITLE	0x08L
#define OPTF_TRUNCATE	0x10L
#define OPTF_QUIET	0x20L
#define OPTF_RAMSIZE	0x40L

unsigned long ulOptions;

/*
 * Misc. variables
 *
 */

unsigned char NintendoChar[48] = {
	0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B,
	0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D,
	0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E,
	0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99,
	0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC,
	0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E,
};
/*
 * Misc. routines
 *
 */

void 
PrintUsage(void)
{
	printf("RGBFix v" RGBFIX_VERSION
	    " (part of ASMotor " ASMOTOR_VERSION ")\n\n");

	printf("Usage: rgbfix [options] image[.gb]\n");
	printf("Options:\n");
	printf("  -h\t\tThis text\n");
	printf("  -d\t\tDebug: Don't change image\n");
	printf("  -m<hx>\tChange RAM size of cartridge\n");
	printf("  -p[<hx>]\tPad image to valid size\n"
		"\t\tPads to 32/64/128/256/512kB as appropriate\n"
		"\t\tAn optional hexadecimal pad value can be supplied (default is 0)");
	printf("  -r\t\ttRuncate image to valid size\n\t\t\tTruncates to 32/64/128/256/512kB as appropriate\n");
	printf("  -t<name>\tChange cartridge title field (16 characters)\n");
	printf("  -v\t\tValidate header\n"
		"\t\tCorrects - Nintendo Character Area (0x0104)\n"
		"\t\t\t - ROM type (0x0147)\n"
		"\t\t\t - ROM size (0x0148)\n"
		"\t\t\t - Checksums (0x014D-0x014F)\n");
	printf("  -q\t\tExecute quietly (suppress all text except errors)\n");

	exit(0);
}

void 
vFatalError(char *s, va_list ap)
{
	fprintf(stderr, "*ERROR* : ");
	vfprintf(stderr, s, ap);
	fprintf(stderr, "\n");
}

void 
FatalError(char *s,...)
{
	va_list ap;
	va_start(ap, s);
	vFatalError(s, ap);
	va_end(ap);
	exit(5);
}

void 
vWarning(char *s, va_list ap)
{
	fprintf(stderr, "*WARNING* : ");
	vfprintf(stderr, s, ap);
	fprintf(stderr, "\n");
}

void 
Warning(char *s,...)
{
	va_list ap;
	va_start(ap, s);
	vWarning(s, ap);
	va_end(ap);
}

long int 
FileSize(FILE * f)
{
	long prevpos;
	long r;

	fflush(f);
	prevpos = ftell(f);
	fseek(f, 0, SEEK_END);
	r = ftell(f);
	fseek(f, prevpos, SEEK_SET);
	return (r);
}

int 
FileExists(char *s)
{
	FILE *f;

	if ((f = fopen(s, "rb")) != NULL) {
		fclose(f);
		return (1);
	} else
		return (0);
}
/*
 * Das main
 *
 */

int 
main(int argc, char *argv[])
{
	int argn = 1;
	char filename[512];
	char cartname[32];
	FILE *f;
	int pad_value = 0;
	int ram_size = 0;

	ulOptions = 0;

	if (argc == 1)
		PrintUsage();

	while (argn < argc) {
		if (argv[argn][0] == '-') {
			switch (argv[argn][1]) {
			case '?':
			case 'h':
				PrintUsage();
				break;
			case 'd':
				ulOptions |= OPTF_DEBUG;
				break;
			case 'm':
				ulOptions |= OPTF_RAMSIZE;
				if (strlen(argv[argn] + 2) > 0 && strlen(argv[argn] + 2) <= 2) {
					int result;
					result = sscanf(argv[argn] + 2,"%x", &ram_size);
					if (!((result == EOF) || (result == 1))) {
						FatalError("Invalid argument for option 'm'");
					}
				} else if (strlen(argv[argn] + 2) > 0) {
					FatalError("RAM size must be between 0 and FF");
				} else {
					FatalError("Invalid argument for option 'm'");
				}
				break;
			case 'p':
				ulOptions |= OPTF_PAD;
				if (strlen(argv[argn] + 2) > 0 && strlen(argv[argn] + 2) <= 2) {
					int result;
					result = sscanf(argv[argn] + 2, "%x", &pad_value);
					if (!((result == EOF) || (result == 1))) {
						FatalError("Invalid argument for option 'p'");
					}
				}
				break;
			case 'r':
				ulOptions |= OPTF_TRUNCATE;
				break;
			case 'v':
				ulOptions |= OPTF_VALIDATE;
				break;
			case 't':
				strncpy(cartname, argv[argn] + 2, 16);
				ulOptions |= OPTF_TITLE;
				break;
			case 'q':
				ulOptions |= OPTF_QUIET;
				break;
			}
		}
		argn++;
	}

	if (argv[argc - 1][0] == '-')
		PrintUsage();

	strcpy(filename, argv[argc - 1]);

	if (!FileExists(filename))
		strcat(filename, ".gb");

	f = fopen(filename, "rb+");
	if ((f = fopen(filename, "rb+")) != NULL) {

		/*
		 * -d (Debug) option code
		 *
		 */

		if ((ulOptions & OPTF_DEBUG) && !(ulOptions & OPTF_QUIET)) {
			printf("-d (Debug) option enabled...\n");
		}
		/*
		 * -p (Pad) option code
		 *
		 */

		if (ulOptions & OPTF_PAD) {
			long size, padto;
			long bytesadded = 0;

			size = FileSize(f);
			padto = 0x8000L;
			while (size > padto)
				padto *= 2;

			if (!(ulOptions & OPTF_QUIET)) {
				printf("Padding to %ldkB with pad value %#02X\n", padto / 1024, pad_value & 0xFF);
			}
			/*
			   if( padto<=0x80000L )
			   {
			   */
			if (size != padto) {
				fflush(stdout);

				fseek(f, 0, SEEK_END);
				while (size < padto) {
					size += 1;
					if ((ulOptions & OPTF_DEBUG) == 0)
						fputc(pad_value & 0xFF, f);
					bytesadded += 1;
				}
				fflush(f);

				if (!(ulOptions & OPTF_QUIET)) {
					printf("\tAdded %ld bytes\n", bytesadded);
				}
			} else {
				if (!(ulOptions & OPTF_QUIET)) {
					printf("\tNo padding needed\n");
				}
			}
			/*
			   }
			   else
			   FatalError( "Image size exceeds 512kB" );
			   */
		}
		/*
		 * -r (Truncate) option code
		 *
		 */

		if (ulOptions & OPTF_TRUNCATE) {
			long size, padto;
			char tempfile[] = "/tmp/rgbfix-XXXXXX";
			FILE *tf;

			size = FileSize(f);
			padto = 256 * 32768;
			while (size < padto)
				padto /= 2;

			if (!(ulOptions & OPTF_QUIET)) {
				printf("Truncating to %ldkB:\n", padto / 1024);
			}
			mkstemp(tempfile);

			if ((ulOptions & OPTF_DEBUG) == 0) {
				if ((tf = fopen(tempfile, "wb")) != NULL) {
					fseek(f, 0, SEEK_SET);
					while (padto--) {
						fputc(fgetc(f), tf);
					}
					fclose(f);
					fclose(tf);
					remove(filename);
					rename(tempfile, filename);
					f = fopen(filename, "rb+");
				}
			}
		}
		/*
		 * -t (Set carttitle) option code
		 *
		 */

		if (ulOptions & OPTF_TITLE) {
			if (!(ulOptions & OPTF_QUIET)) {
				printf("Setting cartridge title:\n");
			}
			if ((ulOptions & OPTF_DEBUG) == 0) {
				fflush(f);
				fseek(f, 0x0134L, SEEK_SET);
				fwrite(cartname, 16, 1, f);
				fflush(f);
			}
			if (!(ulOptions & OPTF_QUIET)) {
				printf("\tTitle set to %s\n", cartname);
			}
		}
		/*
		 * -m (Set ram size) option code
		 *
		 */
		if (ulOptions & OPTF_RAMSIZE) {
			/* carttype byte can be anything? */
			if (!(ulOptions & OPTF_QUIET)) {
				printf("Setting RAM size\n");
			}
			fflush(f);
			fseek(f, 0x149L, SEEK_SET);
			fputc(ram_size, f);
			fflush(f);

			if (!(ulOptions & OPTF_QUIET)) {
				printf("\tRAM size set to %#02X\n", ram_size);
			}
		}
		/*
		 * -v (Validate header) option code
		 *
		 */

		if (ulOptions & OPTF_VALIDATE) {
			long i, byteschanged = 0;
			long cartromsize, calcromsize = 0, filesize;
			long carttype;
			unsigned short cartchecksum = 0, calcchecksum = 0;
			unsigned char cartcompchecksum = 0, calccompchecksum =
			0;
			int ch;

			if (!(ulOptions & OPTF_VALIDATE)) {
				printf("Validating header:\n");
			}
			fflush(stdout);

			/* Nintendo Character Area */

			fflush(f);
			fseek(f, 0x0104L, SEEK_SET);

			for (i = 0; i < 48; i += 1) {
				int ch;

				ch = fgetc(f);
				if (ch == EOF)
					ch = 0x00;
				if (ch != NintendoChar[i]) {
					byteschanged += 1;

					if ((ulOptions & OPTF_DEBUG) == 0) {
						fseek(f, -1, SEEK_CUR);
						fputc(NintendoChar[i], f);
						fflush(f);
					}
				}
			}

			fflush(f);

			if (!(ulOptions & OPTF_QUIET)) {
				if (byteschanged)
					printf
					    ("\tChanged %ld bytes in the Nintendo Character Area\n",
					    byteschanged);
				else
					printf("\tNintendo Character Area is OK\n");
			}
			/* ROM size */

			fflush(f);
			fseek(f, 0x0148L, SEEK_SET);
			cartromsize = fgetc(f);
			if (cartromsize == EOF)
				cartromsize = 0x00;
			filesize = FileSize(f);
			while (filesize > (0x8000L << calcromsize))
				calcromsize += 1;

			if (calcromsize != cartromsize) {
				if ((ulOptions & OPTF_DEBUG) == 0) {
					fseek(f, -1, SEEK_CUR);
					fputc(calcromsize, f);
					fflush(f);
				}
				if (!(ulOptions & OPTF_QUIET)) {
					printf("\tChanged ROM size byte from 0x%02lX (%ldkB) to 0x%02lX (%ldkB)\n",
					    cartromsize,
					    (0x8000L << cartromsize) / 1024,
					    calcromsize,
					    (0x8000L << calcromsize) / 1024);
				}
			} else if (!(ulOptions & OPTF_QUIET)) {
				printf("\tROM size byte is OK\n");
			}
			/* Cartridge type */

			fflush(f);
			fseek(f, 0x0147L, SEEK_SET);
			carttype = fgetc(f);
			if (carttype == EOF)
				carttype = 0x00;

			if (FileSize(f) > 0x8000L) {
				/* carttype byte must != 0x00 */
				if (carttype == 0x00) {
					if ((ulOptions & OPTF_DEBUG) == 0) {
						fseek(f, -1, SEEK_CUR);
						fputc(0x01, f);
						fflush(f);
					}
					if (!(ulOptions & OPTF_QUIET)) {
						printf
						    ("\tCartridge type byte changed to 0x01\n");
					}
				} else if (!(ulOptions & OPTF_QUIET)) {
					printf("\tCartridge type byte is OK\n");
				}
			} else {
				/* carttype byte can be anything? */
				if (!(ulOptions & OPTF_QUIET)) {
					printf("\tCartridge type byte is OK\n");
				}
			}

			/* Checksum */

			fflush(f);
			fseek(f, 0, SEEK_SET);

			for (i = 0; i < (0x8000L << calcromsize); i += 1) {
				ch = fgetc(f);
				if (ch == EOF)
					ch = 0;

				if (i < 0x0134L)
					calcchecksum += ch;
				else if (i < 0x014DL) {
					calccompchecksum += ch;
					calcchecksum += ch;
				} else if (i == 0x014DL)
					cartcompchecksum = ch;
				else if (i == 0x014EL)
					cartchecksum = ch << 8;
				else if (i == 0x014FL)
					cartchecksum |= ch;
				else
					calcchecksum += ch;
			}

			calccompchecksum = 0xE7 - calccompchecksum;
			calcchecksum += calccompchecksum;

			if (cartchecksum != calcchecksum) {
				fflush(f);
				fseek(f, 0x014EL, SEEK_SET);
				if ((ulOptions & OPTF_DEBUG) == 0) {
					fputc(calcchecksum >> 8, f);
					fputc(calcchecksum & 0xFF, f);
				}
				fflush(f);
				if (!(ulOptions & OPTF_QUIET)) {
					printf
					    ("\tChecksum changed from 0x%04lX to 0x%04lX\n",
					    (long) cartchecksum, (long) calcchecksum);
				}
			} else {
				if (!(ulOptions & OPTF_QUIET)) {
					printf("\tChecksum is OK\n");
				}
			}

			if (cartcompchecksum != calccompchecksum) {
				fflush(f);
				fseek(f, 0x014DL, SEEK_SET);
				if ((ulOptions & OPTF_DEBUG) == 0)
					fputc(calccompchecksum, f);
				fflush(f);
				if (!(ulOptions & OPTF_QUIET)) {
					printf
					    ("\tCompChecksum changed from 0x%02lX to 0x%02lX\n",
					    (long) cartcompchecksum,
					    (long) calccompchecksum);
				}
			} else {
				if (!(ulOptions & OPTF_QUIET)) {
					printf("\tCompChecksum is OK\n");
				}
			}

		}
		fclose(f);
	} else {
		FatalError("File '%s' not found", filename);
	}

	return (0);
}