#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <unistd.h>
#include <sys/fcntl.h>
#include <getopt.h>
#include <arpa/inet.h>
#include <sysexits.h>
#include <errno.h>
#include <string.h>

uint64_t ntoh64(uint64_t num) {
        int t = 1234;
        union conv {
                uint64_t i64;
                uint32_t i32[2];
        } *in, out;

        if( ntohl(t) == t ) {
                out.i64 = num;
                return out.i64;
        }
        in = (union conv *)&num;
        out.i32[1] = ntohl(in->i32[0]);
        out.i32[0] = ntohl(in->i32[1]);
        return(out.i64);
}

struct ppcrominfo {
	uint32_t lane0;
	uint32_t lane1;
	uint32_t lane2;
	uint32_t lane3;
	uint32_t lane4;
	uint32_t lane5;
	uint32_t lane6;
	uint32_t lane7;
	uint64_t sum;
	uint32_t rombase;
	uint32_t romsize;
	uint32_t romversion;
	uint32_t macromoffset;
	uint32_t macromsize;
};

void rominfo_byteswap(struct ppcrominfo *rominfo) {
	rominfo->lane0 = ntohl(rominfo->lane0);
	rominfo->lane1 = ntohl(rominfo->lane1);
	rominfo->lane2 = ntohl(rominfo->lane2);
	rominfo->lane3 = ntohl(rominfo->lane3);
	rominfo->lane4 = ntohl(rominfo->lane4);
	rominfo->lane5 = ntohl(rominfo->lane5);
	rominfo->lane6 = ntohl(rominfo->lane6);
	rominfo->lane7 = ntohl(rominfo->lane7);

	rominfo->sum = ntoh64(rominfo->sum);
	rominfo->rombase = ntohl(rominfo->rombase);
	rominfo->romsize = ntohl(rominfo->romsize);
	rominfo->romversion = ntohl(rominfo->romversion);
	rominfo->macromoffset = ntohl(rominfo->macromoffset);
	rominfo->macromsize = ntohl(rominfo->macromsize);

	return;
}

void rominfo_print(struct ppcrominfo rominfo) {
	printf("ROM checksum of byte lane 0: %" PRIx32 "\n", rominfo.lane0);
	printf("ROM checksum of byte lane 1: %" PRIx32 "\n", rominfo.lane1);
	printf("ROM checksum of byte lane 2: %" PRIx32 "\n", rominfo.lane2);
	printf("ROM checksum of byte lane 3: %" PRIx32 "\n", rominfo.lane3);
	printf("ROM checksum of byte lane 4: %" PRIx32 "\n", rominfo.lane4);
	printf("ROM checksum of byte lane 5: %" PRIx32 "\n", rominfo.lane5);
	printf("ROM checksum of byte lane 6: %" PRIx32 "\n", rominfo.lane6);
	printf("ROM checksum of byte lane 7: %" PRIx32 "\n", rominfo.lane7);

	printf("ROM checksum: %" PRIx64 "\n", rominfo.sum);

	printf("Image Base: %" PRIx32 "\n", rominfo.rombase);
	printf("ROM Size: %" PRIx32 "\n", rominfo.romsize);
	printf("ROM version: %" PRIx32 "\n", rominfo.romversion);
	printf("Mac ROM Offset: %" PRIx32 "\n", rominfo.macromoffset);
	printf("Mac ROM Size: %" PRIx32 "\n", rominfo.macromsize);

	return;
}

