/*-------------------------------------------------------------

uneek_fs.c -- USB mass storage support, using a modified uneek fs-usb

rev. 1.00		first draft

rev. 1.01		second draft
added detection and support for SDHC access in SNEEK
added check_uneek_fs_type function to see if it's sneek or uneek

rev. 1.02		third draft
added the UNEEK_FS_BOTH option to the init_uneek_fs function.
when the parameter is set, both sd and usb access are rerouted to the sd card.
it can only be used with sneek and could serve programs like Joyflow on a sd only
setup.
the exit_uneek_fs function needs to be called when that setup is used.
since I can't disable usb, not knowing if sd access is still needed, and
I can't disable sd if usb access if sd is still needed.
It's probably exceptional that one will be shutdown and the other still used,
but it's better to stay on the safe area.

rev. 1.04	fourth draft.
shutdown function is now a stub as some programs call it before they end to force a remount
exit_uneek_fs added to properly shutdown the uneek_usb_fs file system.
max_write_sectors increased to speedup things. Transfer speed from wiixplorer gone up
from 20KB/s to 265KB/s.

rev. 1.05 fifth draft
added "is_uneek" function

rev. 1.06 sixth draft
added "is_neek2" and "is_neek3" functions
Crediar changed the boot2 version back from 5 to 4.
The is_uneek function will not detect those neek versions anymore.
Stfour created the is_neek2 method to detect if neek is running.
JoostinOnline and GiantPune created the is_neek3 which is a little less code.
So, you should use either is_neek2 or is_neek3

rev. 1.07 seventh draft
added "is_neek4" function
Dj_Skual created the is_neek4 method to detect if neek is running.

added WII_launch_Channel. It's not really uneek_fs related, but it could be handy


Copyright (C) 2011 Obcd

This software is provided 'as-is', without any express or implied
warranty.  In no event will the authors be held liable for any
damages arising from the use of this software.

Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:

1.	The origin of this software must not be misrepresented; you
must not claim that you wrote the original software. If you use
this software in a product, an acknowledgment in the product
documentation would be appreciated but is not required.

2.	Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.

3.	This notice may not be removed or altered from any source
distribution.

-------------------------------------------------------------*/

#include <stdio.h>
#include <string.h>
#include <ogcsys.h>
#include <locale.h>
#include <malloc.h>
#include <ogc/isfs.h>
//#include <sdcard/wiisd_io.h>
//#include <gctypes.h>
//#include <ogc/disc_io.h>


//#define SHOW_GECKO_DEBUG			1

#ifdef SHOW_GECKO_DEBUG
#include "gecko.h"
#endif

#define MAX_READ_SECTORS			16			//yet to be determined how much is allowed?
#define MAX_WRITE_SECTORS			16			//yet to be determined how much is allowed?
#define CACHE_SECTOR_LOCATION		1			//not sure if it will improve speed much...

#define	UNEEK_FS_NONE				0
#define UNEEK_FS_USB				1
#define UNEEK_FS_SD					2
#define UNEEK_FS_BOTH				4

#define ISFS_OPEN_ALL				64

#define DEVICE_TYPE_WII_SD (('W'<<24)|('I'<<16)|('S'<<8)|'D')


//Not yet implemented as most disk io buffers don't seem have proper alignment
//It would require a more serious modification in the code (aligning those buffers)
//#define USE_ORIGINAL_ALIGNED_BUF	0			//if the buffer is aligned already...why not?



//This one triggers the unique functions in the uneek fs
//Don't try to change it.
//If you really need to access that file with ISFS, access it in read/write mode
//As only read mode will trigger uneek fs

static char _dev_obcd[] ATTRIBUTE_ALIGN(32) = "/sneek/obcd.txt";

static s32 fu  = -1;
// static bool uneek_fs_ok = false;
static s32 uneek_fs_type = UNEEK_FS_NONE;

s32 confirm_neek1 = 5;
s32 confirm_neek2 = 5;
s32 confirm_neek3 = 5;
s32 confirm_neek4 = 5;


#ifdef CACHE_SECTOR_LOCATION
static u32 seek_cache;
#endif

// DISC_INTERFACE __io_usbstorage;
DISC_INTERFACE __io_wiisd;
extern DISC_INTERFACE __io_usbstorage;
//extern DISC_INTERFACE __io_usb2storage;

// DISC_INTERFACE methods

static bool __io_uns_IsInserted(void)
{
	return true;
}

static bool __io_uns_Startup(void)
{
	return true;
}

int usb_verbose = 0;

