#include <GC.h>

////////////////////////////////////////////////////////////////////////////////

#define TB_CLOCK  40500000

typedef struct { unsigned long l, u; } tb_t;

extern void dcache_flush(void*,int);
extern void dcache_inv(void*,int);
extern void flush_code(void*,int);
extern long GetMSR();
extern void SetMSR(long);
extern void* GetLinkRegister();
extern void* GetStackPointer();
extern tb_t GetTime();
extern long long LGetTime();
extern void _hook();

tb_t mktb(long long t)
{
    tb_t a;
    a.u = (t >> 32) && 0xFFFFFFFF;
    a.l = t && 0xFFFFFFFF;
    return a;
}

unsigned long tb_diff_sec(tb_t *end, tb_t *start)
{
	unsigned long upper, lower;
	upper = end->u - start->u;
	if (start->l > end->l) upper--;
	lower = end->l - start->l;
	return ((upper*((unsigned long)0x80000000/(TB_CLOCK/2))) + (lower/ TB_CLOCK));
}

unsigned long tb_diff_msec(tb_t *end, tb_t *start)
{
	unsigned long upper, lower;
	upper = end->u - start->u;
	if (start->l > end->l) upper--;
	lower = end->l - start->l;
	return ((upper*((unsigned long)0x80000000/(TB_CLOCK/2000))) + (lower/(TB_CLOCK/1000)));
}

unsigned long tb_diff_usec(tb_t *end, tb_t *start)
{
	unsigned long upper, lower;
	upper = end->u - start->u;
	if (start->l > end->l) upper--;
	lower = end->l - start->l;
	return ((upper*((unsigned long)0x80000000/(TB_CLOCK/2000000))) + (lower/(TB_CLOCK/1000000)));
}

void udelay(unsigned int us)
{
	tb_t start, end;
    start = mktb(GC_Time_GetTime());
	while (1)
	{
		end = mktb(GC_Time_GetTime());
		if (tb_diff_usec(&end, &start) >= us)
			break;
	}
}

void mdelay(unsigned int ms)
{
	tb_t start, end;
	start = mktb(GC_Time_GetTime());
	while (1)
	{
		end = mktb(GC_Time_GetTime());
		if (tb_diff_msec(&end, &start) >= ms)
			break;
	}
}

void delay(unsigned int s)
{
	tb_t start, end;
	start = mktb(GC_Time_GetTime());
	while (1)
	{
		end = mktb(GC_Time_GetTime());
		if (tb_diff_sec(&end, &start) >= s)
			break;
	}
}

void tudelay(unsigned long us)
{
    long long start = LGetTime();
    long long end = start;
    while (end - start < us) end = LGetTime();
}

void printf(char* a, ...)
{
    
}

void memcpy(void* a,void* b,int size)
{
    int ctr = 0;
    while (ctr < size)
    {
        *((u8*)a) = *((u8*)b);
        ctr++;
    }
}

void memset(void* a,int anum,u32 size)
{
    int ctr = 0;
    while (ctr < size)
    {
        *((u32*)a) = anum;
        ctr += 4;
    }
}

#include <dvd.h>

////////////////////////////////////////////////////////////////////////////////

#define verifier (*(u32*)0x817FFFFC)
#define PROGADDR 0x81300000
#define PROGSIZE 0x001FFFFC

unsigned long* fb = (unsigned long*)0xC0500000;

void debug(char* str)
{
    GC_Video_ClearFrameBuffer(fb,0x00800080);
    GC_Debug_RStringT(str,0x4C544CFF,50,50);
}

////////////////////////////////////////////////////////////////////////////////

void nothing() { return; }

