/*
 *  Copyright (C) 2008 Nuke (wiinuke@gmail.com)
 *  Copyright (C) 2008 WiiGator
 *  Copyright (C) 2009 WiiPower
 *
 *
 *  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 <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <malloc.h>
#include <gccore.h>
#include <ogcsys.h>
#include <sys/unistd.h>
#include <wiiuse/wpad.h>
#include <ogc/lwp_watchdog.h>
#include <sdcard/wiisd_io.h>
#include <fat.h>
#include <time.h>

#include "tools.h"
#include "apploader.h"
#include "geckodvd.h"
#include "patchcode.h"
#include "font.h"
#include "dvd_broadway.h"
#include "patchcode.h"
#include "geckomenu.h"

#include "libcios.h"
#include "easywbfs.h"
#include "wbfs.h"
#include "config.h"
#include "Toy.h"
#include "Toy_video.h"
#include "sdhc.h"
#include "dol.h"
#include "tools.h"
#include "codes.h"
#include "wiip.h"
#include "storage.h"

#define appbuffer 0x80100000
#define BC 0x0000000100000100ULL
#define MIOS 0x0000000100000101ULL
#define SIG_RSA_2048 0x00010001
#define SIG_RSA_4096 0x00010000
#define SECTOR_SIZE 0x800


#define DVD_CHECK() \
	do { \
		char err_buffer[64]; \
		if (ret < 0) { \
			snprintf(err_buffer, sizeof(err_buffer), "DVD Ioctl Error (%d)", __LINE__); \
			write_font(185, 202, err_buffer); \
			return -1; \
		} \
		t = time(NULL) + 30; \
		while (dvddone == 0) \
		{ \
			if (time(NULL) >= t) \
			{ \
				write_font(185, 202, "timeout"); \
				return -1; \
			} \
		} \
		if (dvddone != 1) { \
			if (IOS_GetVersion() == CIOS_VERSION) { \
				snprintf(err_buffer, sizeof(err_buffer), "DVD Read Error (%d)", __LINE__); \
			} else { \
				snprintf(err_buffer, sizeof(err_buffer), "DVD Read Err (%d) no cIOS", __LINE__); \
			} \
			write_font(185, 202, err_buffer); \
			return -1; \
		} \
	} while(0)

void wait(u32 s)
{
	time_t t;
	t = time(NULL) + s; 
	while (time(NULL) < t) ;
}

static char gameidbuffer[8];
bool Country_Strings_Patched;

extern GXRModeObj TVEurgb60Hz480Int;
static GXRModeObj *rmode = NULL;

static vu32 dvddone = 0;
static u32 Video_Mode;
static time_t t;
bool hookpatched;


extern u32 *xfb;

struct _toc
{
	u32 bootInfoCnt;
	u32 partInfoOff;
};

struct _pinfo
{
	u32 offset;
	u32 len;
};

//extern int findcodes(char* codes);
extern s32 __IOS_InitializeSubsystems();
extern void jumpentry(u32 entryp);

static dvddiskid *g_diskID = (dvddiskid*)0x80000000;
static char _gameTocBuffer[0x20] ATTRIBUTE_ALIGN(32);
static char _gamePartInfo[0x20] ATTRIBUTE_ALIGN(32);
static char _gameTmdBuffer[18944] ATTRIBUTE_ALIGN(32);
static u32 *__dvd_Tmd = NULL;
static struct _toc *__dvd_gameToc = NULL;
static struct _pinfo *__dvd_partInfo = NULL;
static struct _pinfo *__dvd_bootGameInfo = NULL;
static tikview view ATTRIBUTE_ALIGN(32);


char video_options2[9][9] =
{{"Wii"},
{"Disc"},
{"NTSC480i"},
{"NTSC480p"},
{"PAL480i"},
{"PAL480p"},
{"PAL576i"},
{"MPAL480i"},
{"MPAL480p"}};

char bool_options2[2][6] = 
{{"No"},
{"Yes"}};

char videopatch_options2[4][8] =
{{"No"},
{"Normal"},
{"More"},
{"All"}};



static void __dvd_readidcb(s32 result)
{
	dvddone = result;
}

void set_videomode()
{	
	/* Set video mode to PAL or NTSC */
	*(u32*)0x800000CC = Video_Mode;

	// Overwrite all progressive video modes as they are broken in libogc
	if (videomode_interlaced(rmode) == 0)
	{
		rmode = &TVNtsc480Prog;
	}
	VIDEO_Configure(rmode);
	VIDEO_ClearFrameBuffer(rmode, xfb, COLOR_BLACK);
	VIDEO_SetNextFramebuffer(xfb);

	VIDEO_SetBlack(FALSE);
	VIDEO_Flush();
	VIDEO_WaitVSync();
	if(rmode->viTVMode&VI_NON_INTERLACE) VIDEO_WaitVSync();
}