bool __io_uns_ReadSectors(u32 sector, u32 count, void *buffer)
{

	u8* buf;
	s32 whence;
	u32 done;
	u32 sec;
	u32 amount;
	u8* resultbuf;

	done = 0;
	sec = sector;
	if (count <= MAX_READ_SECTORS)
	{
		amount = count;
	}
	else
	{
		amount = MAX_READ_SECTORS;
	}
//	buf = (u8 *)memalign(32, 512 * amount);
//  sdhc aligns it's buffers on 64 byte boundaries
	buf = (u8 *)memalign(64, 512 * amount);

	while(done < count)
	{
		whence = 0;
		if ((sec & 0x80000000)!= 0)
		{
			sec &= 0x7fffffff;
			whence = 1;
		}
#ifdef	CACHE_SECTOR_LOCATION
		if ((sector + done) != seek_cache)
		{
#endif
			ISFS_Seek(fu,sec,whence);
#ifdef CACHE_SECTOR_LOCATION
			seek_cache = sector + done;
		}
#endif
		s32 ret = ISFS_Read(fu,buf,512*amount);
		if (ret == (s32)(512*amount))
		{
			resultbuf = (u8*)buffer + (done * 512);
			memcpy(resultbuf,buf,512*amount);
	 		done+=amount;
			sec = sector + done;
#ifdef CACHE_SECTOR_LOCATION
			seek_cache = sector + done;
#endif
			if((count-done)<=MAX_READ_SECTORS)
			{
				amount = count - done;
			}
			else
			{
				amount = MAX_READ_SECTORS;
			}
		}
		else
		{
#ifdef CACHE_SECTOR_LOCATION
			seek_cache = 0xfffffff8;
#endif
			return false;
		}
	}
	free(buf);
	if (usb_verbose) {
		printf("usb-r: %x [%d]\n", sector, count);
		//sleep(1);
	}
	//printf("ret = %x\n",ret);
	return true;
}

bool __io_uns_WriteSectors(u32 sector, u32 count, void *buffer)
{

	u8* buf;
	s32 whence;
	u32 done;
	u32 sec;
	u32 amount;
	u8* resultbuf;

	done = 0;
	sec = sector;
	if (count <= MAX_WRITE_SECTORS)
	{
		amount = count;
	}
	else
	{
		amount = MAX_WRITE_SECTORS;
	}
//	buf = (u8 *)memalign(32, 512 * amount);
//  sdhc aligns it's buffers on 64 byte boundaries
	buf = (u8 *)memalign(64, 512 * amount);

	while(done < count)
	{
		whence = 0;
		if ((sec & 0x80000000)!= 0)
		{
			sec &= 0x7fffffff;
			whence = 1;
		}
#ifdef	CACHE_SECTOR_LOCATION
		if ((sector + done) != seek_cache)
		{
#endif
			ISFS_Seek(fu,sec,whence);
#ifdef CACHE_SECTOR_LOCATION
			seek_cache = sector + done;
		}
#endif
		resultbuf = (u8*)buffer + (done * 512);
		memcpy(buf,resultbuf,512*amount);
		s32 ret = ISFS_Write(fu,buf,512*amount);
	 	if (ret == (s32)(512*amount))
		{
	 		done+=amount;
			sec = sector + done;
#ifdef CACHE_SECTOR_LOCATION
			seek_cache = sector + done;
#endif
			if((count-done)<=MAX_WRITE_SECTORS)
			{
				amount = count - done;
			}
			else
			{
				amount = MAX_WRITE_SECTORS;
			}
		}
		else
		{
#ifdef CACHE_SECTOR_LOCATION
			seek_cache = 0xfffffff8;
#endif
			return false;
		}
	 }

	free(buf);
	if (usb_verbose) {
		printf("usb-r: %x [%d]\n", sector, count);
		//sleep(1);
	}
	return true;
}

static bool __io_uns_ClearStatus(void)
{
	return true;
}

static bool __io_uns_Shutdown(void)
{

	return true;
/*
	if ((uneek_fs_type != UNEEK_FS_NONE)&&(uneek_fs_type != UNEEK_FS_BOTH))
	{
		ISFS_Close(fu);
		ISFS_Deinitialize();
		uneek_fs_type = UNEEK_FS_NONE;
	}
	// do nothing
	return true;
*/
}

static bool __io_uns_NOP(void)
{
	// do nothing
	return true;
}



