/*
 *  Copyright (C) 2008 Nuke (wiinuke@gmail.com)
 *
 *  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 <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <gccore.h>	
#include <gccore.h>
#include <ogcsys.h>
#include <sys/unistd.h>
#include <wiiuse/wpad.h>
#include <ogc/lwp_watchdog.h>
#include <time.h>
#include <fat.h>
#include "tools.h"
#include "sdcard/wiisd_io.h"
#include "fst.h"
#include "apploader.h"
#include "geckodvd.h"
#include "patchcode.h"
#include "font.h"
#include "dvd_broadway.h"
#include "patchcode.h"
#include "geckomenu.h"
#include "fst.h"
#include "libcios.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; \
		} \
		while(dvddone==0); \
		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)

static char gameidbuffer[8];

extern GXRModeObj TVEurgb60Hz480Int;
static GXRModeObj *rmode = NULL;

static vu32 dvddone = 0;
static u32 writentsc_CC;
static u32 forcedpal = 0;
static u32 forcedntsc = 0;

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 u32 apploader_backup(void);

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);


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

void settime(u64 time);


static void __setforceflags()
{
	
	
	write_font(185, 202, "PAL60 Forced:");
	write_font(185, 226, "NTSC Forced:");
	write_font(185, 250, "VIDTV Patch:");
	write_font(185, 274, "PAL50 Forced:");
	
	switch(configbytes[1])
	{
		case 0x00: // apply no video settings
			vipatchon = 0;
			write_font(390, 202, "NO");
			write_font(390, 226, "NO");
			write_font(390, 250, "NO");
			write_font(390, 274, "NO");
		break;

		case 0x01: 
			vipatchon = 0;
			rmode =	&TVEurgb60Hz480Int; // PAL60 forced
			forcedpal = 1;
			write_font(390, 202, "YES");
			write_font(390, 226, "NO");
			write_font(390, 250, "NO");
			write_font(390, 274, "NO");
		break;

		case 0x02: 
			vipatchon = 1;
			write_font(390, 202, "NO");
			write_font(390, 226, "NO");
			write_font(390, 250, "YES");
			write_font(390, 274, "NO");
		break;

		case 0x03: 
			rmode =	&TVEurgb60Hz480Int; // PAL60 forced
			forcedpal = 1;
			vipatchon = 1;
			write_font(390, 202, "YES");
			write_font(390, 226, "NO");
			write_font(390, 250, "YES");
			write_font(390, 274, "NO");
		break;

		case 0x04: 
			vipatchon = 0;
			forcedntsc = 1;
			rmode = &TVNtsc480IntDf;	// NTSC forced
			write_font(390, 202, "NO");
			write_font(390, 226, "YES");
			write_font(390, 250, "NO");
			write_font(390, 274, "NO");
		break;

		case 0x05: 
			rmode = &TVNtsc480IntDf;	// NTSC forced
			forcedntsc = 1;
			vipatchon = 1;
			write_font(390, 202, "NO");
			write_font(390, 226, "YES");
			write_font(390, 250, "YES");
			write_font(390, 274, "NO");
		break;

		case 0x06: 
			rmode = &TVPal528IntDf;	// PAL50 forced
			forcedpal = 1;
			vipatchon = 0;
			write_font(390, 202, "NO");
			write_font(390, 226, "NO");
			write_font(390, 250, "NO");
			write_font(390, 274, "YES");
		break;

		case 0x07: 
			rmode = &TVPal528IntDf;	// PAL forced & VI Patch
			forcedpal = 1;
			vipatchon = 1;
			write_font(390, 202, "NO");
			write_font(390, 226, "NO");
			write_font(390, 250, "YES");
			write_font(390, 274, "YES");
		break;
	}
}

static void __setappvideo()
{
	switch(gameidbuffer[3])
	{
		case 0x50:		// PAL default
			writentsc_CC = 1;
			rmode = &TVPal528IntDf;
		break;

		case 0x44:		// PAL German
			writentsc_CC = 1;
			rmode = &TVPal528IntDf;
		break;

		case 0x45:		// USA default
			writentsc_CC = 0;
			rmode = &TVNtsc480IntDf;
		break;

		case 0x46:		// F (french PAL)
			writentsc_CC = 1;
			rmode = &TVPal528IntDf;
		break;

		case 0x4A:		// JAP default
			writentsc_CC = 0;
			rmode = &TVNtsc480IntDf;
		break;

		case 0x58:		// PAL X euro
			writentsc_CC = 1;
			rmode = &TVPal528IntDf;
		break;

		case 0x59:		// PAL Y euro
			writentsc_CC = 1;
			rmode = &TVPal528IntDf;
		break;
	}
	
}

void nothing()
{
}

u32 ocarinarebooter()
{
	s32 ret = 0;

	if(ocarinaselect == 1){
	write_font(185, 178, "Searching SD 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();

	/* If ID NULL then DVD must be invalid */
	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);
	sleep(1);

	sdio_Startup();
	do_sd_code(gameidbuffer);
		
	if (!fatUnmount(PI_INTERNAL_SD)) {
		fatUnsafeUnmount(PI_INTERNAL_SD);
	}

	}
	sleep(1);
	return 1;
}