static void displayflags()
{
	write_font(185, 178, "Language:");
	write_font(185, 202, "Force Video:");
	write_font(185, 226, "Patch Video:");
	write_font(185, 250, "VIDTV Patch:");
	//write_font(185, 274, "PAL50 Forced:");

	write_font(390, 202, video_options2[videoselect]);
	write_font(390, 226, videopatch_options2[videopatchselect]);
	write_font(390, 250, bool_options2[viselect]);
//	write_font(390, 274, "No");

	switch(langselect)
	{
		case 0: 
			write_font(390, 178, "Default");
		break;

		case 1: 
			write_font(390, 178, "Japanese");
		break;

		case 2: 
			write_font(390, 178, "English");
		break;

		case 3: 
			write_font(390, 178, "German");
		break;

		case 4: 
			write_font(390, 178, "French");
		break;

		case 5: 
			write_font(390, 178, "Spanish");
		break;

		case 6: 
			write_font(390, 178, "Italian");
		break;

		case 7: 
			write_font(390, 178, "Dutch");
		break;

		case 8: 
			write_font(390, 178, "S. Chinese");
		break;

		case 9: 
			write_font(390, 178, "T. Chinese");
		break;

		case 10: 
			write_font(390, 178, "Korean");
		break;

		case 11: 
			switch(gameidbuffer[3])
			{
				case 0x45:		// USA default
					write_font(390, 178, "English");
				break;
				
				case 0x4A:		// JAP default
					write_font(390, 178, "Japanese");
				break;
				
				default:
					write_font(390, 178, "Default");
			}
		}
}

static void __setappvideo()
{
	// Get rmode and Video_Mode for Wii Video Mode first
	u32 tvmode = CONF_GetVideo();

	// Attention: This returns &TVNtsc480Prog for all progressive video modes
    rmode = VIDEO_GetPreferredMode(0);

	switch (tvmode) 
	{
		case CONF_VIDEO_PAL:
			if (CONF_GetEuRGB60() > 0) 
			{
				Video_Mode = 5;
			}
			else 
			{
				Video_Mode = 5;
			}
			break;

		case CONF_VIDEO_MPAL:
			Video_Mode = 4;
			break;

		case CONF_VIDEO_NTSC:
		default:
			Video_Mode = 0;
	}

	// Overwrite rmode and Video_Mode when disc region video mode is selected and Wii region doesn't match disc region
	if (videoselect == 1)
	{
		switch (gameidbuffer[3]) 
		{
			case 'P':
			case 'D':
			case 'F':
			case 'X':
			case 'Y':
				if (CONF_GetVideo() != CONF_VIDEO_PAL)
				{
					Video_Mode = 5;

					if (CONF_GetProgressiveScan() > 0 && VIDEO_HaveComponentCable())
					{
						rmode = &TVNtsc480Prog; // This seems to be correct!
					}
					else
					{
						rmode = &TVEurgb60Hz480IntDf;
					}				
				}
				break;

			case 'E':
			case 'J':
			default:
				if (CONF_GetVideo() != CONF_VIDEO_NTSC)
				{
					Video_Mode = 0;

					if (CONF_GetProgressiveScan() > 0 && VIDEO_HaveComponentCable())
					{
						rmode = &TVNtsc480Prog;
					}
					else
					{
						rmode = &TVNtsc480IntDf;
					}				
				}
		}
	}
	
	if (videoselect >= 2)
	{
		if (videoselect == 2)
		{
			rmode = &TVNtsc480IntDf;
		}
		if (videoselect == 3)
		{
			rmode = &TVNtsc480Prog;
		}
		if (videoselect == 4)
		{
			rmode = &TVEurgb60Hz480IntDf;
		}
		if (videoselect == 5)
		{
			rmode = &TVEurgb60Hz480Prog;
		}
		if (videoselect == 6)
		{
			rmode = &TVPal528IntDf;
		}
		if (videoselect == 7)
		{
			rmode = &TVMpal480IntDf;
		}
		if (videoselect == 8)
		{
			rmode = &TVMpal480Prog;
		}
		Video_Mode = (rmode->viTVMode) >> 2;
	}
}


void nothing()
{
}

