//
// Asura_PlatformFileAccess.cpp
//
// Created by Kevin Lea - 07/09/2001 16:21:41
// 
// Copyright (C) 2001 Rebellion, All Rights Reserved.
//

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

#ifdef ASURA_PS2_USE_MULTISTREAM_FOR_FILE_ACCESS

//  This file's header.
#include "FileAccess/Asura_Platform_FileAccess.h"

// Standard platform-specific.
#include <eetypes.h>

// Engine independent.
#include "Core/Asura_Handle.h"
#include "Core/Asura_Maths.h"
#include "Core/Asura_String.h"
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_Logfile.h"
#include "FileAccess/Asura_FileAccess.h"

// Engine platform-specific.
#include "Core/Asura_PS2_OS.h"
#include "Sound/Asura_PS2_Multistream.h"

#include <sifdev.h> // sceDopen etc.

// D E F I N E S /////////////////////////////////////////////////////////////

#define ENABLE_FAST_COPYBYTES (1)

// T Y P E S /////////////////////////////////////////////////////////////////

struct BufferInfo
{
#if _DEBUG
    bool          bInUse;
    bool          bPreload;
#endif
    u_int         uBytesRemaining; // Num bytes remaining in cur MS buffer.
    const u_char* pucData;         // The next address that we'll be reading from in the MS buffer we're using.
};

// C O N S T A N T S /////////////////////////////////////////////////////////

static const u_int COPYBYTES_BY_QW_THRESHOLD = 256;        // Bytes.
static const u_int LARGE_SKIP_THRESHOLD      = 128 * 1024; //   "

// G L O B A L S /////////////////////////////////////////////////////////////

static BufferInfo g_axBufferInfo[ASURA_MAX_NO_OF_OPEN_FILES];

// P R O T O T Y P E S   A N D   I N L I N E   F U N C T I O N S /////////////

static void CopyBytes( u_char* pucDest, const u_char* pucSrc, u_int uNumBytes );
static bool GetMoreDataFromMS( Asura_Handle iHandle );

// C L A S S   I M P L E M E N T A T I O N S /////////////////////////////////

//
// Asura_FileAccess.
//

bool Asura_FileAccess::Platform_Close( Asura_Handle iHandle )
{
#if _DEBUG
    Asura_Assert( g_axBufferInfo[iHandle].bInUse, "No file open for this handle." );
    g_axBufferInfo[iHandle].bInUse = false;
#endif

    Asura_PS2_Multistream_FastLoad::CloseFile( iHandle );
    return true;
}

void Asura_FileAccess::Platform_Initialise( void )
{
#if _DEBUG
    for( u_int u = 0; u < ASURA_MAX_NO_OF_OPEN_FILES; ++u )
    {
        g_axBufferInfo[u].bInUse = false;
    }
#endif
}

bool Asura_FileAccess::Platform_Open( Asura_Handle iHandle, const char* szFilename )
{
    if( !Asura_PS2_Multistream_FastLoad::OpenFile(iHandle, szFilename) )
    {
        return false;
    }

#if _DEBUG
    Asura_Assert( !g_axBufferInfo[iHandle].bInUse, "File already open for this handle." );
    g_axBufferInfo[iHandle].bInUse = true;
    g_axBufferInfo[iHandle].bPreload = false;
#endif

    g_axBufferInfo[iHandle].uBytesRemaining = 0;
    return true;
}

// (MP - 03.02.2003) While it seems like it should be a win, my tests show
// that passing in an uncached pointer into this function is actually slower
// than using plain old cached memory.
bool Asura_FileAccess::Platform_ReadData( Asura_Handle iHandle, void* pvDest, u_int uNumBytes )
{
#if _DEBUG
    Asura_Assert( g_axBufferInfo[iHandle].bInUse, "No file open for this handle." );
#endif

    u_char* pucDest = reinterpret_cast<u_char*>( pvDest );
    while( uNumBytes )
    {
        // Any useful data in existing buffer?
        if( g_axBufferInfo[iHandle].uBytesRemaining )
        {
            u_int uToCopy = Asura_Maths::Min( uNumBytes, g_axBufferInfo[iHandle].uBytesRemaining );
            CopyBytes( pucDest, g_axBufferInfo[iHandle].pucData, uToCopy );

            g_axBufferInfo[iHandle].pucData         += uToCopy;
            g_axBufferInfo[iHandle].uBytesRemaining -= uToCopy;
            pucDest                                 += uToCopy;
            uNumBytes                               -= uToCopy;
        }

        // Need more data?
        if( !g_axBufferInfo[iHandle].uBytesRemaining )
        {
            if( !GetMoreDataFromMS(iHandle) )
            {
                break;
            }
        }
    }

    // If we reached this point with bytes remaining then we reached EOF
    // prematurely.
    if( uNumBytes )
    {
        return false;
    }

    return true;
}

