/*
 *	cxmb.c is part of cxmb
 *	Copyright (C) 2008  Poison
 *
 *	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 3 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, see <http://www.gnu.org/licenses/>.
 */

/*
 *	Description:	
 *	Author:			Poison <hbpoison@gmail.com>
 *	Date Created:	2008-03-01
 */

#include <pspkernel.h>
#include <psputilsforkernel.h>
#include <pspsysmem_kernel.h>
#include <psploadcore.h>
#include <pspsdk.h>

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

PSP_MODULE_INFO("cxmb", 0x1000, 1, 1);
PSP_MAIN_THREAD_ATTR(0);

#if _CXMB_LITE == 0
#define CXMB_MKCTF_BUF_SIZE	0x003C0000
#endif

#define printf				printlog
#define CXMB_THEME_FILE 	"ms0:/PSP/THEME/Default.ctf"
#define CXMB_THEME_FILE_C	"/PSP/THEME/Default.ctf"
#define CXMB_SUPPORT_FOLDER	"ms0:/cxmb/support"

typedef struct tSceModule {
	struct tSceModule	*next;
	unsigned short		attribute;
	unsigned char		version[2];
	char				modname[27];
	char				terminal;
	unsigned int		unknown1;
	unsigned int		unknown2;
	int					modid;
	unsigned int		unknown3[4];
	void *				ent_top;
	unsigned int		ent_size;
	void *				stub_top;
	unsigned int		stub_size;
	unsigned int		unknown4[5];
	unsigned int		entry_addr;
	unsigned int		gp_value;
	unsigned int		text_addr;
	unsigned int		text_size;
	unsigned int		data_size;
	unsigned int		bss_size;
	unsigned int		nsegment;
	unsigned int		segmentaddr[4];
	unsigned int		segmentsize[4];
} tSceModule;

typedef struct CtfHeader {
	char name[64];
	unsigned int start;
	unsigned int size;
} __attribute__((packed)) CtfHeader;

typedef struct CtfHandler {
	void * arg;
	int num;
	int offset;
} CtfHandler;

#if _PSP_FW_VERSION == 390
static unsigned int cxmb_magic = 0xDEAD0390;	//cxmb version magic
#endif
#if _PSP_FW_VERSION == 371
static unsigned int cxmb_magic = 0xDEAD0371;	//cxmb version magic
#endif
static unsigned int ctf_sig = 0;
static unsigned int header_size = 0;
static unsigned int handler_count = 0;
static CtfHeader * ctf_header = NULL;
static CtfHandler * ctf_handler = NULL;
static int mem_id = -1;
static PspIoDrv * lflash_drv;
static PspIoDrv * fatms_drv;
static PspIoDrvArg * ms_drv = NULL;

typedef int (* StartModuleHandler)(tSceModule *);

StartModuleHandler ( * setStartModuleHandler )( StartModuleHandler );
StartModuleHandler previous = NULL;

#if _DEBUG >= 1
char lstr[128];
int printlog( const char * str )
{
	int fd = sceIoOpen( "ms0:/log.txt", PSP_O_RDWR | PSP_O_CREAT | PSP_O_APPEND, 0777 );
	sceIoWrite( fd, str, strlen( str ) );
	sceIoClose( fd );
	return 0;
}
#endif

#if _DEBUG >= 2
int printFileArg( PspIoDrvFileArg * arg )
{
	sprintf( lstr, "unk1: %08x\nfs_num: %08x\ndrv: %08x\nunk2: %08x\narg: %08x\n",
		( unsigned int )arg->unk1, ( unsigned int )arg->fs_num, ( unsigned int )arg->drv, ( unsigned int )arg->unk2, ( unsigned int )arg->arg );
	printf( lstr );
	if ( !arg->arg )
		return 0;
	unsigned int * tmp = arg->arg;
	sprintf( lstr, "arg0: %08x\narg1: %08x\narg2: %08x\narg3: %08x\n",
	tmp[0], tmp[1], tmp[2], tmp[3] );
	printf( lstr);
	return 0;
}
#endif

static int cmpistr( const char * str1, const char * str2 )
{
	for( ; *str1 && *str2; str1 ++, str2 ++ )
	{
		if ( tolower( *str1 ) != tolower( *str2 ) )
			return 1;
	}
	if ( *str1 != *str2 )
		return 1;
	return 0;
}

static int inCtf( const char * file )
{
	int i;
	for ( i = 0; i < header_size; i ++ )
	{
		if ( !cmpistr( file, ctf_header[i].name ) )
			return i;
	}
	return -1;
}

static int isRedirected( PspIoDrvFileArg * arg )
{
	int i;
	for( i = 0; i < handler_count; i ++ )
	{
		if( arg->arg == ctf_handler[i].arg )
			return i;
	}
	return -1;
}