u32 ocarinarebooter()
{
	s32 ret = 0;

	// IOS Reload + Rebooter results in system files corrupted
	if (ocarinaselect > 1)
	{
		write_font(185, 226, "No IOS reload");
		wait(2);
	}

	if ((ocarinaselect != 0 || debuggerselect != 0) && hookselect != 0)
	{
		if (ocarinaselect != 0)
		{
			if (storageselect == 0)
			{
				write_font(185, 178, "Searching SD Codes");
			} else
			{
				write_font(185, 178, "Searching USB Codes");
			}

			ret = bwDVD_LowInit();

			dvddone = 0;
			ret = bwDVD_LowReset(__dvd_readidcb);
			DVD_CHECK();
			
			/* clear existing GameID */
			memset((char*)0x80000000, 0, 6);

			dvddone = 0;
			ret = bwDVD_LowReadID(g_diskID,__dvd_readidcb);
			DVD_CHECK();

			memset(gameidbuffer, 0, 8);
			memcpy(gameidbuffer, (char*)0x80000000, 6);		

			if(gameidbuffer[1] == 0 && gameidbuffer[2] == 0){
				write_font(185, 202, "Error: No DVD (o)");
				return 0;
			}

			write_font(185, 202, "Game ID:");
			write_font(325, 202, gameidbuffer);
			wait(2);
		}

		do_codes();
	}

	wait(1);
	return 1;
}

/*******************************************************************************
 * Patch_Country_Strings: Patches the Country Strings
 * -----------------------------------------------------------------------------
 * Return Values:
 *	returns true if patched
 *
 ******************************************************************************/

bool Patch_Country_Strings(void *Address, int Size)
{
	u8 SearchPattern[4];
	u8 PatchData[2];
	u8 *Addr			= (u8*)Address;

	int wiiregion = CONF_GetRegion();

	switch (wiiregion)
	{
		case CONF_REGION_JP:
			SearchPattern[0] = 0x00;
			SearchPattern[1] = 0x4A; // J
			SearchPattern[2] = 0x50; // P
			SearchPattern[3] = 0x00;
			break;
		case CONF_REGION_EU:
			SearchPattern[0] = 0x02;
			SearchPattern[1] = 0x45; // E
			SearchPattern[2] = 0x55; // U
			SearchPattern[3] = 0x00;
			break;
		case CONF_REGION_KR:
			SearchPattern[0] = 0x04;
			SearchPattern[1] = 0x4B; // K
			SearchPattern[2] = 0x52; // R
			SearchPattern[3] = 0x00;
			break;
		case CONF_REGION_CN:
			SearchPattern[0] = 0x05;
			SearchPattern[1] = 0x43; // C
			SearchPattern[2] = 0x4E; // N
			SearchPattern[3] = 0x00;
			break;
		case CONF_REGION_US:
		default:
			SearchPattern[0] = 0x01;
			SearchPattern[1] = 0x55; // U
			SearchPattern[2] = 0x53; // S
			SearchPattern[3] = 0x00;
	}

	switch (*(char*)0x80000003) 
	{
		case 'J':
			PatchData[0] = 0x4A; // J
			PatchData[1] = 0x50; // P
			break;

		case 'P':
		case 'D':
		case 'F':
		case 'X':
		case 'Y':
			PatchData[0] = 0x45; // E
			PatchData[1] = 0x55; // U
			break;

		case 'E':
		default:
			PatchData[0] = 0x55; // U
			PatchData[1] = 0x53; // S
	}

	while (Size >= 4)
	{
		if (Addr[0] == SearchPattern[0] && Addr[1] == SearchPattern[1] && Addr[2] == SearchPattern[2] && Addr[3] == SearchPattern[3])
		{
			Addr += 1;
			*Addr = PatchData[0];
			Addr += 1;
			*Addr = PatchData[1];
			return true;
		} else
		{
			Addr += 4;
			Size -= 4;
		}
	}
	return false;
}

bool Remove_001_Protection(void *Address, int Size)
{
	u8 SearchPattern[16] = 	{ 0x40, 0x82, 0x00, 0x0C, 0x38, 0x60, 0x00, 0x01, 0x48, 0x00, 0x02, 0x44, 0x38, 0x61, 0x00, 0x18 };
	u8 PatchData[16] = 		{ 0x40, 0x82, 0x00, 0x04, 0x38, 0x60, 0x00, 0x01, 0x48, 0x00, 0x02, 0x44, 0x38, 0x61, 0x00, 0x18 };

	void *Addr = Address;
	void *Addr_end = Address+Size;

	while(Addr <= Addr_end-sizeof(SearchPattern))
	{
		if(memcmp(Addr, SearchPattern, sizeof(SearchPattern))==0) 
		{
			memcpy(Addr,PatchData,sizeof(PatchData));
			return true;
		}
		Addr += 4;
	}
	return false;
}

void Anti_002_fix(void *Address, int Size)
{
	u8 SearchPattern[12] = 	{ 0x2C, 0x00, 0x00, 0x00, 0x48, 0x00, 0x02, 0x14, 0x3C, 0x60, 0x80, 0x00 };
	u8 PatchData[12] = 		{ 0x2C, 0x00, 0x00, 0x00, 0x40, 0x82, 0x02, 0x14, 0x3C, 0x60, 0x80, 0x00 };

	void *Addr = Address;
	void *Addr_end = Address+Size;

	while(Addr <= Addr_end-sizeof(SearchPattern))
	{
		if(memcmp(Addr, SearchPattern, sizeof(SearchPattern))==0) 
		{
			memcpy(Addr,PatchData,sizeof(PatchData));
		}
		Addr += 4;
	}
}