u32 apploader(void)
{
	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;
	u32 appentrypoint;
	u8 entryholder[4];
	int retval;
	
	patched = 0;
	s32 ret,i = 0;

	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();

	/* If ID NULL then DVD must be invalid */
	memset(gameidbuffer, 0, 8);
	memcpy(gameidbuffer, (char*)0x80000000, 6);		

	if ((*((u32 *) gameidbuffer) == SIG_RSA_2048)
		|| (*((u32 *) gameidbuffer) == SIG_RSA_4096)) {
		/* Decrypted game. */
		apploader_backup();
		return 0;
	}
	if(gameidbuffer[1] == 0 && gameidbuffer[2] == 0){
		write_font(185, 202, "Error: No DVD (n)");
		return 0;
	}

	/* Check not a Gamecube Game */
	if(gameidbuffer[0] == 0x47 || gameidbuffer[0] == 0x55 || gameidbuffer[0] == 0x44){
		retval = ES_GetTicketViews(BC, &view, 1);
		if (retval != 0){
			write_font(185, 202, "Gamecube Error");
		}
		
		__setappvideo();
		__setforceflags();
		WPAD_Shutdown();
		*(volatile unsigned int *)0xCC003024 |= 7;

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

	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++;
	}

	write_font(185, 298, "Game ID:");
	write_font(325,298, gameidbuffer);

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

	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();

	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);

	__setappvideo();
	__setforceflags();
	if(progmode){	// Progressive mode override
		rmode = &TVNtsc480Prog;
	}

	write_font(185, 298, "Game ID:");
	write_font(325,298, gameidbuffer);
	sleep(1);

	// Set the clock
	settime(secs_to_ticks(time(NULL) - 946684800));
	
	while (1)
	{
		void* dst = 0;
		int len = 0,offset = 0;
		int res = app_main(&dst, &len, &offset);
		
		if (!res){
			break;
		}

		dvddone = 0;
		ret = bwDVD_LowRead(dst,len,offset/4<<2,__dvd_readidcb);
		DVD_CHECK();
		DCFlushRange(dst, len);

		dogamehooks(dst,len);
		if(vipatchon){
			vidolpatcher(dst,len);
		}
		if(debugmode){
			patchdebug(dst,len);
		}
		langpatcher(dst,len);
	
		DCFlushRange(dst, len);

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

	/* read in Arena, FST, Max FST from DOL */
	u32 arealow = *(u32*)0x80000034;
	u32 areahigh = *(u32*)0x80000038;
	u32 maxfst = *(u32*)0x8000003c;
	u32 bi2 = *(u32*)0x800000f4;
	
	if(writentsc_CC){
		*(u32*)0x800000CC = 1;
	}
	else {
		*(u32*)0x800000CC = 0;
	}

	if(forcedpal){
		*(u32*)0x800000CC = 1;		// color fix, values just reverse
	}
	if(forcedntsc){
		*(u32*)0x800000CC = 0;
	}

	/* 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*)0x80000034 = arealow; // Arena High - get from DVD
	*(vu32*)0x80000038 = areahigh; // FST Start - get from DVD
	*(vu32*)0x8000003C = maxfst; // Max FST size - get from DVD
	
	*(vu32*)0x800000EC = 0x81800000; // Dev Debugger Monitor Address
	*(vu32*)0x800000F0 = 0x01800000; // Dev Debugger Monitor Address
	*(vu32*)0x800000F4 = bi2; // BI2
	*(vu32*)0x800000F8 = 0x0E7BE2C0; // Console Bus Speed
	*(vu32*)0x800000FC = 0x2B73A840; // Console CPU Speed
	*(vu32*)0xCD00643C = 0x00000000;	// 32Mhz on Bus

	memcpy((void*)0x80001800, (char*)0x80000000, 6);	// For WiiRD
	memcpy((void*)0x80003180, (char*)0x80000000, 4);	// online check code, seems offline games clear it?
    *(vu32*)0x800030F0 = 0x0000001C;
    *(vu32*)0x8000318C = 0x00000000;
    *(vu32*)0x80003190 = 0x00000000;
	
	appentrypoint = app_final();
	entryholder[0] = appentrypoint >> 24;
	entryholder[1] = appentrypoint >> 16;
	entryholder[2] = appentrypoint >> 8;
	entryholder[3] = appentrypoint;
	
	// Ocarina stuff
	if(ocarinaselect == 1 && nowiird == 1){
		sdio_Startup();
		do_sd_code(gameidbuffer);
		
		if (!fatUnmount(PI_INTERNAL_SD)) {
			fatUnsafeUnmount(PI_INTERNAL_SD);
		}
	}

	DCFlushRange((void*)0x80000000, 0x3f00);
	shutdown_all();
	jumpentry(appentrypoint);
	return 1;
}

/* Identify as DVD title, created by WiiGator. */
static int DVD_Identify(void)
{
	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);

	return ret;
}