int main(int argc, char *argv[]) {
	int fd;
	int loptind = 0;
	char c = 0;
	char *filename = NULL;
	uint32_t fileoffset = 0; // 0030d000
	struct ppcrominfo rominfo;
	struct option o[] = {
		{"file", 1, 0, 'f'},
		{"offset", 1, 0, 'o'},
		{0,0,0,0}
	};

	while((c = getopt_long(argc, argv, "f:o:", o, &loptind)) != -1) {
		switch(c) {
		case 'f':
			if(!optarg) {
				fprintf(stderr, "--file/-f requires an argument\n");
				exit(EX_USAGE);
			}

			filename = optarg;
			break;
		case 'o':
			if(!optarg) {
				fprintf(stderr, "--offset/-o requires an argument\n");
				exit(EX_USAGE);
			}
			fileoffset = strtoul(optarg, NULL, 16);
			break;
		default:
			break;
		}
	}

	fd = open(filename, O_RDONLY);
	if(fd < 0) {
		fprintf(stderr, "Could not open file %s: %d %s\n", filename, errno, strerror(errno));
		exit(EX_CONFIG);
	}

	if(pread(fd, &rominfo, sizeof(rominfo), fileoffset) == -1) {
		fprintf(stderr, "Could not read rominfo from file %s at offset %x: %d %s\n", filename, fileoffset, errno, strerror(errno));
		exit(EX_CONFIG);
	}

	rominfo_byteswap(&rominfo);

	rominfo_print(rominfo);

	int64_t csum = 0;
	int32_t lowcsum = 0;
	int32_t highcsum = 0;
	int32_t csum0 = 0;
	int32_t csum1 = 0;
	int32_t csum2 = 0;
	int32_t csum3 = 0;
	int32_t csum4 = 0;
	int32_t csum5 = 0;
	int32_t csum6 = 0;
	int32_t csum7 = 0;
	uint32_t i;
	lseek(fd, fileoffset, SEEK_SET);

	// checksum the checksums first
	for(i = 0; i < 5; i ++) {
		uint64_t bigchunk;
		uint32_t chunk1, chunk2;

		read(fd, &bigchunk, sizeof(bigchunk));

		bigchunk = ntoh64(bigchunk);
		csum += bigchunk;

		chunk1 = bigchunk >> 32;
		chunk2 = bigchunk;

		csum0 += (chunk1 >> 24) & 0xFF;
		csum1 += (chunk1 >> 16) & 0xFF;
		csum2 += (chunk1 >> 8) & 0xFF;
		csum3 += (chunk1 >> 0) & 0xFF;
		csum4 += (chunk2 >> 24) & 0xFF;
		csum5 += (chunk2 >> 16) & 0xFF;
		csum6 += (chunk2 >> 8) & 0xFF;
		csum7 += (chunk2 >> 0) & 0xFF;
	}

	csum0 = - csum0;
	csum1 = - csum1;
	csum2 = - csum2;
	csum3 = - csum3;
	csum4 = - csum4;
	csum5 = - csum5;
	csum6 = - csum6;
	csum7 = - csum7;
	csum = - csum;

	// checksum the whole rom
	lseek(fd, 0, SEEK_SET);
	for(i = 0; i < (rominfo.romsize/8); i++) {
		uint64_t bigchunk;
		uint32_t chunk1, chunk2;

		read(fd, &bigchunk, sizeof(bigchunk));

		bigchunk = ntoh64(bigchunk);
		csum += bigchunk;

		chunk1 = bigchunk >> 32;
		chunk2 = bigchunk;

		csum0 += (chunk1 >> 24) & 0xFF;
		csum1 += (chunk1 >> 16) & 0xFF;
		csum2 += (chunk1 >> 8) & 0xFF;
		csum3 += (chunk1 >> 0) & 0xFF;
		csum4 += (chunk2 >> 24) & 0xFF;
		csum5 += (chunk2 >> 16) & 0xFF;
		csum6 += (chunk2 >> 8) & 0xFF;
		csum7 += (chunk2 >> 0) & 0xFF;
	}

	printf("Calculated checksum: %" PRIx64 "\n", csum);
	printf("Lane 0 checksum: %" PRIx32 "\n", csum0);
	printf("Lane 1 checksum: %" PRIx32 "\n", csum1);
	printf("Lane 2 checksum: %" PRIx32 "\n", csum2);
	printf("Lane 3 checksum: %" PRIx32 "\n", csum3);
	printf("Lane 4 checksum: %" PRIx32 "\n", csum4);
	printf("Lane 5 checksum: %" PRIx32 "\n", csum5);
	printf("Lane 6 checksum: %" PRIx32 "\n", csum6);
	printf("Lane 7 checksum: %" PRIx32 "\n", csum7);

	if(rominfo.lane0 != csum0) {
		printf("Lane 0 checksum mismatch\n");
	} else if(rominfo.lane1 != csum1) {
		printf("Lane 1 checksum mismatch\n");
	} else if(rominfo.lane2 != csum2) {
		printf("Lane 2 checksum mismatch\n");
	} else if(rominfo.lane3 != csum3) {
		printf("Lane 3 checksum mismatch\n");
	} else if(rominfo.lane4 != csum4) {
		printf("Lane 4 checksum mismatch\n");
	} else if(rominfo.lane5 != csum5) {
		printf("Lane 5 checksum mismatch\n");
	} else if(rominfo.lane6 != csum6) {
		printf("Lane 6 checksum mismatch\n");
	} else if(rominfo.lane7 != csum7) {
		printf("Lane 7 checksum mismatch\n");
	} else if(rominfo.sum != csum) {
		printf("Whole ROM checksum mismatch\n");
	} else {
		printf("All checksums match\n");
	}

	close(fd);
}