void Asura_FileAccess::Platform_Shutdown( void )
{
}

bool Asura_FileAccess::Platform_SkipUncompressedData( Asura_Handle iHandle, u_int uNumBytes )
{
#if _DEBUG
    Asura_Assert( g_axBufferInfo[iHandle].bInUse, "No file open for this handle." );
#endif

    if( uNumBytes >= LARGE_SKIP_THRESHOLD )
    {
        // Skipping a lot of data so just seek.
        g_axBufferInfo[iHandle].uBytesRemaining = 0;
        Asura_PS2_Multistream_FastLoad::Seek( iHandle, GetFilePosition(iHandle) + uNumBytes );
    }
    else
    {
        while( true )
        {
            if( g_axBufferInfo[iHandle].uBytesRemaining >= uNumBytes )
            {
                // Enough data left in buffer to cover what we're skipping.
                g_axBufferInfo[iHandle].pucData         += uNumBytes;
                g_axBufferInfo[iHandle].uBytesRemaining -= uNumBytes;
                break;
            }
            else
            {
                // Skip remainder of buffer.
                uNumBytes -= g_axBufferInfo[iHandle].uBytesRemaining;

                // And get more data.
                if( !GetMoreDataFromMS(iHandle) )   
                {
                    // Error: reached EOF before skipping enough data.
                    return false;
                }           
            }
        }
    }

    return true;
}

bool Asura_FileAccess::Platform_FileExists( const char* szFilename )
{
	Asura_Handle iHandle = Open( szFilename );
	if( ASURA_INVALID_HANDLE == iHandle )
	{
		return false;
	}
	else
	{
		Close( iHandle );
		return true;
	}	
}

//
// Asura_Platform_FileAccess.
//

void Asura_Platform_FileAccess::BeginDirectMultistreamAccess( Asura_Handle iHandle )
{
#if _DEBUG
    Asura_Assert( g_axBufferInfo[iHandle].bInUse, "No file open for this handle." );
#endif
    
    g_axBufferInfo[iHandle].uBytesRemaining = 0;
}

void Asura_Platform_FileAccess::EndDirectMultistreamAccess( Asura_Handle iHandle, u_int uFilePos )
{
    Asura_FileAccess::SetFilePosition( iHandle, uFilePos );
}

// F U N C T I O N   I M P L E M E N T A T I O N S ///////////////////////////

#if ENABLE_FAST_COPYBYTES