PspIoDrv * findDriver( char * drvname )
{
	int k1 = pspSdkSetK1( 0 );
	tSceModule * mod_iofilemgr = ( tSceModule * )sceKernelFindModuleByName( "sceIOFileManager" );
	if ( !mod_iofilemgr )
	{
		pspSdkSetK1( k1 );
		return NULL;
	}
	unsigned int text_addr = mod_iofilemgr->text_addr;
	#if _PSP_FW_VERSION == 390
	unsigned int * ( * getDevice )( char * ) = ( void * )( text_addr + 0x2808 ); // 0x2800 for FW_VERSION <= 371
	#endif
	#if _PSP_FW_VERSION == 371
	unsigned int * ( * getDevice )( char * ) = ( void * )( text_addr + 0x2844 );
	#endif
	unsigned int *u;
	u = getDevice( drvname );
	if ( !u )
	{
		pspSdkSetK1( k1 );
		return NULL;
	}
	pspSdkSetK1( k1 );
	#if _DEBUG >= 1
	sprintf( lstr, "%s found!\nu0: %08x\nu1: %08x\nu2: %08x\nu3: %08x\n",
		drvname, u[0], u[1], u[2], u[3] );
	printf( lstr );
	#endif
	return ( PspIoDrv * )u[1];
}

unsigned int * findExport( const char * szMod, const char * szLib, unsigned int nid )
{
	SceLibraryEntryTable *entry;
	tSceModule *pMod;
	void *entTab;
	int entLen;
	pMod = ( tSceModule * )sceKernelFindModuleByName( szMod );
	if ( !pMod )
	{
		return 0;
	}
	int i = 0;
	entTab = pMod->ent_top;
	entLen = pMod->ent_size;
	while( i < entLen )
	{
		int count;
		int total;
		unsigned int *vars;

		entry = ( SceLibraryEntryTable * )( entTab + i );

		if( entry->libname && !strcmp( entry->libname, szLib ) )
		{
			total = entry->stubcount + entry->vstubcount;
			vars = entry->entrytable;

			if( entry->stubcount > 0 )
			{
				for( count = 0; count < entry->stubcount; count ++ )
				{
					if ( vars[count] == nid )
					{
						#if _DEBUG >= 1
						sprintf( lstr, "export %08x in %s of %s found:\n%08x\n",
							nid, szLib, szMod, ( unsigned int )&vars[count+total] );
						printf( lstr );
						#endif
						return &vars[count+total];	
					}				
				}
			}
		}

		i += ( entry->len * 4 );
	}
	return NULL;
}

unsigned int findProc( const char * szMod, const char * szLib, unsigned int nid )
{
	unsigned int * export = findExport( szMod, szLib, nid );
	if( export )
	{
		#if _DEBUG >= 1
		sprintf( lstr, "func %08x in %s of %s found:\n%08x\n",
			nid, szLib, szMod, *export );
		printf( lstr );
		#endif
		return *export;
	}
	return 0;
}

int parseDiff( const char * file, tSceModule * mod )
{
	int off = inCtf( file );
	if ( off < 0 )
	{
		#if _DEBUG >= 1
		sprintf( lstr, "there's no patch for %s\n", file );
		printf( lstr );
		#endif
		return 0;
	}
	
	int ctf = sceIoOpen( CXMB_THEME_FILE, PSP_O_RDONLY, 0644 );
	if ( ctf < 0 )
	{
		#if _DEBUG >= 1
		printf( "no ctf file found!\n" );
		#endif
		return -1;
	}
	sceIoLseek( ctf, ctf_header[off].start, PSP_SEEK_SET );
	
	#if _DEBUG >= 2
	sprintf( lstr, "ms0:/%s.ori", file );
	int fd = sceIoOpen( lstr, PSP_O_RDWR | PSP_O_CREAT | PSP_O_TRUNC, 0777 );
	sceIoWrite( fd, ( void * )mod->text_addr, mod->text_size );
	sceIoClose( fd );
	#endif
	
	#if _DEBUG >= 1
	sprintf( lstr, "patch %s!\nstart: %08x\nsize: %08x\n", file, ctf_header[off].start, ctf_header[off].size );
	printf( lstr );
	#endif
	
	unsigned int attr[2];
	int i = 0;
	while( i < ctf_header[off].size )
	{
		sceIoRead( ctf, attr, 8 );
		#if _DEBUG >= 2
		sprintf( lstr, "patch_offset: %08x\npatch_size: %08x\n", mod->text_addr + attr[0], attr[1] );
		printf( lstr );
		#endif
		sceIoRead( ctf, ( void * )( mod->text_addr + attr[0] ), attr[1] );
		i ++;
	}
	sceIoClose( ctf );
	
	sceKernelIcacheInvalidateAll();
	sceKernelDcacheWritebackInvalidateAll();
	
	#if _DEBUG >= 2
	sprintf( lstr, "ms0:/%s.patched", file );
	fd = sceIoOpen( lstr, PSP_O_RDWR | PSP_O_CREAT | PSP_O_TRUNC, 0777 );
	sceIoWrite( fd, ( void * )mod->text_addr, mod->text_size );
	sceIoClose( fd );
	#endif
	
	#if _DEBUG >= 1
	sprintf( lstr, "%s patched!\n", file );
	printf( lstr );
	#endif
	return 0;
}

int OnModuleStart( tSceModule * mod )
{
	#if _DEBUG >= 1
	sprintf( lstr, "on module %s start\n", mod->modname );
	printf( lstr );
	#endif
	if ( strcmp( mod->modname, "scePaf_Module" ) == 0 )
	{
		parseDiff("/vsh/module/paf.prx", mod );
	}
	else if ( strcmp( mod->modname, "sceVshCommonGui_Module" ) == 0 )
	{
		parseDiff("/vsh/module/common_gui.prx", mod );
	}
	else if ( strcmp( mod->modname, "vsh_module" ) == 0 )
	{
		parseDiff("/vsh/module/vshmain.prx", mod );
		
	}
	if ( !previous )
		return 0;
	return previous( mod );
}