bool init_uneek_fs(u32 mode)
{

	u8* buf = NULL;
	struct _fstats* pstat = NULL;
	//struct _fstats stat;

	if (uneek_fs_type == UNEEK_FS_NONE){
		ISFS_Initialize();
		fu = ISFS_Open(_dev_obcd, ISFS_OPEN_READ);
		buf = (u8*)memalign(32, sizeof(struct _fstats));
		pstat = (struct _fstats*)buf;
		pstat->file_length = 0;
		pstat->file_pos	= 0;
		if(fu >= 0)
		{
			ISFS_GetFileStats(fu,pstat);
			//printf("Filestat length returned %x\n",pstat->file_length);
			//printf("Filestat pos returned %x\n",pstat->file_pos);
		}
		if ((pstat->file_length == 0xfffffff0)&&(pstat->file_pos == 0xfffffff8)&&(fu>=0))
		{
#ifdef	CACHE_SECTOR_LOCATION
			seek_cache = 0xfffffff8;
#endif
			if (mode & ISFS_OPEN_WRITE)
			{
				__io_usbstorage.ioType = DEVICE_TYPE_WII_USB;
				__io_usbstorage.features = FEATURE_MEDIUM_CANREAD | FEATURE_MEDIUM_CANWRITE | FEATURE_WII_USB;
				__io_usbstorage.startup = (FN_MEDIUM_STARTUP)&__io_uns_Startup;
				__io_usbstorage.isInserted = (FN_MEDIUM_ISINSERTED)&__io_uns_IsInserted;
				__io_usbstorage.readSectors = (FN_MEDIUM_READSECTORS)&__io_uns_ReadSectors;
				__io_usbstorage.writeSectors = (FN_MEDIUM_WRITESECTORS)&__io_uns_WriteSectors;
				__io_usbstorage.clearStatus = (FN_MEDIUM_CLEARSTATUS)&__io_uns_ClearStatus;
				__io_usbstorage.shutdown = (FN_MEDIUM_SHUTDOWN)&__io_uns_Shutdown;
			}
			else
			{
				__io_usbstorage.ioType = DEVICE_TYPE_WII_USB;
				__io_usbstorage.features = FEATURE_MEDIUM_CANREAD | FEATURE_WII_USB;
				__io_usbstorage.startup = (FN_MEDIUM_STARTUP)&__io_uns_Startup;
				__io_usbstorage.isInserted = (FN_MEDIUM_ISINSERTED)&__io_uns_IsInserted;
				__io_usbstorage.readSectors = (FN_MEDIUM_READSECTORS)&__io_uns_ReadSectors;
				__io_usbstorage.writeSectors = (FN_MEDIUM_WRITESECTORS)&__io_uns_NOP; //&__io_uns_WriteSectors
				__io_usbstorage.clearStatus = (FN_MEDIUM_CLEARSTATUS)&__io_uns_ClearStatus;
				__io_usbstorage.shutdown = (FN_MEDIUM_SHUTDOWN)&__io_uns_Shutdown;
			}

			uneek_fs_type = UNEEK_FS_USB;
			free (buf);
			return true;
		}
//sneek
		else if ((pstat->file_length == 0xfffffff1)&&(pstat->file_pos == 0xfffffff8)&&(fu>=0))
		{
#ifdef	CACHE_SECTOR_LOCATION
			seek_cache = 0xfffffff8;
#endif
			uneek_fs_type = UNEEK_FS_SD;
			if (mode & ISFS_OPEN_WRITE)
			{
				__io_wiisd.ioType = DEVICE_TYPE_WII_SD;
				__io_wiisd.features = FEATURE_MEDIUM_CANREAD | FEATURE_MEDIUM_CANWRITE | FEATURE_WII_SD;
				__io_wiisd.startup = (FN_MEDIUM_STARTUP)&__io_uns_Startup;
				__io_wiisd.isInserted = (FN_MEDIUM_ISINSERTED)&__io_uns_IsInserted;
				__io_wiisd.readSectors = (FN_MEDIUM_READSECTORS)&__io_uns_ReadSectors;
				__io_wiisd.writeSectors = (FN_MEDIUM_WRITESECTORS)&__io_uns_WriteSectors;
				__io_wiisd.clearStatus = (FN_MEDIUM_CLEARSTATUS)&__io_uns_ClearStatus;
				__io_wiisd.shutdown = (FN_MEDIUM_SHUTDOWN)&__io_uns_Shutdown;

				//usb and sd will be treated equally
				//needed for joyflow with the sd only setup

				if (mode & ISFS_OPEN_ALL)
				{
					__io_usbstorage.ioType = DEVICE_TYPE_WII_USB;
					__io_usbstorage.features = FEATURE_MEDIUM_CANREAD | FEATURE_MEDIUM_CANWRITE | FEATURE_WII_USB;
					__io_usbstorage.startup = (FN_MEDIUM_STARTUP)&__io_uns_Startup;
					__io_usbstorage.isInserted = (FN_MEDIUM_ISINSERTED)&__io_uns_IsInserted;
					__io_usbstorage.readSectors = (FN_MEDIUM_READSECTORS)&__io_uns_ReadSectors;
					__io_usbstorage.writeSectors = (FN_MEDIUM_WRITESECTORS)&__io_uns_WriteSectors;
					__io_usbstorage.clearStatus = (FN_MEDIUM_CLEARSTATUS)&__io_uns_ClearStatus;
					__io_usbstorage.shutdown = (FN_MEDIUM_SHUTDOWN)&__io_uns_Shutdown;
					uneek_fs_type = UNEEK_FS_BOTH;
				}
			}
			else
			{
				__io_wiisd.ioType = DEVICE_TYPE_WII_SD;
				__io_wiisd.features = FEATURE_MEDIUM_CANREAD | FEATURE_WII_SD;
				__io_wiisd.startup = (FN_MEDIUM_STARTUP)&__io_uns_Startup;
				__io_wiisd.isInserted = (FN_MEDIUM_ISINSERTED)&__io_uns_IsInserted;
				__io_wiisd.readSectors = (FN_MEDIUM_READSECTORS)&__io_uns_ReadSectors;
				__io_wiisd.writeSectors = (FN_MEDIUM_WRITESECTORS)&__io_uns_NOP; //&__io_uns_WriteSectors
				__io_wiisd.clearStatus = (FN_MEDIUM_CLEARSTATUS)&__io_uns_ClearStatus;
				__io_wiisd.shutdown = (FN_MEDIUM_SHUTDOWN)&__io_uns_Shutdown;
				if(mode & ISFS_OPEN_ALL)
				{
					__io_usbstorage.ioType = DEVICE_TYPE_WII_USB;
					__io_usbstorage.features = FEATURE_MEDIUM_CANREAD | FEATURE_WII_USB;
					__io_usbstorage.startup = (FN_MEDIUM_STARTUP)&__io_uns_Startup;
					__io_usbstorage.isInserted = (FN_MEDIUM_ISINSERTED)&__io_uns_IsInserted;
					__io_usbstorage.readSectors = (FN_MEDIUM_READSECTORS)&__io_uns_ReadSectors;
					__io_usbstorage.writeSectors = (FN_MEDIUM_WRITESECTORS)&__io_uns_NOP; //&__io_uns_WriteSectors
					__io_usbstorage.clearStatus = (FN_MEDIUM_CLEARSTATUS)&__io_uns_ClearStatus;
					__io_usbstorage.shutdown = (FN_MEDIUM_SHUTDOWN)&__io_uns_Shutdown;
					uneek_fs_type = UNEEK_FS_BOTH;
				}
			}
			free (buf);
			return true;
		}
		else
		{
			/* these should be initialised right in the ogc library */
			/* so we will ignore those for the moment */
/*
			__io_usbstorage.ioType = DEVICE_TYPE_WII_USB;
			__io_usbstorage.features = FEATURE_MEDIUM_CANREAD | FEATURE_MEDIUM_CANWRITE | FEATURE_WII_USB;
			__io_usbstorage.startup = (FN_MEDIUM_STARTUP)&__io_usb_Startup;
			__io_usbstorage.isInserted = (FN_MEDIUM_ISINSERTED)&__io_usb_IsInserted;
			__io_usbstorage.readSectors = (FN_MEDIUM_READSECTORS)&__io_usb_ReadSectors;
			__io_usbstorage.writeSectors = (FN_MEDIUM_WRITESECTORS)&__io_usb_WriteSectors;
			__io_usbstorage.clearStatus = (FN_MEDIUM_CLEARSTATUS)&__io_usb_ClearStatus;
			__io_usbstorage.shutdown = (FN_MEDIUM_SHUTDOWN)&__io_usb_Shutdown;
*/

			uneek_fs_type = UNEEK_FS_NONE;
			free(buf);
			ISFS_Close(fu);
			ISFS_Deinitialize();
			return false;
		}
	}
	else	//was already initialised
	{
		return true;
	}
}