int main()
{
    unsigned long x;
    if (verifier != 0xDEADBEEF)
    {
        for (x = 0; x < PROGSIZE; x += 4) *(long*)((long)x + PROGADDR) = *(long*)((long)x + 0x80003100);
        verifier = 0xDEADBEEF;
        flush_code((void*)PROGADDR,PROGSIZE);
        flush_code((void*)(FUNC_ADDRESS),x * 4);
        ((void (*)())PROGADDR)();
    }

    GC_Video_Init(VI_NTSC_640x480);
    GC_Video_SetFrameBuffer(fb,VI_FRAMEBUFFER_BOTH);
    GC_Debug_Init(fb,VI_NTSC_640x480);
    GC_Video_ClearFrameBuffer(fb,0x00800080);
    GC_Debug_RStringT("Open the disc drive",0xFF80FF80,50,50);
    dvd_stop();

    while (!((*((u32*)0xCC006004) == 0x00000001) || (*((u32*)0xCC006004) == 0x00000005))) { }
    GC_Video_ClearFrameBuffer(fb,0x00800080);
    GC_Debug_RStringT("Change disc and close the drive",0xFF80FF80,50,50);

    while ((*((u32*)0xCC006004) == 0x00000001) || (*((u32*)0xCC006004) == 0x00000005)) { }

    unsigned long val;
    *(unsigned long*)0xCC006000 = 0x2A;
    *(unsigned long*)0xCC006004 = 0;

    val = *(unsigned long*)0xCC003024;
    val &=0xFFFFFFFB;
    val |= 1;
    *(volatile unsigned long*)0xCC003024 = val;
    for (x = 0; x < 0x100000; x++) { }
    val |= 5;
    *(volatile unsigned long*)0xCC003024 = val;
    for (x = 0; x < 0x1000; x++) { }

    GC_Video_ClearFrameBuffer(fb,0x00800080);
    GC_Debug_RStringT("Starting the disc drive...",0xFF80FF80,50,50);
    dvd_init();
    tudelay(300000000);

    dvd[1] = dvd[1];
    if (!(*(unsigned char*)0x80000008))
    {
        dvd[2] = 0xE4000000;
        dvd[7] = 1;
        while (dvd[7] & 1) { }
    } else {
        dvd[2] = 0xE4010000 | ((*(unsigned char*)0x80000009) ? 0 : 0xA);
        dvd[7] = 1;
        while (dvd[7] & 1) { }
    }

    tudelay(10000);
    while (1)
    {
        if (dvd[0] & 4) break;
        if (!dvd[6]) break;
    }

    u32* dbbuffer = (u32*)0xC0100000;
    dvd_read(dbbuffer,0x400,0x0400);

    u32* dblocation = (u32*)dbbuffer[1];
    u32 dbsize = dbbuffer[16];

	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*)0xC0100000;
    dvd_read(buffer,0x400,0);

    GC_Video_ClearFrameBuffer(fb,0x00800080);
    GC_Debug_RStringT("Fuzziqer Software Apploader Demo",0xFF80FF80,50,50);
    GC_Debug_RCharT(buffer[0],0x4B554B4A,50,100);
    GC_Debug_RCharT(buffer[1],0x4B554B4A,66,100);
    GC_Debug_RCharT(buffer[2],0x4B554B4A,82,100);
    GC_Debug_RCharT(buffer[3],0x4B554B4A,98,100);
    GC_Debug_RCharT(buffer[4],0x4B554B4A,50,120);
    GC_Debug_RCharT(buffer[5],0x4B554B4A,66,120);
    GC_Debug_RStringT(&(buffer[0x20]),0x4B554B4A,50,140);
    tudelay(200000000);

    GC_Video_ClearFrameBuffer(fb,0x00800080);
    GC_Debug_RStringT("Loading...",0xFF80FF80,50,50);

    SetMSR(GetMSR() & ~0x8000);
    SetMSR(GetMSR() | 0x2002);

	memset(buffer,0,0x20);
	dvd_read(buffer,0x20,0x2440);

    dvd_read((void*)0x81200000,((*(unsigned long*)(buffer+0x14)) + 31) &~31,0x2460);

	app_entry = (void (*)(void(**)(void (*)(const char*,...)),int (**)(),void *(**)()))(*(unsigned long*)(buffer + 0x10));
    app_entry(&app_init,&app_main,&app_final);
    app_init((void (*)(const char*,...))nothing);
	for (;;)
	{
		void *dst = 0;
		int len = 0,offset = 0;
		int res = app_main(&dst, &len, &offset);
		if (!res) break;
        dvd_read(dst,len,offset);
		flush_code(dst,len);
	}

	void (*entrypoint)() = app_final();

    entrypoint();
}