void uninstall_cxmb();

int ( * msIoOpen )( PspIoDrvFileArg * arg, char * file, int flags, SceMode mode );

int msIoOpen_new( PspIoDrvFileArg * arg, char * file, int flags, SceMode mode )
{
	ms_drv = arg->drv;
	return msIoOpen( arg, file, flags, mode );
}

int ( * IoOpen )( PspIoDrvFileArg * arg, char * file, int flags, SceMode mode );
int ( * IoRead )( PspIoDrvFileArg * arg, char * data, int len );
SceOff ( * IoLseek )( PspIoDrvFileArg * arg, SceOff ofs, int whence );
int ( * IoIoctl )( PspIoDrvFileArg * arg, unsigned int cmd, void * indata, int inlen, void * outdata, int outlen );
int ( * IoClose )( PspIoDrvFileArg * arg );
int ( * IoGetstat )( PspIoDrvFileArg * arg, const char * file, SceIoStat * stat );

int IoOpen_new( PspIoDrvFileArg * arg, char * file, int flags, SceMode mode )
{
	PspIoDrvArg * drv = arg->drv;
	#if _DEBUG >= 4
	sprintf( lstr, "--------open---------\n%s\n", file );
	printf( lstr );
	printFileArg( arg );
	#endif
	if( arg->fs_num == 0 )
	{
		ctf_handler[handler_count].num = inCtf( file );
		if ( ctf_handler[handler_count].num >= 0 )
		{
			arg->drv = ms_drv;
			int ret = fatms_drv->funcs->IoOpen( arg, CXMB_THEME_FILE_C, flags, mode);
			#if _DEBUG >= 2
			sprintf( lstr, "redirect ----open---- %s\n", file );
			printf( lstr );
			printFileArg( arg );
			#endif
			if ( ret < 0 )
			{
				#if _DEBUG >= 2
				sprintf( lstr, "failed in redirect ----open---- %s\n", file );
				printf( lstr );
				#endif
				arg->drv = drv;
			}
			else
			{
				ctf_handler[handler_count].offset = fatms_drv->funcs->IoLseek( arg, ctf_header[ctf_handler[handler_count].num].start, PSP_SEEK_SET );
				ctf_handler[handler_count].arg = arg->arg;
				handler_count ++;
				if ( handler_count % 32 == 0 )
				{
					CtfHandler * tmp = sceKernelAllocHeapMemory( mem_id, sizeof( CtfHandler ) * ( handler_count + 32 ) );
					memcpy( tmp, ctf_handler, sizeof( CtfHandler ) * handler_count );
					sceKernelFreeHeapMemory( mem_id, ctf_handler );
					ctf_handler = tmp;
				}
				arg->drv = drv;
				return ret;
			}
		}
	}
	int ret = IoOpen( arg, file, flags, mode );
	#if _DEBUG >= 4
	printFileArg( arg );
	#endif
	return ret;
}

int IoReopen( PspIoDrvFileArg * arg, int * num )
{
	//sceIoOpen( "ms0:/dummy.prx", PSP_O_RDONLY, 0644 );
	unsigned int sig = 0;
	int err = 0;
	CtfHandler tmp;
	memcpy( &tmp, &ctf_handler[*num], sizeof( CtfHandler ) );
	int fd = -1, i = 0;
	for ( i = 0; fd < 0 && i < 20; i ++ )
	{
		sceKernelDelayThread( 100000 );
		fd = sceIoOpen( CXMB_THEME_FILE, PSP_O_RDONLY, 0644 );
	}
	//int fd = sceIoOpen( CXMB_THEME_FILE, PSP_O_RDONLY, 0644 );
	if ( fd >= 0 )
	{
		sceIoLseek( fd, 8, PSP_SEEK_SET );
		sceIoRead( fd, &sig, 4 );
		sceIoClose( fd );
		if ( sig == ctf_sig )
		{
			*num = isRedirected( arg );
			if ( *num >= 0 && ctf_handler[*num].num == tmp.num )
			{
				lflash_drv->funcs->IoClose( arg );
				lflash_drv->funcs->IoOpen( arg, ctf_header[tmp.num].name, PSP_O_RDONLY, 0644 );
				ctf_handler[*num].offset = tmp.offset;
				arg->drv = ms_drv;
				int ret = fatms_drv->funcs->IoLseek( arg, ctf_handler[*num].offset, PSP_SEEK_SET );
				if ( ret >= 0 )
				{
					return ret;
				}
				else
				{
					#if _DEBUG >= 1
					sprintf( lstr, "failed in seek the reopen file!\noffset: %08x\n, ret: %08x\n", tmp.offset, ret );
					#endif
					uninstall_cxmb();
					return ret;
				}
			}
			else
			{
				#if _DEBUG >= 1
				printf( "failed in reopen ctf theme file!\n" );
				#endif
				err = -2;
			}
		}
		else
		{
			#if _DEBUG >= 1
			sprintf( lstr, "ctf theme file changed!\nori %08x now: %08x\n", ctf_sig, sig );
			printf( lstr );
			#endif
			err = -3;
		}
	}
	else
	{
		#if _DEBUG >= 1
		printf( "ctf theme file removed!\n" );
		#endif
		err = -4;
	}
	lflash_drv->funcs->IoClose( arg );
	uninstall_cxmb();
	lflash_drv->funcs->IoOpen( arg, ctf_header[tmp.num].name, PSP_O_RDONLY, 0644 );
	#if _DEBUG >= 1
	printf( "uninstalled!\n" );
	#endif
	return err;
	/*if ( fd < 0 )
	{
		#if _DEBUG >= 1
		printf( "ctf theme file removed!\n" );
		#endif
		return -1;
	}
	sceIoLseek( fd, 8, PSP_SEEK_SET );
	sceIoRead( fd, &sig, 4 );
	sceIoClose( fd );
	if ( sig != ctf_sig )
	{
		#if _DEBUG >= 1
		sprintf( lstr, "ctf theme file changed!\nori %08x now: %08x\n", ctf_sig, sig );
		printf( lstr );
		#endif
		return -2;
	}
	int offset = ctf_handler[num].offset;
	lflash_drv->funcs->IoClose( arg );
	int tmp = handler_count;
	lflash_drv->funcs->IoOpen( arg, ctf_header[ctf_handler[num].num].name, PSP_O_RDONLY, 0644 );
	if ( tmp == handler_count )
	{
		#if _DEBUG >= 1
		printf( "failed in reopen ctf theme file!\n" );
		#endif
		return -3;
	}
	ctf_handler[handler_count - 1].offset = offset;
	arg->drv = ms_drv;
	int ret = fatms_drv->funcs->IoLseek( arg, offset, PSP_SEEK_SET );
	if ( ret < 0 )
	{
		#if _DEBUG >= 1
		sprintf( lstr, "failed in seek the reopen file!\noffset: %08x\n, ret: %08x\n", offset, ret );
		#endif
		return -4;
	}
	return ret;*/
}