bool mount_disc()
{
	// Reset to disc first
	SetWBFSMode(0, NULL);
	
	if (gameselected != getGameCount() && wbfsdevice > 0)
	{
		if (!WBFS_isalive(wbfsdevice))
		{
			return false;
		}
		if (SetWBFSMode(wbfsdevice, getGameHeader(gameselected)->id) != 0)
		{
			return false;
		}
	}	

	return true;
}


void do_wip_patches()
{
	storage_init();

	static char buf[128];
	FILE *fp = NULL;
	u32 filesize = 0;

	char tempbuffer[8];
	memset(tempbuffer, 0, 7);
	memcpy(tempbuffer, (char*)0x80000000, 6);		
	snprintf(buf, 128, "fat:/NeoGamma/%s.wip", tempbuffer);
	fp = fopen(buf, "rb");
	if (!fp)
	{
		memset(tempbuffer, 0, 7);
		memcpy(tempbuffer, (char*)0x80000000, 4);		
		snprintf(buf, 128, "fat:/NeoGamma/%s.wip", tempbuffer);
		fp = fopen(buf, "rb");
	}

	if (!fp)
	{
		write_font(185, 346, "No patch found");
		wait(3);		
		return;
	}	

	fseek(fp, 0, SEEK_END);
	filesize = ftell(fp);
	fseek(fp, 0, SEEK_SET);

	u8 *buffer = malloc(filesize+1);
	if (buffer == NULL)
	{
		write_font(185, 346, "Out of memory");
		wait(3);		
		fclose(fp);
		return;
	}

	fread(buffer, filesize, 1, fp);
	buffer[filesize] = 0;

	wipparsebuffer(buffer, filesize);
	
	free(buffer);

	fclose(fp);
}


void maindolpatches(void *dst, u32 len)
{
	wipregisteroffset((u32)dst, len);
	
	DCFlushRange(dst, len);
	
	if(viselect)
	{
		vidolpatcher(dst,len);
	}
	
	//if(debugmode)
	//{
	//	patchdebug(dst,len);
	//}
	langpatcher(dst,len);
	if (!Country_Strings_Patched) Patch_Country_Strings(dst, len);
	//if (anti002fixselect) Anti_002_fix(dst, len);

	if (dogamehooks(dst,len))
	{
		hookpatched = true;
	}
	
	DCFlushRange(dst, len);
	
	toy_register_apploader_file_loading(dst, len);
}


typedef struct {
    u8 filetype;
    char name_offset[3];
    u32 fileoffset;
    u32 filelen;
} __attribute__((packed)) FST_ENTRY;


char *fstfilename(u32 index)
{
	FST_ENTRY *fst = (FST_ENTRY *)*(u32 *)0x80000038;
	u32 count = fst[0].filelen;
	u32 stringoffset;
	if (index < count)
	{
		stringoffset = *(u32 *)&(fst[index]) % (256*256*256);
		return (char *)(*(u32 *)0x80000038 + count*12 + stringoffset);
	} else
	{
		return NULL;
	}
}

u32 fstfileoffset(u32 index)
{
	FST_ENTRY *fst = (FST_ENTRY *)*(u32 *)0x80000038;
	u32 count = fst[0].filelen;
	if (index < count)
	{
		return fst[index].fileoffset;
	} else
	{
		return 0;
	}
}

u32 Load_Dol_from_disc(u32 doloffset)
{
	int ret;
	void *dol_header;	
	u32 entrypoint;

	dol_header = allocate_memory( sizeof(dolheader));
	if (dol_header == NULL)
	{
		write_font(185, 274, "Out of memory");
		wait(3);
		return 0;
	}

	dvddone = 0;
	ret = bwDVD_LowRead(dol_header, sizeof(dolheader), doloffset, __dvd_readidcb);
	DVD_CHECK();

	entrypoint = load_dol_start(dol_header);

	if (entrypoint == 0)
	{
		write_font(185, 274, "Invalid .dol");
		wait(3);
		free(dol_header);
		return 0;
	}
	
	void *offset;
	u32 pos;
	u32 len;
	
	while (load_dol_image(&offset, &pos, &len))
	{
		if (len != 0)
		{
			dvddone = 0;
			ret = bwDVD_LowRead(offset, len, (doloffset+pos/4), __dvd_readidcb);
			DVD_CHECK();

			maindolpatches(offset, len);
			//Remove_001_Protection(offset, len);
		}	
	}
	
	free(dol_header);

	return entrypoint;
}	

