///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
#include "gsCommon.h"
#include "gsPlatformUtil.h"

// Include platform separated functions
#if defined(_X360)
	//#include "x360/gsUtilX360.c"
#elif defined(_XBOX)
	//#include "xbox/gsUtilXBox.c"
#elif defined(_WIN32)
	//#include "win32/gsUtilWin32.c"
#elif defined(_LINUX)
	//#include "linux/gsUtilLinux.c"
#elif defined(_MACOSX)
	//#include "macosx/gsUtilMacOSX.c"
#elif defined(_NITRO)
	//#include "nitro/gsUtilNitro.c"
#elif defined(_PS2)
	//#include "ps2/gsUtilPs2.c"
#elif defined(_PS3)
	#include "ps3/gsUtilPs3.c"
#elif defined(_PSP)
	#include "psp/gsUtilPSP.c"
#elif defined(_REVOLUTION)
	#include "revolution/gsUtilRevolution.c"
#else
	#error "Missing or unsupported platform"
#endif

#if defined(__cplusplus)
extern "C" {
#endif

	

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// ********** ASYNC DNS ********** //

//struct is used in both threaded and non-threaded versions
typedef struct GSIResolveHostnameInfo
{
    char * hostname;
    unsigned int ip;

#if defined(_WIN32) /*|| defined(_PS2)*/ || defined(_UNIX) || defined (_REVOLUTION)
    int finishedResolving;
    GSIThreadID threadID;
#endif

/*#if defined(_PSP)
    int finishedResolving;
    GSIThreadID threadID;
#endif*/
} GSIResolveHostnameInfo;

////////////////////////////////////////////////////////////////////////////////
// for asynch DNS, must have:
// * platform that supports threaded lookup AND
// * threading enabled
// * and async lookup enabled
////////////////////////////////////////////////////////////////////////////////
#if	(defined(_WIN32) || /*defined(_PS2) ||*/ defined(_UNIX) || defined (_REVOLUTION)) && !defined(GSI_NO_THREADS) && !defined(GSI_NO_ASYNC_DNS)

////////////////////////////////////////////////////////////////////////////////
#if defined(_WIN32) /*|| defined(_PS2)*/
	#if defined(_WIN32)
		DWORD WINAPI gsiResolveHostnameThread(void * arg)
	/*#elif defined(_PS2)
		static void gsiResolveHostnameThread(void * arg)*/
	#endif
	{
		HOSTENT * hostent;
		GSIResolveHostnameHandle handle = (GSIResolveHostnameHandle)arg;

		SocketStartUp();
		
		#ifdef SN_SYSTEMS
		sockAPIregthr();
		#endif

		// do the gethostbyname
		hostent = gethostbyname(handle->hostname);
		if(hostent)
		{
			// got the ip
			handle->ip = *(unsigned int *)hostent->h_addr_list[0];
		}
		else
		{
			// didn't resolve
			handle->ip = GSI_ERROR_RESOLVING_HOSTNAME;
		}

		SocketShutDown();

		// finished resolving
		handle->finishedResolving = 1;

		#ifdef SN_SYSTEMS
		sockAPIderegthr();
		#endif

		// explicitly exit the thread to free resources
		gsiExitThread(handle->threadID);

		#if defined(_WIN32)
		return 0;
		#endif
}
#endif //defined _WIN32
////////////////////////////////////////////////////////////////////////////////


#ifdef _REVOLUTION
///////////////////////////////////////////////////////////////////////////////
static void *gsiResolveHostnameThread(void * arg)
{
	static GSICriticalSection aHostnameCrit;
	static int aInitialized = 0;
	//SOAddrInfo *aHostAddr;
	HOSTENT *aHostAddr;
	//int retval;
	GSIResolveHostnameHandle handle = (GSIResolveHostnameHandle)arg;
		
	if (!aInitialized)
	{
		gsiInitializeCriticalSection(&aHostnameCrit);
		aInitialized = 1;
	}
	gsiEnterCriticalSection(&aHostnameCrit);
	
	//retval = getaddrinfo(handle->hostname, NULL, NULL, &aHostAddr);
	aHostAddr = gethostbyname(handle->hostname);
	if (aHostAddr != 0)
	{
		char * ip;
		// first convert to character string for debug output
		ip = inet_ntoa(*(in_addr *)aHostAddr->addrList[0]);

		gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment,
				"Resolved host '%s' to ip '%s'\n", handle->hostname, ip);
		
		handle->ip = inet_addr(ip);
		//freeaddrinfo(aHostAddr);
	}
	else
	{
		// couldnt reach host - debug output is printed later
		handle->ip = GSI_ERROR_RESOLVING_HOSTNAME;
	}
	
	
	// finished resolving
	handle->finishedResolving = 1;
	
	gsiLeaveCriticalSection(&aHostnameCrit);	
}
#endif // _REVOLUTION
////////////////////////////////////////////////////////////////////////////////

//
// Linux/MacOSX implementation of multithreaded DNS lookup
// Uses getaddrinfo instead of gethostbyname - since the latter
// has static declarations and is thus un-safe for pthreads
//
// NOTE: The compiler option "-lpthread" must used for this
#if defined(_UNIX)
////////////////////////////////////////////////////////////////////////////////
static void gsiResolveHostnameThread(void * arg)
{
	GSIResolveHostnameHandle handle = (GSIResolveHostnameHandle)arg;
	struct addrinfo hints, *result = NULL;
	int error;
	char *ip;

	SocketStartUp();

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = PF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;

	// DNS lookup (works with pthreads)
	error = getaddrinfo(handle->hostname, "http", &hints, &result);

	if (!error)
	{
		// first convert to character string for debug output
		ip = inet_ntoa((*(struct sockaddr_in*)result->ai_addr).sin_addr);

		gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment,
			"Resolved host '%s' to ip '%s'\n", handle->hostname, ip);

		// now convert to unsigned int and store it
		handle->ip = inet_addr(ip);

		// free the memory used
		freeaddrinfo(result);
	}
	else
	{
		// couldnt reach host - debug output is printed later
		handle->ip = GSI_ERROR_RESOLVING_HOSTNAME;
	}

	SocketShutDown();

	// finished resolving
	handle->finishedResolving = 1;

	// explicitly exit the thread to free resources
	gsiExitThread(handle->threadID);
}
#endif //_UNIX
////////////////////////////////////////////////////////////////////////////////


