/*
 *  Copyright (C) 2008 Nuke (wiinuke@gmail.com)
 *  Copyright (C) 2009 WiiPower
 *
 *  this file is part of GeckoOS for USB Gecko
 *  http://www.usbgecko.com
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <ogc/es.h>
#include <ogcsys.h>
#include <sys/unistd.h>
#include "apploader.h"
#include "patchcode.h"
#include "tools.h"
#include "geckomenu.h"
#include "font.h"
#include "identify.h"
#include "sysmenu.h"
#include "multidol.h"
#include "config.h"
#include "codes.h"


extern u32 *xfb;
extern const unsigned char kenobiwii[];
extern const int kenobiwii_size;
void _unstub_start();

void* 				Address_Array[64];			//TODO: variable size
int					Section_Size_Array[64];		//TODO: variable size
int 				last_index;

typedef struct _dolheader
{
	u32 text_pos[7];
	u32 data_pos[11];
	u32 text_start[7];
	u32 data_start[11];
	u32 text_size[7];
	u32 data_size[11];
	u32 bss_start;
	u32 bss_size;
	u32 entry_point;
} dolheader;


u8 iosReloadOrig[] = {
	0x7f, 0x06, 0xc3, 0x78,
	0x7f, 0x25, 0xcb, 0x78,
	0x38, 0x7e, 0x02, 0xc0,
	0x4c, 0xc6, 0x31, 0x82
};

u8 iosReloadPatch[] = {
	0x3b, 0x20, 0x00, 0x01,
	0x3b, 0x00, 0x00, 0xf9
};

void iosReloadFix(void *addr, u32 len)
{
	u8 *addr_start = addr;
	u8 *addr_end = addr+len-sizeof(iosReloadOrig);

	iosReloadPatch[7] = CIOS_VERSION;

	while (addr_start < addr_end)
	{
		if (memcmp(addr_start, iosReloadOrig, sizeof(iosReloadOrig))==0) 
		{
			memcpy(addr_start, iosReloadPatch, sizeof(iosReloadPatch));
			return;
		}
		addr_start += 4;
	}
}

void skipdiscupdates(u16 version)
{
	switch (version)
	{
		case 289: // NTSC-U 3.2
			*(u32 *)0x813791A0 = 0x60000000;
			*(u32 *)0x813791A4 = 0x60000000;
		break;		
		
		case 290: // PAL 3.2
			*(u32 *)0x813791F0 = 0x60000000;
			*(u32 *)0x813791F4 = 0x60000000;
		break;
		
		case 417: // NTSC-U 4.0
			*(u32 *)0x8137ED54 = 0x60000000;
			*(u32 *)0x8137ED58 = 0x60000000;
		break;
		
		case 418: // PAL 4.0
			*(u32 *)0x8137EE54 = 0x60000000;
			*(u32 *)0x8137EE58 = 0x60000000;
			*(u32 *)0x8137EDFC = 0x60000000;
			*(u32 *)0x8137EE00 = 0x60000000;
			*(u32 *)0x8137ED54 = 0x60000000;
			*(u32 *)0x8137ED58 = 0x60000000;
		break;		
		
		case 448: //NTSC-J 4.1
			*(u32 *)0x8137E35C = 0x60000000;
			*(u32 *)0x8137E360 = 0x60000000;
			*(u32 *)0x8137E304 = 0x60000000;
			*(u32 *)0x8137E308 = 0x60000000;
			*(u32 *)0x8137E25C = 0x60000000;
			*(u32 *)0x8137E260 = 0x60000000;
		
		case 449: //NTSC-U 4.1
			*(u32 *)0x8137EEA8 = 0x60000000;
			*(u32 *)0x8137EEAC = 0x60000000;
			*(u32 *)0x8137EE50 = 0x60000000;
			*(u32 *)0x8137EE54 = 0x60000000;
			*(u32 *)0x8137EDA8 = 0x60000000;
			*(u32 *)0x8137EDAC = 0x60000000;
		break;		
		
		case 450: // PAL 4.1
			*(u32 *)0x8137EF50 = 0x60000000;
			*(u32 *)0x8137EF54 = 0x60000000;
			*(u32 *)0x8137EEF8 = 0x60000000;
			*(u32 *)0x8137EEFC = 0x60000000;
			*(u32 *)0x8137EE50 = 0x60000000;
			*(u32 *)0x8137EE54 = 0x60000000;	
		break;
		
		default:
		break;
	}
}

s32 read_file(char *filepath, u8 **buffer, u32 *filesize)
{
	s32 Fd;
	int ret;

	if (buffer == NULL)
	{
		//printf("NULL Pointer\n");
		return -1;
	}

	Fd = ISFS_Open(filepath, ISFS_OPEN_READ);
	if (Fd < 0)
	{
		//printf("ISFS_Open %s failed %d\n", filepath, Fd);
		return Fd;
	}

	fstats *status;
	status = allocate_memory(sizeof(fstats));
	if (status == NULL)
	{
		//printf("Out of memory for status\n");
		return -1;
	}
	
	ret = ISFS_GetFileStats(Fd, status);
	if (ret < 0)
	{
		//printf("ISFS_GetFileStats failed %d\n", ret);
		ISFS_Close(Fd);
		free(status);
		return -1;
	}
	
	*buffer = allocate_memory(status->file_length);
	if (*buffer == NULL)
	{
		//printf("Out of memory for buffer\n");
		ISFS_Close(Fd);
		free(status);
		return -1;
	}
		
	ret = ISFS_Read(Fd, *buffer, status->file_length);
	if (ret < 0)
	{
		//printf("ISFS_Read failed %d\n", ret);
		ISFS_Close(Fd);
		free(status);
		free(*buffer);
		return ret;
	}
	ISFS_Close(Fd);

	*filesize = status->file_length;
	free(status);

	return 0;
}

s32 identify(u64 titleid, u16 *bootapp, u16 *version)
{
	char filepath[ISFS_MAXPATH] ATTRIBUTE_ALIGN(0x20);
	u8 *tmdBuffer = NULL;
	u32 tmdSize;
	u8 *tikBuffer = NULL;
	u32 tikSize;
	u8 *certBuffer = NULL;
	u32 certSize;
	
	int ret;

	//printf("Reading TMD...");
	//fflush(stdout);
	
	sprintf(filepath, "/title/%08x/%08x/content/title.tmd", TITLE_UPPER(titleid), TITLE_LOWER(titleid));
	ret = read_file(filepath, &tmdBuffer, &tmdSize);
	if (ret < 0)
	{
		//printf("Reading TMD failed\n");
		return ret;
	}
	//printf("done\n");

	u16 bootindex;
	bootindex = ((tmd *)SIGNATURE_PAYLOAD((signed_blob *)tmdBuffer))->boot_index;
	tmd_content *p_cr;
	p_cr = TMD_CONTENTS(((tmd *)SIGNATURE_PAYLOAD((signed_blob *)tmdBuffer)));
	*bootapp = p_cr[bootindex].cid;

	*version = *(u16 *)(&tmdBuffer[0x1dc]);

	//printf("Reading ticket...");
	//fflush(stdout);

	sprintf(filepath, "/ticket/%08x/%08x.tik", TITLE_UPPER(titleid), TITLE_LOWER(titleid));
	ret = read_file(filepath, &tikBuffer, &tikSize);
	if (ret < 0)
	{
		//printf("Reading ticket failed\n");
		free(tmdBuffer);
		return ret;
	}
	//printf("done\n");

	//printf("Reading certs...");
	//fflush(stdout);

	sprintf(filepath, "/sys/cert.sys");
	ret = read_file(filepath, &certBuffer, &certSize);
	if (ret < 0)
	{
		//printf("Reading certs failed\n");
		free(tmdBuffer);
		free(tikBuffer);
		return ret;
	}
	//printf("done\n");
	
	//printf("ES_Identify...");
	//fflush(stdout);

	ret = ES_Identify((signed_blob*)certBuffer, certSize, (signed_blob*)tmdBuffer, tmdSize, (signed_blob*)tikBuffer, tikSize, NULL);
	if (ret < 0)
	{
		switch(ret)
		{
			case ES_EINVAL:
				//printf("Error! ES_Identify (ret = %d;) Data invalid!\n", ret);
				break;
			case ES_EALIGN:
				//printf("Error! ES_Identify (ret = %d;) Data not aligned!\n", ret);
				break;
			case ES_ENOTINIT:
				//printf("Error! ES_Identify (ret = %d;) ES not initialized!\n", ret);
				break;
			case ES_ENOMEM:
				//printf("Error! ES_Identify (ret = %d;) No memory!\n", ret);
				break;
			default:
				//printf("Error! ES_Identify (ret = %d)\n", ret);
				break;
		}
		free(tmdBuffer);
		free(tikBuffer);
		free(certBuffer);
		return ret;
	}
	//printf("done\n");
	
	free(tmdBuffer);
	free(tikBuffer);
	free(certBuffer);
	return 0;
}

s32 ISFS_Load_dol(u64 titleid, u16 index)
{
	last_index = -1;
	u32 i;
	s32 ret;
	s32 Fd;

	dolheader *dolfile = (dolheader*)allocate_memory(sizeof(dolheader));
	if (dolfile == NULL)
	{
		clearscreen(xfb, 20, 178, 278, 24, 0x00800080);
		write_font(185, 178, "Out of memory");
		return -1;
	}

	char filepath[ISFS_MAXPATH] ATTRIBUTE_ALIGN(0x20);
	
	// Preloader moves the system menu .dol here:
	sprintf(filepath, "/title/%08x/%08x/content/1%07x.app", TITLE_UPPER(titleid), TITLE_LOWER(titleid), index);

	Fd = ISFS_Open(filepath, ISFS_OPEN_READ);
	if (Fd < 0)
	{
		sprintf(filepath, "/title/%08x/%08x/content/%08x.app", TITLE_UPPER(titleid), TITLE_LOWER(titleid), index);

		Fd = ISFS_Open(filepath, ISFS_OPEN_READ);
		if (Fd < 0)
		{
			clearscreen(xfb, 20, 178, 278, 24, 0x00800080);
			write_font(185, 178, "ISFS_Open %d", Fd);
			free(dolfile);
			return Fd;
		}
	}

	ret = ISFS_Read(Fd, (void *)dolfile, sizeof(dolheader));
	if (ret < 0)
	{
		clearscreen(xfb, 20, 178, 278, 24, 0x00800080);
		write_font(185, 178, "ISFS_Read %d", ret);
		free(dolfile);
		return ret;
	}
	
	memset((void *)dolfile->bss_start, 0, dolfile->bss_size);
	
	for (i = 0; i < 7; i++)
	{
		if(dolfile->data_start[i] < sizeof(dolheader))
			continue;
		
		ret = ISFS_Seek(Fd, dolfile->text_pos[i], 0);
		if (ret < 0)
		{
			clearscreen(xfb, 20, 178, 278, 24, 0x00800080);
			write_font(185, 178, "ISFS_Seek %d", ret);
			free(dolfile);
			return ret;
		}
		ret = ISFS_Read(Fd, (void *)dolfile->text_start[i], dolfile->text_size[i]);
		if (ret < 0)
		{
			clearscreen(xfb, 20, 178, 278, 24, 0x00800080);
			write_font(185, 178, "ISFS_Read %d", ret);
			free(dolfile);
			return ret;
		}
		DCFlushRange((void *)dolfile->text_start[i], dolfile->text_size[i]);

		last_index++;	
		Address_Array[last_index] = (void *)dolfile->text_start[i];
		Section_Size_Array[last_index] = dolfile->text_size[i];
	}

	for(i = 0; i < 11; i++)
	{
		if(dolfile->data_start[i] < sizeof(dolheader))
			continue;
				
		ret = ISFS_Seek(Fd, dolfile->data_pos[i], 0);
		if (ret < 0)
		{
			clearscreen(xfb, 20, 178, 278, 24, 0x00800080);
			write_font(185, 178, "ISFS_Seek %d", ret);
			free(dolfile);
			return ret;
		}
		ret = ISFS_Read(Fd, (void *)dolfile->data_start[i], dolfile->data_size[i]);
		if (ret < 0)
		{
			clearscreen(xfb, 20, 178, 278, 24, 0x00800080);
			write_font(185, 178, "ISFS_Read %d", ret);
			free(dolfile);
			return ret;
		}
		DCFlushRange((void *)dolfile->data_start[i], dolfile->data_size[i]);

		last_index++;	
		Address_Array[last_index] = (void *)dolfile->data_start[i];
		Section_Size_Array[last_index] = dolfile->data_size[i];
	}
	
	ISFS_Close(Fd);
	free(dolfile);
	
	return 0;
}

extern void *codelist;

void boot_menu()
{
	s32 ret;
	int i;
	u16 bootapp;
	u16 version;
	
	clearscreen(xfb, 0, 110, 319, 270, 0x00800080);
	write_font(185, 154, "Rebooting With Hooks");
	
	ocarinarebooter();

	ISFS_Initialize();

	ret = identify(0x0000000100000002ULL, &bootapp, &version);
	if (ret < 0)
	{
		write_font(185, 178, "Identify failed %d", ret);
		sleep(5);
		return;
	}

	ret = ES_SetUID(0x0000000100000002ULL);
	if (ret < 0)
	{
		write_font(185, 178, "ES_SetUID failed %d", ret);
		sleep(5);
		return;
	}
	
	ret = ISFS_Load_dol(0x0000000100000002ULL, bootapp);
	if (ret < 0)
	{
		sleep(5);
		return;
	}

	ISFS_Deinitialize();
	
	if (hookselect != 0)
	{
		// Load Sysmenu patch handler
		memset((void*)0x81700000,0,sysmenu_size);
		memcpy((void*)0x81700000,sysmenu,sysmenu_size);
		memcpy((void*)0x81700096, &codelist, 2);
		memcpy((void*)0x8170009A, ((u8*) &codelist) + 2, 2);
		switch(hookselect)
		{
			case 0x01:
				memcpy((void*)0x81700224,viwiihooks,12);
				memcpy((void*)0x81700220,viwiihooks+3,4);
				break;
			case 0x02:
				memcpy((void*)0x81700224,kpadhooks,12);
				memcpy((void*)0x81700220,kpadhooks+3,4);
				break;
			case 0x03:
				memcpy((void*)0x81700224,joypadhooks,12);
				memcpy((void*)0x81700220,joypadhooks+3,4);
				break;
			case 0x04:
				memcpy((void*)0x81700224,gxdrawhooks,12);
				memcpy((void*)0x81700220,gxdrawhooks+3,4);
				break;
			case 0x05:
				memcpy((void*)0x81700224,gxflushhooks,12);
				memcpy((void*)0x81700220,gxflushhooks+3,4);
				break;
			case 0x06:
				memcpy((void*)0x81700224,ossleepthreadhooks,12);
				memcpy((void*)0x81700220,ossleepthreadhooks+3,4);
				break;
			case 0x07:
				memcpy((void*)0x81700224,axnextframehooks,12);
				memcpy((void*)0x81700220,axnextframehooks+3,4);
				break;
			case 0x08:
				//if (customhooksize == 16)
				//{
				//	memcpy((void*)0x81700224,customhook,12);
				//	memcpy((void*)0x81700220,customhook+3,4);
				//}
				break;
		}
		DCFlushRange((void*)0x81700000,sysmenu_size);
	}

	if (skipupdateselect)
	{
		skipdiscupdates(version);
	}

	for (i = 0; i <= last_index; i++)
	{
		// Multidol hook
		if (hookselect != 0x00)
		{
			patchmenu(Address_Array[i], Section_Size_Array[i], 6);
		}
		
		if (recoveryselect)
		{
			patchmenu(Address_Array[i], Section_Size_Array[i], 0);
		}
		// Region Free
		if(regionfreeselect)
		{
			patchmenu(Address_Array[i], Section_Size_Array[i], 2);	
		}
		// Health check
		if(buttonskipselect)
		{
			patchmenu(Address_Array[i], Section_Size_Array[i], 3);
		}
		// No copy bit remove
		if(nocopyselect)
		{
			patchmenu(Address_Array[i], Section_Size_Array[i], 4);
		}		
		
		// viwiihook
		if (hookselect != 0)
		{
			patchmenu(Address_Array[i], Section_Size_Array[i], 1);
		}
		
		// move dvd channel
		//patchmenu((void*)0x8132ff80, 0x380000, 5);

		// Use IOS249 for starting games
		iosReloadFix(Address_Array[i], Section_Size_Array[i]);

		DCFlushRange(Address_Array[i], Section_Size_Array[i]);
	}
	//memset((void*)0x800027E8,0,4); 


	//DCFlushRange((void*)0x80000000, 0x3f00);

	shutdown_all();
	if (hookselect == 0)
	{
		__asm__(
				"bl DCDisable\n"
				"bl ICDisable\n"
				"li %r3, 0\n"
				"mtsrr1 %r3\n"
				"lis %r4, 0x0000\n"
				"ori %r4,%r4,0x3400\n"
				"mtsrr0 %r4\n"
				"rfi\n"
				);
	}
	else
	{
		__asm__(
				"lis %r3, returnpointmenu@h\n"
				"ori %r3, %r3, returnpointmenu@l\n"
				"mtlr %r3\n"
				"lis %r3, 0x8000\n"
				"ori %r3, %r3, 0x18A8\n"
				"mtctr %r3\n"
				"bctr\n"
				"returnpointmenu:\n"
				"bl DCDisable\n"
				"bl ICDisable\n"
				"li %r3, 0\n"
				"mtsrr1 %r3\n"
				"lis %r4, 0x0000\n"
				"ori %r4,%r4,0x3400\n"
				"mtsrr0 %r4\n"
				"rfi\n"
				);
	}
}