u32 Load_Dol_from_disc_menu()
{
	FST_ENTRY *fst = (FST_ENTRY *)*(u32 *)0x80000038;
	u32 count = fst[0].filelen;
	int i;

	u32 dolcount = 0;
	u32 dolindex[10];

	for (i=1;i<count;i++)
	{		
		if (strstr(fstfilename(i), ".dol") != NULL)
		{
			if (dolcount < 10)
			{
				dolindex[dolcount] = i;
				dolcount++;
			}
		}		
	}
	
	if (dolcount == 0)
	{
		return 0;
		
	}	
	
	u32 pressed;
	u32 pressedGC;	
	int dolselect = 0;
	
	while (true)
	{
		clearscreen(xfb, 0, 274, 319, 24, 0x00800080);
		if (dolselect == 0)
		{
			write_font(185, 274, "<main.dol>");
		} else
		{
			write_font(185, 274, "<%s>", fstfilename(dolindex[dolselect-1]));
		}
		
		pressed = 0;
		pressedGC = 0;
		
		while (pressed == 0 && pressedGC == 0)
		{
			WPAD_ScanPads();
			pressed = WPAD_ButtonsDown(0);

			PAD_ScanPads();
			pressedGC = PAD_ButtonsDown(0);
		}		
		
		if (pressed == WPAD_BUTTON_LEFT || pressedGC == PAD_BUTTON_LEFT)
		{
			if (dolselect > 0)
			{
				dolselect--;
			} else
			{
				dolselect = dolcount;
			}
		}

		if (pressed == WPAD_BUTTON_RIGHT || pressedGC == PAD_BUTTON_RIGHT)
		{
			if (dolselect < dolcount)
			{
				dolselect++;
			} else
			{
				dolselect = 0;
			}
		}

		if (pressed == WPAD_BUTTON_A || pressedGC == PAD_BUTTON_A)
		{
			clearscreen(xfb, 0, 274, 319, 24, 0x00800080);
			if (dolselect == 0)
			{
				return 0;
			} else
			{
				return fstfileoffset(dolindex[dolselect-1]);			
			}			
		}		
	}		
}



u32 Load_Dol_from_storage()
{
	if (wbfsdevice == 2 && storageselect == 0)
	{
		SDHC_Close();
	}
	
	int ret;
	FILE* file;
	void *dol_header;	
	u32 entrypoint;
	
	ret = storage_init();
	if (ret < 0)
	{
		wait(3);
		return ret;
	}

	char buf[128];
	char tempbuffer[7];
	
	memset(tempbuffer, 0, 7);
	memcpy(tempbuffer, (char*)0x80000000, 6);		
	snprintf(buf, 128, "fat:/NeoGamma/%s.dol", tempbuffer);

	file = fopen( buf, "rb");
	
	if (file == NULL) 
	{
		memset(tempbuffer, 0, 7);
		memcpy(tempbuffer, (char*)0x80000000, 4);		
		snprintf(buf, 128, "fat:/NeoGamma/%s.dol", tempbuffer);

		file = fopen( buf, "rb");

		if (file == NULL) 
		{
			memset(tempbuffer, 0, 7);
			memcpy(tempbuffer, (char*)0x80000000, 6);		
			if (storageselect == 0)
			{
				write_font(185, 274, "sd:/NeoGamma/%s.dol missing", tempbuffer);
			} else
			{
				write_font(185, 274, "usb:/NeoGamma/%s.dol missing", tempbuffer);
			}

			wait(5);                
			return 0;
		}
	}
	
	int filesize;
	fseek(file, 0, SEEK_END);
	filesize = ftell(file);
	fseek(file, 0, SEEK_SET);

	dol_header = malloc(sizeof(dolheader));
	if (dol_header == NULL)
	{
		write_font(185, 274, "Out of memory");
		wait(3);
		fclose(file);
		return 0;
	}

	ret = fread( dol_header, 1, sizeof(dolheader), file);
	if(ret != sizeof(dolheader))
	{
		write_font(185, 274, "Error reading dol header");
		wait(3);
		free(dol_header);
		fclose(file);
		return 0;
	}
	
	entrypoint = load_dol_start(dol_header);
	
	if (entrypoint == 0)
	{
		write_font(185, 274, "Invalid .dol");
		wait(3);
		free(dol_header);
		fclose(file);
		return 0;
	}
	
	void *offset;
	u32 pos;
	u32 len;
	
	while (load_dol_image(&offset, &pos, &len))
	{
		if(pos+len > filesize)
		{
			write_font(185, 274, ".dol too small");
			wait(3);
			free(dol_header);
			fclose(file);
			return 0;
		}		
		
		if (len != 0)
		{
			fseek(file, pos, 0);
			ret = fread( offset, 1, len, file);
			if(ret != len)
			{
				write_font(185, 274, "Error reading .dol");
				wait(3);
				free(dol_header);
				fclose(file);
				return 0;
			}
			maindolpatches(offset, len);
			if (IOS_GetRevision() < 14)
			{
				Remove_001_Protection(offset, len);
			}
		}	
	}
	
	free(dol_header);
	fclose(file);

	if (wbfsdevice == 2 && storageselect == 0)
	{
		SDHC_Init();
	}

	return entrypoint;
}	