static void CopyBytes( u_char* pucDest, const u_char* pucSrc, u_int uNumBytes )
{
    // (MP - 01.02.2003) TODO: For really big transfers (that can be aligned
    // to QW boundaries as below), it would be *much* quicker to DMA the data
    // (mem->SPR->mem) than to copy. Of course, this is awkward because it
    // requires SPR to be free and there's no way we can get information from
    // the independent interface about whether or not it's okay to use SPR...

    if( uNumBytes > COPYBYTES_BY_QW_THRESHOLD )
    {
        const u_int uDestAlign = (reinterpret_cast<u_int>(pucDest) & 15);
        const u_int uSrcAlign  = (reinterpret_cast<u_int>(pucSrc)  & 15);
        if( uDestAlign == uSrcAlign )
        {
            // Get pointers to QW alignment.
            if( uDestAlign )
            {
                u_int uToPtrAlignment = 16 - uDestAlign;
                uNumBytes -= uToPtrAlignment;
                while( uToPtrAlignment-- )
                {
                    *pucDest++ = *pucSrc++;
                }
            }

            // Now copy in pairs of QWs as far as possible. We do 2 QWs at a
            // time because this allows us to fill up the minimum number of
            // loop cycles. This is a win even if it does mean we may have to
            // copy a QW at the end in words or bytes.
            {
                u_int uNumDoubleQWs = (uNumBytes >> 5);
                uNumBytes -= (uNumDoubleQWs << 5);

                for( ; uNumDoubleQWs; --uNumDoubleQWs )
                {
                    *(reinterpret_cast<u_long128*>(pucDest +  0)) = *(reinterpret_cast<const u_long128*>(pucSrc +  0));
                    *(reinterpret_cast<u_long128*>(pucDest + 16)) = *(reinterpret_cast<const u_long128*>(pucSrc + 16));
                    pucDest += 32;
                    pucSrc  += 32;
                }
            }
        }
    }

    // Copy any bytes remaining.
    while( uNumBytes-- )
    {
        *pucDest++ = *pucSrc++;
    }
}

#else // not ENABLE_FAST_COPYBYTES

static void CopyBytes( u_char* pucDest, const u_char* pucSrc, u_int uNumBytes )
{
	while( uNumBytes-- )
	{
		*pucDest++ = *pucSrc++;
	}
}

#endif // else not ENABLE_FAST_COPYBYTES

static bool GetMoreDataFromMS( Asura_Handle iHandle )
{
    int iBytesRead = Asura_PS2_Multistream_FastLoad::GetData( iHandle, reinterpret_cast<const void**>(&g_axBufferInfo[iHandle].pucData) );
    if( Asura_PS2_Multistream_FastLoad::REACHED_EOF == iBytesRead )
    {
        return false;
    }

    Asura_Assert( iBytesRead >= 0, "" );
    g_axBufferInfo[iHandle].uBytesRemaining = iBytesRead;
    return true;
}

#else // not defined ASURA_PS2_USE_MULTISTREAM_FOR_FILE_ACCESS

// Standard platform-specific.
#include <eekernel.h>
#ifdef ASURA_PS2_CDEMUL
#include <libcdvd.h>
#endif
#include <sifdev.h>

// Engine independent.
#include "Debugging/Asura_Assert.h"
#include "FileAccess/Asura_FileAccess.h"
#include "Core/Asura_Maths.h"
#include "Debugging/Asura_Logfile.h"

// Engine platform-specific.
#include "Core/Asura_PS2_OS.h"
#ifdef ASURA_PS2_CDEMUL
	#include "FileAccess/Asura_PS2_TOCCache.h"
#endif

// Kevin Lea - 07/09/2001 16:22:31 - implementation note
//
// valid fileHandles are in the range 0 to (ASURA_MAX_NO_OF_OPEN_FILES-1) inclusive
// 

/////////////
// DEFINES //
/////////////
#ifdef ASURA_PS2_CDEMUL
	#define MAX_RETRIES     (256)	//	(MH - 03.12.2002) was 100. Seeking from one 
									// end of the CD to the other seems to take longer 
									// than 100 retries though
    #define CD_SECTOR_SIZE						(2048)
    #define CD_NUM_SECTORS_IN_READ_BUFFER		(50)
    #define	READ_BUFFER_SIZE_BYTES				(CD_NUM_SECTORS_IN_READ_BUFFER * CD_SECTOR_SIZE)
#else
    #define	READ_BUFFER_SIZE_BYTES				(2048*50)
#endif

/////////////////////
// LOCAL VARIABLES //
/////////////////////
int		 FileUsageArray[ ASURA_MAX_NO_OF_OPEN_FILES ];
int		 ReadBufferOffsetArray[ ASURA_MAX_NO_OF_OPEN_FILES ];
char	*FileReadBuffers[ ASURA_MAX_NO_OF_OPEN_FILES ];

#ifdef ASURA_PS2_CDEMUL
	static sceCdlFILE	OpenFileInfo[ ASURA_MAX_NO_OF_OPEN_FILES ];
#else
	static int	FileHandleArray[ ASURA_MAX_NO_OF_OPEN_FILES ];
#endif