int gsiStartResolvingHostname(const char * hostname, GSIResolveHostnameHandle * handle)
{
	GSIResolveHostnameInfo * info;

	//PS2 Threading unsupported in current build - this should never be reached
#if defined(_PS2)
	GS_ASSERT_STR(gsi_false, "PS2 Threading unsupported in current version of the SDK\n");
#endif

	// allocate a handle
	info = (GSIResolveHostnameInfo *)gsimalloc(sizeof(GSIResolveHostnameInfo));
	if(!info)
		return -1;

	// make a copy of the hostname so the thread has access to it
	info->hostname = goastrdup(hostname);
	if(!info->hostname)
	{
		gsifree(info);
		return -1;
	}

	// not resolved yet
	info->finishedResolving = 0;

	gsDebugFormat(GSIDebugCat_Common, GSIDebugType_State, GSIDebugLevel_Comment, 
		"(Asynchrounous) DNS lookup starting\n");

	// start the thread
	if(gsiStartThread(gsiResolveHostnameThread, (0x1000), info, &info->threadID) == -1)
	{
		gsifree(info->hostname);
		info->hostname = NULL;
		gsifree(info);
		info = NULL;
		return -1;
	}

	// set the handle to the info
	*handle = info;

	return 0;
}

void gsiCancelResolvingHostname(GSIResolveHostnameHandle handle)
{
	// cancel the thread
	gsiCancelThread(handle->threadID);

	gsifree(handle->hostname);
	handle->hostname = NULL;
	gsifree(handle);
	handle = NULL;
}

unsigned int gsiGetResolvedIP(GSIResolveHostnameHandle handle)
{
	unsigned int ip;

	// check if we haven't finished
	if(!handle->finishedResolving)
		return GSI_STILL_RESOLVING_HOSTNAME;

	// save the ip
	ip = handle->ip;

	// free resources
	gsiCleanupThread(handle->threadID);
	gsifree(handle->hostname);
	gsifree(handle);

	return ip;
}


#else	// if * not a supported platform OR * no threads allowed OR * no async lookup allowed
		///////////////////////////////////////////////////////////////////////////////////
		// if !(_WIN32 ||_PS2 || _LINUX || _MACOSX || _REVOLUTION) || GSI_NO_THREADS || GSI_NO_ASYNC_DNS


///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// ********** NON-ASYNC DNS ********** //
// 
// These are the non-threaded version of the above functions.
// The following platforms have synchronous DNS lookups:
// _NITRO || _XBOX || _X360 || _PS3 || _PS2 || _PSP
///////////////////////////////////////////////////////////////////////////////

int gsiStartResolvingHostname(const char * hostname, GSIResolveHostnameHandle * handle)
{
	GSIResolveHostnameInfo * info;
	HOSTENT * hostent;

	gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, 
		"(NON-Asynchrounous) DNS lookup starting\n");
	
	// do the lookup now
	hostent = gethostbyname(hostname);
	if(hostent == NULL)
		return -1;

	// allocate info to store the result
	info = (GSIResolveHostnameHandle)gsimalloc(sizeof(GSIResolveHostnameInfo));
	if(!info)
		return -1;

	// we already have the ip
	info->ip = *(unsigned int *)hostent->h_addr_list[0];

	// set the handle to the info
	*handle = info;

	return 0;
}

void gsiCancelResolvingHostname(GSIResolveHostnameHandle handle)
{
	gsifree(handle);
}

unsigned int gsiGetResolvedIP(GSIResolveHostnameHandle handle)
{
	// we always to the resolve in the initial call for systems without
	// an async version, so we'll always have the IP at this point
	unsigned int ip = handle->ip;
	gsifree(handle);
	return ip;
}

///////////////////////////////////////////////////////////////////////////////
#endif // synch DNS lookup


///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
char * goastrdup(const char *src)
{
	char *res;
	if(src == NULL)      //PANTS|02.11.00|check for NULL before strlen
		return NULL;
	res = (char *)gsimalloc(strlen(src) + 1);
	if(res != NULL)      //PANTS|02.02.00|check for NULL before strcpy
		strcpy(res, src);
	return res;
}

#if !defined(_WIN32)

char *_strlwr(char *string)
{
	char *hold = string;
	while (*string)
	{
		*string = (char)tolower(*string);
		string++;
	}

	return hold;
}

char *_strupr(char *string)
{
	char *hold = string;
	while (*string)
	{
		*string = (char)toupper(*string);
		string++;
	}

	return hold;
}
#endif


///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void SocketStartUp()
{
#if defined(_WIN32) 
	WSADATA data;

	#if defined(_X360)
		XNetStartupParams xnsp;
		memset(&xnsp,0,sizeof(xnsp));
		xnsp.cfgSizeOfStruct=sizeof(xnsp);
		xnsp.cfgFlags=XNET_STARTUP_BYPASS_SECURITY;
		if(0 != XNetStartup(&xnsp))
		{
			OutputDebugString("XNetStartup failed\n");
		}
	#endif

	// added support for winsock2
	#if (!defined(_XBOX) || defined(_X360)) && (defined(GSI_WINSOCK2) || defined(_X360))
		WSAStartup(MAKEWORD(2,2), &data);
	#else
		WSAStartup(MAKEWORD(1,1), &data);
	#endif
	// end added
#endif
}

void SocketShutDown()
{
#if defined(_WIN32)
	WSACleanup();
	#if defined(_X360)
		XNetCleanup();
	#endif
#endif
}


///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
#ifdef _PS2
extern int sceCdReadClock();

#if !defined(__MWERKS__) && !defined(_PS2)
typedef unsigned char u_char;
#endif

typedef struct {
        u_char stat;            /* status */
        u_char second;          /* second */
        u_char minute;          /* minute */
        u_char hour;            /* hour   */

        u_char pad;             /* pad    */
        u_char day;             /* day    */
        u_char month;           /* month  */
        u_char year;            /* year   */
} sceCdCLOCK;

static unsigned long GetTicks()
{
	unsigned long ticks;
	asm volatile (" mfc0 %0, $9 " : "=r" (ticks));
    return ticks;
}

#define DEC(x) (10*(x/16)+(x%16))
#define _BASE_YEAR 			 70L
#define _MAX_YEAR 			138L
#define _LEAP_YEAR_ADJUST 	 17L
int _days[] = {-1, 30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333, 364};

static time_t _gmtotime_t (
        int yr,     /* 0 based */
        int mo,     /* 1 based */
        int dy,     /* 1 based */
        int hr,
        int mn,
        int sc
        )
{
        int tmpdays;
        long tmptim;
        struct tm tb;

        if ( ((long)(yr -= 1900) < _BASE_YEAR) || ((long)yr > _MAX_YEAR) )
                return (time_t)(-1);

        tmpdays = dy + _days[mo - 1];
        if ( !(yr & 3) && (mo > 2) )
                tmpdays++;

        tmptim = (long)yr - _BASE_YEAR;

        tmptim = ( ( ( ( tmptim ) * 365L
                 + ((long)(yr - 1) >> 2) - (long)_LEAP_YEAR_ADJUST
                 + (long)tmpdays )
                 * 24L + (long)hr )
                 * 60L + (long)mn )
                 * 60L + (long)sc;

        tb.tm_yday = tmpdays;
        tb.tm_year = yr;
        tb.tm_mon = mo - 1;
        tb.tm_hour = hr;
        
        return (tmptim >= 0) ? (time_t)tmptim : (time_t)(-1);
}