int IoRead_new( PspIoDrvFileArg * arg, char * data, int len )
{
	#if _DEBUG >= 5
	printf( "--------read---------\n" );
	printFileArg( arg );
	#endif
	PspIoDrvArg * drv = arg->drv;
	int num = isRedirected( arg );
	if( arg->fs_num == 0 && num >= 0 )
	{
		arg->drv = ms_drv;
		int ret = fatms_drv->funcs->IoLseek( arg, 0, PSP_SEEK_CUR );
		if ( ret < 0 )
		{
			#if _DEBUG >= 1
			sprintf( lstr, "error: %08x when read %s\n", ret, ctf_header[ctf_handler[num].num].name );
			printf( lstr );
			#endif
			arg->drv = drv;
			ret = IoReopen( arg, &num );
			if ( ret < 0 )
			{
				return ret;
			}
		}
		int sub = ret + len - ctf_header[ctf_handler[num].num].start - ctf_header[ctf_handler[num].num].size;
		if ( sub <= 0 )
		{
			sub = 0;
		}
		ret =  fatms_drv->funcs->IoRead( arg, data, len - sub );
		ctf_handler[num].offset += ret;
		#if _DEBUG >= 3
		sprintf( lstr, "redirected ----read---- \nlen_ori: %d, len_mod: %d\n%08x bytes read!\n", len, len - sub, ret );
		printf( lstr );
		printFileArg( arg );
		#endif
		arg->drv = drv;
		return ret;
	}
	int ret = IoRead( arg, data, len );
	#if _DEBUG >= 5
	sprintf( lstr, "%08x bytes read!\n", ret );
	printf( lstr );
	printFileArg( arg );
	#endif
	return ret;
}

SceOff IoLseek_new( PspIoDrvFileArg * arg, SceOff ofs, int whence )
{
	#if _DEBUG >= 5
	printf( "--------seek---------\n" );
	printFileArg( arg );
	#endif
	PspIoDrvArg * drv = arg->drv;
	int num = isRedirected( arg );
	if( arg->fs_num == 0 && num >= 0 )
	{
		arg->drv = ms_drv;
		int ret = fatms_drv->funcs->IoLseek( arg, 0, PSP_SEEK_CUR );
		if ( ret < 0 )
		{
			#if _DEBUG >= 1
			sprintf( lstr, "error: %08x when seek %s\n", ret, ctf_header[ctf_handler[num].num].name );
			printf( lstr );
			#endif
			arg->drv = drv;
			ret = IoReopen( arg, &num );
			if ( ret < 0 )
			{
				return ret;
			}
		}
		switch ( whence )
		{
			case PSP_SEEK_SET:
				fatms_drv->funcs->IoLseek( arg, ctf_header[ctf_handler[num].num].start, PSP_SEEK_SET );
				break;
			case PSP_SEEK_END:
				fatms_drv->funcs->IoLseek( arg, ctf_header[ctf_handler[num].num].start + ctf_header[ctf_handler[num].num].size, PSP_SEEK_SET );
				break;
		}
		ret = fatms_drv->funcs->IoLseek( arg, ofs, PSP_SEEK_CUR );
		if ( ret < ctf_header[ctf_handler[num].num].start )
		{
			ret = fatms_drv->funcs->IoLseek( arg, ctf_header[ctf_handler[num].num].start, PSP_SEEK_SET );
		}
		else if ( ret > ( ctf_header[ctf_handler[num].num].start + ctf_header[ctf_handler[num].num].size ) )
		{
			ret = fatms_drv->funcs->IoLseek( arg, ctf_header[ctf_handler[num].num].start + ctf_header[ctf_handler[num].num].size, PSP_SEEK_SET );
		}
		ctf_handler[num].offset = ret;
		ret -= ctf_header[ctf_handler[num].num].start;
		#if _DEBUG >= 3
		sprintf( lstr, "redirected ----lseek---- \noff_mod: %08x\n", ret );
		printf( lstr );
		printFileArg( arg );
		#endif
		arg->drv = drv;
		return ret;
	}
	int ret =  IoLseek( arg, ofs, whence );
	#if _DEBUG >= 5
	sprintf( lstr, "offset: %08x\n", ret );
	printf( lstr );
	printFileArg( arg );
	#endif
	return ret;
}