////////////////////////////////
// LOCAL FUNCTION DEFINITIONS //
////////////////////////////////
static bool  IsDataInReadBufferUseful( Asura_Handle fileHandle, int noOfBytes );
static void  CopyBytes( char* pDest, const char* pSrc, u_int uNumBytes );
static bool  FillReadBuffer( Asura_Handle fileHandle, int iNumBytesReqd );
static char* CopyCompleteBuffers( Asura_Handle fileHandle, char* pTempDest, u_int* noOfBytes );

////////////////////////////////////////////////
// Asura_FileAccess - Platform implementation //
////////////////////////////////////////////////
bool Asura_FileAccess::Platform_Open(Asura_Handle fileHandle, const char* szFilename)
{
    if (!FileReadBuffers[fileHandle])
    {
        FileReadBuffers[fileHandle] = reinterpret_cast<char*>( Asura_AllocateAlign(READ_BUFFER_SIZE_BYTES, ASURA_ALIGN_64_BYTE) );
        if (!FileReadBuffers[fileHandle])
        {
            Asura_Assert(0, "failed to create file buffer");
            return false;
        }
    }

    const char* szFullPath = Asura_PS2_OS::GetFullPath( szFilename );
    
#ifdef ASURA_PS2_CDEMUL

    int iRetVal = 0;

    // Try to locate the file on the disc
    sceCdlFILE* const pxFileDescriptor = &OpenFileInfo[fileHandle];

#if ASURA_PS2_USE_TOC_CACHE
    // (PSJE - 13.01.2003) Replace sceCdSearchFile with TOC caching
	iRetVal = Asura_PS2_TOCCache::CdSearchFile( pxFileDescriptor, szFullPath );
#else
	for (u_int uRetry = 0; uRetry < MAX_RETRIES; ++uRetry)
	{
		iRetVal = sceCdSearchFile( pxFileDescriptor, szFullPath );
		if (iRetVal == 1)
		{
			break;
		}
		else
		{
			Asura_OutputToDebugger(("sceCdSearchFile error 0x%x. Retry %d\n", sceCdGetError(), uRetry));
		}
	}
#endif
    
    if( iRetVal != 1 )
    {
        Asura_OutputToDebugger(("Failed to open file '%s'\n", szFullPath));
        return false;
    }
	else
	{
        Asura_OutputToDebugger(("opened file '%s'\n", szFullPath));
		ReadBufferOffsetArray[fileHandle] = -1;
		FileUsageArray[fileHandle]        = 0;
		return true;
	}

#else // not defined ASURA_PS2_CDEMUL

	FileHandleArray[fileHandle] = sceOpen( szFullPath, SCE_RDONLY );
	if( FileHandleArray[fileHandle] < 0 )
	{
		return false;
	}
	else
	{
		ReadBufferOffsetArray[fileHandle] = -1;
		FileUsageArray[fileHandle]        = 0;
		return true;
	}

#endif // else not defined ASURA_PS2_CDEMUL
}

bool Asura_FileAccess::Platform_Close(Asura_Handle fileHandle)
{
#ifndef ASURA_PS2_CDEMUL
	sceClose( FileHandleArray[ fileHandle ] );
#endif
    return true;
}


void Asura_PS2_FileAccess_Shutdown()
{
    for (int i = 0; i < ASURA_MAX_NO_OF_OPEN_FILES; i++)
    {
        if (FileReadBuffers[i])
        {
            Asura_DeallocateAlign(FileReadBuffers[i]);
        }
    }
}


/*
 * 
 * RMS: I found this on the newsgroups...(sce.dev.prog)
 * 
 * Anyway, the problem as stated previously, isn't that files have to be
 * 16-byte aligned, the problem is, when you sceRead, you have to read 16-byte
 * aligned amount, you can sceLSeek to position 20 in the file, then read the
 * remaining data ( look at the Dino sample from George Bain ), and it'll load
 * perfectly, but, just try and subtract 1 from the 16-byte aligned read size,
 * and you'll loose the last few bytes of your data.
*/