time_t time(time_t *timer)
{
	time_t tim;
	sceCdCLOCK clocktime; /* defined in libcdvd.h */

	sceCdReadClock(&clocktime); /* libcdvd.a */

	tim =   _gmtotime_t ( DEC(clocktime.year)+2000,
							DEC(clocktime.month),
							DEC(clocktime.day),
							DEC(clocktime.hour),
							DEC(clocktime.minute),
							DEC(clocktime.second));

	if(timer)
		*timer = tim;
		
	return tim;
}

#endif /* _PS2 */


///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
#if defined(_PS3)
// PS3 time() support is not yet available
time_t time(time_t *timer)
{
	time_t now = 0;

	assert(timer == NULL);

	// Get tick count, and normalize to January 1, 1970 (universal coordinated time)
	now = current_time() / 1000;
	return now;
}
#endif // _PS3


///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
gsi_time current_time()  //returns current time in milliseconds
{ 
#if defined(_WIN32)
	return (GetTickCount()); 

#elif defined(_PS2)
	unsigned int ticks;
	static unsigned int msec = 0;
	static unsigned int lastticks = 0;
	sceCdCLOCK lasttimecalled; /* defined in libcdvd.h */

	if(!msec)
	{
		sceCdReadClock(&lasttimecalled); /* libcdvd.a */
		msec =  (unsigned int)(DEC(lasttimecalled.day) * 86400000) +
				(unsigned int)(DEC(lasttimecalled.hour) * 3600000) +
				(unsigned int)(DEC(lasttimecalled.minute) * 60000) +
				(unsigned int)(DEC(lasttimecalled.second) * 1000);
	}

	ticks = (unsigned int)GetTicks();
	if(lastticks > ticks)
		msec += (unsigned int)(((unsigned int)(-1) - lastticks) + ticks) / 300000;
	else
		msec += (unsigned int)(ticks-lastticks) / 300000;
	lastticks = ticks;

	return msec;

#elif defined(_UNIX)
	struct timeval time;
	
	gettimeofday(&time, NULL);
	return (time.tv_sec * 1000 + time.tv_usec / 1000);

#elif defined(_NITRO)
	assert(OS_IsTickAvailable() == TRUE);
	return (gsi_time)OS_TicksToMilliSeconds(OS_GetTick());

#elif defined(_PSP)
	struct SceRtcTick ticks;
	int result = 0;

	result = sceRtcGetCurrentTick(&ticks);
	if (result < 0)
	{
		ScePspDateTime time;
		result = sceRtcGetCurrentClock(&time, 0);
		if (result < 0)
			return 0; // um...error handling? //Nope, should return zero since time cannot be zero					  
		result = sceRtcGetTick(&time, &ticks);
		if (result < 0)
			return 0; //Nope, should return zero since time cannot be zero
	}

	return (gsi_time)(ticks.tick / 1000);

#elif defined(_PS3)
	return (gsi_time)(sys_time_get_system_time()/1000);

#elif defined(_REVOLUTION)
	OSTick aTickNow= OSGetTick();
	gsi_time aMilliseconds = (gsi_time)OSTicksToMilliseconds(aTickNow);
	return aMilliseconds;
#else
	// unrecognized platform! contact devsupport
	assert(0);
#endif
	
}

gsi_time current_time_hires()  // returns current time in microseconds
{
#ifdef _WIN32
#if (!defined(_M_IX86) || (defined(_INTEGRAL_MAX_BITS) && _INTEGRAL_MAX_BITS >= 64))
	static LARGE_INTEGER counterFrequency;
	static BOOL haveCounterFrequency = FALSE;
	static BOOL haveCounter = FALSE;
	LARGE_INTEGER count;

	if(!haveCounterFrequency)
	{
		haveCounter = QueryPerformanceFrequency(&counterFrequency);
		haveCounterFrequency = TRUE;
	}

	if(haveCounter)
	{
		if(QueryPerformanceCounter(&count))
		{
			return (gsi_time)(count.QuadPart * 1000000 / counterFrequency.QuadPart);
		}
	}
#endif
	
	return (current_time() / 1000);
#endif

#ifdef _PS2
	unsigned int ticks;
	static unsigned int msec = 0;
	static unsigned int lastticks = 0;
	sceCdCLOCK lasttimecalled; /* defined in libcdvd.h */

	if(!msec)
	{
		sceCdReadClock(&lasttimecalled); /* libcdvd.a */
		msec =  (unsigned int)(DEC(lasttimecalled.day) * 86400000) +
				(unsigned int)(DEC(lasttimecalled.hour) * 3600000) +
				(unsigned int)(DEC(lasttimecalled.minute) * 60000) +
				(unsigned int)(DEC(lasttimecalled.second) * 1000);
		msec *= 1000;
	}

	ticks = (unsigned int)GetTicks();
	if(lastticks > ticks)
		msec += ((sizeof(unsigned int) - lastticks) + ticks) / 300;
	else
		msec += (unsigned int)(ticks-lastticks) / 300;
	lastticks = ticks;

	return msec;
#endif

#ifdef _PSP
	struct SceRtcTick ticks;
	int result = 0;

	result = sceRtcGetCurrentTick(&ticks);
	if (result < 0)
	{
		ScePspDateTime time;
		result = sceRtcGetCurrentClock(&time, 0);
		if (result < 0)
			return 0; // um...error handling? //Nope, should return zero since time cannot be zero
		result = sceRtcGetTick(&time, &ticks);
		if (result < 0)
			return 0; //Nope, should return zero since time cannot be zero
	}

	return (gsi_time)(ticks.tick);
#endif

#ifdef _UNIX
	struct timeval time;
	
	gettimeofday(&time, NULL);
	return (time.tv_sec * 1000000 + time.tv_usec);
#endif

#ifdef _NITRO
	assert(OS_IsTickAvailable() == TRUE);
	return (gsi_time)OS_TicksToMicroSeconds(OS_GetTick());
#endif

#ifdef _PS3
	return (gsi_time)sys_time_get_system_time();
#endif
}


void msleep(gsi_time msec)
{
#if defined(_WIN32)
	Sleep(msec);

#elif defined(_PS2)
	#ifdef SN_SYSTEMS
		sn_delay((int)msec);
	#endif
	#ifdef EENET
		if(msec >= 1000)
		{
			sleep(msec / 1000);
			msec -= (msec / 1000);
		}
		if(msec)
			usleep(msec * 1000);
	#endif
	#ifdef INSOCK
		DelayThread(msec * 1000);
	#endif

#elif defined(_PSP)
	sceKernelDelayThread(msec * 1000);

#elif defined(_UNIX)
	usleep(msec * 1000);

#elif defined(_NITRO)
	OS_Sleep(msec);

#elif defined(_PS3)
	sys_timer_usleep(msec* 1000);
#elif defined (_REVOLUTION)
	OSSleepMilliseconds(msec);
#else
	assert(0); // missing platform handler, contact devsupport
#endif
}

