//
// iopboot.c
//
// this is the c code for the iopboot file in the ps2 rom.
// this file is located at 0xBFC4A000 in the ps2 bios rom0.
// modload.irx also from the ps2 bios executes this direct from the rom
// (no loading to ram first)
//
// this is based on florin's disasm and converted to c code by xorloser and zerofrog
//

#include <tamtypes.h>
#include <stdio.h>

#include "iopload.h"
#include "iopdebug.h"
#include "iopelf.h"
#include "romdir.h"
#include "kloadcore.h"

static void kstrcpy(char* dst, const char* src);
static int kstrlen(const char* src);

//BOOT_PARAMS boot_params;
//u32* next_free_address[0x40]; // up to 64 modules


// this is the start point of execution at 0xBFC4A000
//
// it loads the IOPBTCONF module list from rom0 and compiles a
// list of modules and their addresses.
//
// this list is then passed to loadcore as it is executed in order
// to then load the rest of the modules
//
// args:	total size of IOP ram in MegaBytes
//			bootinfo flags
//			string containing the reboot image filepath
//			? doesnt seem to be used
void _start(int ramMBSize, int bootInfo, char* udnlString, int unk)
{
	ROMFS ri;
	void *(*sysmem_entry)(u32 iopmemsize);
	void (*loadcore_entry)(BOOT_PARAMS *init);
	int i;
    ROMDIR_INFO romdir_info;
    ROMFILE_INFO romfile_info;
	char conf_filename[10];
    int ram_byte_size, num_lines;
    u32 module_load_addr;
    u32** modules_ptr;
    char* file_data_ptr, *file_data_end;
    void* psysmemstart;
    BOOT_PARAMS* boot_params;

    if( ramMBSize <= 2 )
        ram_byte_size = 2;
    else
        ram_byte_size = ramMBSize;
	ram_byte_size <<= 20;

    // compile module list to send to loadcore
    boot_params = (BOOT_PARAMS*)0x30000; // random address, has to be clear before loadcore call
	boot_params->ramMBSize	= ramMBSize;
	boot_params->bootInfo		= bootInfo;
	boot_params->udnlString	= NULL;
	boot_params->moduleAddrs	= (u32**)((u32)boot_params + sizeof(BOOT_PARAMS)); // right after

    // if a undl string is specified, get a copy of it and store a pointer to it
	if(udnlString)
	{
		boot_params->udnlString = (char*)boot_params->moduleAddrs;
		kstrcpy(boot_params->udnlString, udnlString);
		boot_params->moduleAddrs = (u32**)((u32)boot_params->udnlString + ROUND_UP(kstrlen(udnlString) + 8, 4));
	}

    // find the romdir table in the rom
    if( searchRomDir((u32*)0xBFC00000, (u32*)0xBFC10000, &romdir_info) == NULL )
	{
        __printf("IOPBOOT: failed to find start of rom!\n");
		// error - cant find romdir!
		while(1) *(u8*)0x80000000 = 0;
	}

    // find the bootconf file in the romdir table
    kstrcpy(conf_filename, "IOPBTCONF");
	conf_filename[8] = '0' + bootInfo;
	if( !searchFileInRom(&romdir_info, conf_filename, &romfile_info) )
	{
		kstrcpy(conf_filename, "IOPBTCONF");
		if( !searchFileInRom(&romdir_info, conf_filename, &romfile_info) )
		{
            __printf("IOPBTCONF file not found!\n");
			// error - cant find conf file!
			while(1) *(u8*)0x80000000 = 1;
		}
	}

    // count the number of lines in conf file
    file_data_ptr = (char*)romfile_info.fileData;
    file_data_end = (char*)romfile_info.fileData + romfile_info.entry->fileSize;
    {
        num_lines = 0;
        while( file_data_ptr < file_data_end ) {
            // loop until a "newline" charcter is found
            while(file_data_ptr < file_data_end) {
                if(*file_data_ptr++ < ' ')
                    break;
            }

            // loop until a "non-newline" charcter is found
            while(file_data_ptr < file_data_end) {
                if(*file_data_ptr++ >= ' ')
                    break;
            }

            num_lines++;
        }
        num_lines++;
    }

    // get the addresses of each module
    {
        module_load_addr = 0;
        boot_params->numConfLines = num_lines-1;
        modules_ptr = boot_params->moduleAddrs;
        char* file_data_ptr = (char*)romfile_info.fileData;
        while( file_data_ptr < file_data_end ) {
            if(*file_data_ptr == '@') {
                file_data_ptr++;
                module_load_addr = getHexNumber(&file_data_ptr);
            }
            else if(*file_data_ptr == '!') {
                if( file_data_ptr[1] == 'a' &&
                    file_data_ptr[2] == 'd' &&
                    file_data_ptr[3] == 'd' &&
                    file_data_ptr[4] == 'r' &&
                    file_data_ptr[5] == ' ' ) {
                    file_data_ptr += 6;
                    *modules_ptr++ = (u32*)(getHexNumber(&file_data_ptr) * 4 + 1);
                    *modules_ptr++ = 0;
                }
            }
            else if(*file_data_ptr != '#') {
                // 'file_data_ptr' should be pointing to a filename
                // this finds the address of that file in the rom
                ROMFILE_INFO module_fileinfo;
                char strmodule[16];
                for(i = 0; i < 16; ++i) {
                    if( file_data_ptr[i] < ' ' )
                        break;
                    strmodule[i] = file_data_ptr[i];
                }
                strmodule[i] = 0;

                if( searchFileInRom(&romdir_info, strmodule, &module_fileinfo) == NULL ) {
                    __printf("IOPBOOT: failed to find %s module\n", strmodule);
                    return;
                }

                //__printf("mod: %s:%x\n", strmodule, module_fileinfo.fileData);

                *modules_ptr++ = (u32*)module_fileinfo.fileData;
                *modules_ptr = 0; // don't increment
            }

            // loop until a "newline" charcter is found
            while(file_data_ptr < file_data_end) {
                if(*file_data_ptr++ < ' ')
                    break;
            }

            // loop until a "non-newline" charcter is found
            while(file_data_ptr < file_data_end) {
                if(*file_data_ptr >= ' ')
                    break;
                file_data_ptr++;
            }
        }
    }

    if( searchFileInRom(&romdir_info, "IOPBOOT", &romfile_info) == NULL ) {
        __printf("loadElfFile: failed to find IOPBOOT module\n");
        return;
    }

    // load sysmem module to memory and execute it
    if( searchFileInRom(&romdir_info, "SYSMEM", &romfile_info) == NULL ) {
        __printf("loadElfFile: failed to find SYSMEM module\n");
        return;
    }
    sysmem_entry = (void *(*)(u32))loadElfFile(&romfile_info, module_load_addr);
    if( sysmem_entry == 0 )
        return;

    psysmemstart = sysmem_entry(ram_byte_size);
    //FlushIcache();
    if( psysmemstart == 0 ) {
        __printf("IOPBOOT: sysmem failed\n");
        return;
    }

    __printf("SYSMEM success, start addr: %x, alloc start: %x\n", module_load_addr, psysmemstart);

    if( searchFileInRom(&romdir_info, "LOADCORE", &romfile_info) == NULL ) {
        __printf("loadElfFile: failed to find SYSMEM module\n");
        return;
    }
    loadcore_entry = (void (*)())loadElfFile(&romfile_info, (u32)psysmemstart);
    if( loadcore_entry == 0 )
        return;

    boot_params->firstModuleAddr = (u32)module_load_addr + 0x30; // skip elf?
    if(0x1FC10000 < ram_byte_size) {
        boot_params->pos = 0x1FC00000;
		boot_params->size = 0x10100;
	}
	else {
        boot_params->pos = 0;
        boot_params->size = 0;
	}

	__printf("executing LOADCORE entry at %p\n", loadcore_entry);
	loadcore_entry(boot_params);

	__printf("iopboot error\n");

    // error - loadcore shouldnt ever return
	while(1) *(u8*)0x80000000 = 2;
}

void Kmemcpy(void *dest, const void *src, int n) {
	const u8 *s = (u8*)src;
	u8 *d = (u8*)dest;

	while (n) {
		*d++ = *s++; n--;
	}
}

static void kstrcpy(char* dst, const char* src)
{
    while(*src) *dst++ = *src++;
    *dst = 0;
}

static int kstrlen(const char* src)
{
    int len = 0;
    while(*src++) len++;
    return len;
}