/* Apploader for DVD backups, created by WiiGator. */
static u32 apploader_backup(void)
{
	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;
	u32 appentrypoint;
	u8 entryholder[4];
	
	patched = 0;
	s32 ret = 0;

	dvddone = 0;
	ret = bwDVD_SetDecryption(1, __dvd_readidcb);
	DVD_CHECK();

	if (DVD_Identify() < 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();

	/* If ID NULL then DVD must be invalid */
	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, gameidbuffer);

	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();

	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);

	__setappvideo();
	__setforceflags();
	if(progmode){	// Progressive mode override
		rmode = &TVNtsc480Prog;
	}

	write_font(185, 298, "Game ID:");
	write_font(325,298, gameidbuffer);
	sleep(1);

	// Set the clock
	settime(secs_to_ticks(time(NULL) - 946684800));
	
	while (1)
	{
		void* dst = 0;
		int len = 0,offset = 0;
		int res = app_main(&dst, &len, &offset);
		
		if (!res){
			break;
		}

		dvddone = 0;
		ret = bwDVD_LowRead(dst,len,offset/4<<2,__dvd_readidcb);
		DVD_CHECK();
		DCFlushRange(dst, len);

		dogamehooks(dst,len);
		if(vipatchon){
			vidolpatcher(dst,len);
		}
		if(debugmode){
			patchdebug(dst,len);
		}
		langpatcher(dst,len);
	
		DCFlushRange(dst, len);

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

	/* read in Arena, FST, Max FST from DOL */
	u32 arealow = *(u32*)0x80000034;
	u32 areahigh = *(u32*)0x80000038;
	u32 maxfst = *(u32*)0x8000003c;
	u32 bi2 = *(u32*)0x800000f4;
	
	if(writentsc_CC){
		*(u32*)0x800000CC = 1;
	}
	else {
		*(u32*)0x800000CC = 0;
	}

	if(forcedpal){
		*(u32*)0x800000CC = 1;		// color fix, values just reverse
	}
	if(forcedntsc){
		*(u32*)0x800000CC = 0;
	}

	/* 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*)0x80000034 = arealow; // Arena High - get from DVD
	*(vu32*)0x80000038 = areahigh; // FST Start - get from DVD
	*(vu32*)0x8000003C = maxfst; // Max FST size - get from DVD
	
	*(vu32*)0x800000EC = 0x81800000; // Dev Debugger Monitor Address
	*(vu32*)0x800000F0 = 0x01800000; // Dev Debugger Monitor Address
	*(vu32*)0x800000F4 = bi2; // BI2
	*(vu32*)0x800000F8 = 0x0E7BE2C0; // Console Bus Speed
	*(vu32*)0x800000FC = 0x2B73A840; // Console CPU Speed
	*(vu32*)0xCD00643C = 0x00000000;	// 32Mhz on Bus

	memcpy((void*)0x80001800, (char*)0x80000000, 6);	// For WiiRD
	memcpy((void*)0x80003180, (char*)0x80000000, 4);	// online check code, seems offline games clear it?
    *(vu32*)0x800030F0 = 0x0000001C;
    *(vu32*)0x8000318C = 0x00000000;
    *(vu32*)0x80003190 = 0x00000000;
	
	appentrypoint = app_final();
	entryholder[0] = appentrypoint >> 24;
	entryholder[1] = appentrypoint >> 16;
	entryholder[2] = appentrypoint >> 8;
	entryholder[3] = appentrypoint;
	
	// Ocarina stuff
	if(ocarinaselect == 1 && nowiird == 1){
		sdio_Startup();
		do_sd_code(gameidbuffer);
		
		if (!fatUnmount(PI_INTERNAL_SD)) {
			fatUnsafeUnmount(PI_INTERNAL_SD);
		}
	}

	DCFlushRange((void*)0x80000000, 0x3f00);
	shutdown_all();
	jumpentry(appentrypoint);
	return 1;
}