//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
// Cross-platform GSI wrapper time conversion functions
// 
// NOTE: some portions of this copied from standard C library
#if defined(_NITRO) || defined(_REVOLUTION)

// if an error occurs when calling mktime, return -1
#define MKTIME_ERROR	  (time_t)(-1)

// define common conversions for mktime
#define DAY_SEC           (24L * 60L * 60L)    /* secs in a day */
#define YEAR_SEC          (365L * DAY_SEC)    /* secs in a year */
#define FOUR_YEAR_SEC     (1461L * DAY_SEC)   /* secs in a 4 year interval */
#define DEC_SEC           315532800L           /* secs in 1970-1979 */
#define BASE_DOW          4                    /* 01-01-70 was a Thursday */
#define BASE_YEAR         70L                  /* 1970 is the base year */
#define LEAP_YEAR_ADJUST  17L                  /* Leap years 1900 - 1970 */
#define MAX_YEAR          138L                 /* 2038 is the max year */

// ChkAdd evaluates to TRUE if dest = src1 + src2 has overflowed
#define ChkAdd(dest, src1, src2)   ( ((src1 >= 0L) && (src2 >= 0L) \
    && (dest < 0L)) || ((src1 < 0L) && (src2 < 0L) && (dest >= 0L)) )

// ChkMul evaluates to TRUE if dest = src1 * src2 has overflowed
#define ChkMul(dest, src1, src2)   ( src1 ? (dest/src1 != src2) : 0 )

int _lpdays[] = { -1, 30, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 };
int _days[] = { -1, 30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333, 364 };

const char _dnames[] = { "SunMonTueWedThuFriSat" };
/*  Month names must be Three character abbreviations strung together */
const char _mnames[] = { "JanFebMarAprMayJunJulAugSepOctNovDec" };

static struct tm tb = { 0 };    /* time block used in SecondsToDate */

static char buf[26];			/* buffer used to store string in SecondsToString */


static char * store_dt(char *, int);
static char * store_dt(char *p, int val)
{
        *p++ = (char)(_T('0') + val / 10);
        *p++ = (char)(_T('0') + val % 10);
        return(p);
}
#endif //_NITRO || _REVOLUTION

//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
// GSI equivalent of Standard C-lib "gmtime function"
struct tm * gsiSecondsToDate(const time_t *timp)
{
#if !defined(_NITRO) && !defined(_REVOLUTION)

	// for all platforms that support the standard C 'gmtime' use that
	return gmtime(timp);

#else
	time_t caltim = *timp;            /* calendar time to convert */
	int islpyr = 0;                 /* is-current-year-a-leap-year flag */
	int tmptim;
	int *mdays;                /* pointer to days or lpdays */
	struct tm *ptb = &tb;


	if ( caltim < 0L )
		return(NULL);

	/*
	 * Determine years since 1970. First, identify the four-year interval
	 * since this makes handling leap-years easy (note that 2000 IS a
	 * leap year and 2100 is out-of-range).
	 */
	tmptim = (int)(caltim / FOUR_YEAR_SEC);
	caltim -= ((long)tmptim * FOUR_YEAR_SEC);

	/*
	 * Determine which year of the interval
	 */
	tmptim = (tmptim * 4) + 70;         /* 1970, 1974, 1978,...,etc. */

	if ( caltim >= YEAR_SEC ) 
	{
		tmptim++;                       /* 1971, 1975, 1979,...,etc. */
		caltim -= YEAR_SEC;

		if ( caltim >= YEAR_SEC ) 
		{
			tmptim++;                   /* 1972, 1976, 1980,...,etc. */
			caltim -= YEAR_SEC;

			/*
			 * Note, it takes 366 days-worth of seconds to get past a leap
			 * year.
			 */
			if ( caltim >= (YEAR_SEC + DAY_SEC) ) 
			{
				tmptim++;           /* 1973, 1977, 1981,...,etc. */
				caltim -= (YEAR_SEC + DAY_SEC);
			}
			else 
			{
				/*
				 * In a leap year after all, set the flag.
				 */
				islpyr++;
			}
		}
	}

	/*
	 * tmptim now holds the value for tm_year. caltim now holds the
	 * number of elapsed seconds since the beginning of that year.
	 */
	ptb->tm_year = tmptim;

	/*
	 * Determine days since January 1 (0 - 365). This is the tm_yday value.
	 * Leave caltim with number of elapsed seconds in that day.
	 */
	ptb->tm_yday = (int)(caltim / DAY_SEC);
	caltim -= (long)(ptb->tm_yday) * DAY_SEC;

	/*
	 * Determine months since January (0 - 11) and day of month (1 - 31)
	 */
	if ( islpyr )
		mdays = _lpdays;
	else
		mdays = _days;


	for ( tmptim = 1 ; mdays[tmptim] < ptb->tm_yday ; tmptim++ ) ;

	ptb->tm_mon = --tmptim;

	ptb->tm_mday = ptb->tm_yday - mdays[tmptim];

	/*
	 * Determine days since Sunday (0 - 6)
	 */
	ptb->tm_wday = ((int)(*timp / DAY_SEC) + BASE_DOW) % 7;

	/*
	 *  Determine hours since midnight (0 - 23), minutes after the hour
	 *  (0 - 59), and seconds after the minute (0 - 59).
	 */
	ptb->tm_hour = (int)(caltim / 3600);
	caltim -= (long)ptb->tm_hour * 3600L;

	ptb->tm_min = (int)(caltim / 60);
	ptb->tm_sec = (int)(caltim - (ptb->tm_min) * 60);

	ptb->tm_isdst = 0;
	return( (struct tm *)ptb );
#endif
}