// (MP - 19.09.2002) You can speed up loading by allocating cache-aligned
// buffers (recall that such allocations guarantee the memory will not be in
// the cache) and then passing in an uncached accelerated pointer to
// Asura_FileAccess::ReadData().

bool Asura_FileAccess::Platform_ReadData( Asura_Handle fileHandle, void* destination, u_int noOfBytes )
{
	char*	pTempDest;
	char*	pTempSrc;

#if (READ_BUFFER_SIZE_BYTES & 0xF)
	#error RMS: Error - Buffer read size must be a multiple of 16 bytes
#endif

	pTempDest = (char*)destination;

// Is any of the requested data in the read buffer already?
	if( IsDataInReadBufferUseful( fileHandle, noOfBytes ) )
	{
	// How far into the buffer does the useful data start?
		int	nOffsetToUsefulData = FileUsageArray[ fileHandle ] - ReadBufferOffsetArray[ fileHandle ];
		int	nNumBytesAvailabale = READ_BUFFER_SIZE_BYTES - nOffsetToUsefulData;

		int	nNumUsefulBytes		= Asura_Maths::Min( noOfBytes, static_cast<u_int>(nNumBytesAvailabale) );

	// Copy the useful bytes out
		pTempSrc = FileReadBuffers[ fileHandle ] + nOffsetToUsefulData;
		CopyBytes( pTempDest, pTempSrc, nNumUsefulBytes );
		pTempDest += nNumUsefulBytes;
		FileUsageArray[ fileHandle ] += nNumUsefulBytes;
		noOfBytes -= nNumUsefulBytes;

	// How many more bytes are needed?
		if( noOfBytes == 0 )
		{
			return true;
		}
		else
		{
		// Copy any complete buffers if possible
			pTempDest = CopyCompleteBuffers( fileHandle, pTempDest, &noOfBytes );
			if( NULL == pTempDest )
			{
				Asura_OutputToDebugger_Retail(("Error reading\n"));
				return false;
			}

		// Is there a remainder?
			if( noOfBytes == 0 )
			{
				return true;
			}

		// Sort the remainder out - we can do this by calling this function again!
			return( Platform_ReadData( fileHandle, pTempDest, noOfBytes ) );
		}
	}
	else
	{
	// Copy any complete buffers if possible
		pTempDest = CopyCompleteBuffers( fileHandle, pTempDest, &noOfBytes );
		if( NULL == pTempDest )
		{
			Asura_OutputToDebugger_Retail(("Error reading\n"));
			return false;
		}

	// Is there a remainder?
		if( noOfBytes == 0 )
		{
			return true;
		}

	// Fill the read buffer
		if( !FillReadBuffer(fileHandle, noOfBytes) )
		{
			return false;
		}

	// Sort the remainder out - we can do this by calling this function again!
		return( Platform_ReadData( fileHandle, pTempDest, noOfBytes ) );
	}

	return true;
}

void Asura_FileAccess::Platform_Initialise( void ) { }

void Asura_FileAccess::Platform_Shutdown( void ) { }


bool Asura_FileAccess::Platform_SkipUncompressedData( Asura_Handle iFileHandle, u_int uNumBytes )
{
    // (MP - 06.02.2003) Horribly slow implementation just so this stuff at
    // least compiles.
    u_char ucData;
    while( uNumBytes-- )
    {
        if( !Platform_ReadData(iFileHandle, &ucData, 1) )
        {
            return false;
        }
    }
    return true;
}

static bool IsDataInReadBufferUseful( Asura_Handle fileHandle, int noOfBytes )
{
	int	nReadBufferOffset = ReadBufferOffsetArray[ fileHandle ];

// If the current read buffer offset is negative then there is no data in the buffer at all
	if( nReadBufferOffset < 0 )
	{
		return false;
	}

// The desired data in the file starts at offset ( FileUsageArray[ fileHandle ] )
// and ends at offset ( FileUsageArray[ fileHandle ] + noOfBytes )
	int	nStartOfDesiredData	= FileUsageArray[ fileHandle ];
//	int	nEndOfDesiredData	= FileUsageArray[ fileHandle ] + noOfBytes;

// The available data in the buffer starts at file offset ( nReadBufferOffset ) and ends
// at file offset ( nReadBufferOffset + READ_BUFFER_SIZE_BYTES )
	int	nStartBufferData	= nReadBufferOffset;
	int	nEndBufferData		= nReadBufferOffset + READ_BUFFER_SIZE_BYTES;

// Now the test is simply if either the start or end of the desired data lie within the boundary
// of available data
	Asura_Assert( nStartOfDesiredData >= nStartBufferData, "RMS: Error - requested data comes before the read buffer start pos!" );

	if( nStartOfDesiredData >= nStartBufferData &&
		nStartOfDesiredData < nEndBufferData )
	{
		return true;
	}

	return false;

} // End Function 'IsDataInReadBufferUseful'