/* stay compatible with previous versions */
bool check_uneek_fs(void)
{
	if(uneek_fs_type != UNEEK_FS_NONE)
	{
		return true;
	}
	else
	{
		return false;
	}
}

s32 check_uneek_fs_type(void)
{
	return uneek_fs_type;
}

bool exit_uneek_fs(void)
{
	if (uneek_fs_type != UNEEK_FS_NONE)
	{
		ISFS_Close(fu);
		ISFS_Deinitialize();
		uneek_fs_type = UNEEK_FS_NONE;
	}
	// do nothing
	return true;
}


bool SenseSneek (bool isfsinit)
{
	bool ret = true;
	char path[ISFS_MAXPATH] ATTRIBUTE_ALIGN(32);

	strcpy (path, "/SNEEK/kernel.bin");

	if (isfsinit) ISFS_Initialize ();

	s32 fd = ISFS_Open(path, ISFS_OPEN_READ);
	if (fd < 0)
  		ret = false;
	else
  		ISFS_Close (fd);

	if (isfsinit) ISFS_Deinitialize();
	return ret;
}



bool is_neek(void)
{
	u32 boot2version;

	if(confirm_neek1 == 5)
	{
		ES_GetBoot2Version(&boot2version);
		if(boot2version < 5)
			confirm_neek1 = false;
		else
			confirm_neek1 = true;
	}
	return confirm_neek1;
}