//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
// GSI equivalent of Standard C-lib "mktime function"
time_t gsiDateToSeconds(struct tm *tb)
{
#if !defined(_NITRO) && !defined(_REVOLUTION)

	// for all platforms that support the standard C 'mktime' use that
	return mktime(tb);

#else
	time_t tmptm1, tmptm2, tmptm3;
	struct tm *tbtemp;
	/*
	 * First, make sure tm_year is reasonably close to being in range.
	 */
	if ( ((tmptm1 = tb->tm_year) < BASE_YEAR - 1) || (tmptm1 > MAX_YEAR + 1) )
		return MKTIME_ERROR;


	/*
     * Adjust month value so it is in the range 0 - 11.  This is because
	 * we don't know how many days are in months 12, 13, 14, etc.
	 */

	if ( (tb->tm_mon < 0) || (tb->tm_mon > 11) ) {

		/*
		 * no danger of overflow because the range check above.
		 */
		tmptm1 += (tb->tm_mon / 12);

		if ( (tb->tm_mon %= 12) < 0 ) {
			tb->tm_mon += 12;
			tmptm1--;
		}

		/*
         * Make sure year count is still in range.
         */
		if ( (tmptm1 < BASE_YEAR - 1) || (tmptm1 > MAX_YEAR + 1) )
			return MKTIME_ERROR;
	}

	/***** HERE: tmptm1 holds number of elapsed years *****/

	/*
	 * Calculate days elapsed minus one, in the given year, to the given
     * month. Check for leap year and adjust if necessary.
     */
	tmptm2 = _days[tb->tm_mon];
	if ( !(tmptm1 & 3) && (tb->tm_mon > 1) )
                tmptm2++;

	/*
	 * Calculate elapsed days since base date (midnight, 1/1/70, UTC)
	 *
	 *
	 * 365 days for each elapsed year since 1970, plus one more day for
	 * each elapsed leap year. no danger of overflow because of the range
	 * check (above) on tmptm1.
	 */
    tmptm3 = (tmptm1 - BASE_YEAR) * 365L + ((tmptm1 - 1L) >> 2)
			 - LEAP_YEAR_ADJUST;

	/*
	 * elapsed days to current month (still no possible overflow)
	 */
	tmptm3 += tmptm2;

	/*
	 * elapsed days to current date. overflow is now possible.
	 */
	tmptm1 = tmptm3 + (tmptm2 = (long)(tb->tm_mday));
	if ( ChkAdd(tmptm1, tmptm3, tmptm2) )
		return MKTIME_ERROR;

	/***** HERE: tmptm1 holds number of elapsed days *****/

	/*
	 * Calculate elapsed hours since base date
	 */
	tmptm2 = tmptm1 * 24L;
	if ( ChkMul(tmptm2, tmptm1, 24L) )
		return MKTIME_ERROR;

	tmptm1 = tmptm2 + (tmptm3 = (long)tb->tm_hour);
	if ( ChkAdd(tmptm1, tmptm2, tmptm3) )
		return MKTIME_ERROR;

	/***** HERE: tmptm1 holds number of elapsed hours *****/

	/*
	 * Calculate elapsed minutes since base date
	 */

	tmptm2 = tmptm1 * 60L;
	if ( ChkMul(tmptm2, tmptm1, 60L) )
		return MKTIME_ERROR;

	tmptm1 = tmptm2 + (tmptm3 = (long)tb->tm_min);
	if ( ChkAdd(tmptm1, tmptm2, tmptm3) )
		return MKTIME_ERROR;

	/***** HERE: tmptm1 holds number of elapsed minutes *****/

	/*
	 * Calculate elapsed seconds since base date
	 */

	tmptm2 = tmptm1 * 60L;
	if ( ChkMul(tmptm2, tmptm1, 60L) )
		return MKTIME_ERROR;

	tmptm1 = tmptm2 + (tmptm3 = (long)tb->tm_sec);
	if ( ChkAdd(tmptm1, tmptm2, tmptm3) )
		return MKTIME_ERROR;

	/***** HERE: tmptm1 holds number of elapsed seconds *****/

	if ( (tbtemp = gsiSecondsToDate(&tmptm1)) == NULL )
		return MKTIME_ERROR;
        

	/***** HERE: tmptm1 holds number of elapsed seconds, adjusted *****/
	/*****       for local time if requested                      *****/

	*tb = *tbtemp;
	return (time_t)tmptm1;
#endif
}

//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
// GSI equivalent of Standard C-lib "ctime function"
char * gsiSecondsToString(const time_t *timp)
{
#if !defined(_NITRO) && !defined(_REVOLUTION)

	// for all platforms that support the standard C 'ctime' use that
	return ctime(timp);

#else
	char *p = buf;
	int day, mon;
	int i;
	struct tm *ptm;

	ptm = gsiSecondsToDate(timp);	 /* parse seconds into date structure */

    p = buf;						 /* use static buffer */

	/* copy day and month names into the buffer */

	day = ptm->tm_wday * 3;          /* index to correct day string */
	mon = ptm->tm_mon * 3;           /* index to correct month string */

	for (i=0; i < 3; i++,p++) {
		*p = *(_dnames + day + i);
		*(p+4) = *(_mnames + mon + i);
	}

	*p = _T(' ');                   /* blank between day and month */

	p += 4;

	*p++ = _T(' ');
	p = store_dt(p, ptm->tm_mday);   /* day of the month (1-31) */
	*p++ = _T(' ');
	p = store_dt(p, ptm->tm_hour);   /* hours (0-23) */
	*p++ = _T(':');
	p = store_dt(p, ptm->tm_min);    /* minutes (0-59) */
	*p++ = _T(':');
	p = store_dt(p, ptm->tm_sec);    /* seconds (0-59) */
	*p++ = _T(' ');
	p = store_dt(p, 19 + (ptm->tm_year/100)); /* year (after 1900) */
	p = store_dt(p, ptm->tm_year%100);
	*p++ = _T('\n');
	*p = _T('\0');

	return ((char *) buf);
#endif
}


///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Cross platform random number generator
#define RANa 16807                 // multiplier
#define LONGRAND_MAX 2147483647L   // 2**31 - 1

static long randomnum = 1;

static long nextlongrand(long seed)
{
	unsigned

	long lo, hi;
	lo = RANa *(unsigned long)(seed & 0xFFFF);
	hi = RANa *((unsigned long)seed >> 16);
	lo += (hi & 0x7FFF) << 16;

	if (lo > LONGRAND_MAX)
	{
		lo &= LONGRAND_MAX;
		++lo;
	}
	lo += hi >> 15;

	if (lo > LONGRAND_MAX)
	{
		lo &= LONGRAND_MAX;
		++lo;
	}

	return(long)lo;
}

// return next random long
static long longrand(void)
{
	randomnum = nextlongrand(randomnum);
	return randomnum;
}

// to seed it
void Util_RandSeed(unsigned long seed)
{
	// nonzero seed
	randomnum = seed ? (long)(seed & LONGRAND_MAX) : 1;
}

int Util_RandInt(int low, int high)
{
	unsigned int range = (unsigned int)high-low;
	int num;
	
	if (range == 0)
		return (low); // Prevent divide by zero

	num = (int)(longrand() % range);

	return(num + low);
}


///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
/*****************************
UNICODE ENCODING
******************************/

static void QuartToTrip(char *quart, char *trip, int inlen)
{
	if (inlen >= 2)
		trip[0] = (char)(quart[0] << 2 | quart[1] >> 4);
	if (inlen >= 3)
		trip[1] = (char)((quart[1] & 0x0F) << 4 | quart[2] >> 2);
	if (inlen >= 4)
		trip[2] = (char)((quart[2] & 0x3) << 6 | quart[3]);
}