#define ENABLE_FAST_COPYBYTES (1)

#if ENABLE_FAST_COPYBYTES

static const u_int COPYBYTES_QW_THRESHOLD = 256;

static void CopyBytes( char* pcDest, const char* pcSrc, u_int uNumBytes )
{
    if( uNumBytes > COPYBYTES_QW_THRESHOLD )
    {
        const u_int uDestAlign = (reinterpret_cast<u_int>(pcDest) & 15);
        const u_int uSrcAlign  = (reinterpret_cast<u_int>(pcSrc)  & 15);
        if( uDestAlign == uSrcAlign )
        {
            // Get pointers to QW alignment.
            if( uDestAlign )
            {
                u_int uToPtrAlignment = 16 - uDestAlign;
                uNumBytes -= uToPtrAlignment;
                while( uToPtrAlignment-- )
                {
                    *pcDest++ = *pcSrc++;
                }
            }

            // Now copy in pairs of QWs as far as possible. We do 2 QWs at a
            // time because this allows us to fill up the minimum number of
            // loop cycles. This is a win even if it does mean we may have to
            // copy a QW at the end in words or bytes.
            {
                u_int uNumDoubleQWs = (uNumBytes >> 5);
                uNumBytes -= (uNumDoubleQWs << 5);
                while( uNumDoubleQWs-- )
                {
                    *(reinterpret_cast<u_long128*>(pcDest +  0)) = *(reinterpret_cast<const u_long128*>(pcSrc +  0));
                    *(reinterpret_cast<u_long128*>(pcDest + 16)) = *(reinterpret_cast<const u_long128*>(pcSrc + 16));
                    pcDest += 32;
                    pcSrc  += 32;
                }
            }
            
            // We may have some bytes left but that will be taken care of
            // below.
        }
    }

    // Copy any bytes remaining.
    while( uNumBytes-- )
    {
        *pcDest++ = *pcSrc++;
    }
}

#else // not ENABLE_FAST_COPYBYTES

static void CopyBytes( char* pDest, const char* pSrc, u_int nNumBytes )
{
    // MH: this is almost certainly a bottleneck - could at least read a load of DWORDS
	for( u_int n = 0; n < nNumBytes; n++ )
	{
		*pDest = *pSrc;
		pDest++;
		pSrc++;
	}
}
// End Function 'CopyBytes'

#endif // else not ENABLE_FAST_COPYBYTES

static void DisplayCdErrorMessage()
{
}