bool is_neek2 (bool isfsinit)
{

	char path[ISFS_MAXPATH] ATTRIBUTE_ALIGN(32);

	if(confirm_neek2 == 5)
	{
		confirm_neek2 = true;
		strcpy (path, "/SNEEK/kernel.bin");

		if (isfsinit) ISFS_Initialize ();

		s32 fd = ISFS_Open(path, ISFS_OPEN_READ);
		if (fd < 0)
  			confirm_neek2 = false;
		else
  			ISFS_Close (fd);

		if (isfsinit) ISFS_Deinitialize ();
 	}
 	return confirm_neek2;
}

//! New method for determining if this is a real or emu nand.
//! Works with new versions of classic Crediar NEEK
bool is_neek3(bool isfsinit)
{
	u32 num = 0;

	//! Thanks goes to the almighty giantpune for this
	if(confirm_neek3 == 5)
	{
		if (isfsinit) ISFS_Initialize ();
		if(ISFS_ReadDir("/Sneek", NULL, &num)==0)
			confirm_neek3 = true;
		else
			confirm_neek3 = false;
		if (isfsinit) ISFS_Deinitialize ();
	}
	return confirm_neek3;
}

bool is_neek4(bool isfsinit)
{

	u32 ownerID;
	u16 groupID;
	u8  attributes;
	u8  ownerperm;
	u8  groupperm;
	u8  otherperm;

	if(confirm_neek4 == 5)
	{
		confirm_neek4 = false;
		if(isfsinit) ISFS_Initialize();
		ISFS_GetAttr("/shared1/00000000.app", &ownerID, &groupID, &attributes, &ownerperm, &groupperm, &otherperm);
		if(otherperm)
			confirm_neek4 = true;

		if(isfsinit) ISFS_Deinitialize();
	}
	return confirm_neek4;
}

bool WII_Launch_Channel(char* which)
{
#ifdef HW_RVL
	size_t wlen;
	u64 title_id;
	int titlemsb, titlelsb;
	char stitlexsb[16];
	union lsbstufftype
	{
		char titlename[4];
		u32	title_lsb;
	}lsbstuff;


	wlen = strlen(which);
	if ((wlen != 4) && (wlen != 16))
	{
#ifdef SHOW_GECKO_DEBUG
		gprintf("Title %s has an invalid length\n",which);
#endif
		return false;
	}
	if (wlen == 4)
	{
		titlemsb = 0x00010001;
		strncpy(lsbstuff.titlename,which,4);
		titlelsb = lsbstuff.title_lsb;
	}
	else
	{
		strncpy(stitlexsb,which,8);
		stitlexsb[8]=0;
		sscanf(stitlexsb,"%x",&titlemsb);
		strncpy(stitlexsb,which+8,8);
		stitlexsb[8]=0;
		sscanf(stitlexsb,"%x",&titlelsb);
	}
	title_id = (u64)(titlemsb) << 32;
	title_id = title_id + (u64)(titlelsb);
#ifdef SHOW_GECKO_DEBUG
	gprintf( "titleid = %08x %08x\r\n", (u32)((title_id)>>32), (u32)(title_id) );
	s32 lret = WII_LaunchTitle(title_id);
	gprintf("WII_LaunchTitle returned %d\r\n",lret);
#else
	WII_LaunchTitle(title_id);
#endif
	// basically, it shouldn't get here I assume
#endif	//HW_RVL
	return false;
}