static void TripToQuart(const char *trip, char *quart, int inlen)
{
	unsigned char triptemp[3];
	int i;
	for (i = 0; i < inlen ; i++)
	{
		triptemp[i] = (unsigned char)trip[i];
	}
	while (i < 3) //fill the rest with 0
	{
		triptemp[i] = 0;
		i++;
	}
	quart[0] = (char)(triptemp[0] >> 2);
	quart[1] = (char)(((triptemp[0] & 3) << 4) | (triptemp[1] >> 4));
	quart[2] = (char)((triptemp[1] & 0x0F) << 2 | (triptemp[2] >> 6));
	quart[3] = (char)(triptemp[2] & 0x3F);

}

const char defaultEncoding[] = {'+','/','='};
const char alternateEncoding[] = {'[',']','_'};
const char urlSafeEncodeing[] = {'-','_','='};

void B64Decode(const char *input, char *output, int inlen, int * outlen, int encodingType)
{
	const char *encoding = NULL;
	const char *holdin = input;
	int readpos = 0;
	int writepos = 0;
	char block[4];
	
	//int outlen = -1;
	//int inlen = (int)strlen(input);

	// 10-31-2004 : Added by Saad Nader
	// now supports URL safe encoding
	////////////////////////////////////////////////
	switch(encodingType)
	{	
		case 1: 
			encoding = alternateEncoding;
			break;
		case 2:
			encoding = urlSafeEncodeing;
			break;
		default: encoding = defaultEncoding;
	}

	GS_ASSERT(inlen >= 0);
	if (inlen <= 0)
	{
		if (outlen)
			*outlen = 0;
		output[0] = '\0';
		return;
	}

	// Break at end of string or padding character
	while (readpos < inlen && input[readpos] != encoding[2])
	{
		//    'A'-'Z' maps to 0-25
		//    'a'-'z' maps to 26-51
		//    '0'-'9' maps to 52-61
		//    62 maps to encoding[0]
		//    63 maps to encoding[1]
		if (input[readpos] >= '0' && input[readpos] <= '9')
			block[readpos%4] = (char)(input[readpos] - 48 + 52);
		else if (input[readpos] >= 'a' && input[readpos] <= 'z')
			block[readpos%4] = (char)(input[readpos] - 71);
		else if (input[readpos] >= 'A' && input[readpos] <= 'Z')
			block[readpos%4] = (char)(input[readpos] - 65);
		else if (input[readpos] == encoding[0])
			block[readpos%4] = 62;
		else if (input[readpos] == encoding[1])
			block[readpos%4] = 63;

		// padding or '\0' characters also mark end of input
		else if (input[readpos] == encoding[2])
			break;
		else if (input[readpos] == '\0')
			break;
		else 
		{
			//	(assert(0)); //bad input data
			if (outlen)
				*outlen = 0;
			output[0] = '\0';
			return; //invaid data
		}

		// every 4 bytes, convert QuartToTrip into destination
		if (readpos%4==3) // zero based, so (3%4) means four bytes, 0-1-2-3
		{
			QuartToTrip(block, &output[writepos], 4);
			writepos += 3;
		}
		readpos++;
	}

	// Convert any leftover characters in block
	if ((readpos != 0) && (readpos%4 != 0))
	{
		// fill block with pad (required for QuartToTrip)
		memset(&block[readpos%4], encoding[2], (unsigned int)4-(readpos%4)); 
		QuartToTrip(block, &output[writepos], readpos%4);

		// output bytes depend on the number of non-pad input bytes
		if (readpos%4 == 3)
			writepos += 2;
		else 
			writepos += 1;
	}

	if (outlen)
		*outlen = writepos;

	GSI_UNUSED(holdin);
}



void B64Encode(const char *input, char *output, int inlen, int encodingType)
{
	const char *encoding;
	char *holdout = output;
	char *lastchar;
	int todo = inlen;
	
	// 10-31-2004 : Added by Saad Nader
	// now supports URL safe encoding
	////////////////////////////////////////////////
	switch(encodingType)
	{	
		case 1: 
			encoding = alternateEncoding;
			break;
		case 2:
			encoding = urlSafeEncodeing;
			break;
		default: encoding = defaultEncoding;
	}
	
//assume interval of 3
	while (todo > 0)
	{
		TripToQuart(input, output, min(todo, 3));
		output += 4;
		input += 3;
		todo -= 3;
	}
	lastchar = output;
	if (inlen % 3 == 1)
		lastchar -= 2;
	else if (inlen % 3 == 2)
		lastchar -= 1;
	*output = 0; //null terminate!
	while (output > holdout)
	{
		output--;
		if (output >= lastchar) //pad the end
			*output = encoding[2];
		else if (*output <= 25)
			*output = (char)(*output + 65);
		else if (*output <= 51)
			*output = (char)(*output + 71);
		else if (*output <= 61)
			*output = (char)(*output + 48 - 52);
		else if (*output == 62)
			*output = encoding[0];
		else if (*output == 63)
			*output = encoding[1];
	} 
}

int B64DecodeLen(const char *input, int encodingType)
{
	const char *encoding;
	const char *holdin = input;

	switch(encodingType)
	{	
		case 1: 
			encoding = alternateEncoding;
			break;
		case 2:
			encoding = urlSafeEncodeing;
			break;
		default: encoding = defaultEncoding;
	}

	while (*input)
	{
		if (*input == encoding[2])
			return (input - holdin) / 4 * 3 + (input - holdin - 1) % 4;
		input++;
	}

	return (input - holdin) / 4 * 3;
}

void B64InitEncodeStream(B64StreamData *data, const char *input, int len, int encodingType)
{
	data->input = input;
	data->len = len;
	data->encodingType = encodingType;
}