int IoIoctl_new( PspIoDrvFileArg * arg, unsigned int cmd, void * indata, int inlen, void * outdata, int outlen )
{
	#if _DEBUG >= 4
	sprintf( lstr, "--------ioctl---------\ncmd: %08x\n", cmd );
	printf( lstr );
	printFileArg( arg );
	#endif
	PspIoDrvArg * drv = arg->drv;
	int num = isRedirected( arg );
	if( arg->fs_num == 0 && num >= 0 )
	{
		arg->drv = ms_drv;
		int ret = fatms_drv->funcs->IoIoctl( arg, cmd, indata, inlen, outdata, outlen );
		if ( ret < 0 )
		{
			#if _DEBUG >= 1
			sprintf( lstr, "error: %08x when ioctl cmd %08x %s\n", ret, cmd, ctf_header[ctf_handler[num].num].name );
			printf( lstr );
			#endif
			/*arg->drv = drv;
			ret = IoReopen( arg, num );
			if ( ret < 0 )
			{
				char name[128];
				strcpy( name, ctf_header[ctf_handler[num].num].name );
				uninstall_cxmb();
				if( ret != -2 )
					lflash_drv->funcs->IoOpen( arg, name, PSP_O_RDONLY, 0644 );
				return IoIoctl( arg, cmd, indata, inlen, outdata, outlen );
			}
			num = handler_count - 1;
			ret = fatms_drv->funcs->IoIoctl( arg, cmd, indata, inlen, outdata, outlen );*/
		}
		#if _DEBUG >= 2
		printf( "redirected ----ioctl---- \n" );
		printFileArg( arg );
		#endif
		arg->drv = drv;
		return ret;
	}
	int ret =  IoIoctl( arg, cmd, indata, inlen, outdata, outlen );
	#if _DEBUG >= 4
	printFileArg( arg );
	#endif
	return ret;
}

int IoClose_new( PspIoDrvFileArg * arg )
{
	#if _DEBUG >= 4
	printf( "--------close---------\n" );
	printFileArg( arg );
	#endif
	PspIoDrvArg * drv = arg->drv;
	int num = isRedirected( arg );
	if( arg->fs_num == 0 && num >= 0 )
	{
		arg->drv = ms_drv;
		handler_count --;
		memcpy( &ctf_handler[num], &ctf_handler[num + 1], sizeof( CtfHandler ) * ( handler_count - num ) );
		int ret = fatms_drv->funcs->IoClose( arg );
		#if _DEBUG >= 2
		printf( "redirected ----close---- \n" );
		printFileArg( arg );
		#endif
		arg->drv = drv;
		return ret;
	}
	arg->drv = drv;
	int ret = IoClose(arg);
	#if _DEBUG >= 4
	printFileArg( arg );
	#endif
	return ret;
}

int IoGetstat_new( PspIoDrvFileArg * arg, const char * file, SceIoStat * stat )
{
	#if _DEBUG >= 4
	sprintf( lstr, "--------getstat---------\n%s\n", file );
	printf( lstr );
	printFileArg( arg );
	#endif
	int ret = IoGetstat( arg, file, stat );
	int num = isRedirected( arg );
	if( arg->fs_num == 0 && num >= 0 )
	{
		stat->st_size = ctf_header[ctf_handler[num].num].size;
		#if _DEBUG >= 2
		sprintf( lstr, "redirected ----getstat---- %s\n", file );
		printf( lstr );
		printFileArg( arg );
		#endif
	}
	#if _DEBUG >= 4
	printFileArg( arg );
	#endif
	return ret;
}

#if _CXMB_LITE == 0
static const char * diff_files[] = {
	"paf.prx",
	"common_gui.prx",
	"vshmain.prx"
};
static int diff_count = 3;

static const char * endwithistr( const char * str1, const char * str2 )
{
	if ( !*str2 || strlen( str2 ) > strlen( str1 ) )
	{
		return NULL;
	}
	const char * ostr1, * ostr2;
	for ( ostr1 = str1 + strlen( str1 ) - 1, ostr2 = str2 + strlen( str2 ) - 1; ostr1 >= str1 && ostr2 >= str2; ostr1 -- , ostr2 --)
	{
		if ( tolower( *ostr1 ) != tolower( *ostr2 ) )
		{
			break;
		}
	}
	if ( ostr2 == ( str2 - 1 ) )
	{
		return ostr1 + 1;
	}
	return NULL;
}

static const char * endwithistrs( const char * str, const char ** strs, int count )
{
	int i;
	const char * ret;
	for ( i = 0; i < count; i ++ )
	{
		ret = endwithistr( str, strs[i] );
		if ( ret )
			return strs[i];
	}
	return NULL;
}