static bool FillReadBuffer( Asura_Handle fileHandle, int iBytesReqd )
{
//	static u_int uBufferRefills = 0;
//	uBufferRefills++;
//	Asura_OutputToDebugger_Retail(("FillReadBuffer() - %d\n", uBufferRefills));
//	if (uBufferRefills == 338)
//	{
//		asm("break;");
//	}
#ifdef ASURA_PS2_CDEMUL

    char*	pDest;
	pDest = FileReadBuffers[ fileHandle ];

    sceCdRMode xReadMode;

    xReadMode.trycount      = 0;
    xReadMode.spindlctrl    = SCECdSpinNom;
    xReadMode.datapattern   = SCECdSecS2048;

#if READ_BUFFER_SIZE_BYTES != CD_NUM_SECTORS_IN_READ_BUFFER * CD_SECTOR_SIZE
	#error
#endif

	u_int uReadErrorRetries = 1;
	while (uReadErrorRetries)
	{
		u_int uIssueRetries = 1;
	    while( uIssueRetries )
		{
			if ( sceCdRead( OpenFileInfo[fileHandle].lsn, CD_NUM_SECTORS_IN_READ_BUFFER, pDest, &xReadMode ) != 0 )
			{
				// Issued successfully
				break;
			}

			Asura_OutputToDebugger(("!-!-! CD Read Issue failed retry %d...\n", uIssueRetries));
			uIssueRetries++;
	        
			// Sleep for 10 milliseconds so other threads get a chance
			Asura_PS2_OS::DelayThread(10);

			if ( uReadErrorRetries > 100 )
			{
				// It's been at least a second and we still aren't getting the data
				// Time to display an error message
				DisplayCdErrorMessage();
			}
		}

	// (MH - 07.05.2002) using the blocking version of this function seems to cause a crash. No idea why.
	//    sceCdSync( 0 );
	    for( int nSync = 1; nSync; )
		{
	        nSync = sceCdSync( 1 );
	
			if (nSync)
			{
				// Sleep for 100 milliseconds so other threads get a chance
				Asura_PS2_OS::DelayThread(10);
			}
		}

		// The command is complete .. check if it was succeful
		int iRetVal = sceCdGetError();
		if ( iRetVal == SCECdErNO )
		{
			// No errors
			break;
		}

		Asura_OutputToDebugger_Retail(("!-!-! CD READ ERROR (%d)!-!-!  retry %d...\n", iRetVal, uReadErrorRetries));
		uReadErrorRetries++;

		if ( uReadErrorRetries > 100 )
		{
			// It's been at least a second and we still aren't getting the data
			// Time to display an error message
			DisplayCdErrorMessage();
		}
	}

    OpenFileInfo[fileHandle].lsn += CD_NUM_SECTORS_IN_READ_BUFFER;

#else // not defined ASURA_PS2_CDEMUL

	int&	file = FileHandleArray[ fileHandle ];

	int iBytes = sceRead( file, FileReadBuffers[fileHandle], READ_BUFFER_SIZE_BYTES );

	// On the last read from a file (assuming reading to the very end) it is likely that the
	// read will NOT be the same size as the BUFFER...therefore we will allow it to continue!
    Asura_Assert( iBytesReqd <= READ_BUFFER_SIZE_BYTES, "" );
    if( READ_BUFFER_SIZE_BYTES == iBytesReqd && READ_BUFFER_SIZE_BYTES != iBytes )
    {
        return false;
	}

#endif // else not defined ASURA_PS2_CDEMUL

	ReadBufferOffsetArray[ fileHandle ] = FileUsageArray[ fileHandle ];

	return true;

} // End Function 'FillReadBuffer'

static char* CopyCompleteBuffers( Asura_Handle fileHandle, char* pTempDest, u_int* noOfBytes )
{
    u_int iNumBytes = *noOfBytes;

	while( iNumBytes >= READ_BUFFER_SIZE_BYTES )
	{
		if( !FillReadBuffer(fileHandle, READ_BUFFER_SIZE_BYTES) )
		{
			return NULL;
		}

	// Copy the buffer
		CopyBytes( pTempDest, FileReadBuffers[fileHandle], READ_BUFFER_SIZE_BYTES );
		pTempDest += READ_BUFFER_SIZE_BYTES;
		FileUsageArray[ fileHandle ] += READ_BUFFER_SIZE_BYTES;

		iNumBytes -= READ_BUFFER_SIZE_BYTES;
	}

    *noOfBytes = iNumBytes;

	return pTempDest;

} // End Function 'CopyCompleteBuffers'

#endif // end else not defined ASURA_PS2_USE_MULTISTREAM_FOR_FILE_ACCESS

bool Asura_FileAccess::Platform_LoadAsynchronous(Asura_Handle iHandle,const char* szFilename)
{
    if( !Asura_PS2_Multistream_FastLoad::OpenFileForPreload(iHandle, szFilename) )
    {
        return false;
    }

#if _DEBUG
    Asura_Assert( !g_axBufferInfo[iHandle].bInUse, "File already open for this handle." );
    g_axBufferInfo[iHandle].bInUse = true;
    g_axBufferInfo[iHandle].bPreload = true;
#endif

    g_axBufferInfo[iHandle].uBytesRemaining = 0;
    return true;
}

