	/* ipad eu hack by dr. matrix */
	/* many fixes and improvements by www.randgruppe.info/ipod */
#define VERSION "2.2"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

void backup_firmware(int fd)
{
	int bfd;
	unsigned char *b = malloc(3 * 1024 * 1024);

	if ((bfd = open("ipod_backup", O_RDONLY)) >= 0)
	{
		fprintf(stderr, "ipod_backup already exists - skipping firmware backup!\n");
		close(bfd);
		return;
	}

	fprintf(stderr, "backing up firmware...\n");
	if (read(fd, b, 3 * 1024 * 1024) != 3 * 1024 * 1024)
	{
		fprintf(stderr, "firmware backup failed. aborting.\n");
		exit(4);
	}
	
	bfd = open("ipod_backup", O_CREAT|O_WRONLY, 0666);
	if (bfd < 0)
	{
		perror("create 'ipod_backup'");
		fprintf(stderr, "make sure that the current directory is writable.\n");
		exit(5);
	}
	write(bfd, b, 3*1024*1024);
	close(bfd);
	fprintf(stderr, "!! firmware backup successful.\n"
		"!! to restore, use \"dd if=ipod_backup of=<ipod-firmware-partition>\".\n"
		"!! be careful not to overwrite your local harddisk!\n");
}

void do_csum_fix(int fd, int offset, int delta)
{
	unsigned char cs[4];
	
	if (lseek(fd, offset, SEEK_SET) < 0)
	{
		perror("seek");
		exit(2);
	}
	
	if (read(fd, cs, 4) != 4)
	{
		perror("read");
		exit(2);
	}
	
	int csum = cs[0] | (cs[1] << 8) | (cs[2] << 16) | (cs[3] << 24);
	csum += delta;
	cs[0] = csum & 0xFF;
	cs[1] = csum >> 8;
	cs[2] = csum >> 16;
	cs[3] = csum >> 24;
	
	if (lseek(fd, offset, SEEK_SET) < 0)
	{
		perror("seek");
		exit(2);
	}
	
	if (write(fd, cs, 4) != 4)
	{
		perror("write");
		exit(2);
	}
}

int do_patch(int fd, int offset, unsigned char *exp, int elen, unsigned char *patch, int plen)
{
	unsigned char tmp[elen];
	int csum;
	int i;
	if (lseek(fd, offset, SEEK_SET) < 0)
	{
		perror("seek");
		exit(2);
	}
	
	if (read(fd, tmp, elen) != elen)
	{
		perror("read");
		exit(2);
	}
	
	if (memcmp(tmp, exp, elen))
	{
		fprintf(stderr, "patch failed: unexpected data! restore version " VERSION " and try again!\n");
		exit(2);
	}
	
	csum = 0;
	for (i=0; i<plen; ++i)
		csum += patch[i] - exp[i];

	if (lseek(fd, offset, SEEK_SET) < 0)
	{
		perror("seek");
		exit(2);
	}
	
	if (write(fd, patch, plen) != plen)
	{
		perror("write");
		exit(2);
	}
	return csum;
}

int main(int argc, char **argv)
{
	int fd, csum;
	
	fprintf(stderr, "THIS DOES ONLY WORK FOR 3G IPODS WITH VERSION 2.2\n");
	fprintf(stderr, "  - original patch: Dr. Matrix in 2004\n");
	fprintf(stderr, "  - patch fix and improvements: www.randgruppe.info/ipod\n\n");
	if ((argc != 2) && (argc != 3))
	{
		fprintf(stderr, " usage: %s <ipod-firmware-partition> [<max volume>]\n", *argv);
		fprintf(stderr, "  max. volume is normally 256 for US and 216 for non-US\n");
		fprintf(stderr, "  default and maximum is 256\n");
		return 1;
	}
	
	fd = open(argv[1], O_RDWR);
	if (fd < 0)
	{
		perror(argv[1]);
		return 1;
	}
	
	backup_firmware(fd);

	csum = do_patch(fd, 0x825E0, "\x01\x00\x00\xe2", 4, "\x00", 1);
	csum += do_patch(fd, 0xDDE14, "\x10\x00\x50\xE3\x00\xF1\x8F\x90\x32\x00\x00\xEA", 12, "\0\0\xA0\xE1\0\0\xA0\xE1\0\0\xA0\xE1", 12);
	csum += do_patch(fd, 0x139d6c, "-\0\0\0LL", 6, "nocap", 6);
	
	if (argc == 3)
	{
		int maxvol = strtoul(argv[2], 0, 0x10);
		if ((maxvol > 0) && (maxvol < 0x100))
		{
			unsigned char mv = maxvol >> 2;
			csum += do_patch(fd, 0xbd6e0, "\x40\x0f\xa0\x03", 4, &mv, 1);
		}
	}

	do_csum_fix(fd, 0x421C, csum);
	do_csum_fix(fd, 0x401C, csum);
	
	fprintf(stderr, "** everything ok! reboot player and check volume!\n");
	fprintf(stderr, "** DON'T DAMAGE YOU EARS! THEIR WARRANTY PERIOD IS OVER SINCE YEARS!\n");
	fprintf(stderr, "** do disable the hack, just restore your original firmware.\n");
	close(fd);
	return 0;
}