static const char * cmpistrs( const char * str, const char ** strs, int count )
{
	int i, ret;
	for ( i = 0; i < count; i ++ )
	{
		ret = cmpistr( str, strs[i] );
		if ( !ret )
			return strs[i];
	}
	return NULL;
}

int readPrx( const char * file, int heap_id, u8 ** outbuf )
{
	#if _DEBUG >= 1
	sprintf( lstr, "read %s to memory!\n", file );
	printf( lstr );
	#endif
	int fd = sceIoOpen( file, PSP_O_RDONLY, 0644 );
	if ( fd < 0 )
	{
		#if _DEBUG >= 1
		sprintf( lstr, "failed in opening %s\n", file );
		printf( lstr );
		#endif
		return -1;
	}
	u8 * inbuf = ( u8 * )sceKernelAllocHeapMemory( heap_id, CXMB_MKCTF_BUF_SIZE / 2 );
	int size = sceIoRead( fd, inbuf, CXMB_MKCTF_BUF_SIZE / 2 );
	if ( inbuf[0x150] == 0x1F && inbuf[0x151] == 0x8B )
	{
		#if _DEBUG >= 1
		printf( "uncompressed gzip file!\n" );
		#endif
		*outbuf = ( u8 * )sceKernelAllocHeapMemory( heap_id, CXMB_MKCTF_BUF_SIZE / 2 );
		int size = sceKernelGzipDecompress( *outbuf, CXMB_MKCTF_BUF_SIZE / 2, &inbuf[0x150], 0 );
		#if _DEBUG >= 1
		sprintf( lstr, "uncompressed size %08x!\n", size );
		printf( lstr );
		#endif
		sceKernelFreeHeapMemory( heap_id, inbuf );
		if ( size > 0 )
		{
			return size;
		}
		else
		{
			sceKernelFreeHeapMemory( heap_id, outbuf );
			return -1;
		}
	}
	else
	{
		*outbuf = inbuf;
		return size;
	}
	return -1;
}

int makeDiff( const char * file, const char * ori, int heap_id, int ctf )
{
	#if _DEBUG >= 1
	sprintf( lstr, "detect %s, start to make diff!\n", file );
	printf( lstr );
	#endif
	u8 * buf = NULL, * buf_ori = NULL;
	int bytes = readPrx( file, heap_id, &buf );
	if ( bytes < 0 )
	{
		#if _DEBUG >= 1
		sprintf( lstr, "failed in read %s!\n", file );
		printf( lstr );
		#endif
		return -1;
	}
	if ( readPrx( ori, heap_id, &buf_ori ) != bytes )
	{
		#if _DEBUG >= 1
		sprintf( lstr, "failed in read %s!\n", ori );
		printf( lstr );
		#endif
		sceKernelFreeHeapMemory( heap_id, buf );
		sceKernelFreeHeapMemory( heap_id, buf_ori );
		return -1;
	}
	int sub = ( strstr( file, diff_files[1] ) ? 0xA0: 0xC0 );
	int diff_count = 0;
	unsigned int offset = 0, rec_attr[2];
	int rec = 0;
	memset( rec_attr, 0, 8 );
	while( offset < bytes )
	{
		if ( buf[offset] != buf_ori[offset] )
		{
			if ( !rec )
			{
				rec_attr[0] = offset - sub;
				rec_attr[1] = 0;
				rec = 1;
			}
			rec_attr[1] ++;
		}
		else
		{
			if ( rec )
			{
				#if _DEBUG >= 1
				sprintf( lstr, "diff_start: %08x\nsize: %08x\n", rec_attr[0], rec_attr[1] );
				printf( lstr );
				#endif
				sceIoWrite( ctf, rec_attr, 8 );
				sceIoWrite( ctf, &buf[rec_attr[0] + sub], rec_attr[1] );
				diff_count ++;
				rec = 0;
			}
		}
		offset ++;
	}
	sceKernelFreeHeapMemory( heap_id, buf_ori );
	sceKernelFreeHeapMemory( heap_id, buf );
	return diff_count;
}