/* Identify as DVD title, created by WiiGator. */
static int DVD_Identify(u8 *ios)
{
	static u8 buffer[SECTOR_SIZE] __attribute__((aligned(32)));
	signed_blob *p_certs = NULL, *p_tik = NULL, *p_tmd = NULL;
	u32 certs_len, tik_len, tmd_len;
	int ret;

	ret = __CIOS_GetCerts(&p_certs, &certs_len);
	if (ret < 0) 
	{
		return -1;
	}
	dvddone = 0;
	ret = bwDVD_LowUnencryptedRead(buffer, SECTOR_SIZE, 0,__dvd_readidcb);
	DVD_CHECK();

	p_tik = (signed_blob *) buffer;

	tik_len = SIGNED_TIK_SIZE(p_tik);

	int mod_size = ((tik_len + 64 - 1) & ~(64 - 1));
	p_tmd = (signed_blob *)(((u32) p_tik) + mod_size);
	tmd_len = SIGNED_TMD_SIZE(p_tmd);

	ret = ES_Identify(p_certs, certs_len, p_tmd, tmd_len, p_tik, tik_len, NULL);

	*ios = ((u8 *)p_tmd)[0x18b];

	return ret;
}

u32 apploader()
{
	toy_reset();
	wipreset();

	void (*app_init)(void (*report)(const char* fmt, ...));
	int (*app_main)(void** dst, int* size, int* offset);
	void* (*app_final)();
	void (*app_entry)(void(**init)(void (*report)(const char* fmt, ...)),
	int (**main)(), void *(**final)());
	char* buffer = (char*)appbuffer;
	int retval;
	
	s32 ret,i = 0;
	hookpatched = false;

	ret = bwDVD_LowInit();

	if (!mount_disc())
	{
		write_font(185, 202, "Connection to WBFS lost");
		wait(3);
		return 0;
	}
	
	dvddone = 0;
	ret = bwDVD_LowReset(__dvd_readidcb);
	DVD_CHECK();

	// If a previous loading was canceled a partition could still be opened, which would result in an error
	dvddone = 0;
	ret = bwDVD_LowClosePartition(__dvd_readidcb);
	DVD_CHECK();

	/* clear existing GameID */
	memset((char*)0x80000000, 0, 6);

	dvddone = 0;
	ret = bwDVD_LowReadID(g_diskID,__dvd_readidcb);
	DVD_CHECK();

	memset(gameidbuffer, 0, 8);
	memcpy(gameidbuffer, (char*)0x80000000, 6);		

	// Invalid disc id
	if(gameidbuffer[1] == 0 && gameidbuffer[2] == 0){
		write_font(185, 202, "Error: No DVD (n)");
		return 0;
	}

	/* Gamecube Game */
	// 0xc2339f3d == magic word for gamecube discs
	if (*(u32 *)0x8000001c == 0xc2339f3d)
	{
		write_font(185, 298, "Game ID:");
		write_font(325, 298, "%s (GC)", gameidbuffer);
		bwDVD_LowClose();

		retval = ES_GetTicketViews(BC, &view, 1);
		if (retval != 0){
			write_font(185, 202, "Gamecube Error");
		}
		
		displayflags();
		__setappvideo();
		
		WPAD_Shutdown();
		*(volatile unsigned int *)0xCC003024 |= 7;

		wait(3);
		set_videomode();
		
		retval = ES_LaunchTitle(BC, &view);	// bushing's code
		write_font(185, 202, "Gamecube BC Error");
		return 0;
	}

	// Don't show this for gamecube games ...
	if (hookselect == 0 && (ocarinaselect != 0 || debuggerselect != 0))
	{
		write_font(185,  125 + 1*24, "You are stupid");
		sleep(10);
		SYS_ResetSystem(SYS_RETURNTOMENU, 0, 0);
	}

	u8 ios;
	if ((*((u32 *) gameidbuffer) == SIG_RSA_2048) || (*((u32 *) gameidbuffer) == SIG_RSA_4096)) 
	{	
		// Decrypted disc
		dvddone = 0;
		ret = bwDVD_SetDecryption(1, __dvd_readidcb);
		DVD_CHECK();

		if (DVD_Identify(&ios) < 0) {
			write_font(195, 226, "Error: DVD identify failed.");
			return 0;
		}

		dvddone = 0;
		ret = bwDVD_SetOffset(SECTOR_SIZE >> 2, __dvd_readidcb);
		DVD_CHECK();

		/* clear existing GameID */
		memset((char*)0x80000000, 0, 6);

		dvddone = 0;
		ret = bwDVD_LowRead(g_diskID,0x20, 0, __dvd_readidcb);
		DVD_CHECK();

		memset(gameidbuffer, 0, 8);
		memcpy(gameidbuffer, (char*)0x80000000, 6);		

		if(gameidbuffer[1] == 0 && gameidbuffer[2] == 0){
			write_font(185, 202, "Error: No DVD (b)");
			return 0;
		}
		write_font(185, 298, "Game ID:");
		write_font(325, 298, "%s (decrypted)", gameidbuffer);
	} else
	{
		// 1:1 disc
		write_font(185, 298, "Game ID:");
		write_font(325, 298, gameidbuffer);

		dvddone = 0;
		__dvd_gameToc = (struct _toc*)_gameTocBuffer;
		ret = bwDVD_LowUnencryptedRead(_gameTocBuffer,0x20,0x00010000,__dvd_readidcb);
		DVD_CHECK();

		dvddone = 0;
		__dvd_partInfo = (struct _pinfo*)_gamePartInfo;
		ret = bwDVD_LowUnencryptedRead(_gamePartInfo,0x20,__dvd_gameToc->partInfoOff,__dvd_readidcb);
		DVD_CHECK();

		i = 0;
		__dvd_bootGameInfo = NULL;

		while(i<__dvd_gameToc->bootInfoCnt) {
			if(__dvd_partInfo[i].len==0) {	// 1 for update partition
				__dvd_bootGameInfo = &__dvd_partInfo[i];
			}
			i++;
		}

		dvddone = 0;
		__dvd_Tmd = (u32*)_gameTmdBuffer;
		ret = bwDVD_LowOpenPartition(__dvd_bootGameInfo->offset,NULL,0,NULL,(void*)_gameTmdBuffer,__dvd_readidcb);
		DVD_CHECK();
		
		ios = ((u8*)_gameTmdBuffer)[0x18b];
	}
	
	dvddone = 0;
	ret = bwDVD_LowRead(buffer,0x20,0x2440/4,__dvd_readidcb);
	DVD_CHECK();
	DCFlushRange(buffer, 0x20);

	dvddone = 0;
	ret = bwDVD_LowRead((void*)0x81200000,((*(u32*)(buffer + 0x14)) + 31) & ~31,0x2460/4,__dvd_readidcb);
	DVD_CHECK();

	/* copy game ID and channel patches (set stuff thats missing) */
	*(vu32*)0x80000020 = 0xD15EA5E;	// Boot from DVD
	*(vu32*)0x80000024 = 1; 			// Version
	*(vu32*)0x80000030 = 0; 			// Arena Low
	
	*(vu32*)0x800000EC = 0x81800000; // Dev Debugger Monitor Address
	*(vu32*)0x800000F0 = 0x01800000; // Dev Debugger Monitor Address
	*(vu32*)0x800000F8 = 0x0E7BE2C0; // Console Bus Speed
	*(vu32*)0x800000FC = 0x2B73A840; // Console CPU Speed
	*(vu32*)0xCD00643C = 0x00000000;	// 32Mhz on Bus

	memcpy((void*)0x80003180, (char*)0x80000000, 4);	// online check code, seems offline games clear it?
    *(vu32*)0x800030F0 = 0x0000001C;
    *(vu32*)0x8000318C = 0x00000000;
    *(vu32*)0x80003190 = 0x00000000;
	
	// Fix for Sam & Max
	*(vu32*)0x80003184	= 0x80000000;	// Game ID Address
	
	app_entry = (void (*)(void(**)(void (*)(const char*, ...)), int (**)(), void *(**)()))(*(u32*)(buffer + 0x10));
	app_entry(&app_init, &app_main, &app_final);
	app_init((void (*)(const char*, ...))nothing);

	displayflags();
	__setappvideo();

	// Set the clock
	settime(secs_to_ticks(time(NULL) - 946684800));
	
	Country_Strings_Patched = !(countrystringselect);

	void* dst_array[64];
	int len_array[64];
	int last_index = -1;
	int fststart;

	while (1)
	{
		void* dst = 0;
		int len = 0,offset = 0;
		int res = app_main(&dst, &len, &offset);
		
		if (!res){
			break;
		}
		
		last_index++;
		dst_array[last_index] = dst;
		len_array[last_index] = len;

		dvddone = 0;
		ret = bwDVD_LowRead(dst,len,offset/4<<2,__dvd_readidcb);
		DVD_CHECK();
		DCFlushRange(dst, len);
	}
	
	dst_array[last_index+1] = (void *)0x81800000;
	int j = 0;
	fststart = 0;
	while ( j <= last_index && (u32)dst_array[last_index-j] + len_array[last_index-j] == (u32)dst_array[last_index-j+1]) 
	{
		fststart = last_index - j;
		j++;
	}		
	if (fststart == 0)
	{
		for (j = 4; j <= last_index; j++)
		{
			if ((u32)dst_array[j] == *(u32 *)0x80000038)
			{
				fststart = j;
			}
		}
		if (fststart == 0)
		{
			fststart = last_index;
		}
	}

	appentrypoint = (u32)app_final();
	
	if (alternativedolselect == 2)
	{
		u32 doloffset = Load_Dol_from_disc_menu();
		
		if (doloffset != 0)
		{
			for (i=3;i<fststart;i++)
			{
				memset(dst_array[i], 0, len_array[i]);
			}

			appentrypoint = Load_Dol_from_disc(doloffset);

			if (appentrypoint == 0)
			{
				return 0;
			}
			write_font(185, 274, ".dol loaded");
			wait(2);
		} else
		{
			for (i=3;i<fststart;i++)
			{
				maindolpatches(dst_array[i], len_array[i]);
			}	
		}
	} else
	if (alternativedolselect == 1)
	{
		for (i=3;i<fststart;i++)
		{
			memset(dst_array[i], 0, len_array[i]);
		}
		
		appentrypoint = Load_Dol_from_storage();
		
		if (appentrypoint == 0)
		{
			return 0;
		}
		write_font(185, 274, ".dol loaded");
		wait(2);
	} else
	{
		for (i=3;i<fststart;i++)
		{
			maindolpatches(dst_array[i], len_array[i]);
		}	
	}
	
	if (patchselect == 1)
	{
		do_wip_patches();
	}

	// Ocarina stuff
	if (hookselect != 0)
	{
		// Allow using regular sd code when loading from sd
		if (wbfsdevice == 2 && storageselect == 0)
		{
			SDHC_Close();
		}
		
		if ((ocarinaselect == 0 && debuggerselect == 1) || ocarinaselect == 1)
		{
			do_codes();
			if (wbfsdevice == 2 && storageselect == 0)
			{
				SDHC_Init();
			}
		} else
		{
			int ios;
			if (ocarinaselect == 2)
			{
				ios = 36;
			} else
			{
				ios = 61;
			}

			bwDVD_LowClose();
			
			storage_shutdown();
			ret = Load_IOS(ios);

			do_codes();

			if (ret < 0)
			{
				write_font(185, 346, "Error loading IOS");
				wait(3);
			}
			
			storage_shutdown();
			Load_IOS(CIOS_VERSION);

			// Reinit the drive
			ret = bwDVD_LowInit();
			if (!mount_disc())
			{
				write_font(185, 202, "Connection to WBFS lost");
				wait(3);
				return 0;
			}

			dvddone = 0;
			ret = bwDVD_LowReset(__dvd_readidcb);
			DVD_CHECK();

			// This is required! Without it, the next dvd command returns an error
			dvddone = 0;
			ret = bwDVD_LowReadID(g_diskID,__dvd_readidcb);
			DVD_CHECK();

			dvddone = 0;
			__dvd_Tmd = (u32*)_gameTmdBuffer;
			ret = bwDVD_LowOpenPartition(__dvd_bootGameInfo->offset,NULL,0,NULL,(void*)_gameTmdBuffer,__dvd_readidcb);
			DVD_CHECK();
		}
	}
	
	app_pokevalues();

	if (hookpatched == false)
	{
		clearscreen(xfb, 20, 346, 278, 24, 0x00800080);
		write_font(185, 346, "Hook error");
		sleep(5);
	}		
	
	toy_patch_video_modes_to(rmode);

	set_videomode();
	
	// Remove 002
	if (ios == IOS_GetVersion())
	{
		*(u32 *)0x80003188 = *(u32 *)0x80003140;
	} else
	{
		*(u8 *)0x80003141 = ios;
		*(u8 *)0x80003189 = ios;
		*(u16 *)0x80003142 = 0xffff;
		*(u16 *)0x8000318A = 0x0001;
	}

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

	shutdown_all();

	if (hookselect == 0)
	{
		__asm__(
			"lis %r3, appentrypoint@h\n"
			"ori %r3, %r3, appentrypoint@l\n"
			"lwz %r3, 0(%r3)\n"
			"mtlr %r3\n"
			"blr\n"
		);
	} else
	{
		__asm__(
			"lis %r3, appentrypoint@h\n"
			"ori %r3, %r3, appentrypoint@l\n"
			"lwz %r3, 0(%r3)\n"
			"mtlr %r3\n"
			"lis %r3, 0x8000\n"
			"ori %r3, %r3, 0x18A8\n"
			"mtctr %r3\n"
			"bctr\n"
		);
	}

	//jumpentry(appentrypoint);
	return 1;
}
