#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_CHUNK_SIZE	256	/* I'm lazy ... */

struct patch_chunk {
	unsigned long	 offset;
	unsigned long	 length;
	char		*before;
	char		*after;
};

struct patch {
	char *file;
	struct patch_chunk *chunks;
};
	
struct patch_chunk mi_chunks[] = {
	{
		.offset = 0x00006e98,
		.length = 4,
		.before = "\x00\x40\xe0\xe3",
		.after  = "\x00\x40\xa0\xe3",
	},
	{ }	/* length == 0 -> end entry */
};

struct patch_chunk sb_chunks[] = {
	{
		.offset = 0x00011f18,
		.length = 4,
		.before = "\x01\x00\xa0\x03",
		.after  = "\x01\x00\xa0\xe3",
	},
	{
		.offset = 0x00017730,
		.length = 4,
		.before = "\x01\x40\xa0\x03",
		.after  = "\x01\x40\xa0\xe3",
	},
	{
		.offset = 0x000a2670,
		.length = 4,
		.before = "\x03\x00\x00\x1a",
		.after  = "\x02\x00\x50\xe3",	/* repeat previous cmp / dummy */
	},
	{ }	/* length == 0 -> end entry */
};

#ifdef RUN_ON_DEVICE
#define PATH_MOBILEINSTALLATION	"/System/Library/PrivateFrameworks/MobileInstallation.framework/MobileInstallation"
#define PATH_SPRINGBOARD	"/System/Library/CoreServices/SpringBoard.app/SpringBoard"
#else
#define PATH_MOBILEINSTALLATION	"MobileInstallation"
#define PATH_SPRINGBOARD	"SpringBoard"
#endif

struct patch patches[] = {
	{
		.file = PATH_MOBILEINSTALLATION,
		.chunks = mi_chunks,
	},
	{
		.file = PATH_SPRINGBOARD,
		.chunks = sb_chunks,
	},
	{ },	/* file == NULL -> end entry */
};

static int
apply_patch_chunk(FILE *f, struct patch_chunk *chunk, int dry_run)
{
	int rv;
	char buf[MAX_CHUNK_SIZE];

	rv = fseek(f, chunk->offset, SEEK_SET);
	if (rv) {
		fprintf(stderr, "[!] Error seeking to offset %08lx\n", chunk->offset);
		return -1;
	}

	rv = fread(buf, chunk->length, 1, f);
	if (rv != 1) {
		fprintf(stderr, "[!] Error reading at offset %08lx\n", chunk->offset);
		return -1;
	}

	rv = memcmp(buf, chunk->before, chunk->length);
	if (rv) {
		rv = memcmp(buf, chunk->after, chunk->length);
		if (rv) {
			fprintf(stderr, "[!] Didn't find the expected data at offset %08lx\n", chunk->offset);
			return -1;
		}
		if (!dry_run)
			fprintf(stderr, "[w] Chunk at offset %08lx already patched\n", chunk->offset);
		return 0;
	}

	rv = fseek(f, chunk->offset, SEEK_SET);
	if (rv) {
		fprintf(stderr, "[!] Error seeking to offset %08lx\n", chunk->offset);
		return -1;
	}

	if (dry_run)
		return 0;

	rv = fwrite(chunk->after, chunk->length, 1, f);
	if (rv != 1) {
		fprintf(stderr, "[!] Error writing at offset %08lx\n", chunk->offset);
		return -1;
	}

	return 0;
}

static int
apply_patch(struct patch *patch, int dry_run)
{
	FILE *f;
	struct patch_chunk *chunk;

	f = fopen(patch->file, dry_run ? "rb" : "rb+");
	if (!f) {
		fprintf(stderr, "[!] Error opening %s for patching\n", patch->file);
		return -1;
	}

	for (chunk=patch->chunks; chunk->length; chunk++)
		if (apply_patch_chunk(f, chunk, dry_run)) {
			fprintf(stderr, "[!] Error patching %s\n", patch->file);
			fclose(f);
			return -1;
		}

	fclose(f);

	return 0;
}

int main(int argc, char *argv[])
{
	struct patch *patch;

	fprintf(stderr, "[+] Start dry-run phase\n");
	for (patch=patches; patch->file; patch++) {
		if (apply_patch(patch, 1)) {
			fprintf(stderr, "[!] Something bad happenned in dry-run. Nothing has been done\n");
			return -1;
		}
	}

	fprintf(stderr, "[+] Start real patching\n");
	for (patch=patches; patch->file; patch++) {
		if (apply_patch(patch, 0)) {
			fprintf(stderr, "[!] Something bad happenned in real patch phase. State is indeterminate -> BAD\n");
			return -2;
		}
	}
	
	return 0;
}