gsi_bool B64EncodeStream(B64StreamData *data, char output[4])
{
	const char *encoding;
	char *c;
	int i;

	if(data->len <= 0)
		return gsi_false;
	
	// 10-31-2004 : Added by Saad Nader
	// now supports URL safe encoding
	////////////////////////////////////////////////
	switch(data->encodingType)
	{	
		case 1: 
			encoding = alternateEncoding;
			break;
		case 2:
			encoding = urlSafeEncodeing;
			break;
		default: encoding = defaultEncoding;
	}

	TripToQuart(data->input, output, min(data->len, 3));
	data->input += 3;
	data->len -= 3;

	for(i = 0 ; i < 4 ; i++)
	{
		c = &output[i];
		if (*c <= 25)
			*c = (char)(*c + 65);
		else if (*c <= 51)
			*c = (char)(*c + 71);
		else if (*c <= 61)
			*c = (char)(*c + 48 - 52);
		else if (*c == 62)
			*c = encoding[0];
		else if (*c == 63)
			*c = encoding[1];
	}

	if(data->len < 0)
	{
		output[3] = encoding[2];
		if(data->len == -2)
			output[2] = encoding[2];
	}

	return gsi_true;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void gsiPadRight(char *cArray, char padChar, int cLength);
char * gsiXxteaAlg(const char *sIn, int nIn, char key[XXTEA_KEY_SIZE], int bEnc, int *nOut);

void gsiPadRight(char *cArray, char padChar, int cLength)
{
	int diff;
	int length = (int)strlen(cArray);
	
	diff = cLength - length;
	memset(&cArray[length], padChar, (size_t)diff);
}


///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// The heart of the XXTEA encryption/decryption algorithm.
//
// sIn:  Input stream.
// nIn:  Input length (bytes).
// key:  Key (only first 128 bits are significant).
// bEnc: Encrypt (else decrypt)?
char * gsiXxteaAlg(const char *sIn, int nIn, char key[XXTEA_KEY_SIZE], int bEnc, int *nOut)
{
	int	i, p, n1;
	unsigned int *k, *v, z, y;
	char *oStr = NULL, *pStr = NULL;
	char *sIn2 = NULL;
	/////////////////////////////////
	// ERROR CHECK!
	if (!sIn || !key[0] || nIn == 0)
		return NULL;
	
	// Convert stream length to a round number of 32-bit words
	// Convert byte	count to 32-bit	word count
	if (nIn % 4 == 0)			// Fix for null terminated strings divisible by 4
		nIn	= (nIn/4)+1;
	else
		nIn	= (nIn + 3)/4;

	if ( nIn <=	1 )		// XXTEA requires at least 64 bits
		nIn	= 2;

	// Load	and	zero-pad first 16 characters (128 bits)	of key
	gsiPadRight( key , '\0', XXTEA_KEY_SIZE);
	k = (unsigned int *)key;

	// Load and zero-pad entire input stream as 32-bit words
	sIn2 = (char *)gsimalloc((size_t)(4 * nIn));
	strcpy(sIn2, sIn);
	gsiPadRight( sIn2, '\0', 4*nIn);
	v = (unsigned int *)sIn2;

	// Prepare to encrypt or decrypt
	n1 = nIn - 1;
	z = v[ n1 ];
	y = v[ 0 ];
	i = ( int )( 6 + 52/nIn );

	if (bEnc == 1)		// Encrypt
	{
		unsigned int sum = 0;
		while ( i-- != 0 ) 
		{
			int	e;
			sum += 0x9E3779B9;
			e = ( int )( sum >> 2 );
			for ( p = -1; ++p < nIn; ) 
			{
				y = v[( p < n1 ) ? p + 1 : 0 ];
				z = ( v[ p ] +=
					(	 (( z >> 5 ) ^ ( y << 2 ))
					+ (( y >> 3 ) ^ ( z << 4 )))
					^ (	 ( sum ^ y )
					+ ( k[( p ^ e ) & 3 ] ^ z )));
			}
		}
	}
	else if (bEnc == 0)			// Decrypt
	{
		unsigned int sum = ( unsigned int ) i * 0x9E3779B9;
		while ( sum != 0 ) 
		{
			int	e = ( int )( sum >> 2 );
			for ( p = nIn; p-- != 0; )
			{
				z = v[( p != 0 ) ? p - 1 : n1 ];
				y = ( v[ p ] -=
					(	 (( z >> 5 ) ^ ( y << 2 ))
					+ (( y >> 3 ) ^ ( z << 4 )))
					^ (	 ( sum ^ y )
					+ ( k[( p ^ e ) & 3 ] ^ z )));
			}
			sum -= 0x9E3779B9;
		}
	}
	else return NULL;
	// Convert result from 32-bit words to a byte stream
	
	
	oStr = (char *)gsimalloc((size_t)(4 * nIn + 1));
	pStr = oStr;
	*nOut = 4 *nIn;
	for ( i = -1; ++i < nIn; ) 
	{
		unsigned int q = v[ i ];
		
		*pStr++ = (char)(q & 0xFF);
		*pStr++ = (char)(( q >>  8 ) & 0xFF);
		*pStr++ = (char)(( q >> 16 ) & 0xFF);
		*pStr++ = (char)(( q >> 24 ) & 0xFF);
	}
	*pStr = '\0';
	gsifree(sIn2);

	return oStr;	
}


///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// XXTEA Encrpyt
// params
// iStr    : the input string to be encrypted
// iLength : the length of the input string
// key     : the key used to encrypt
char * gsXxteaEncrypt(const char * iStr, int iLength, char key[XXTEA_KEY_SIZE], int *oLength)
{
	return gsiXxteaAlg( iStr, iLength, key, 1, oLength );
}


///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// XXTEA Decrypt
// params
// iStr    : the input string to be decrypted
// iLength : the length of the input string
// key     : the key used to decrypt
char * gsXxteaDecrypt(const char * iStr, int iLength, char key[XXTEA_KEY_SIZE], int *oLength)
{
	return gsiXxteaAlg( iStr, iLength, key, 0, oLength);
}


///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
#if defined(_DEBUG)

void gsiCheckStack(void)
{
#if defined(_NITRO)
#if 1
	OS_CheckStack(OS_GetCurrentThread());
#elif 1
	static gsi_bool checkFailed = gsi_false;
	if(!checkFailed)
	{
		OSStackStatus status = OS_GetStackStatus(OS_GetCurrentThread());
		if(status != 0)
		{
			const char * reason;
			if(status == OS_STACK_OVERFLOW)
				reason = "OVERFLOW";
			else if(status == OS_STACK_ABOUT_TO_OVERFLOW)
				reason = "ABOUT TO OVERFLOW";
			else if(status == OS_STACK_UNDERFLOW)
				reason = "UNDERFLOW";
			else
				reason = "UNKOWN REASON";

			OS_TPrintf("STACK CHECK FAILED!: %s\n", reason);

			checkFailed = gsi_true;
		}
	}
#endif
#endif // nitro
}
#endif // _DEBUG


///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
#ifdef SN_SYSTEMS
int GOAGetLastError(SOCKET s)
{
	int val = 0;
	int soval = sizeof(val);
	if (0 != getsockopt(s,SOL_SOCKET,SO_ERROR,&val,&soval))
		return 0; // getsockopt failed
	else
		return val;
}
#endif

#ifdef _NITRO
static const char * GOAGetUniqueID_Internal(void)
{
	static char keyval[17];
	u8 MAC[MAC_ALEN];

	// check if we already have the Unique ID
	if(keyval[0])
		return keyval;

	// get the MAC
	IP_GetMacAddr(NULL, MAC);

	// format it
	sprintf(keyval, "%02X%02X%02X%02X%02X%02X0000",
		MAC[0] & 0xFF,
		MAC[1] & 0xFF,
		MAC[2] & 0xFF,
		MAC[3] & 0xFF,
		MAC[4] & 0xFF,
		MAC[5] & 0xFF);

	return keyval;
}
#endif


#ifdef _PS2 
#ifdef UNIQUEID

#if defined(EENET)

#include <net/if_dl.h>
// Removed due to updated sony libraries,  Saad Nader
//#include <net/if_types.h>
#include <net/if_ether.h>

static const char * GetMAC(void)
{
	static struct sceEENetEtherAddr linkAddress;
	struct sceEENetIfname * interfaces;
	struct sceEENetIfname * interface;
	int num;
	int type;
	int len;
	int i;
	const unsigned char * MAC = NULL;

	// get the local interfaces
	sceEENetGetIfnames(NULL, &num);
	interfaces = (struct sceEENetIfname *)gsimalloc(num * sizeof(struct sceEENetIfname));
	if(!interfaces)
		return NULL;
	sceEENetGetIfnames(interfaces, &num);

	// loop through the interfaces
	for(i = 0 ; i < num ; i++)
	{
		// the next interface
		interface = &interfaces[i];
		//printf("eenet%d: %s\n", i, interface->ifn_name);

		// get the type
		len = sizeof(type);
		if(sceEENetGetIfinfo(interface->ifn_name, sceEENET_IFINFO_IFTYPE, &type, &len) != 0)
			continue;
		//printf("eenet%d type: %d\n", i, type);

		// check for ethernet
		if(type != sceEENET_IFTYPE_ETHER)
			continue;
		//printf("eenet%d: ethernet\n", i);

		// get the address
		len = sizeof(linkAddress);
		if(sceEENetGetIfinfo(interface->ifn_name, sceEENET_IFINFO_MACADDR, &linkAddress, &len) != 0)
			continue;
		MAC = linkAddress.ether_addr_octet;
		//printf("eenet%d: MAC: %02X-%02X-%02X-%02X-%02X-%02X\n", i, MAC[0], MAC[1], MAC[2], MAC[3], MAC[4], MAC[5]);

		break;
	}

	// free the interfaces
	gsifree(interfaces);

	return MAC;
}

#elif defined(SN_SYSTEMS)

	static const char * GetMAC(void)
	{
		static char MAC[6];
		int len = sizeof(MAC);
		int rcode;

		// get the MAC
		rcode = sndev_get_status(0, SN_DEV_STAT_MAC, MAC, &len);
		if((rcode != 0) || (len != 6))
			return NULL;

		return MAC;
	}

#elif defined(INSOCK)

	static const char * GetMAC(void)
	{
		// Get the MAC address using the interface control
		static char MAC[16];
		extern sceSifMClientData gGSIInsockClientData;
		extern u_int             gGSIInsockSocketBuffer[NETBUFSIZE] __attribute__((aligned(64)));

		int result = sceInetInterfaceControl(&gGSIInsockClientData, &gGSIInsockSocketBuffer,
			                                 1, sceInetCC_GetHWaddr, MAC, sizeof(MAC));		
		if (result == sceINETE_OK)
			return MAC;

		// error
		return NULL;
	}

#endif

static const char * GOAGetUniqueID_Internal(void)
{
	static char keyval[17];
	const char * MAC;

	// check if we already have the Unique ID
	if(keyval[0])
		return keyval;

	// get the MAC
	MAC = GetMAC();
	if(!MAC)
	{
		// error getting the MAC
		static char errorMAC[6] = { 1, 2, 3, 4, 5, 6 };
		MAC = errorMAC;
	}

	// format it
	sprintf(keyval, "%02X%02X%02X%02X%02X%02X0000",
		MAC[0] & 0xFF,
		MAC[1] & 0xFF,
		MAC[2] & 0xFF,
		MAC[3] & 0xFF,
		MAC[4] & 0xFF,
		MAC[5] & 0xFF);

	return keyval;
}

#endif // UNIQUEID
#endif // _PS2


#if ((defined(_WIN32) && !defined(_XBOX)) || defined(_UNIX))

static void GenerateID(char *keyval)
{
	int i;
	const char crypttab[63] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
#ifdef _WIN32
	LARGE_INTEGER l1;
	UINT seed;
	if (QueryPerformanceCounter(&l1))
		seed = (l1.LowPart ^ l1.HighPart);
	else
		seed = 0;
	Util_RandSeed(seed ^ GetTickCount() ^ (unsigned long)time(NULL) ^ clock());
#else
	Util_RandSeed(time(NULL) ^ clock());
#endif
	for (i = 0; i < 19; i++)
		if (i == 4 || i == 9 || i == 14)
			keyval[i] = '-';
	else
		keyval[i] = crypttab[Util_RandInt(0, 62)];
	keyval[19] = 0;
}

#ifndef PATH_MAX
#define PATH_MAX MAX_PATH
#endif

#ifdef _WIN32
#define REG_KEY	  "Software\\GameSpy\\GameSpy 3D\\Registration"
#endif

const char * GOAGetUniqueID_Internal(void)
{
	static char keyval[PATH_MAX] = "";
	unsigned int ret;

#ifdef _WIN32
	int docreate;
	HKEY thekey;
	DWORD thetype = REG_SZ;
	DWORD len = MAX_PATH;
	DWORD disp;

	if (RegOpenKeyExA(HKEY_CURRENT_USER, REG_KEY, 0, KEY_ALL_ACCESS, &thekey) != ERROR_SUCCESS)
		docreate = 1;
	else
		docreate = 0;
	ret = RegQueryValueExA(thekey, (LPCSTR)"Crypt", 0, &thetype, (LPBYTE)keyval, &len);
#else
	FILE *f;
	f = fopen("id.bin","r");
	if (!f)
		ret = 0;
	else
	{
		ret = fread(keyval,1,19,f);
		keyval[ret] = 0;
		fclose(f);
	}
#endif

	if (ret != 0 || strlen(keyval) != 19)//need to generate a new key
	{
		GenerateID(keyval);
#ifdef _WIN32
		if (docreate)
		{
			ret = RegCreateKeyExA(HKEY_CURRENT_USER, REG_KEY, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &thekey, &disp);
		}
		RegSetValueExA(thekey, (LPCSTR)"Crypt", 0, REG_SZ, (const LPBYTE)keyval, strlen(keyval)+1);
#else
		f = fopen("id.bin","w");
		if (f)
		{
			fwrite(keyval,1,19,f);
			fclose(f);
		} else
			keyval[0] = 0; //don't generate one each time!!
#endif
	}

#ifdef _WIN32
	RegCloseKey(thekey);
#endif

	// Strip out the -'s.
	/////////////////////
	memmove(keyval + 4, keyval + 5, 4);
	memmove(keyval + 8, keyval + 10, 4);
	memmove(keyval + 12, keyval + 15, 4);
	keyval[16] = '\0';
	
	return keyval;
}

#endif

#ifdef _PSP
// Included here so that the implementation can appear in gsPlatformPSP.c
const char * GOAGetUniqueID_Internal(void);
#endif


#if (!defined(_PS2) && !defined(_PS3) && !defined(_XBOX) && !defined(_PSP)) || defined(UNIQUEID)
GetUniqueIDFunction GOAGetUniqueID = GOAGetUniqueID_Internal;
#endif

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
#if defined(__cplusplus)
}
#endif
