/*
 * Copyright (C) 2004-2009 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define BLOCKSIZE	512

const char *progname;
const char *filename;

static long
getnearestpoweroftwo(long n)
{
	long x;
	
	x = 1;
	while (x < n) {
		x <<= 1;
	}
	return x;
}

static void
bin2bios(void)
{
	unsigned char image[0x10 * 0x10000];
	int len;
	int fd;
	int found;
	unsigned char sum;
	int i;
	int j;
	int ret;

	/*
	 * Read file.
	 */
	fd = open(filename, O_RDONLY);
	if (fd < 0) {
		fprintf(stderr, "%s: can't open %s: %s.\n", progname,
				filename, strerror(errno));
		exit(1);
	}

	len = read(fd, image, sizeof(image));
	if (len < 0) {
		fprintf(stderr, "%s: can't read %s: %s.\n", progname,
				filename, strerror(errno));
		exit(1);
	}
	
	memset(image + len, 0, sizeof(image) - len);

	ret = close(fd);
	assert(0 <= ret);

	fprintf(stderr, "%s: %d bytes\n", filename, len);

	/*
	 * Patch image.
	 */
	/* Calculate new image size. */
	len = getnearestpoweroftwo(len);

	/* Patch size byte. */
	image[2] = (len + BLOCKSIZE - 1) / BLOCKSIZE;

	/* Patch "_32_" checksum entry. */
	found = 0;
	for (i = 0; i < len; i += 16) {
		if (image[i + 0x00] == '_'
		 && image[i + 0x01] == '3'
		 && image[i + 0x02] == '2'
		 && image[i + 0x03] == '_'
		 && image[i + 0x08] == 0
		 && image[i + 0x0a] == 0xff
		 && image[i + 0x0b] == 0
		 && image[i + 0x0c] == 0
		 && image[i + 0x0d] == 0
		 && image[i + 0x0e] == 0
		 && image[i + 0x0f] == 0) {
			if (! found) {
				fprintf(stderr, "_32_ structure found at offset 0x%04x.\n", i);
			} else {
				fprintf(stderr, "WARNING: another _32_ structure found at offset 0x%04x.\n", i);
			}

			sum = 0;
			for (j = 0; j < 16; j++) {
				sum += image[i + j];
			}
			image[i + 0xa] -= sum;

			found = 1;
		}
	}

	/* Patch "$PnP" checksum entry. */
	found = 0;
	for (i = 0; i < len; i += 16) {
		if (image[i + 0x00] == '$'
		 && image[i + 0x01] == 'P'
		 && image[i + 0x02] == 'n'
		 && image[i + 0x03] == 'P') {
			uint16_t size;

			size = image[i + 5];

			if (! found) {
				fprintf(stderr, "$PnP structure found at offset 0x%04x (size=0x%x).\n", i, size);
			} else {
				fprintf(stderr, "WARNING: another $PnP structure found at offset 0x%04x.\n", i);
			}

			sum = 0;
			for (j = 0; j < size; j++) {
				sum += image[i + j];
			}
			image[i + 0x8] -= sum;

			found = 1;
		}
	}

	/* Patch "$PIR" checksum entry. */
	found = 0;
	for (i = 0; i < len; i += 16) {
		if (image[i + 0x00] == '$'
		 && image[i + 0x01] == 'P'
		 && image[i + 0x02] == 'I'
		 && image[i + 0x03] == 'R') {
			unsigned short size;

			size = (image[i + 6] << 0) + (image[i + 7] << 8);

			if (! found) {
				fprintf(stderr, "$PIR structure found at offset 0x%04x (size=0x%x).\n", i, size);
			} else {
				fprintf(stderr, "WARNING: another $PIR structure found at offset 0x%04x.\n", i);
			}

			sum = 0;
			for (j = 0; j < size; j++) {
				sum += image[i + j];
			}
			image[i + 0x1f] -= sum;

			found = 1;
		}
	}

	/* Patch "_DMI_" checksum entry. */
	found = 0;
	for (i = 0; i < len; i += 16) {
		if (image[i + 0x00] == '_'
		 && image[i + 0x01] == 'D'
		 && image[i + 0x02] == 'M'
		 && image[i + 0x03] == 'I'
		 && image[i + 0x04] == '_') {
			if (! found) {
				fprintf(stderr, "_DMI_ structure found at offset 0x%04x.\n", i);
			} else {
				fprintf(stderr, "WARNING: another _DMI_ structure found at offset 0x%04x.\n", i);
			}

			sum = 0;
			for (j = 0; j < 15; j++) {
				sum += image[i + j];
			}
			image[i + 0x05] -= sum;
		}
	}

	/* Patch checksum of BIOS. */
	sum = 0;
	for (i = 0; i < len; i++) {
		sum += image[i];
	}
	image[len - 1] -= sum;

	/*
	 * Write file to stdout.
	 */
	ret = write(1, image, len);
	if (ret < 0) {
		fprintf(stderr, "%s: can't write to stdout: %s.\n", progname,
				strerror(errno));
		exit(1);
	}
	if (ret < len) {
		fprintf(stderr, "%s: can't write to stdout: %s.\n", progname,
				"Short write");
		exit(1);
	}
	assert(ret == len);
}

static void
usage(int retval) __attribute__((__noreturn__));
static void
usage(int retval)
{
	fprintf(stderr, "Usage: %s [-p] [-e] filename\n", progname);
	fprintf(stderr, "Options:\n");
	fprintf(stderr, "-e   Do patch the bios entry\n");
	fprintf(stderr, "-p   Do not pad the size of the image\n");
	fprintf(stderr, "     to the nearest power of 2\n");
	exit(retval);
}

int
main(int argc, char **argv)
{
	int c;

	/*
	 * Get program name.
	 */
	progname = *argv;

	/*
	 * Get options.
	 */
	while ((c = getopt(argc, argv, "ep")) != -1) {
		switch (c) {
		default:
			usage(1);
		}
	}
	argv += optind;
	argc -= optind;

	/*
	 * Get parameter.
	 */
	if (0 < argc) {
		filename = *argv;
		argv++;
		argc--;
	} else {
		usage(1);
	}

	if (argc != 0) {
		usage(1);
	}

	bin2bios();

	return 0;
}