int makeCxmbThemeFile()
{
	const char * folders_name[] = {
		"/data/cert",
		"/dic",
		"/font",
		"/kd",
		"/kd/resource"
		"/vsh/etc",
		"/vsh/module",
		"/vsh/resource"
	};
	int folders_count = 8;
	const char * support_exts[] = {
		".prx",
		".rco",
		".bmp",
		".pmf",
		".res",
		".pgf",
		".bwfon",
		".rsc",
		".dat",
		".img",
		".bin",
		".cet",
		".dic"
	};
	int exts_count = 13;
	// dectect if ms0:/PSP/THEME/default.ctf exist
	int ctf = sceIoOpen( CXMB_THEME_FILE, PSP_O_RDONLY, 0644 );
	if ( ctf >= 0 )
	{
		#if _DEBUG >= 1
		printf( "Default.ctf exist!\n" );
		#endif
		sceIoClose( ctf );
		return 0;
	}
	int dfd = sceIoDopen( "ms0:/cxmb" );
	if ( dfd < 0 )
	{
		#if _DEBUG >= 1
		printf( "no cxmb them folder found!\n" );
		#endif
		return 0;
	}
	sceIoClose( dfd );
	// create CXMB_MKCTF_BUF_SIZE + 32kb heap
	int heap_id = sceKernelCreateHeap( 2, CXMB_MKCTF_BUF_SIZE + 1024 * 32 , 1, "cxmb_tmp_heap");
	if ( heap_id < 0 )
	{
		#if _DEBUG >= 1
		printf( "failed in create heap in making cxmb theme file!\n" );
		#endif
		return -1;
	}
	CtfHeader * ch = ( CtfHeader * )sceKernelAllocHeapMemory( heap_id, sizeof( CtfHeader ) * 64 );
	memset( ch, 0, sizeof( CtfHeader ) * 64 );
	SceIoDirent * ent = ( SceIoDirent * )sceKernelAllocHeapMemory( heap_id, sizeof( SceIoDirent ) );
	memset( ent, 0, sizeof( SceIoDirent ) );
	int i, fd, bytes, file_count = 0;
	char path[128], file[128];
	u8 * buf;
	sceIoMkdir( "ms0:/PSP/THEME", 0777 );
	ctf = sceIoOpen( CXMB_THEME_FILE, PSP_O_RDWR | PSP_O_CREAT, 0777 );
	if ( ctf < 0 )
	{
		#if _DEBUG >= 1
		sprintf( lstr, "failed in opening %s\n", CXMB_THEME_FILE );
		printf( lstr );
		#endif
		return -1;
	}
	sceIoWrite( ctf, &cxmb_magic, 4 );
	sceIoLseek( ctf, 8, PSP_SEEK_CUR );
	for ( i = 0; i < folders_count; i ++ )
	{
		sprintf( path, "ms0:/cxmb%s", folders_name[i] );
		dfd = sceIoDopen( path );
		if ( dfd < 0 )
		{
			#if _DEBUG >= 1
			sprintf( lstr, "folder %s not found!\n", path );
			printf( lstr );
			#endif
			continue;
		}
		#if _DEBUG >= 1
		sprintf( lstr, "parsing %s\n", path );
		printf( lstr );
		#endif
		while ( sceIoDread( dfd, ent ) > 0 )
		{
			#if _DEBUG >= 1
			sprintf( lstr, "found %s\n", ent->d_name );
			printf( lstr );
			#endif
			if ( ( ent->d_stat.st_attr & FIO_SO_IFDIR ) || ent->d_name[0] == '.' )
			{
				#if _DEBUG >= 1
				sprintf( lstr, "ignore %s\n", ent->d_name );
				printf( lstr );
				#endif
				continue;
			}
			if ( endwithistrs( ent->d_name, support_exts, exts_count ) )
			{
				sprintf( file, "%s/%s", path, ent->d_name );
				sprintf( ch[file_count].name, "%s/%s", folders_name[i], ent->d_name );
				ch[file_count].start = sceIoLseek( ctf, 0, PSP_SEEK_CUR );
				ch[file_count].size = 0;
				if ( cmpistrs( ent->d_name, diff_files, diff_count ) )
				{
					char ori_file[128];
					sprintf( ori_file, "%s/%s", CXMB_SUPPORT_FOLDER, ent->d_name );
					ch[file_count].size = makeDiff( file, ori_file, heap_id, ctf );
				}
				else
				{
					#if _DEBUG >= 1
					sprintf( lstr, "dealing with %s\n", ent->d_name );
					printf( lstr );
					#endif
					fd = sceIoOpen( file, PSP_O_RDONLY, 0644 );
					if ( fd < 0 )
					{
						#if _DEBUG >= 1
						sprintf( lstr, "failed in opening %s\n", file );
						printf( lstr );
						#endif
						continue;
					}
					buf = ( u8 * )sceKernelAllocHeapMemory( heap_id, CXMB_MKCTF_BUF_SIZE );
					bytes = sceIoRead( fd, buf, CXMB_MKCTF_BUF_SIZE );
					while( bytes > 0 )
					{
						ch[file_count].size += sceIoWrite( ctf, buf, bytes );
						bytes = sceIoRead( fd, buf, CXMB_MKCTF_BUF_SIZE );
					}
					sceKernelFreeHeapMemory( heap_id, buf );
					sceIoClose( fd );
				}
				if ( ch[file_count].size > 0 )
					file_count ++;
			}
			#if _DEBUG >= 1
			else
			{
				sprintf( lstr, "ignore %s\n", ent->d_name );
				printf( lstr );
			}
			#endif
		}
		sceIoDclose( dfd );
	}
	#if _DEBUG >= 1
	sprintf( lstr, "file_count: %d\n", file_count );
	printf( lstr );
	#endif
	if ( file_count > 0 )
	{
		u8 sha1[20];
		sceKernelUtilsSha1Digest( ( u8 * )ch, sizeof( CtfHeader ) * file_count, sha1 );
		sceIoWrite( ctf, ch, sizeof( CtfHeader ) * file_count );
		sceIoLseek( ctf, 4, PSP_SEEK_SET );
		sceIoWrite( ctf, &file_count, 4 );
		sceIoWrite( ctf, &sha1[0], 4 );
		sceIoClose( ctf );
	}
	else
	{
		sceIoClose( ctf );
		sceIoRemove( CXMB_THEME_FILE );
	}
	sceKernelFreeHeapMemory( heap_id, ent );
	sceKernelFreeHeapMemory( heap_id, ch );
	sceKernelDeleteHeap( heap_id );
	return 0;
}
#endif