enum ASURA_ASYNCLOADING_STATUS Asura_FileAccess::Platform_GetAsynchronousLoadingStatus(Asura_Handle iHandle)
{
#if _DEBUG
    Asura_Assert( g_axBufferInfo[iHandle].bPreload, "File not opened with Platform_LoadAsynchronous." );
#endif
	static u_int ls_uLastBytesLoaded = 0xffffffff;
	u_int uCurBytesLoaded = Asura_PS2_Multistream_FastLoad::GetNumBytesPreloaded( iHandle );
	if (ls_uLastBytesLoaded != uCurBytesLoaded)
	{
		Asura_OutputToDebugger(("loaded %d bytes\n", uCurBytesLoaded));
		ls_uLastBytesLoaded = uCurBytesLoaded;
	}
	return Asura_PS2_Multistream_FastLoad::IsPreloadFinished(iHandle) ? ASURA_ASYNCLOADING_STATUS_FINISHEDLOADING : ASURA_ASYNCLOADING_STATUS_LOADING;
}

// JAT 20/02/06 placeholder to get PS2 debug build to link
bool Asura_FileAccess::Platform_OpenFromMemory( Asura_Handle iFileHandle, const void* pMem, u_int uSize )
{
	Asura_Assert( false, "" );
	return false;
}

u_int Asura_FileAccess::Platform_GetFileSize( const char* szFilename )
{
	u_int uFileSize = 0;

	Asura_Handle iFileHandle = Asura_FileAccess::Open( szFilename );
	if( iFileHandle != ASURA_INVALID_HANDLE )
	{
		uFileSize = Platform_GetFileSize( iFileHandle );
		Asura_FileAccess::Close( iFileHandle );
	}

	return uFileSize;
}

u_int Asura_FileAccess::Platform_GetFileSize(const Asura_Handle iFileHandle)
{
	u_int uFileSize = 0;

	if( iFileHandle != ASURA_INVALID_HANDLE )
	{
		uFileSize = Asura_PS2_Multistream_FastLoad::GetFileSize( iFileHandle );
	}
	return uFileSize;
}

void Asura_FileAccess::Platform_FindFiles(const char* szDirectory, const char* szExtension, void (*pfnCallback)(const char* szFilename), bool bRecursive /* = true */)
{
	int iDirHandle;

	Asura_Char szCurrentPath[256];
	const char* szFullPath = Asura_PS2_OS::GetFullPath( szDirectory, true, NULL, true );
	iDirHandle = sceDopen( szFullPath );
	if( iDirHandle < 0 )
	{
		Asura_OutputToDebugger_Retail(( "Failed to open directory '%s'. Error code: %i.\n", szFullPath, -iDirHandle ));
		return;
	}

	sce_dirent xEntry;
	while( true )
	{
		int iRet = sceDread( iDirHandle, &xEntry );
		if( iRet <= 0 ) { break; }

		if( xEntry.d_name && (strlen(xEntry.d_name) > 2) ) // ignore . and ..
		{
			// Force to lower case.
			strlwr( xEntry.d_name );

			// Strip trailing ";1". Only happens in CD builds but anyway...
			u_int uLen = strlen( xEntry.d_name );
			if( uLen > 2 && xEntry.d_name[uLen - 2] == ';' && xEntry.d_name[uLen - 1] == '1' )
			{
				xEntry.d_name[uLen -= 2] = '\0';
			}

			const bool bDirectory = (xEntry.d_stat.st_mode & 0xF000u) == 0x1000u;
			Asura_PrintfToBfr( szCurrentPath, "%s\\%s", 256, true, szDirectory, xEntry.d_name );		

			// Recurse subdirectories.
			if( bDirectory ) 
			{ 
				if(bRecursive)
				{
					FindFiles( szCurrentPath, szExtension, pfnCallback );
				}
			}
			else
			{
				if(Asura_StringUtil::EndsWith(szCurrentPath, szExtension, true))
				{
					pfnCallback(szCurrentPath);
				}
			}
		}
	}
	sceDclose( iDirHandle );
}