/*
 * StarCraft Power Saver
 *
 * Copyright (C) 2008  Denver Gingerich
 *
 * The contents of this file can be redistributed and/or modified under the
 * terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * This file is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program. If not, see http://www.gnu.org/licenses/.
 *
 */

#include "md5.h"
#include <windows.h>
#include <stdio.h>

const unsigned int EXE_SIZE = 1220608;
const unsigned int OFFSET_JMP = 0xd94bc;
const unsigned int OFFSET_SLEEP = 0xd96c6;

/*	e9 05 02 00 00		jmp 0x4d96c6
 *	cc			int3
 *	cc			int3
 */
char code_jmp[] = {	
	0xe9, 0x05, 0x02, 0x00, 0x00,
	0xcc,
	0xcc
};

/*	0f b6 0d 9d ce 51 00	movzbl 0x51ce9d,%ecx (replaced by above jmp)
 *	68 01 00 00 00		push   $0x1
 *	ff 15 0c e1 4f 00	call   *0x4fe10c (Sleep(1))
 *	e9 e6 fd ff ff		jmp    0x4d94c3
 */
char code_sleep[] = {
	0x0f, 0xb6, 0x0d, 0x9d, 0xce, 0x51, 0x00,
	0x68, 0x01, 0x00, 0x00, 0x00,
	0xff, 0x15, 0x0c, 0xe1, 0x4f, 0x00,
	0xe9, 0xe6, 0xfd, 0xff, 0xff
};

char md5_orig[] = {	/* 8f35e0719fa15e56b6605e67b19ba97c */
	0x8f, 0x35, 0xe0, 0x71, 0x9f, 0xa1, 0x5e, 0x56,
	0xb6, 0x60, 0x5e, 0x67, 0xb1, 0x9b, 0xa9, 0x7c
};

char md5_mod[] = {	/* 2f3b4fe514d161fb4f7069c457b3a310 */
	0x2f, 0x3b, 0x4f, 0xe5, 0x14, 0xd1, 0x61, 0xfb,
	0x4f, 0x70, 0x69, 0xc4, 0x57, 0xb3, 0xa3, 0x10
};


int main(int argc, char** argv)
{
	FILE* file;

	md5_byte_t buf[EXE_SIZE];
	md5_state_t pms;
	size_t bytes_read;
	md5_byte_t digest[16];

	char* orig_filename;
	char* backup_filename;
	int suffix;

	size_t bytes_written;

	printf( "StarCraft Power Saver v0.1\n"
		"Copyright 2008 Denver Gingerich\n"
		"http://ossguy.com/starcraft_power_saver/\n"
		"\n"
		"This program is free software; you may redistribute it under the terms of\n"
		"the GNU General Public License.\n"
		"\n"
		"\n"
	);

	if (2 != argc) {
		printf("Usage: starcraft_power_saver <path_to_StarCraft.exe>\n");
		return -1;
	}

	orig_filename = argv[1];
	file = fopen(orig_filename, "rb");
	if (NULL == file) {
		printf("could not open file '%s': %s\n", orig_filename, strerror(errno));
		return -2;
	}

	md5_init(&pms);
	bytes_read = fread(buf, sizeof(md5_byte_t), EXE_SIZE, file);
	fclose(file);

	if (bytes_read != EXE_SIZE) {
		printf("'%s' (%d bytes) is not a StarCraft: Brood War 1.15.2 executable; the expected size is (%d bytes)\n", orig_filename, bytes_read, EXE_SIZE);
		return -3;
	}

	md5_append(&pms, buf, EXE_SIZE);
	md5_finish(&pms, digest);

	if (0 == memcmp(digest, md5_mod, 16)) {
		printf("'%s' has already been patched; exiting\n", orig_filename);
		return 0;
	}

	if (0 != memcmp(digest, md5_orig, 16)) {
		printf("'%s' is not a StarCraft: Brood War 1.15.2 executable; the expected MD5 checksum is 8f35e0719fa15e56b6605e67b19ba97c\n", orig_filename);
		return -4;
	}

	backup_filename = (char*)malloc(strlen(orig_filename) + 5);
	if (NULL == backup_filename) {
		printf("allocation failed for backup filename: %s\n", strerror(errno));
		return -5;
	}
	for (suffix = 0; suffix < 1000; suffix++) {
		sprintf(backup_filename, "%s.%03d", orig_filename, suffix);
		if (CopyFile(orig_filename, backup_filename, TRUE)) {
			break;
		}
	}
	printf("backup saved to: %s\n", backup_filename);
	free(backup_filename);
	if (1000 == suffix) {
		printf("could not create backup file; exiting\n");
		return -6;
	}

	file = fopen(orig_filename, "rb+");
	if (NULL == file) {
		printf("could not open file '%s': %s\n", orig_filename, strerror(errno));
		return -7;
	}

	if (fseek(file, OFFSET_JMP, SEEK_SET)) {
		printf("could not seek to jmp location: %s\n", strerror(errno));
		return -8;
	}
	bytes_written = fwrite(code_jmp, sizeof(char), sizeof(code_jmp)/sizeof(char), file);
	if (sizeof(code_jmp)/sizeof(char) != bytes_written) {
		printf("could not write jmp code: %s\n", strerror(errno));
		return -9;
	}

	if (fseek(file, OFFSET_SLEEP, SEEK_SET)) {
		printf("could not seek to sleep location: %s\n", strerror(errno));
		return -10;
	}
	bytes_written = fwrite(code_sleep, sizeof(char), sizeof(code_sleep)/sizeof(char), file);
	if (sizeof(code_sleep)/sizeof(char) != bytes_written) {
		printf("could not write sleep code: %s\n", strerror(errno));
		return -11;
	}

	fclose(file);

	printf("'%s' successfully patched\n", orig_filename);
	printf( "\n"
		"Using a modified executable may get you suspended from Battle.net.\n"
		"If you want to use Battle.net, replace your StarCraft executable with\n"
		"the backup file listed above.  For more information, see README.txt.\n"
	);

	return 0;
}