void uninstall_cxmb()
{
	//free the cxmb heap
	sceKernelFreeHeapMemory( mem_id, ctf_handler );
	sceKernelFreeHeapMemory( mem_id, ctf_header );
	sceKernelDeleteHeap( mem_id );
	
	//restore io func
	int intr = sceKernelCpuSuspendIntr();
	fatms_drv->funcs->IoOpen		= msIoOpen;
	lflash_drv->funcs->IoOpen		= IoOpen;
	lflash_drv->funcs->IoRead		= IoRead;
	lflash_drv->funcs->IoLseek		= IoLseek;
	lflash_drv->funcs->IoIoctl		= IoIoctl;
	lflash_drv->funcs->IoClose		= IoClose;
	lflash_drv->funcs->IoGetstat	= IoGetstat;
	sceKernelCpuResumeIntr( intr );
	sceKernelIcacheInvalidateAll();
	sceKernelDcacheWritebackInvalidateAll();
	
	//restore startmodulehandler
	setStartModuleHandler( previous );
}

int install_cxmb()
{
	#if _CXMB_LITE == 0
	makeCxmbThemeFile();
	#endif
	
	int fd = sceIoOpen( CXMB_THEME_FILE, PSP_O_RDONLY, 0644 );
	if ( fd < 0 )
	{
		#if _DEBUG >= 1
		printf( "no ctf file found!\n" );
		#endif
		return -1;
	}
	unsigned int magic;
	sceIoRead( fd, &magic, 4 );
	if ( magic != cxmb_magic )
	{
		#if _DEBUG >= 1
		printf( "magic not match!\n" );
		#endif
		sceIoClose( fd );
		return -1;
	}
	sceIoRead( fd, &header_size, 4 );
	sceIoRead( fd, &ctf_sig, 4 );
	#if _DEBUG >= 1
	sprintf( lstr, "header size: %d\n", header_size );
	printf( lstr );
	#endif
	
	mem_id = sceKernelCreateHeap( 1, 1024 * 5 , 1, "cxmb_heap");
	if ( mem_id < 0 )
	{
		#if _DEBUG >= 1
		printf( "failed in creating cxmb_heap!\n" );
		#endif
		sceIoClose( fd );
		return -1;
	}
	ctf_header = sceKernelAllocHeapMemory( mem_id, sizeof( CtfHeader ) * header_size );
	int offset = - sizeof( CtfHeader ) * header_size;
	sceIoLseek( fd, offset, PSP_SEEK_END );
	sceIoRead( fd, ctf_header, sizeof( CtfHeader ) * header_size );
	sceIoClose( fd );
	
	#if _DEBUG >= 2
	sprintf( lstr, "seek header size: %d\n", offset );
	printf( lstr );
	int tmp = sceIoOpen( "ms0:/om.bin", PSP_O_RDWR | PSP_O_CREAT, 0777 );
	sceIoWrite( tmp, ctf_header, sizeof( CtfHeader ) * header_size );
	sceIoClose( tmp );
	#endif
	
	#if _DEBUG >= 1
	printf( "read ctf_header!\n" );
	#endif
	
	ctf_handler = sceKernelAllocHeapMemory( mem_id, sizeof( CtfHandler ) * 32 );
	memset( ctf_handler, 0, sizeof( CtfHandler ) * 32 );
	
	lflash_drv = findDriver( "flashfat" );
	fatms_drv = findDriver( "fatms" );
	
	if ( !lflash_drv || !fatms_drv || !fatms_drv->funcs->IoOpen )
		return -1;
		
	msIoOpen	= fatms_drv->funcs->IoOpen;
	IoOpen		= lflash_drv->funcs->IoOpen;
	IoRead		= lflash_drv->funcs->IoRead;
	IoLseek		= lflash_drv->funcs->IoLseek;
	IoIoctl		= lflash_drv->funcs->IoIoctl;
	IoClose		= lflash_drv->funcs->IoClose;
	IoGetstat	= lflash_drv->funcs->IoGetstat;
	
	int intr = sceKernelCpuSuspendIntr();
	
	fatms_drv->funcs->IoOpen	= msIoOpen_new;
	lflash_drv->funcs->IoOpen	= IoOpen_new;
	lflash_drv->funcs->IoRead	= IoRead_new;
	lflash_drv->funcs->IoLseek	= IoLseek_new;
	lflash_drv->funcs->IoIoctl	= IoIoctl_new;
	lflash_drv->funcs->IoClose	= IoClose_new;
	lflash_drv->funcs->IoGetstat= IoGetstat_new;
	
	sceKernelCpuResumeIntr( intr );
	sceKernelIcacheInvalidateAll();
	sceKernelDcacheWritebackInvalidateAll();
	
	sceIoOpen( "ms0:/dummy.prx", PSP_O_RDONLY, 0644 );
	
	#if _DEBUG >= 1
	printf( "redirected io_driver!\n" );
	sprintf( lstr, "ms_drv_arg: %08x\n", ( unsigned int )ms_drv );
	printf( lstr );
	#endif
	setStartModuleHandler = ( void * )findProc( "SystemControl", "SystemCtrlForKernel", 0x1C90BECB );
	previous = setStartModuleHandler( OnModuleStart );
	#if _DEBUG >= 1
	printf("startModuleHandler setup!\n");
	#endif
	return 0;
}

int module_start( SceSize args, void *argp )
{
	install_cxmb();
	return 0;
}

int module_stop( SceSize args, void *argp )
{
	return 0;
}