
#include <tamtypes.h>

#include "ksysmem.h"
#include "kloadcore.h"
#include "iopdebug.h"
#include "iopelf.h"
#include "romdir.h"
#include "irx.h"		// ps2sdk file for IMPORT_MAGIC

#define BOOTMODE_START	*((volatile u32**)0x3F0)
#define BOOTMODE_END	*((volatile u32**)0x3F4)

void _start(BOOT_PARAMS *init); // has to be declared first!

struct tag_LC_internals {
	struct export* let_next, *let_prev;
	struct export* mda_next, *mda_prev;//.prev==free
	imageInfo *image_info; // 0x800?0x830?
	int		module_count;
	int		module_index;
} lc_internals;

u32 free;
u32 sysmem_00;
u32 modules_count;
u32	module_index;
u32 *place;	// addr of 8 * 31 = 8 * linesInIopbtconf	//place[2][31]
u32	bootmodes[17]; // was 16?
u32 bootmodes_size;
int debug=0;

u32 bm_end;

#define _dprintf(fmt, args...) \
	if (debug > 0) __printf("loadcore:%d: " fmt, __LINE__, ## args)

void retonly();
struct tag_LC_internals* GetLibraryEntryTable();
void FlushIcache();
void FlushDcache();
int RegisterLibraryEntries(struct export *es);
int  ReleaseLibraryEntries(struct export *e);
int  _LinkImports(u32 *addr, int size);
int  _UnlinkImports(void *addr, int size);
int  RegisterNonAutoLinkEntries(struct export *e);
int  QueryLibraryEntryTable(struct export *e);
u32 *QueryBootMode(int id);
void RegisterBootMode(struct bootmode *b);
int  SetNonAutoLinkFlag(struct export *e);
int  UnsetNonAutoLinkFlag(struct export *e);
void _LinkModule(imageInfo *ii);
void _UnlinkModule(imageInfo *ii);
int  _RegisterBootupCBFunc(int (*function)(int *, int), int priority, int *result);
void _SetCacheCtrl(u32 val);
int  _ReadModuleHeader(void *image, fileInfo *result);
int  _LoadModule(void *image, fileInfo *fi);
u32  _FindImageInfo(void *addr);

struct export loadcore_stub __attribute__((section(".text"))) ={
	EXPORT_MAGIC,
	0,
	VER(1, 1),	// 1.1 => 0x101
	0,
	"loadcore",
	(func)_start,	// entrypoint
	(func)retonly,
	(func)retonly,
	(func)GetLibraryEntryTable,
	(func)FlushIcache,
	(func)FlushDcache,
	(func)RegisterLibraryEntries,
	(func)ReleaseLibraryEntries,
	(func)_LinkImports,
	(func)_UnlinkImports,
	(func)RegisterNonAutoLinkEntries,
	(func)QueryLibraryEntryTable,
	(func)QueryBootMode,
	(func)RegisterBootMode,
	(func)SetNonAutoLinkFlag,
	(func)UnsetNonAutoLinkFlag,
	(func)_LinkModule,
	(func)_UnlinkModule,
	(func)retonly,
	(func)retonly,
	(func)_RegisterBootupCBFunc,
	(func)_SetCacheCtrl,
	(func)_ReadModuleHeader,
	(func)_LoadModule,
	(func)_FindImageInfo,
	0
};

///////////////////////////////////////////////////////////////////////
void retonly(){}

///////////////////////////////////////////////////////////////////////
void RegisterBootMode(struct bootmode *b) {
	int i;

	_dprintf("%s\n", __FUNCTION__);
	if (((b->len + 1) * 4) < (16-bootmodes_size)) {
		u32 *p = &bootmodes[bootmodes_size];
		for (i=0; i<b->len + 1; i++) p[i]=((u32*)b)[i];
		p[i]=0;
		bootmodes_size+= b->len + 1;
	}
}

///////////////////////////////////////////////////////////////////////
u32 *QueryBootMode(int id) {
	u32 *b;

	for (b = (u32*)&bootmodes[0]; *b; b += ((struct bootmode*)b)->len + 1)
		if (id == ((struct bootmode*)b)->id)
			return b;
	return NULL;
}

///////////////////////////////////////////////////////////////////////
int  match_name(struct export *src, struct export *dst){
	return (*(int*)(src->name+0) == *(int*)(dst->name+0)) &&
	       (*(int*)(src->name+4) == *(int*)(dst->name+4));
}

///////////////////////////////////////////////////////////////////////
int  match_version_major(struct export *src, struct export *dst){
	return ((src->version>>8) - (dst->version>>8));
}

///////////////////////////////////////////////////////////////////////
int  match_version_minor(struct export *src, struct export *dst){
	return ((unsigned char)src->version - (unsigned char)dst->version);
}

///////////////////////////////////////////////////////////////////////
int  fix_imports(struct import *imp, struct export *exp){
    func	*ef;
    struct func_stub *fs;
    int  count=0, ordinal;

	for (ef=exp->func; *ef; ef++){
		count++;		//count number of exported functions
	}
	_dprintf("%s (%d functions)\n", __FUNCTION__, count);
	
	for (fs=imp->func; fs->jr_ra; fs++) {
		if ((fs->addiu0 >> 26) != INS_ADDIU)	break;

		ordinal = fs->addiu0 & 0xFFFF;
		if (ordinal < count) {
//			_dprintf("%s linking ordinal %d to %x\n", __FUNCTION__, ordinal, exp->func[ordinal]);
			fs->jr_ra=(((u32)exp->func[ordinal]>>2) & 0x3FFFFFF) | INS_J;
		} else {
			fs->jr_ra=INS_JR_RA;
		}
	}

	imp->flags |=FLAG_IMPORT_QUEUED;
	return 0;
}

///////////////////////////////////////////////////////////////////////
// Check the structure of the import table.
// Return 0 if a bad or empty import table is detected.
// Return a non-zero value if a valid import table is detected.
//
int check_import_table(struct import* imp){
    struct func_stub *f;

	if (imp->magic != IMPORT_MAGIC)
		return 0;
	for (f=imp->func; f->jr_ra; f++){
		if (f->addiu0 >> 26 != INS_ADDIU)
			return 0;
		if ((f->jr_ra!=INS_JR_RA) && (f->jr_ra>26!=INS_JR))
			return 0;
	}
	if (f->addiu0)
		return 0;
	return (imp->func < f);
}

///////////////////////////////////////////////////////////////////////[OK]

// Return 0 if successful
int  link_client(struct import *imp){
	struct export *e;

//	_dprintf("%s\n", __FUNCTION__);
	for (e=lc_internals.let_next; e; e=(struct export*)e->magic_link) {
		if (debug > 0){
			// Zero terminate the name before printing it
			char ename[9], iname[9];
			*(int*)ename = *(int*)e->name;   *(int*)(ename+4) = *(int*)(e->name+4);  ename[8] = 0;
			*(int*)iname = *(int*)imp->name; *(int*)(iname+4) = *(int*)(imp->name+4);iname[8] = 0;
			
			//__printf("loadcore: %s: %s, %s\n", __FUNCTION__, ename, iname);
		}

		if (!(e->flags & FLAG_NO_AUTO_LINK)){	
            if ( match_name(e, (struct export*)imp)){
		        if (match_version_major(e, (struct export*)imp)==0) {
					fix_imports(imp, e);
					imp->next=(struct import*)e->next;
					e->next=(struct export*)imp;
					FlushIcache();
					return 0;
				} else {
//					_dprintf("%s: version does not match\n", __FUNCTION__);					
				}
			} else {
//				_dprintf("%s: name does not match\n", __FUNCTION__);	
			}
		} else {
//			_dprintf("%s: e->flags bit 0 is 0\n", __FUNCTION__);	
		}
	}
	_dprintf("%s: FAILED to find a match\n", __FUNCTION__);
	return -1;
}

///////////////////////////////////////////////////////////////////////[OK]
int  _LinkImports(u32 *addr, int size)
{
	struct import *p;
	int i;

	_dprintf("%s: %x, %d\n", __FUNCTION__, addr, size);
	for (i=0; i<size/4; i++, addr++) {
		p = (struct import *)addr;
		if ((p->magic == IMPORT_MAGIC) &&
			check_import_table(p) &&
		    ((p->flags & 7) == 0) &&
		    link_client(p)) {
			_UnlinkImports(p, size);
			return -1;
		}
	}
	return 0;
}

///////////////////////////////////////////////////////////////////////[OK]
int  unlink_client(struct import *i1, struct import *i2){
	struct import *i, *tmp;

	if (i1->next == i2){
		i1->next=i2->next;
		return 0;
	}
	for (i = i1->next; i->next;) {
		tmp=i->next;
		if (tmp==i2) {
			i->next=tmp->next;
			tmp->next=NULL;
			return 0;
		}
		i=tmp;
	}
	return -1;
}

///////////////////////////////////////////////////////////////////////[OK]
void restore_imports(struct import* imp){
    struct func_stub *f;

	for (f=imp->func; (f->jr_ra) && ((f->addiu0 >> 26) == INS_ADDIU); f++)
		f->jr_ra=INS_JR_RA;
}

///////////////////////////////////////////////////////////////////////[OK]
int  _UnlinkImports(void *addr, int size)
{
    struct export *e;
    struct export *i;
    struct export *tmp;
    void 		*limit = addr + (size & ~0x3);
	
	for (e = (struct export*)lc_internals.let_next; e; e=(struct export*)e->magic_link){
		for (i = e->next; i; i=i->next) {
			if (((u32)i >= (u32)addr) && ((u32)i < (u32)limit)) {
				if (unlink_client((struct import*)e, (struct import*)i))
					return -1;
				i->flags &= ~0x7;
				restore_imports((struct import*)i);
			}
		}
		if (((u32)e >= (u32)addr) && ((u32)e < (u32)limit))
			ReleaseLibraryEntries(e);
	}
/*
	for (i=let.mda; i->next; )
		if ((i->next >= addr) && (i->next < limit)){
			i->next->flags &= ~0x7;
			restore_imports(i->next);
			tmp = i->next->next;
			i->next->next=NULL;
			i->next=tmp;
		}else
			i=i->next;
*/
	return 0;
}

///////////////////////////////////////////////////////////////////////
int  SetNonAutoLinkFlag(struct export *e)
{
	return (e->flags |= FLAG_NO_AUTO_LINK);
}

///////////////////////////////////////////////////////////////////////
int  UnsetNonAutoLinkFlag(struct export *e)
{
	return (e->flags &= ~FLAG_NO_AUTO_LINK);
}

///////////////////////////////////////////////////////////////////////
int  _RegisterBootupCBFunc(int (*function)(int *, int), int priority, int *result)
{
    int x;
    register int r;
	if (place==NULL){
		x=1;
		r=function(&x, 0);
		if (result)	*result=r;
		return 0;
	}

    __asm__("move %0, $gp\n" : "=r"(x) : );

	place[0]=(u32)function + (priority & 3);
	place[1]=x;
	place[2]=0;
	place+=2;
	return 1;
}

///////////////////////////////////////////////////////////////////////
void _LinkModule(imageInfo *ii)
{
    imageInfo* p;
	for (p=lc_internals.image_info; p->next && (p->next < (u32)ii); p=(imageInfo*)p->next);

	ii->next=p->next;	
	p->next=(u32)ii;

	ii->modid=module_index++;
	modules_count++;
}

///////////////////////////////////////////////////////////////////////
void _UnlinkModule(imageInfo *ii)
{
     imageInfo *p;
	if (ii)
		for (p=lc_internals.image_info; p->next; p=(imageInfo*)p->next)
			if (p->next == (u32)ii){
				p->next=((imageInfo*)p->next)->next;
				modules_count--;
				return;
			}
}

u32  _FindImageInfo(void *addr)
{
	register imageInfo *ii;
	for (ii=lc_internals.image_info; ii; ii=(imageInfo*)ii->next)
		if(((u32)addr>=ii->p1_vaddr) && 
		   ((u32)addr <ii->p1_vaddr + ii->text_size + ii->data_size + ii->bss_size))
			return (u32)ii;
	return 0;
}

///////////////////////////////////////////////////////////////////////
int RegisterLibraryEntries(struct export *es){
    struct export *p;
	struct export *plast;
	struct export *pnext;
	struct export *tmp;
	struct export *snext;

	if ((es == NULL) || (es->magic_link != EXPORT_MAGIC))
		return -1;

	if (debug > 0){
		// Zero terminate the name before printing it
		char ename[9];
		*(int*)ename = *(int*)es->name;
		*(int*)(ename+4) = *(int*)(es->name+4);
		ename[8] = 0;
		
		__printf("loadcore: %s (%x): %s, %x\n", __FUNCTION__, es, es->name, es->version);
	}
		
		
	plast=NULL;
	for (p=(struct export*)lc_internals.let_next; p; p=(struct export*)p->magic_link) {
		if (match_name(es, p) == 0 ||
			match_version_major(es, p) == 0) continue;

		if (match_version_minor(es, p) == 0)
			return -1;
		_dprintf("%s: found match\n", __FUNCTION__);
		pnext = p->next;
		p->next = NULL;
			
		for (tmp = pnext; tmp; ) {
			if (tmp->flags & FLAG_NO_AUTO_LINK) {
				pnext->magic_link = (u32)tmp;
				pnext = tmp->next;
				tmp->next = NULL;
			} else {
				tmp->next=plast;
				plast=tmp;
			}
			tmp=tmp->next;
		}
	}

    if( lc_internals.mda_next ) {
        for (tmp = lc_internals.mda_next; tmp->next; tmp=tmp->next) { //free
            if ((match_name(es, tmp->next)) &&
                (match_version_major(es, tmp->next)==0)){
                _dprintf("%s: freeing module\n", __FUNCTION__);
                snext=tmp->next->next;
                tmp->next->next=plast;
                plast=tmp->next;
                tmp->next=snext;
            } else tmp=tmp->next;
        }
    }
    
    es->next=0;
	while (plast) {
		snext=plast->next;
		fix_imports((struct import*)plast, es);
		plast->next=es->next;
		es->next=plast;
		plast=snext;
	}
	es->flags &= ~FLAG_NO_AUTO_LINK;
	es->magic_link=(u32)lc_internals.let_next;
    lc_internals.let_next=es;
	FlushIcache();

	return 0;
}

///////////////////////////////////////////////////////////////////////
int  ReleaseLibraryEntries(struct export *e)
{
	register struct export *n, *p, *next, *prev;

	p = lc_internals.let_next;
	while ((p) && (p!=e)){
		prev=p;
		p=(struct export*)prev->magic_link;
	}
	if (p != e)	// if (0 != e)
		return -1;		//japanese BUG for e==p==0

	n               =e->next;
	e   ->next      =0;

	prev->magic_link=e->magic_link;
	e   ->magic_link=0x41C00000;

	while(n) {
		next = n->next;
        if (link_client((struct import*)n)){
			restore_imports((struct import*)n);
			n->flags=(n->flags & ~2) | 4;
			n->next = lc_internals.mda_prev;
			lc_internals.mda_prev=n;
		}
		n=next;
	}
    
    return 0;
}

///////////////////////////////////////////////////////////////////////
int  RegisterNonAutoLinkEntries(struct export *e)
{
	if ((e == NULL) || (e->magic_link != EXPORT_MAGIC)){
		return -1;
	}
	e->flags |= FLAG_NO_AUTO_LINK;
	e->magic_link = (u32)lc_internals.let_next;	// --add as first
	lc_internals.let_next = e;				// /
	FlushIcache();

	return 0;
}

///////////////////////////////////////////////////////////////////////
int  QueryLibraryEntryTable(struct export *e)
{
	struct export *p = lc_internals.let_next;
	while (p){
		if ((match_name(p, e)) && (match_version_major(p, e)==0)){
			return (int)p->func;
		}
		p=(struct export*)p->magic_link;
	}
	return 0;
}

///////////////////////////////////////////////////////////////////////
struct tag_LC_internals* GetLibraryEntryTable(){
	return &lc_internals;
}

///////////////////////////////////////////////////////////////////////
void _FlushIcache() {
	u32 status;
	u32 s1450;
	u32 s1578;
	u32 icache;
	u32 *p;

	__asm__ ("mfc0 %0, $12\n" : "=r"(status) : );

	__asm__ ("mtc0 %0, $12\n" :: "r"(0));

	s1450 = *(int*)0xBF801450;
	*(int*)0xBF801450&= ~1;
	*(int*)0xBF801450;

	s1578 = *(int*)0xBF801578;
	*(int*)0xBF801578 = 0;
	*(int*)0xBF801578;

	icache = *(int*)0xFFFE0130;
	*(int*)0xFFFE0130 = 0xC04;
	*(int*)0xFFFE0130;

	__asm__ ("mtc0 %0, $12\n" :: "r"(0x10000));

	for (p=0; p<(u32*)0x400; p+=4)	// 4KB instruction cache
		*p=0;

	__asm__ ("mtc0 %0, $12\n" :: "r"(0));

	*(int*)0xFFFE0130 = icache;
	*(int*)0xFFFE0130;
	*(int*)0xBF801578 = s1578;
	*(int*)0xBF801578;
	*(int*)0xBF801450 = s1450;
	*(int*)0xBF801450;

	__asm__ ("mtc0 %0, $12\n" : : "r"(status) );
}

void FlushIcache() {
	__asm__ (
		"la   $26, %0\n"
		"lui  $27, 0xA000\n"
		"or   $26, $27\n"
		"jr   $26\n"
		"nop\n"
		: : "i"(_FlushIcache)
	);
}

///////////////////////////////////////////////////////////////////////
void _FlushDcache() {
	u32 status;
	u32 s1450;
	u32 s1578;
	u32 icache;
	u32 *p;

	__asm__ ("mfc0 %0, $12\n" : "=r"(status) : );

	__asm__ ("mtc0 %0, $12\n" :: "r"(0));

	s1450 = *(int*)0xBF801450;
	*(int*)0xBF801450&= ~1;
	*(int*)0xBF801450;

	s1578 = *(int*)0xBF801578;
	*(int*)0xBF801578 = 0;
	*(int*)0xBF801578;

	icache = *(int*)0xFFFE0130;
	*(int*)0xFFFE0130 = 0xC4;
	*(int*)0xFFFE0130;

	__asm__ ("mtc0 %0, $12\n" :: "r"(0x10000));

	for (p=0; p<(u32*)0x100; p+=4)	// 1KB data cache
		*p=0;

	__asm__ ("mtc0 %0, $12\n" :: "r"(0));

	*(int*)0xFFFE0130 = icache;
	*(int*)0xFFFE0130;
	*(int*)0xBF801578 = s1578;
	*(int*)0xBF801578;
	*(int*)0xBF801450 = s1450;
	*(int*)0xBF801450;

	__asm__ ("mtc0 %0, $12\n" : : "r"(status) );
}

void FlushDcache(){
	__asm__ (
		"la   $26, %0\n"
		"lui  $27, 0xA000\n"
		"or   $26, $27\n"
		"jr   $26\n"
		"nop\n"
		: : "i"(_FlushDcache)
	);
}

///////////////////////////////////////////////////////////////////////
void _SetIcache(u32 val) {
	u32 status;

	__asm__ ("mfc0 %0, $12\n" : "=r"(status) : );

	__asm__ ("mtc0 %0, $12\n" :: "r"(0));

	*(int*)0xFFFE0130 = val;
	*(int*)0xFFFE0130;

	__asm__ ("mtc0 %0, $12\n" : : "r"(status) );
}

void _SetCacheCtrl(u32 val)
{
	__asm__ (
		"la   $26, %0\n"
		"lui  $27, 0xA000\n"
		"or   $26, $27\n"
		"jr   $26\n"
		"nop\n"
		: : "i"(_SetIcache)
	);
}

///////////////////////////////////////////////////////////////////////
int  _ReadModuleHeader(void *image, fileInfo *result)
{
	COFF_HEADER *coffhdr = image;
	COFF_scnhdr *section = (COFF_scnhdr*)((char*)image+sizeof(COFF_HEADER));//0x4C

	if ((coffhdr->f_magic==0x162) &&		//COFF loading
	    (coffhdr->opthdr.magic == 0x107) &&
	    (coffhdr->f_nscns < 32) &&
	    ((coffhdr->f_opthdr & 0x2FFFF) == 0x20038) &&
	    (section->s_paddr == coffhdr->opthdr.text_start)){

		if (coffhdr->opthdr.vstamp == 0x7001)
			return -1;
		result->type	=1;
		result->entry	=coffhdr->opthdr.entry;
		result->gp_value	=coffhdr->opthdr.gp_value;
		result->p1_vaddr	=coffhdr->opthdr.text_start;
		result->text_size	=coffhdr->opthdr.tsize;
		result->data_size	=coffhdr->opthdr.dsize;
		result->bss_size	=coffhdr->opthdr.bsize;
		result->p1_memsz	=coffhdr->opthdr.bss_start+coffhdr->opthdr.bsize-coffhdr->opthdr.text_start;
		result->moduleinfo	=(moduleInfo*)coffhdr->opthdr.moduleinfo;
		return result->type;
	}else{
        ELF_HEADER* eh = (ELF_HEADER*)image;
		ELF_PHR* ph= (ELF_PHR*)((char*)eh +eh->e_phoff);
		//if ((eh->e_ident[EI_CLASS] != ELFCLASS32) ||
        //(eh->e_ident[EI_DATA]) != ELFDATA2LSB))	return -1;//break
        if( *(u16*)(eh->e_ident+4) != 0x101 )
            return -1;
		if (eh->e_machine != EM_MIPS)
            return -1;//break
		if (eh->e_phentsize != sizeof(ELF_PHR))
            return -1;//break
		if (eh->e_phnum != 2)
            return -1;//break
		if (ph[0].p_type != PT_SCE_IOPMOD)
            return -1;//break
		if (eh->e_type != ET_SCE_IOPRELEXEC){
			if (eh->e_type != eh->e_phnum )//ET_EXEC)
                return -1;//only
			result->type=3;
		}else
			result->type=4;
		ELF_IOPMOD* im= (ELF_IOPMOD*)((char*)image + ph[0].p_offset);
		result->entry	=im->entry;
		result->gp_value=im->gp_value;
		result->p1_vaddr=ph[1].p_vaddr;
		result->text_size=im->text_size;
		result->data_size=im->data_size;
		result->bss_size=im->bss_size;
		result->p1_memsz=ph[1].p_memsz;
		result->moduleinfo=( moduleInfo*)im->moduleinfo;
		return result->type;
	}
	return result->type=-1;
}

#define MODULE_TYPE_COFF	1
#define MODULE_TYPE_2		2
#define MODULE_TYPE_EXEC	3
#define MODULE_TYPE_IOPRELEXEC	4

///////////////////////////////////////////////////////////////////////
void setImageInfo(fileInfo *fi, imageInfo *ii)
{
	ii->next	=0;
	ii->name	=NULL;
	ii->version	=0;
	ii->flags	=0;
	ii->modid	=0;
	if ((int)fi->moduleinfo != -1){
		ii->name	=fi->moduleinfo->name;
		ii->version	=fi->moduleinfo->version;
	}
	ii->entry	=fi->entry;
	ii->gp_value	=fi->gp_value;
	ii->p1_vaddr	=fi->p1_vaddr;
	ii->text_size	=fi->text_size;
	ii->data_size	=fi->data_size;
	ii->bss_size	=fi->bss_size;
}

///////////////////////////////////////////////////////////////////////
void load_type_1(COFF_HEADER *image){
	SHDR* s0=(SHDR*)( (char*)image + *(int*)((char*)image+0x60) );
	lc_memcpy(s0, image->opthdr.text_start, image->opthdr.tsize);
	lc_memcpy((char*)s0+image->opthdr.tsize, image->opthdr.data_start, image->opthdr.dsize);
	if (image->opthdr.bss_start && image->opthdr.bsize)
		lc_zeromem((void*)image->opthdr.bss_start, image->opthdr.bsize/4);
}

///////////////////////////////////////////////////////////////////////
void load_type_3(void *image){
	ELF_PHR *ph=(ELF_PHR*)((char*)image+((ELF_HEADER*)image)->e_phoff);
	lc_memcpy(image+ph[1].p_offset, ph[1].p_vaddr, ph[1].p_filesz);
	if (ph[1].p_filesz < ph[1].p_memsz)
		lc_zeromem(ph[1].p_vaddr+ph[1].p_filesz,
			  (ph[1].p_memsz-ph[1].p_filesz)/4);
}

///////////////////////////////////////////////////////////////////////
#define R_MIPS_32 2
#define R_MIPS_26 4
#define R_MIPS_HI16 5
#define R_MIPS_LO16 6

void load_type_4(ELF_HEADER *image, fileInfo *fi)
{
    ELF_PHR *ph=(ELF_PHR*)((char*)image+image->e_phoff);
    ELF_SHR* sh = (ELF_SHR*)((char*)image+image->e_shoff);
    ELF_REL* rel;
    int i,j,scount;
    u32* b, *b2, tmp;

    //ph[0] - .iopmod, skip
    fi->entry	+= fi->p1_vaddr;
    fi->gp_value	+= fi->p1_vaddr;
    if ((int)fi->moduleinfo != -1)
        fi->moduleinfo = (moduleInfo*)((int)fi->moduleinfo + fi->p1_vaddr);
    lc_memcpy((char*)image+ph[1].p_offset, fi->p1_vaddr, ph[1].p_filesz);

	if (ph[1].p_filesz < ph[1].p_memsz) {
        lc_zeromem(ph[1].p_vaddr+ph[1].p_filesz+fi->p1_vaddr, (ph[1].p_memsz-ph[1].p_filesz));
    }
    
	for (i=1; i<image->e_shnum; i++) {
		if (sh[i].sh_type==SHT_REL) {
            ELF_REL* rel =(ELF_REL*)(sh[i].sh_offset + (char*)image);
            scount=sh[i].sh_size / sh[i].sh_entsize;
			for (j=0; j<scount; j++) {
				b=(u32*)(fi->p1_vaddr + rel[j].r_offset);
				switch((u8)rel[j].r_info){
				case R_MIPS_LO16:
					*b=(*b & 0xFFFF0000) | (((*b & 0x0000FFFF) + fi->p1_vaddr) & 0xFFFF);
					break;
				case R_MIPS_32:
					*b+=fi->p1_vaddr;
					break;
				case R_MIPS_26:
                    *b = (*b & 0xfc000000) | (((*b & 0x03ffffff) + (fi->p1_vaddr >> 2)) & 0x03ffffff);
					break;
				case R_MIPS_HI16:
                    b2  =(u32*)(rel[j+1].r_offset + fi->p1_vaddr);
                    ++j;

                    tmp = (*b << 16) + (int)(*(s16*)b2) + fi->p1_vaddr;
                    *b = (*b&0xffff0000) | ((((tmp>>15)+1)>>1)&0xffff);
                    *b2 = (*b2&0xffff0000) | (tmp&0xffff);
					break;
				}
			}
        }
    }
}

int  _LoadModule(void *image, fileInfo *fi)
{
    u32* ptr;
    int i;
	switch (fi->type){
	case MODULE_TYPE_COFF:		load_type_1(image);	break;
	case MODULE_TYPE_EXEC:		load_type_3(image);	break;
	case MODULE_TYPE_IOPRELEXEC:   	load_type_4(image, fi);	break;
	default:	return -1;
	}

	setImageInfo(fi, (imageInfo*)(fi->p1_vaddr-0x30));

	return 0;
}

///////////////////////////////////////////////////////////////////////[OK]
int lc_memcpy(void *src,void *dest,int len){
    char* _src = (char*)src;
    char* _dst = (char*)dest;
	for (len=len; len>0; len--)
        *_dst++=*_src++;
}

///////////////////////////////////////////////////////////////////////[OK]
int lc_zeromem(void *addr,int len){
	for (; len>0; len--)	*(char*)addr++=0;
}

int  lc_strlen(char *s)
{
    int len;
	if (s==NULL)	return 0;
	for (len=0; *s++; len++);
	return len;
}

int recursive_set_a2(int* start, int* end, int val)
{
    *start++ = val;
    if( start < end )
        return recursive_set_a2(start, end, val);
    return 0;
}

void*  lc_memcpy_overlapping(char *dst,char *src,int len){
	if (dst==NULL)	return 0;

	if (dst>=src)
		while(--len>=0)
			*(dst+len)=*(src+len);
	else
		while (len-->0)
			*dst++=*src++;

	return dst;
}

//////////////////////////////entrypoint///////////////////////////////
void _start(BOOT_PARAMS *init) {
	*(int*)0xFFFE0130 = 0x1e988;
	__asm__ (
		"addiu $26, $0, 0\n"
		"mtc0  $26, $12\n");
		//"move  $fp, %0\n"
		//"move  $sp, %0\n"
		//: : "r"((init->ramMBSize << 20) - 0x40));
	__asm__ (
		"j     loadcore_start\n"
	);
}

extern void* _ftext, *_etext, *_end;
typedef int (*IopEntryFn)(u32,void*,void*,void*);

void loadcore_start(BOOT_PARAMS *pInitParams)
{
    fileInfo fi;
	void (*entry)();
	u32 offset;
	u32 status = 0x401;
    int bm;
	int i;
    void** s0; // pointer in module list to current module?
    BOOT_PARAMS params;
    u32 s1, s2, s3, sp, a2;

	_dprintf("%s\n", __FUNCTION__);

	// Write 0x401 into the co-processor status register?
	// This enables interrupts generally, and disables (masks) them all except hardware interrupt 0?

    lc_memcpy(pInitParams,&params,sizeof(BOOT_PARAMS));
	BOOTMODE_END = BOOTMODE_START = bootmodes;

    //_dprintf("module: %x\n", params.firstModuleAddr);
    
	lc_internals.let_next = (struct export*)params.firstModuleAddr;
	lc_internals.let_prev = (struct export*)params.firstModuleAddr;
	lc_internals.let_next->next = 0;
	lc_internals.module_count = 2;			// SYSMEM + LOADCORE
	lc_internals.mda_prev = lc_internals.mda_next = NULL;
	lc_internals.module_index = 3;			// next available index

	for (i=0; i<17; i++){
		bootmodes[i]=0;
	}
	bootmodes_size=0;

	bm = params.bootInfo | 0x00040000;
	RegisterBootMode((struct bootmode*)&bm);

    lc_internals.image_info = (imageInfo*)((u32)lc_internals.let_prev - 0x30);
	lc_internals.image_info->modid = 1;		// SYSMEM is the first module
	lc_internals.image_info->next = (u32)&_ftext - 0x30;
	((imageInfo*)lc_internals.image_info->next)->modid = 2;	// LOADCORE is the second module
    
	// find & fix LOADCORE imports (to SYSMEM)
	_LinkImports((u32*)&_ftext, (u32)&_etext - (u32)&_ftext);

	RegisterLibraryEntries(&loadcore_stub);

    // reserve LOADCORE memory
	AllocSysMemory(2, (u32)((u32)&_end - (((u32)&_ftext - 0x30) >> 8 << 8)), (void*)((((u32)&_ftext - 0x30) >> 8 << 8) & 0x1FFFFFFF));

	if (params.pos)
		params.pos = (u32)AllocSysMemory(2, params.size, (void*)params.pos);
        
	sp=(u32)alloca(0x10);
	s0 = (void**)((sp - 0xDF0) & 0x1FFFFF00);//=0x001ff100
	recursive_set_a2((int*)s0, (void*)(sp+0x10), 0x11111111);
	if ((u32)s0 < QueryMemSize())
		AllocSysMemory(2, QueryMemSize() - (u32)s0, s0);

	if (params.udnlString){
        int v0 = lc_strlen(params.udnlString);
		int* v1 = (int*)alloca((v0 + 8 + 8) >> 3 << 3);
		lc_memcpy_overlapping((char*)&v1[6], params.udnlString, v0+1);
		params.udnlString = (char*)&v1[6];
		v1[4] = 0x01050000;
		v1[5] = (int)&v1[6];
		RegisterBootMode((struct bootmode*)&v1[4]);	// BTUPDATER bootmode 5
	}

	a2 = (params.numConfLines+1) * 4;
	s0 = alloca((a2 + 7) >> 3 << 3) + 0x10;
	lc_memcpy_overlapping((char*)s0, (char*)params.moduleAddrs, a2);	//0x30020

	s1 = 0;
	params.moduleAddrs = (u32**)s0;
	s2 = (u32)alloca(params.numConfLines << 3) + 0x10;
    place = (u32*)s2;
	*place = 0;
	i = -1;
    s3 = 1;

	_dprintf("loading modules: %d\n", params.numConfLines);
    s0 += 2; // skip first two: SYSMEM, LOADCORE
    for (; *s0; s0+=1) {
		if ((u32)*s0 & 1){
			if (((u32)*s0 & 0xF) == s3)
				s1 = (u32)*s0>>2;
		}else{
			i += 1;
			i &= 0xF;

            _dprintf("load module from %x\n", *s0);
			switch(_ReadModuleHeader(*s0, &fi)){
			case MODULE_TYPE_COFF:
			case MODULE_TYPE_EXEC:
				a2 = ((fi.p1_vaddr - 0x30) >> 8 << 8) & 0x1FFFFFFF;
				if (NULL == AllocSysMemory(2, fi.p1_memsz + fi.p1_vaddr - a2, (void*)a2))
					goto HALT;
				break;

			case MODULE_TYPE_2:
			case MODULE_TYPE_IOPRELEXEC:
				if (fi.p1_vaddr = (u32)((s1 == 0) ?
						AllocSysMemory(0, fi.p1_memsz+0x30, 0) : 
                                   AllocSysMemory(2, fi.p1_memsz+0x30, (void*)s1)) )
					fi.p1_vaddr += 0x30;
				else
					goto HALT;
				break;

			default:
                _dprintf("could not find module at %x\n", *s0);
				goto HALT;
			}

			_LoadModule(*s0, &fi);
            _dprintf("loading module %s: at offset %x, memsz=%x, type=%x, entry=%x\n", fi.moduleinfo->name, fi.p1_vaddr, fi.p1_memsz, fi.type, fi.entry);

			if (0 == _LinkImports((u32*)fi.p1_vaddr, fi.text_size)) {
                
				FlushIcache();
				__asm__("move $gp, %0\n" : : "r"(fi.gp_value));
                // call the entry point
				s1 = ((IopEntryFn)fi.entry)(0, NULL, s0, NULL);
                
				if ((s1 & 3) == 0){
					_LinkModule((imageInfo*)(fi.p1_vaddr - 0x30));
					if (s1 & ~3)
						_RegisterBootupCBFunc((int (*)(int *, int))(s1 & ~3), BOOTUPCB_NORMAL, 0);
				}else{
					_UnlinkImports((void*)fi.p1_vaddr, fi.text_size);
					FreeSysMemory((void*)((fi.p1_vaddr - 0x30) >> 8 << 8));
				}
			}else {
				FreeSysMemory((void*)((fi.p1_vaddr - 0x30) >> 8 << 8));
            }
            
			s1 = 0;
		}
    }
    
	if (params.pos)
		FreeSysMemory((void*)params.pos);

	for (i=BOOTUPCB_FIRST; i<BOOTUPCB_PRIORITIES; i++){
		if (i == BOOTUPCB_LAST)
			place = 0;

		for (s0=(void**)s2; *s0; s0+=2)
			if (((u32)*s0 & 3) == i){
                __asm__("move $gp, %0\n" : : "r"(s0[1]));
				((int (*)(int *, int))((u32)*s0 & ~3))(((u32)*s0 & 3) == BOOTUPCB_LAST ? (int*)&s0[2] : (int*)s0, 1);
			}

		if (i == BOOTUPCB_NORMAL){
			while ((u32)s2 < (u32)s0){
				s0-=2;	// -8 bytes
				if (((u32)*s0 & 3) == BOOTUPCB_LAST)
					break;
				*s0 = 0;
			}
		}
	}

HALT:
	*(char*)0x80000000 = 2;
	goto HALT;
}

